diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/vac |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/vac')
-rwxr-xr-x | sys/src/cmd/vac/dat.h | 38 | ||||
-rwxr-xr-x | sys/src/cmd/vac/error.c | 22 | ||||
-rwxr-xr-x | sys/src/cmd/vac/error.h | 21 | ||||
-rwxr-xr-x | sys/src/cmd/vac/file.c | 2099 | ||||
-rwxr-xr-x | sys/src/cmd/vac/fns.h | 30 | ||||
-rwxr-xr-x | sys/src/cmd/vac/glob.c | 180 | ||||
-rwxr-xr-x | sys/src/cmd/vac/mkfile | 32 | ||||
-rwxr-xr-x | sys/src/cmd/vac/pack.c | 736 | ||||
-rwxr-xr-x | sys/src/cmd/vac/stdinc.h | 8 | ||||
-rwxr-xr-x | sys/src/cmd/vac/testinc.c | 31 | ||||
-rwxr-xr-x | sys/src/cmd/vac/unvac.c | 337 | ||||
-rwxr-xr-x | sys/src/cmd/vac/vac.c | 715 | ||||
-rwxr-xr-x | sys/src/cmd/vac/vac.h | 145 | ||||
-rwxr-xr-x | sys/src/cmd/vac/vacfs.c | 788 |
14 files changed, 5182 insertions, 0 deletions
diff --git a/sys/src/cmd/vac/dat.h b/sys/src/cmd/vac/dat.h new file mode 100755 index 000000000..000a762a8 --- /dev/null +++ b/sys/src/cmd/vac/dat.h @@ -0,0 +1,38 @@ +typedef struct MetaBlock MetaBlock; +typedef struct MetaEntry MetaEntry; + +#define MaxBlock (1UL<<31) + +enum { + BytesPerEntry = 100, /* estimate of bytes per dir entries - determines number of index entries in the block */ + FullPercentage = 80, /* don't allocate in block if more than this percentage full */ + FlushSize = 200, /* number of blocks to flush */ + DirtyPercentage = 50 /* maximum percentage of dirty blocks */ +}; + + +struct MetaEntry +{ + uchar *p; + ushort size; +}; + +struct MetaBlock +{ + int maxsize; /* size of block */ + int size; /* size used */ + int free; /* free space within used size */ + int maxindex; /* entries allocated for table */ + int nindex; /* amount of table used */ + int unbotch; + uchar *buf; +}; + +struct VacDirEnum +{ + VacFile *file; + u32int boff; + int i, n; + VacDir *buf; +}; + diff --git a/sys/src/cmd/vac/error.c b/sys/src/cmd/vac/error.c new file mode 100755 index 000000000..f28f93760 --- /dev/null +++ b/sys/src/cmd/vac/error.c @@ -0,0 +1,22 @@ +#include "stdinc.h" +#include "vac.h" +#include "dat.h" +#include "fns.h" +#include "error.h" + +char ENoDir[] = "directory entry is not allocated"; +char ENoFile[] = "no such file or directory"; +char EBadPath[] = "bad path"; +char EBadDir[] = "corrupted directory entry"; +char EBadMeta[] = "corrupted meta data"; +char ENotDir[] = "not a directory"; +char ENotFile[] = "not a file"; +char EIO[] = "i/o error"; +char EBadOffset[] = "illegal offset"; +char ETooBig[] = "file too big"; +char EReadOnly[] = "read only"; +char ERemoved[] = "file has been removed"; +char ENilBlock[] = "illegal block address"; +char ENotEmpty[] = "directory not empty"; +char EExists[] = "file already exists"; +char ERoot[] = "cannot remove root"; diff --git a/sys/src/cmd/vac/error.h b/sys/src/cmd/vac/error.h new file mode 100755 index 000000000..3d6c7f8da --- /dev/null +++ b/sys/src/cmd/vac/error.h @@ -0,0 +1,21 @@ +/* + * Somehow <errno.h> has been included on Mac OS X + */ +#undef EIO + +extern char ENoDir[]; +extern char EBadDir[]; +extern char EBadMeta[]; +extern char ENilBlock[]; +extern char ENotDir[]; +extern char ENotFile[]; +extern char EIO[]; +extern char EBadOffset[]; +extern char ETooBig[]; +extern char EReadOnly[]; +extern char ERemoved[]; +extern char ENotEmpty[]; +extern char EExists[]; +extern char ERoot[]; +extern char ENoFile[]; +extern char EBadPath[]; diff --git a/sys/src/cmd/vac/file.c b/sys/src/cmd/vac/file.c new file mode 100755 index 000000000..cc88eabd2 --- /dev/null +++ b/sys/src/cmd/vac/file.c @@ -0,0 +1,2099 @@ +#include "stdinc.h" +#include "vac.h" +#include "dat.h" +#include "fns.h" +#include "error.h" + +#define debug 0 + +/* + * Vac file system. This is a simplified version of the same code in Fossil. + * + * The locking order in the tree is upward: a thread can hold the lock + * for a VacFile and then acquire the lock of f->up (the parent), + * but not vice-versa. + * + * A vac file is one or two venti files. Plain data files are one venti file, + * while directores are two: a venti data file containing traditional + * directory entries, and a venti directory file containing venti + * directory entries. The traditional directory entries in the data file + * contain integers indexing into the venti directory entry file. + * It's a little complicated, but it makes the data usable by standard + * tools like venti/copy. + * + */ + +static int filemetaflush(VacFile*, char*); + +struct VacFile +{ + VacFs *fs; /* immutable */ + + /* meta data for file: protected by the lk in the parent */ + int ref; /* holds this data structure up */ + + int partial; /* file was never really open */ + int removed; /* file has been removed */ + int dirty; /* dir is dirty with respect to meta data in block */ + u32int boff; /* block offset within msource for this file's metadata */ + VacDir dir; /* metadata for this file */ + VacFile *up; /* parent file */ + VacFile *next; /* sibling */ + + RWLock lk; /* lock for the following */ + VtFile *source; /* actual data */ + VtFile *msource; /* metadata for children in a directory */ + VacFile *down; /* children */ + int mode; + + uvlong qidoffset; /* qid offset */ +}; + +static VacFile* +filealloc(VacFs *fs) +{ + VacFile *f; + + f = vtmallocz(sizeof(VacFile)); + f->ref = 1; + f->fs = fs; + f->boff = NilBlock; + f->mode = fs->mode; + return f; +} + +static void +filefree(VacFile *f) +{ + vtfileclose(f->source); + vtfileclose(f->msource); + vdcleanup(&f->dir); + memset(f, ~0, sizeof *f); /* paranoia */ + vtfree(f); +} + +static int +chksource(VacFile *f) +{ + if(f->partial) + return 0; + + if(f->source == nil + || ((f->dir.mode & ModeDir) && f->msource == nil)){ + werrstr(ERemoved); + return -1; + } + return 0; +} + +static int +filelock(VacFile *f) +{ + wlock(&f->lk); + if(chksource(f) < 0){ + wunlock(&f->lk); + return -1; + } + return 0; +} + +static void +fileunlock(VacFile *f) +{ + wunlock(&f->lk); +} + +static int +filerlock(VacFile *f) +{ + rlock(&f->lk); + if(chksource(f) < 0){ + runlock(&f->lk); + return -1; + } + return 0; +} + +static void +filerunlock(VacFile *f) +{ + runlock(&f->lk); +} + +/* + * The file metadata, like f->dir and f->ref, + * are synchronized via the parent's lock. + * This is why locking order goes up. + */ +static void +filemetalock(VacFile *f) +{ + assert(f->up != nil); + wlock(&f->up->lk); +} + +static void +filemetaunlock(VacFile *f) +{ + wunlock(&f->up->lk); +} + +uvlong +vacfilegetid(VacFile *f) +{ + /* immutable */ + return f->qidoffset + f->dir.qid; +} + +uvlong +vacfilegetqidoffset(VacFile *f) +{ + return f->qidoffset; +} + +ulong +vacfilegetmcount(VacFile *f) +{ + ulong mcount; + + filemetalock(f); + mcount = f->dir.mcount; + filemetaunlock(f); + return mcount; +} + +ulong +vacfilegetmode(VacFile *f) +{ + ulong mode; + + filemetalock(f); + mode = f->dir.mode; + filemetaunlock(f); + return mode; +} + +int +vacfileisdir(VacFile *f) +{ + /* immutable */ + return (f->dir.mode & ModeDir) != 0; +} + +int +vacfileisroot(VacFile *f) +{ + return f == f->fs->root; +} + +/* + * The files are reference counted, and while the reference + * is bigger than zero, each file can be found in its parent's + * f->down list (chains via f->next), so that multiple threads + * end up sharing a VacFile* when referring to the same file. + * + * Each VacFile holds a reference to its parent. + */ +VacFile* +vacfileincref(VacFile *vf) +{ + filemetalock(vf); + assert(vf->ref > 0); + vf->ref++; + filemetaunlock(vf); + return vf; +} + +int +vacfiledecref(VacFile *f) +{ + VacFile *p, *q, **qq; + + if(f->up == nil){ + /* never linked in */ + assert(f->ref == 1); + filefree(f); + return 0; + } + + filemetalock(f); + f->ref--; + if(f->ref > 0){ + filemetaunlock(f); + return -1; + } + assert(f->ref == 0); + assert(f->down == nil); + + if(f->source && vtfilelock(f->source, -1) >= 0){ + vtfileflush(f->source); + vtfileunlock(f->source); + } + if(f->msource && vtfilelock(f->msource, -1) >= 0){ + vtfileflush(f->msource); + vtfileunlock(f->msource); + } + + /* + * Flush f's directory information to the cache. + */ + filemetaflush(f, nil); + + p = f->up; + qq = &p->down; + for(q = *qq; q; q = *qq){ + if(q == f) + break; + qq = &q->next; + } + assert(q != nil); + *qq = f->next; + + filemetaunlock(f); + filefree(f); + vacfiledecref(p); + return 0; +} + + +/* + * Construct a vacfile for the root of a vac tree, given the + * venti file for the root information. That venti file is a + * directory file containing VtEntries for three more venti files: + * the two venti files making up the root directory, and a + * third venti file that would be the metadata half of the + * "root's parent". + * + * Fossil generates slightly different vac files, due to a now + * impossible-to-change bug, which contain a VtEntry + * for just one venti file, that itself contains the expected + * three directory entries. Sigh. + */ +VacFile* +_vacfileroot(VacFs *fs, VtFile *r) +{ + int redirected; + char err[ERRMAX]; + VtBlock *b; + VtFile *r0, *r1, *r2; + MetaBlock mb; + MetaEntry me; + VacFile *root, *mr; + + redirected = 0; +Top: + b = nil; + root = nil; + mr = nil; + r1 = nil; + r2 = nil; + + if(vtfilelock(r, -1) < 0) + return nil; + r0 = vtfileopen(r, 0, fs->mode); + if(debug) + fprint(2, "r0 %p\n", r0); + if(r0 == nil) + goto Err; + r2 = vtfileopen(r, 2, fs->mode); + if(debug) + fprint(2, "r2 %p\n", r2); + if(r2 == nil){ + /* + * some vac files (e.g., from fossil) + * have an extra layer of indirection. + */ + rerrstr(err, sizeof err); + if(!redirected && strstr(err, "not active")){ + redirected = 1; + vtfileunlock(r); + r = r0; + goto Top; + } + goto Err; + } + r1 = vtfileopen(r, 1, fs->mode); + if(debug) + fprint(2, "r1 %p\n", r1); + if(r1 == nil) + goto Err; + + mr = filealloc(fs); + mr->msource = r2; + r2 = nil; + + root = filealloc(fs); + root->boff = 0; + root->up = mr; + root->source = r0; + r0 = nil; + root->msource = r1; + r1 = nil; + + mr->down = root; + vtfileunlock(r); + + if(vtfilelock(mr->msource, VtOREAD) < 0) + goto Err1; + b = vtfileblock(mr->msource, 0, VtOREAD); + vtfileunlock(mr->msource); + if(b == nil) + goto Err1; + + if(mbunpack(&mb, b->data, mr->msource->dsize) < 0) + goto Err1; + + meunpack(&me, &mb, 0); + if(vdunpack(&root->dir, &me) < 0) + goto Err1; + vtblockput(b); + + return root; +Err: + vtfileunlock(r); +Err1: + vtblockput(b); + if(r0) + vtfileclose(r0); + if(r1) + vtfileclose(r1); + if(r2) + vtfileclose(r2); + if(mr) + filefree(mr); + if(root) + filefree(root); + + return nil; +} + +/* + * Vac directories are a sequence of metablocks, each of which + * contains a bunch of metaentries sorted by file name. + * The whole sequence isn't sorted, though, so you still have + * to look at every block to find a given name. + * Dirlookup looks in f for an element name elem. + * It returns a new VacFile with the dir, boff, and mode + * filled in, but the sources (venti files) are not, and f is + * not yet linked into the tree. These details must be taken + * care of by the caller. + * + * f must be locked, f->msource must not. + */ +static VacFile* +dirlookup(VacFile *f, char *elem) +{ + int i; + MetaBlock mb; + MetaEntry me; + VtBlock *b; + VtFile *meta; + VacFile *ff; + u32int bo, nb; + + meta = f->msource; + b = nil; + if(vtfilelock(meta, -1) < 0) + return nil; + nb = (vtfilegetsize(meta)+meta->dsize-1)/meta->dsize; + for(bo=0; bo<nb; bo++){ + b = vtfileblock(meta, bo, VtOREAD); + if(b == nil) + goto Err; + if(mbunpack(&mb, b->data, meta->dsize) < 0) + goto Err; + if(mbsearch(&mb, elem, &i, &me) >= 0){ + ff = filealloc(f->fs); + if(vdunpack(&ff->dir, &me) < 0){ + filefree(ff); + goto Err; + } + ff->qidoffset = f->qidoffset + ff->dir.qidoffset; + vtfileunlock(meta); + vtblockput(b); + ff->boff = bo; + ff->mode = f->mode; + return ff; + } + vtblockput(b); + b = nil; + } + werrstr(ENoFile); + /* fall through */ +Err: + vtfileunlock(meta); + vtblockput(b); + return nil; +} + +/* + * Open the venti file at offset in the directory f->source. + * f is locked. + */ +static VtFile * +fileopensource(VacFile *f, u32int offset, u32int gen, int dir, uint mode) +{ + VtFile *r; + + if((r = vtfileopen(f->source, offset, mode)) == nil) + return nil; + if(r == nil) + return nil; + if(r->gen != gen){ + werrstr(ERemoved); + vtfileclose(r); + return nil; + } + if(r->dir != dir && r->mode != -1){ + werrstr(EBadMeta); + vtfileclose(r); + return nil; + } + return r; +} + +VacFile* +vacfilegetparent(VacFile *f) +{ + if(vacfileisroot(f)) + return vacfileincref(f); + return vacfileincref(f->up); +} + +/* + * Given an unlocked vacfile (directory) f, + * return the vacfile named elem in f. + * Interprets . and .. as a convenience to callers. + */ +VacFile* +vacfilewalk(VacFile *f, char *elem) +{ + VacFile *ff; + + if(elem[0] == 0){ + werrstr(EBadPath); + return nil; + } + + if(!vacfileisdir(f)){ + werrstr(ENotDir); + return nil; + } + + if(strcmp(elem, ".") == 0) + return vacfileincref(f); + + if(strcmp(elem, "..") == 0) + return vacfilegetparent(f); + + if(filelock(f) < 0) + return nil; + + for(ff = f->down; ff; ff=ff->next){ + if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){ + ff->ref++; + goto Exit; + } + } + + ff = dirlookup(f, elem); + if(ff == nil) + goto Err; + + if(ff->dir.mode & ModeSnapshot) + ff->mode = VtOREAD; + + if(vtfilelock(f->source, f->mode) < 0) + goto Err; + if(ff->dir.mode & ModeDir){ + ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 1, ff->mode); + ff->msource = fileopensource(f, ff->dir.mentry, ff->dir.mgen, 0, ff->mode); + if(ff->source == nil || ff->msource == nil) + goto Err1; + }else{ + ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 0, ff->mode); + if(ff->source == nil) + goto Err1; + } + vtfileunlock(f->source); + + /* link in and up parent ref count */ + ff->next = f->down; + f->down = ff; + ff->up = f; + vacfileincref(f); +Exit: + fileunlock(f); + return ff; + +Err1: + vtfileunlock(f->source); +Err: + fileunlock(f); + if(ff != nil) + vacfiledecref(ff); + return nil; +} + +/* + * Open a path in the vac file system: + * just walk each element one at a time. + */ +VacFile* +vacfileopen(VacFs *fs, char *path) +{ + VacFile *f, *ff; + char *p, elem[VtMaxStringSize], *opath; + int n; + + f = fs->root; + vacfileincref(f); + opath = path; + while(*path != 0){ + for(p = path; *p && *p != '/'; p++) + ; + n = p - path; + if(n > 0){ + if(n > VtMaxStringSize){ + werrstr("%s: element too long", EBadPath); + goto Err; + } + memmove(elem, path, n); + elem[n] = 0; + ff = vacfilewalk(f, elem); + if(ff == nil){ + werrstr("%.*s: %r", utfnlen(opath, p-opath), opath); + goto Err; + } + vacfiledecref(f); + f = ff; + } + if(*p == '/') + p++; + path = p; + } + return f; +Err: + vacfiledecref(f); + return nil; +} + +/* + * Extract the score for the bn'th block in f. + */ +int +vacfileblockscore(VacFile *f, u32int bn, u8int *score) +{ + VtFile *s; + uvlong size; + int dsize, ret; + + ret = -1; + if(filerlock(f) < 0) + return -1; + if(vtfilelock(f->source, VtOREAD) < 0) + goto out; + + s = f->source; + dsize = s->dsize; + size = vtfilegetsize(s); + if((uvlong)bn*dsize >= size) + goto out1; + ret = vtfileblockscore(f->source, bn, score); + +out1: + vtfileunlock(f->source); +out: + filerunlock(f); + return ret; +} + +/* + * Read data from f. + */ +int +vacfileread(VacFile *f, void *buf, int cnt, vlong offset) +{ + int n; + + if(offset < 0){ + werrstr(EBadOffset); + return -1; + } + if(filerlock(f) < 0) + return -1; + if(vtfilelock(f->source, VtOREAD) < 0){ + filerunlock(f); + return -1; + } + n = vtfileread(f->source, buf, cnt, offset); + vtfileunlock(f->source); + filerunlock(f); + return n; +} + +static int +getentry(VtFile *f, VtEntry *e) +{ + if(vtfilelock(f, VtOREAD) < 0) + return -1; + if(vtfilegetentry(f, e) < 0){ + vtfileunlock(f); + return -1; + } + vtfileunlock(f); + if(vtglobaltolocal(e->score) != NilBlock){ + werrstr("internal error - data not on venti"); + return -1; + } + return 0; +} + +/* + * Get the VtEntries for the data contained in f. + */ +int +vacfilegetentries(VacFile *f, VtEntry *e, VtEntry *me) +{ + if(filerlock(f) < 0) + return -1; + if(e && getentry(f->source, e) < 0){ + filerunlock(f); + return -1; + } + if(me){ + if(f->msource == nil) + memset(me, 0, sizeof *me); + else if(getentry(f->msource, me) < 0){ + filerunlock(f); + return -1; + } + } + filerunlock(f); + return 0; +} + +/* + * Get the file's size. + */ +int +vacfilegetsize(VacFile *f, uvlong *size) +{ + if(filerlock(f) < 0) + return -1; + if(vtfilelock(f->source, VtOREAD) < 0){ + filerunlock(f); + return -1; + } + *size = vtfilegetsize(f->source); + vtfileunlock(f->source); + filerunlock(f); + + return 0; +} + +/* + * Directory reading. + * + * A VacDirEnum is a buffer containing directory entries. + * Directory entries contain malloced strings and need to + * be cleaned up with vdcleanup. The invariant in the + * VacDirEnum is that the directory entries between + * vde->i and vde->n are owned by the vde and need to + * be cleaned up if it is closed. Those from 0 up to vde->i + * have been handed to the reader, and the reader must + * take care of calling vdcleanup as appropriate. + */ +VacDirEnum* +vdeopen(VacFile *f) +{ + VacDirEnum *vde; + VacFile *p; + + if(!vacfileisdir(f)){ + werrstr(ENotDir); + return nil; + } + + /* + * There might be changes to this directory's children + * that have not been flushed out into the cache yet. + * Those changes are only available if we look at the + * VacFile structures directory. But the directory reader + * is going to read the cache blocks directly, so update them. + */ + if(filelock(f) < 0) + return nil; + for(p=f->down; p; p=p->next) + filemetaflush(p, nil); + fileunlock(f); + + vde = vtmallocz(sizeof(VacDirEnum)); + vde->file = vacfileincref(f); + + return vde; +} + +/* + * Figure out the size of the directory entry at offset. + * The rest of the metadata is kept in the data half, + * but since venti has to track the data size anyway, + * we just use that one and avoid updating the directory + * each time the file size changes. + */ +static int +direntrysize(VtFile *s, ulong offset, ulong gen, uvlong *size) +{ + VtBlock *b; + ulong bn; + VtEntry e; + int epb; + + epb = s->dsize/VtEntrySize; + bn = offset/epb; + offset -= bn*epb; + + b = vtfileblock(s, bn, VtOREAD); + if(b == nil) + goto Err; + if(vtentryunpack(&e, b->data, offset) < 0) + goto Err; + + /* dangling entries are returned as zero size */ + if(!(e.flags & VtEntryActive) || e.gen != gen) + *size = 0; + else + *size = e.size; + vtblockput(b); + return 0; + +Err: + vtblockput(b); + return -1; +} + +/* + * Fill in vde with a new batch of directory entries. + */ +static int +vdefill(VacDirEnum *vde) +{ + int i, n; + VtFile *meta, *source; + MetaBlock mb; + MetaEntry me; + VacFile *f; + VtBlock *b; + VacDir *de; + + /* clean up first */ + for(i=vde->i; i<vde->n; i++) + vdcleanup(vde->buf+i); + vtfree(vde->buf); + vde->buf = nil; + vde->i = 0; + vde->n = 0; + + f = vde->file; + + source = f->source; + meta = f->msource; + + b = vtfileblock(meta, vde->boff, VtOREAD); + if(b == nil) + goto Err; + if(mbunpack(&mb, b->data, meta->dsize) < 0) + goto Err; + + n = mb.nindex; + vde->buf = vtmalloc(n * sizeof(VacDir)); + + for(i=0; i<n; i++){ + de = vde->buf + i; + meunpack(&me, &mb, i); + if(vdunpack(de, &me) < 0) + goto Err; + vde->n++; + if(!(de->mode & ModeDir)) + if(direntrysize(source, de->entry, de->gen, &de->size) < 0) + goto Err; + } + vde->boff++; + vtblockput(b); + return 0; +Err: + vtblockput(b); + return -1; +} + +/* + * Read a single directory entry from vde into de. + * Returns -1 on error, 0 on EOF, and 1 on success. + * When it returns 1, it becomes the caller's responsibility + * to call vdcleanup(de) to free the strings contained + * inside, or else to call vdunread to give it back. + */ +int +vderead(VacDirEnum *vde, VacDir *de) +{ + int ret; + VacFile *f; + u32int nb; + + f = vde->file; + if(filerlock(f) < 0) + return -1; + + if(vtfilelock2(f->source, f->msource, VtOREAD) < 0){ + filerunlock(f); + return -1; + } + + nb = (vtfilegetsize(f->msource)+f->msource->dsize-1)/f->msource->dsize; + + while(vde->i >= vde->n){ + if(vde->boff >= nb){ + ret = 0; + goto Return; + } + if(vdefill(vde) < 0){ + ret = -1; + goto Return; + } + } + + memmove(de, vde->buf + vde->i, sizeof(VacDir)); + vde->i++; + ret = 1; + +Return: + vtfileunlock(f->source); + vtfileunlock(f->msource); + filerunlock(f); + + return ret; +} + +/* + * "Unread" the last directory entry that was read, + * so that the next vderead will return the same one. + * If the caller calls vdeunread(vde) it should not call + * vdcleanup on the entry being "unread". + */ +int +vdeunread(VacDirEnum *vde) +{ + if(vde->i > 0){ + vde->i--; + return 0; + } + return -1; +} + +/* + * Close the enumerator. + */ +void +vdeclose(VacDirEnum *vde) +{ + int i; + if(vde == nil) + return; + /* free the strings */ + for(i=vde->i; i<vde->n; i++) + vdcleanup(vde->buf+i); + vtfree(vde->buf); + vacfiledecref(vde->file); + vtfree(vde); +} + + +/* + * On to mutation. If the vac file system has been opened + * read-write, then the files and directories can all be edited. + * Changes are kept in the in-memory cache until flushed out + * to venti, so we must be careful to explicitly flush data + * that we're not likely to modify again. + * + * Each VacFile has its own copy of its VacDir directory entry + * in f->dir, but otherwise the cache is the authoratative source + * for data. Thus, for the most part, it suffices if we just + * call vtfileflushbefore and vtfileflush when we modify things. + * There are a few places where we have to remember to write + * changed VacDirs back into the cache. If f->dir *is* out of sync, + * then f->dirty should be set. + * + * The metadata in a directory is, to venti, a plain data file, + * but as mentioned above it is actually a sequence of + * MetaBlocks that contain sorted lists of VacDir entries. + * The filemetaxxx routines manipulate that stream. + */ + +/* + * Find space in fp for the directory entry dir (not yet written to disk) + * and write it to disk, returning NilBlock on failure, + * or the block number on success. + * + * Start is a suggested block number to try. + * The caller must have filemetalock'ed f and have + * vtfilelock'ed f->up->msource. + */ +static u32int +filemetaalloc(VacFile *fp, VacDir *dir, u32int start) +{ + u32int nb, bo; + VtBlock *b; + MetaBlock mb; + int nn; + uchar *p; + int i, n; + MetaEntry me; + VtFile *ms; + + ms = fp->msource; + n = vdsize(dir, VacDirVersion); + + /* Look for a block with room for a new entry of size n. */ + nb = (vtfilegetsize(ms)+ms->dsize-1)/ms->dsize; + if(start == NilBlock){ + if(nb > 0) + start = nb - 1; + else + start = 0; + } + + if(start > nb) + start = nb; + for(bo=start; bo<nb; bo++){ + if((b = vtfileblock(ms, bo, VtOREAD)) == nil) + goto Err; + if(mbunpack(&mb, b->data, ms->dsize) < 0) + goto Err; + nn = (mb.maxsize*FullPercentage/100) - mb.size + mb.free; + if(n <= nn && mb.nindex < mb.maxindex){ + /* reopen for writing */ + vtblockput(b); + if((b = vtfileblock(ms, bo, VtORDWR)) == nil) + goto Err; + mbunpack(&mb, b->data, ms->dsize); + goto Found; + } + vtblockput(b); + } + + /* No block found, extend the file by one metablock. */ + vtfileflushbefore(ms, nb*(uvlong)ms->dsize); + if((b = vtfileblock(ms, nb, VtORDWR)) == nil) + goto Err; + vtfilesetsize(ms, (nb+1)*ms->dsize); + mbinit(&mb, b->data, ms->dsize, ms->dsize/BytesPerEntry); + +Found: + /* Now we have a block; allocate space to write the entry. */ + p = mballoc(&mb, n); + if(p == nil){ + /* mballoc might have changed block */ + mbpack(&mb); + werrstr(EBadMeta); + goto Err; + } + + /* Figure out where to put the index entry, and write it. */ + mbsearch(&mb, dir->elem, &i, &me); + assert(me.p == nil); /* not already there */ + me.p = p; + me.size = n; + vdpack(dir, &me, VacDirVersion); + mbinsert(&mb, i, &me); + mbpack(&mb); + vtblockput(b); + return bo; + +Err: + vtblockput(b); + return NilBlock; +} + +/* + * Update f's directory entry in the block cache. + * We look for the directory entry by name; + * if we're trying to rename the file, oelem is the old name. + * + * Assumes caller has filemetalock'ed f. + */ +static int +filemetaflush(VacFile *f, char *oelem) +{ + int i, n; + MetaBlock mb; + MetaEntry me, me2; + VacFile *fp; + VtBlock *b; + u32int bo; + + if(!f->dirty) + return 0; + + if(oelem == nil) + oelem = f->dir.elem; + + /* + * Locate f's old metadata in the parent's metadata file. + * We know which block it was in, but not exactly where + * in the block. + */ + fp = f->up; + if(vtfilelock(fp->msource, -1) < 0) + return -1; + /* can happen if source is clri'ed out from under us */ + if(f->boff == NilBlock) + goto Err1; + b = vtfileblock(fp->msource, f->boff, VtORDWR); + if(b == nil) + goto Err1; + if(mbunpack(&mb, b->data, fp->msource->dsize) < 0) + goto Err; + if(mbsearch(&mb, oelem, &i, &me) < 0) + goto Err; + + /* + * Check whether we can resize the entry and keep it + * in this block. + */ + n = vdsize(&f->dir, VacDirVersion); + if(mbresize(&mb, &me, n) >= 0){ + /* Okay, can be done without moving to another block. */ + + /* Remove old data */ + mbdelete(&mb, i, &me); + + /* Find new location if renaming */ + if(strcmp(f->dir.elem, oelem) != 0) + mbsearch(&mb, f->dir.elem, &i, &me2); + + /* Pack new data into new location. */ + vdpack(&f->dir, &me, VacDirVersion); +vdunpack(&f->dir, &me); + mbinsert(&mb, i, &me); + mbpack(&mb); + + /* Done */ + vtblockput(b); + vtfileunlock(fp->msource); + f->dirty = 0; + return 0; + } + + /* + * The entry must be moved to another block. + * This can only really happen on renames that + * make the name very long. + */ + + /* Allocate a spot in a new block. */ + if((bo = filemetaalloc(fp, &f->dir, f->boff+1)) == NilBlock){ + /* mbresize above might have modified block */ + mbpack(&mb); + goto Err; + } + f->boff = bo; + + /* Now we're committed. Delete entry in old block. */ + mbdelete(&mb, i, &me); + mbpack(&mb); + vtblockput(b); + vtfileunlock(fp->msource); + + f->dirty = 0; + return 0; + +Err: + vtblockput(b); +Err1: + vtfileunlock(fp->msource); + return -1; +} + +/* + * Remove the directory entry for f. + */ +static int +filemetaremove(VacFile *f) +{ + VtBlock *b; + MetaBlock mb; + MetaEntry me; + int i; + VacFile *fp; + + b = nil; + fp = f->up; + filemetalock(f); + + if(vtfilelock(fp->msource, VtORDWR) < 0) + goto Err; + b = vtfileblock(fp->msource, f->boff, VtORDWR); + if(b == nil) + goto Err; + + if(mbunpack(&mb, b->data, fp->msource->dsize) < 0) + goto Err; + if(mbsearch(&mb, f->dir.elem, &i, &me) < 0) + goto Err; + mbdelete(&mb, i, &me); + mbpack(&mb); + vtblockput(b); + vtfileunlock(fp->msource); + + f->removed = 1; + f->boff = NilBlock; + f->dirty = 0; + + filemetaunlock(f); + return 0; + +Err: + vtfileunlock(fp->msource); + vtblockput(b); + filemetaunlock(f); + return -1; +} + +/* + * That was far too much effort for directory entries. + * Now we can write code that *does* things. + */ + +/* + * Flush all data associated with f out of the cache and onto venti. + * If recursive is set, flush f's children too. + * Vacfiledecref knows how to flush source and msource too. + */ +int +vacfileflush(VacFile *f, int recursive) +{ + int ret; + VacFile **kids, *p; + int i, nkids; + + if(f->mode == VtOREAD) + return 0; + + ret = 0; + filemetalock(f); + if(filemetaflush(f, nil) < 0) + ret = -1; + filemetaunlock(f); + + if(filelock(f) < 0) + return -1; + + /* + * Lock order prevents us from flushing kids while holding + * lock, so make a list and then flush without the lock. + */ + nkids = 0; + kids = nil; + if(recursive){ + nkids = 0; + for(p=f->down; p; p=p->next) + nkids++; + kids = vtmalloc(nkids*sizeof(VacFile*)); + i = 0; + for(p=f->down; p; p=p->next){ + kids[i++] = p; + p->ref++; + } + } + if(nkids > 0){ + fileunlock(f); + for(i=0; i<nkids; i++){ + if(vacfileflush(kids[i], 1) < 0) + ret = -1; + vacfiledecref(kids[i]); + } + filelock(f); + } + free(kids); + + /* + * Now we can flush our own data. + */ + vtfilelock(f->source, -1); + if(vtfileflush(f->source) < 0) + ret = -1; + vtfileunlock(f->source); + if(f->msource){ + vtfilelock(f->msource, -1); + if(vtfileflush(f->msource) < 0) + ret = -1; + vtfileunlock(f->msource); + } + fileunlock(f); + + return ret; +} + +/* + * Create a new file named elem in fp with the given mode. + * The mode can be changed later except for the ModeDir bit. + */ +VacFile* +vacfilecreate(VacFile *fp, char *elem, ulong mode) +{ + VacFile *ff; + VacDir *dir; + VtFile *pr, *r, *mr; + int type; + u32int bo; + + if(filelock(fp) < 0) + return nil; + + /* + * First, look to see that there's not a file in memory + * with the same name. + */ + for(ff = fp->down; ff; ff=ff->next){ + if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){ + ff = nil; + werrstr(EExists); + goto Err1; + } + } + + /* + * Next check the venti blocks. + */ + ff = dirlookup(fp, elem); + if(ff != nil){ + werrstr(EExists); + goto Err1; + } + + /* + * By the way, you can't create in a read-only file system. + */ + pr = fp->source; + if(pr->mode != VtORDWR){ + werrstr(EReadOnly); + goto Err1; + } + + /* + * Okay, time to actually create something. Lock the two + * halves of the directory and create a file. + */ + if(vtfilelock2(fp->source, fp->msource, -1) < 0) + goto Err1; + ff = filealloc(fp->fs); + ff->qidoffset = fp->qidoffset; /* hopefully fp->qidoffset == 0 */ + type = VtDataType; + if(mode & ModeDir) + type = VtDirType; + mr = nil; + if((r = vtfilecreate(pr, pr->psize, pr->dsize, type)) == nil) + goto Err; + if(mode & ModeDir) + if((mr = vtfilecreate(pr, pr->psize, pr->dsize, VtDataType)) == nil) + goto Err; + + /* + * Fill in the directory entry and write it to disk. + */ + dir = &ff->dir; + dir->elem = vtstrdup(elem); + dir->entry = r->offset; + dir->gen = r->gen; + if(mode & ModeDir){ + dir->mentry = mr->offset; + dir->mgen = mr->gen; + } + dir->size = 0; + if(_vacfsnextqid(fp->fs, &dir->qid) < 0) + goto Err; + dir->uid = vtstrdup(fp->dir.uid); + dir->gid = vtstrdup(fp->dir.gid); + dir->mid = vtstrdup(""); + dir->mtime = time(0L); + dir->mcount = 0; + dir->ctime = dir->mtime; + dir->atime = dir->mtime; + dir->mode = mode; + if((bo = filemetaalloc(fp, &ff->dir, NilBlock)) == NilBlock) + goto Err; + + /* + * Now we're committed. + */ + vtfileunlock(fp->source); + vtfileunlock(fp->msource); + ff->source = r; + ff->msource = mr; + ff->boff = bo; + + /* Link into tree. */ + ff->next = fp->down; + fp->down = ff; + ff->up = fp; + vacfileincref(fp); + + fileunlock(fp); + + filelock(ff); + vtfilelock(ff->source, -1); + vtfileunlock(ff->source); + fileunlock(ff); + + return ff; + +Err: + vtfileunlock(fp->source); + vtfileunlock(fp->msource); + if(r){ + vtfilelock(r, -1); + vtfileremove(r); + } + if(mr){ + vtfilelock(mr, -1); + vtfileremove(mr); + } +Err1: + if(ff) + vacfiledecref(ff); + fileunlock(fp); + return nil; +} + +/* + * Change the size of the file f. + */ +int +vacfilesetsize(VacFile *f, uvlong size) +{ + if(vacfileisdir(f)){ + werrstr(ENotFile); + return -1; + } + + if(filelock(f) < 0) + return -1; + + if(f->source->mode != VtORDWR){ + werrstr(EReadOnly); + goto Err; + } + if(vtfilelock(f->source, -1) < 0) + goto Err; + if(vtfilesetsize(f->source, size) < 0){ + vtfileunlock(f->source); + goto Err; + } + vtfileunlock(f->source); + fileunlock(f); + return 0; + +Err: + fileunlock(f); + return -1; +} + +/* + * Write data to f. + */ +int +vacfilewrite(VacFile *f, void *buf, int cnt, vlong offset) +{ + if(vacfileisdir(f)){ + werrstr(ENotFile); + return -1; + } + if(filelock(f) < 0) + return -1; + if(f->source->mode != VtORDWR){ + werrstr(EReadOnly); + goto Err; + } + if(offset < 0){ + werrstr(EBadOffset); + goto Err; + } + + if(vtfilelock(f->source, -1) < 0) + goto Err; + if(f->dir.mode & ModeAppend) + offset = vtfilegetsize(f->source); + if(vtfilewrite(f->source, buf, cnt, offset) != cnt + || vtfileflushbefore(f->source, offset) < 0){ + vtfileunlock(f->source); + goto Err; + } + vtfileunlock(f->source); + fileunlock(f); + return cnt; + +Err: + fileunlock(f); + return -1; +} + +/* + * Set (!) the VtEntry for the data contained in f. + * This let's us efficiently copy data from one file to another. + */ +int +vacfilesetentries(VacFile *f, VtEntry *e, VtEntry *me) +{ + int ret; + + vacfileflush(f, 0); /* flush blocks to venti, since we won't see them again */ + + if(!(e->flags&VtEntryActive)){ + werrstr("missing entry for source"); + return -1; + } + if(me && !(me->flags&VtEntryActive)) + me = nil; + if(f->msource && !me){ + werrstr("missing entry for msource"); + return -1; + } + if(me && !f->msource){ + werrstr("no msource to set"); + return -1; + } + + if(filelock(f) < 0) + return -1; + if(f->source->mode != VtORDWR + || (f->msource && f->msource->mode != VtORDWR)){ + werrstr(EReadOnly); + fileunlock(f); + return -1; + } + if(vtfilelock2(f->source, f->msource, -1) < 0){ + fileunlock(f); + return -1; + } + ret = 0; + if(vtfilesetentry(f->source, e) < 0) + ret = -1; + else if(me && vtfilesetentry(f->msource, me) < 0) + ret = -1; + + vtfileunlock(f->source); + if(f->msource) + vtfileunlock(f->msource); + fileunlock(f); + return ret; +} + +/* + * Get the directory entry for f. + */ +int +vacfilegetdir(VacFile *f, VacDir *dir) +{ + if(filerlock(f) < 0) + return -1; + + filemetalock(f); + vdcopy(dir, &f->dir); + filemetaunlock(f); + + if(!vacfileisdir(f)){ + if(vtfilelock(f->source, VtOREAD) < 0){ + filerunlock(f); + return -1; + } + dir->size = vtfilegetsize(f->source); + vtfileunlock(f->source); + } + filerunlock(f); + + return 0; +} + +/* + * Set the directory entry for f. + */ +int +vacfilesetdir(VacFile *f, VacDir *dir) +{ + VacFile *ff; + char *oelem; + u32int mask; + u64int size; + + /* can not set permissions for the root */ + if(vacfileisroot(f)){ + werrstr(ERoot); + return -1; + } + + if(filelock(f) < 0) + return -1; + filemetalock(f); + + if(f->source->mode != VtORDWR){ + werrstr(EReadOnly); + goto Err; + } + + /* On rename, check new name does not already exist */ + if(strcmp(f->dir.elem, dir->elem) != 0){ + for(ff = f->up->down; ff; ff=ff->next){ + if(strcmp(dir->elem, ff->dir.elem) == 0 && !ff->removed){ + werrstr(EExists); + goto Err; + } + } + ff = dirlookup(f->up, dir->elem); + if(ff != nil){ + vacfiledecref(ff); + werrstr(EExists); + goto Err; + } + werrstr(""); /* "failed" dirlookup poisoned it */ + } + + /* Get ready... */ + if(vtfilelock2(f->source, f->msource, -1) < 0) + goto Err; + if(!vacfileisdir(f)){ + size = vtfilegetsize(f->source); + if(size != dir->size){ + if(vtfilesetsize(f->source, dir->size) < 0){ + vtfileunlock(f->source); + if(f->msource) + vtfileunlock(f->msource); + goto Err; + } + } + } + /* ... now commited to changing it. */ + vtfileunlock(f->source); + if(f->msource) + vtfileunlock(f->msource); + + oelem = nil; + if(strcmp(f->dir.elem, dir->elem) != 0){ + oelem = f->dir.elem; + f->dir.elem = vtstrdup(dir->elem); + } + + if(strcmp(f->dir.uid, dir->uid) != 0){ + vtfree(f->dir.uid); + f->dir.uid = vtstrdup(dir->uid); + } + + if(strcmp(f->dir.gid, dir->gid) != 0){ + vtfree(f->dir.gid); + f->dir.gid = vtstrdup(dir->gid); + } + + f->dir.mtime = dir->mtime; + f->dir.atime = dir->atime; + + mask = ~(ModeDir|ModeSnapshot); + f->dir.mode &= ~mask; + f->dir.mode |= mask & dir->mode; + f->dirty = 1; + + if(filemetaflush(f, oelem) < 0){ + vtfree(oelem); + goto Err; /* that sucks */ + } + vtfree(oelem); + + filemetaunlock(f); + fileunlock(f); + return 0; + +Err: + filemetaunlock(f); + fileunlock(f); + return -1; +} + +/* + * Set the qid space. + */ +int +vacfilesetqidspace(VacFile *f, u64int offset, u64int max) +{ + int ret; + + if(filelock(f) < 0) + return -1; + if(f->source->mode != VtORDWR){ + fileunlock(f); + werrstr(EReadOnly); + return -1; + } + filemetalock(f); + f->dir.qidspace = 1; + f->dir.qidoffset = offset; + f->dir.qidmax = max; + f->dirty = 1; + ret = filemetaflush(f, nil); + filemetaunlock(f); + fileunlock(f); + return ret; +} + +/* + * Check that the file is empty, returning 0 if it is. + * Returns -1 on error (and not being empty is an error). + */ +static int +filecheckempty(VacFile *f) +{ + u32int i, n; + VtBlock *b; + MetaBlock mb; + VtFile *r; + + r = f->msource; + n = (vtfilegetsize(r)+r->dsize-1)/r->dsize; + for(i=0; i<n; i++){ + b = vtfileblock(r, i, VtOREAD); + if(b == nil) + return -1; + if(mbunpack(&mb, b->data, r->dsize) < 0) + goto Err; + if(mb.nindex > 0){ + werrstr(ENotEmpty); + goto Err; + } + vtblockput(b); + } + return 0; + +Err: + vtblockput(b); + return -1; +} + +/* + * Remove the vac file f. + */ +int +vacfileremove(VacFile *f) +{ + VacFile *ff; + + /* Cannot remove the root */ + if(vacfileisroot(f)){ + werrstr(ERoot); + return -1; + } + + if(filelock(f) < 0) + return -1; + if(f->source->mode != VtORDWR){ + werrstr(EReadOnly); + goto Err1; + } + if(vtfilelock2(f->source, f->msource, -1) < 0) + goto Err1; + if(vacfileisdir(f) && filecheckempty(f)<0) + goto Err; + + for(ff=f->down; ff; ff=ff->next) + assert(ff->removed); + + vtfileremove(f->source); + f->source = nil; + if(f->msource){ + vtfileremove(f->msource); + f->msource = nil; + } + fileunlock(f); + + if(filemetaremove(f) < 0) + return -1; + return 0; + +Err: + vtfileunlock(f->source); + if(f->msource) + vtfileunlock(f->msource); +Err1: + fileunlock(f); + return -1; +} + +/* + * Vac file system format. + */ +static char EBadVacFormat[] = "bad format for vac file"; + +static VacFs * +vacfsalloc(VtConn *z, int bsize, int ncache, int mode) +{ + VacFs *fs; + + fs = vtmallocz(sizeof(VacFs)); + fs->z = z; + fs->bsize = bsize; + fs->mode = mode; + fs->cache = vtcachealloc(z, bsize, ncache); + return fs; +} + +static int +readscore(int fd, uchar score[VtScoreSize]) +{ + char buf[45], *pref; + int n; + + n = readn(fd, buf, sizeof(buf)-1); + if(n < sizeof(buf)-1) { + werrstr("short read"); + return -1; + } + buf[n] = 0; + + if(vtparsescore(buf, &pref, score) < 0){ + werrstr(EBadVacFormat); + return -1; + } + if(pref==nil || strcmp(pref, "vac") != 0) { + werrstr("not a vac file"); + return -1; + } + return 0; +} + +VacFs* +vacfsopen(VtConn *z, char *file, int mode, int ncache) +{ + int fd; + uchar score[VtScoreSize]; + char *prefix; + + if(vtparsescore(file, &prefix, score) >= 0){ + if(strcmp(prefix, "vac") != 0){ + werrstr("not a vac file"); + return nil; + } + }else{ + fd = open(file, OREAD); + if(fd < 0) + return nil; + if(readscore(fd, score) < 0){ + close(fd); + return nil; + } + close(fd); + } + return vacfsopenscore(z, score, mode, ncache); +} + +VacFs* +vacfsopenscore(VtConn *z, u8int *score, int mode, int ncache) +{ + VacFs *fs; + int n; + VtRoot rt; + uchar buf[VtRootSize]; + VacFile *root; + VtFile *r; + VtEntry e; + + n = vtread(z, score, VtRootType, buf, VtRootSize); + if(n < 0) + return nil; + if(n != VtRootSize){ + werrstr("vtread on root too short"); + return nil; + } + + if(vtrootunpack(&rt, buf) < 0) + return nil; + + if(strcmp(rt.type, "vac") != 0) { + werrstr("not a vac root"); + return nil; + } + + fs = vacfsalloc(z, rt.blocksize, ncache, mode); + memmove(fs->score, score, VtScoreSize); + fs->mode = mode; + + memmove(e.score, rt.score, VtScoreSize); + e.gen = 0; + e.psize = rt.blocksize; + e.dsize = rt.blocksize; + e.type = VtDirType; + e.flags = VtEntryActive; + e.size = 3*VtEntrySize; + + root = nil; + if((r = vtfileopenroot(fs->cache, &e)) == nil) + goto Err; + if(debug) + fprint(2, "r %p\n", r); + root = _vacfileroot(fs, r); + if(debug) + fprint(2, "root %p\n", root); + vtfileclose(r); + if(root == nil) + goto Err; + fs->root = root; + return fs; +Err: + if(root) + vacfiledecref(root); + vacfsclose(fs); + return nil; +} + +int +vacfsmode(VacFs *fs) +{ + return fs->mode; +} + +VacFile* +vacfsgetroot(VacFs *fs) +{ + return vacfileincref(fs->root); +} + +int +vacfsgetblocksize(VacFs *fs) +{ + return fs->bsize; +} + +int +vacfsgetscore(VacFs *fs, u8int *score) +{ + memmove(score, fs->score, VtScoreSize); + return 0; +} + +int +_vacfsnextqid(VacFs *fs, uvlong *qid) +{ + ++fs->qid; + *qid = fs->qid; + return 0; +} + +void +vacfsjumpqid(VacFs *fs, uvlong step) +{ + fs->qid += step; +} + +/* + * Set *maxqid to the maximum qid expected in this file system. + * In newer vac archives, the maximum qid is stored in the + * qidspace VacDir annotation. In older vac archives, the root + * got created last, so it had the maximum qid. + */ +int +vacfsgetmaxqid(VacFs *fs, uvlong *maxqid) +{ + VacDir vd; + + if(vacfilegetdir(fs->root, &vd) < 0) + return -1; + if(vd.qidspace) + *maxqid = vd.qidmax; + else + *maxqid = vd.qid; + vdcleanup(&vd); + return 0; +} + + +void +vacfsclose(VacFs *fs) +{ + if(fs->root) + vacfiledecref(fs->root); + fs->root = nil; + vtcachefree(fs->cache); + vtfree(fs); +} + +/* + * Create a fresh vac fs. + */ +VacFs * +vacfscreate(VtConn *z, int bsize, int ncache) +{ + VacFs *fs; + VtFile *f; + uchar buf[VtEntrySize], metascore[VtScoreSize]; + VtEntry e; + VtBlock *b; + MetaBlock mb; + VacDir vd; + MetaEntry me; + int psize; + int mbsize; + + if((fs = vacfsalloc(z, bsize, ncache, VtORDWR)) == nil) + return nil; + + /* + * Fake up an empty vac fs. + */ + psize = bsize; + f = vtfilecreateroot(fs->cache, psize, bsize, VtDirType); + vtfilelock(f, VtORDWR); + + /* Metablocks can't be too big -- they have 16-bit offsets in them. */ + mbsize = bsize; + if(mbsize >= 56*1024) + mbsize = 56*1024; + + /* Write metablock containing root directory VacDir. */ + b = vtcacheallocblock(fs->cache, VtDataType); + mbinit(&mb, b->data, mbsize, mbsize/BytesPerEntry); + memset(&vd, 0, sizeof vd); + vd.elem = "/"; + vd.mode = 0777|ModeDir; + vd.uid = "vac"; + vd.gid = "vac"; + vd.mid = ""; + me.size = vdsize(&vd, VacDirVersion); + me.p = mballoc(&mb, me.size); + vdpack(&vd, &me, VacDirVersion); + mbinsert(&mb, 0, &me); + mbpack(&mb); + vtblockwrite(b); + memmove(metascore, b->score, VtScoreSize); + vtblockput(b); + + /* First entry: empty venti directory stream. */ + memset(&e, 0, sizeof e); + e.flags = VtEntryActive; + e.psize = psize; + e.dsize = bsize; + e.type = VtDirType; + memmove(e.score, vtzeroscore, VtScoreSize); + vtentrypack(&e, buf, 0); + vtfilewrite(f, buf, VtEntrySize, 0); + + /* Second entry: empty metadata stream. */ + e.type = VtDataType; + e.dsize = mbsize; + vtentrypack(&e, buf, 0); + vtfilewrite(f, buf, VtEntrySize, VtEntrySize); + + /* Third entry: metadata stream with root directory. */ + memmove(e.score, metascore, VtScoreSize); + e.size = mbsize; + vtentrypack(&e, buf, 0); + vtfilewrite(f, buf, VtEntrySize, VtEntrySize*2); + + vtfileflush(f); + vtfileunlock(f); + + /* Now open it as a vac fs. */ + fs->root = _vacfileroot(fs, f); + if(fs->root == nil){ + werrstr("vacfileroot: %r"); + vacfsclose(fs); + return nil; + } + + return fs; +} + +int +vacfssync(VacFs *fs) +{ + uchar buf[1024]; + VtEntry e; + VtFile *f; + VtRoot root; + + /* Sync the entire vacfs to disk. */ + if(vacfileflush(fs->root, 1) < 0) + return -1; + if(vtfilelock(fs->root->up->msource, -1) < 0) + return -1; + if(vtfileflush(fs->root->up->msource) < 0){ + vtfileunlock(fs->root->up->msource); + return -1; + } + vtfileunlock(fs->root->up->msource); + + /* Prepare the dir stream for the root block. */ + if(getentry(fs->root->source, &e) < 0) + return -1; + vtentrypack(&e, buf, 0); + if(getentry(fs->root->msource, &e) < 0) + return -1; + vtentrypack(&e, buf, 1); + if(getentry(fs->root->up->msource, &e) < 0) + return -1; + vtentrypack(&e, buf, 2); + + f = vtfilecreateroot(fs->cache, fs->bsize, fs->bsize, VtDirType); + vtfilelock(f, VtORDWR); + if(vtfilewrite(f, buf, 3*VtEntrySize, 0) < 0 + || vtfileflush(f) < 0){ + vtfileunlock(f); + vtfileclose(f); + return -1; + } + vtfileunlock(f); + if(getentry(f, &e) < 0){ + vtfileclose(f); + return -1; + } + vtfileclose(f); + + /* Build a root block. */ + memset(&root, 0, sizeof root); + strcpy(root.type, "vac"); + strcpy(root.name, fs->name); + memmove(root.score, e.score, VtScoreSize); + root.blocksize = fs->bsize; + memmove(root.prev, fs->score, VtScoreSize); + vtrootpack(&root, buf); + if(vtwrite(fs->z, fs->score, VtRootType, buf, VtRootSize) < 0){ + werrstr("writing root: %r"); + return -1; + } + if(vtsync(fs->z) < 0) + return -1; + return 0; +} + +int +vacfiledsize(VacFile *f) +{ + VtEntry e; + + if(vacfilegetentries(f,&e,nil) < 0) + return -1; + return e.dsize; +} + +/* + * Does block b of f have the same SHA1 hash as the n bytes at buf? + */ +int +sha1matches(VacFile *f, ulong b, uchar *buf, int n) +{ + uchar fscore[VtScoreSize]; + uchar bufscore[VtScoreSize]; + + if(vacfileblockscore(f, b, fscore) < 0) + return 0; + n = vtzerotruncate(VtDataType, buf, n); + sha1(buf, n, bufscore, nil); + if(memcmp(bufscore, fscore, VtScoreSize) == 0) + return 1; + return 0; +} + diff --git a/sys/src/cmd/vac/fns.h b/sys/src/cmd/vac/fns.h new file mode 100755 index 000000000..0f691b1e1 --- /dev/null +++ b/sys/src/cmd/vac/fns.h @@ -0,0 +1,30 @@ +int mbunpack(MetaBlock *mb, uchar *p, int n); +void mbinsert(MetaBlock *mb, int i, MetaEntry*); +void mbdelete(MetaBlock *mb, int i, MetaEntry*); +void mbpack(MetaBlock *mb); +uchar *mballoc(MetaBlock *mb, int n); +void mbinit(MetaBlock *mb, uchar *p, int n, int entries); +int mbsearch(MetaBlock*, char*, int*, MetaEntry*); +int mbresize(MetaBlock*, MetaEntry*, int); + +int meunpack(MetaEntry*, MetaBlock *mb, int i); +int mecmp(MetaEntry*, char *s); +int mecmpnew(MetaEntry*, char *s); + +enum { + VacDirVersion = 8, + FossilDirVersion = 9, +}; +int vdsize(VacDir *dir, int); +int vdunpack(VacDir *dir, MetaEntry*); +void vdpack(VacDir *dir, MetaEntry*, int); + +VacFile *_vacfileroot(VacFs *fs, VtFile *file); + +int _vacfsnextqid(VacFs *fs, uvlong *qid); +void vacfsjumpqid(VacFs*, uvlong step); + +Reprog* glob2regexp(char*); +void loadexcludefile(char*); +int includefile(char*); +void excludepattern(char*); diff --git a/sys/src/cmd/vac/glob.c b/sys/src/cmd/vac/glob.c new file mode 100755 index 000000000..863eb8486 --- /dev/null +++ b/sys/src/cmd/vac/glob.c @@ -0,0 +1,180 @@ +#include "stdinc.h" +#include "vac.h" +#include "dat.h" +#include "fns.h" +#include "error.h" + +// Convert globbish pattern to regular expression +// The wildcards are +// +// * any non-slash characters +// ... any characters including / +// ? any single character except / +// [a-z] character class +// [~a-z] negated character class +// + +Reprog* +glob2regexp(char *glob) +{ + char *s, *p, *w; + Reprog *re; + int boe; // beginning of path element + + s = malloc(20*(strlen(glob)+1)); + if(s == nil) + return nil; + w = s; + boe = 1; + *w++ = '^'; + *w++ = '('; + for(p=glob; *p; p++){ + if(p[0] == '.' && p[1] == '.' && p[2] == '.'){ + strcpy(w, ".*"); + w += strlen(w); + p += 3-1; + boe = 0; + continue; + } + if(p[0] == '*'){ + if(boe) + strcpy(w, "([^./][^/]*)?"); + else + strcpy(w, "[^/]*"); + w += strlen(w); + boe = 0; + continue; + } + if(p[0] == '?'){ + if(boe) + strcpy(w, "[^./]"); + else + strcpy(w, "[^/]"); + w += strlen(w); + boe = 0; + continue; + } + if(p[0] == '['){ + *w++ = '['; + if(*++p == '~'){ + *w++ = '^'; + p++; + } + while(*p != ']'){ + if(*p == '/') + goto syntax; + if(*p == '^' || *p == '\\') + *w++ = '\\'; + *w++ = *p++; + } + *w++ = ']'; + boe = 0; + continue; + } + if(strchr("()|^$[]*?+\\.", *p)){ + *w++ = '\\'; + *w++ = *p; + boe = 0; + continue; + } + if(*p == '/'){ + *w++ = '/'; + boe = 1; + continue; + } + *w++ = *p; + boe = 0; + continue; + } + *w++ = ')'; + *w++ = '$'; + *w = 0; + + re = regcomp(s); + if(re == nil){ + syntax: + free(s); + werrstr("glob syntax error"); + return nil; + } + free(s); + return re; +} + +typedef struct Pattern Pattern; +struct Pattern +{ + Reprog *re; + int include; +}; + +Pattern *pattern; +int npattern; + +void +loadexcludefile(char *file) +{ + Biobuf *b; + char *p, *q; + int n, inc; + Reprog *re; + + if((b = Bopen(file, OREAD)) == nil) + sysfatal("open %s: %r", file); + for(n=1; (p=Brdstr(b, '\n', 1)) != nil; free(p), n++){ + q = p+strlen(p); + while(q > p && isspace((uchar)*(q-1))) + *--q = 0; + switch(p[0]){ + case '\0': + case '#': + continue; + } + + inc = 0; + if(strncmp(p, "include ", 8) == 0){ + inc = 1; + }else if(strncmp(p, "exclude ", 8) == 0){ + inc = 0; + }else + sysfatal("%s:%d: line does not begin with include or exclude", file, n); + + if(strchr(p+8, ' ')) + fprint(2, "%s:%d: warning: space in pattern\n", file, n); + + if((re = glob2regexp(p+8)) == nil) + sysfatal("%s:%d: bad glob pattern", file, n); + + pattern = vtrealloc(pattern, (npattern+1)*sizeof pattern[0]); + pattern[npattern].re = re; + pattern[npattern].include = inc; + npattern++; + } + Bterm(b); +} + +void +excludepattern(char *p) +{ + Reprog *re; + + if((re = glob2regexp(p)) == nil) + sysfatal("bad glob pattern %s", p); + + pattern = vtrealloc(pattern, (npattern+1)*sizeof pattern[0]); + pattern[npattern].re = re; + pattern[npattern].include = 0; + npattern++; +} + +int +includefile(char *file) +{ + Pattern *p, *ep; + + for(p=pattern, ep=p+npattern; p<ep; p++) + if(regexec(p->re, file, nil, 0)) + return p->include; + return 1; +} + diff --git a/sys/src/cmd/vac/mkfile b/sys/src/cmd/vac/mkfile new file mode 100755 index 000000000..8572d7aef --- /dev/null +++ b/sys/src/cmd/vac/mkfile @@ -0,0 +1,32 @@ +</$objtype/mkfile + +LIBFILES=\ + error\ + file\ + glob\ + pack\ + +LIB=${LIBFILES:%=%.$O} + +HFILES=\ + /sys/include/venti.h\ + stdinc.h\ + error.h\ + vac.h\ + dat.h\ + fns.h\ + +TARG=vac unvac vacfs +BIN=/$objtype/bin + +CFILES=${TARG:%=%.c} ${LIBFILES:%=%.c} + +UPDATE=\ + mkfile\ + $CFILES\ + $HFILES\ + ${TARG:%=/386/bin/%} + +default:V: all + +</sys/src/cmd/mkmany diff --git a/sys/src/cmd/vac/pack.c b/sys/src/cmd/vac/pack.c new file mode 100755 index 000000000..9777464f6 --- /dev/null +++ b/sys/src/cmd/vac/pack.c @@ -0,0 +1,736 @@ +#include "stdinc.h" +#include "vac.h" +#include "dat.h" +#include "fns.h" +#include "error.h" + +typedef struct MetaChunk MetaChunk; + +struct MetaChunk { + ushort offset; + ushort size; + ushort index; +}; + +static int stringunpack(char **s, uchar **p, int *n); + +/* + * integer conversion routines + */ +#define U8GET(p) ((p)[0]) +#define U16GET(p) (((p)[0]<<8)|(p)[1]) +#define U32GET(p) (((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3]) +#define U48GET(p) (((uvlong)U16GET(p)<<32)|(uvlong)U32GET((p)+2)) +#define U64GET(p) (((uvlong)U32GET(p)<<32)|(uvlong)U32GET((p)+4)) + +#define U8PUT(p,v) (p)[0]=(v)&0xFF +#define U16PUT(p,v) (p)[0]=((v)>>8)&0xFF;(p)[1]=(v)&0xFF +#define U32PUT(p,v) (p)[0]=((v)>>24)&0xFF;(p)[1]=((v)>>16)&0xFF;(p)[2]=((v)>>8)&0xFF;(p)[3]=(v)&0xFF +#define U48PUT(p,v,t32) t32=(v)>>32;U16PUT(p,t32);t32=(v);U32PUT((p)+2,t32) +#define U64PUT(p,v,t32) t32=(v)>>32;U32PUT(p,t32);t32=(v);U32PUT((p)+4,t32) + +static int +stringunpack(char **s, uchar **p, int *n) +{ + int nn; + + if(*n < 2) + return -1; + + nn = U16GET(*p); + *p += 2; + *n -= 2; + if(nn > *n) + return -1; + *s = vtmalloc(nn+1); + memmove(*s, *p, nn); + (*s)[nn] = 0; + *p += nn; + *n -= nn; + return 0; +} + +static int +stringpack(char *s, uchar *p) +{ + int n; + + n = strlen(s); + U16PUT(p, n); + memmove(p+2, s, n); + return n+2; +} + + +int +mbunpack(MetaBlock *mb, uchar *p, int n) +{ + u32int magic; + + mb->maxsize = n; + mb->buf = p; + + if(n == 0) { + memset(mb, 0, sizeof(MetaBlock)); + return 0; + } + + magic = U32GET(p); + if(magic != MetaMagic && magic != MetaMagic+1) { + werrstr("bad meta block magic"); + return -1; + } + mb->size = U16GET(p+4); + mb->free = U16GET(p+6); + mb->maxindex = U16GET(p+8); + mb->nindex = U16GET(p+10); + mb->unbotch = (magic == MetaMagic+1); + + if(mb->size > n) { + werrstr("bad meta block size"); + return -1; + } + p += MetaHeaderSize; + n -= MetaHeaderSize; + + USED(p); + if(n < mb->maxindex*MetaIndexSize) { + werrstr("truncated meta block 2"); + return -1; + } + return 0; +} + +void +mbpack(MetaBlock *mb) +{ + uchar *p; + + p = mb->buf; + + U32PUT(p, MetaMagic); + U16PUT(p+4, mb->size); + U16PUT(p+6, mb->free); + U16PUT(p+8, mb->maxindex); + U16PUT(p+10, mb->nindex); +} + + +void +mbdelete(MetaBlock *mb, int i, MetaEntry *me) +{ + uchar *p; + int n; + + assert(i < mb->nindex); + + if(me->p - mb->buf + me->size == mb->size) + mb->size -= me->size; + else + mb->free += me->size; + + p = mb->buf + MetaHeaderSize + i*MetaIndexSize; + n = (mb->nindex-i-1)*MetaIndexSize; + memmove(p, p+MetaIndexSize, n); + memset(p+n, 0, MetaIndexSize); + mb->nindex--; +} + +void +mbinsert(MetaBlock *mb, int i, MetaEntry *me) +{ + uchar *p; + int o, n; + + assert(mb->nindex < mb->maxindex); + + o = me->p - mb->buf; + n = me->size; + if(o+n > mb->size) { + mb->free -= mb->size - o; + mb->size = o + n; + } else + mb->free -= n; + + p = mb->buf + MetaHeaderSize + i*MetaIndexSize; + n = (mb->nindex-i)*MetaIndexSize; + memmove(p+MetaIndexSize, p, n); + U16PUT(p, me->p - mb->buf); + U16PUT(p+2, me->size); + mb->nindex++; +} + +int +meunpack(MetaEntry *me, MetaBlock *mb, int i) +{ + uchar *p; + int eo, en; + + if(i < 0 || i >= mb->nindex) { + werrstr("bad meta entry index"); + return -1; + } + + p = mb->buf + MetaHeaderSize + i*MetaIndexSize; + eo = U16GET(p); + en = U16GET(p+2); + +if(0)print("eo = %d en = %d\n", eo, en); + if(eo < MetaHeaderSize + mb->maxindex*MetaIndexSize) { + werrstr("corrupted entry in meta block"); + return -1; + } + + if(eo+en > mb->size) { + werrstr("truncated meta block"); + return -1; + } + + p = mb->buf + eo; + + /* make sure entry looks ok and includes an elem name */ + if(en < 8 || U32GET(p) != DirMagic || en < 8 + U16GET(p+6)) { + werrstr("corrupted meta block entry"); + return -1; + } + + me->p = p; + me->size = en; + + return 0; +} + +/* assumes a small amount of checking has been done in mbentry */ +int +mecmp(MetaEntry *me, char *s) +{ + int n; + uchar *p; + + p = me->p; + + p += 6; + n = U16GET(p); + p += 2; + + assert(n + 8 < me->size); + + while(n > 0) { + if(*s == 0) + return -1; + if(*p < (uchar)*s) + return -1; + if(*p > (uchar)*s) + return 1; + p++; + s++; + n--; + } + return *s != 0; +} + +int +mecmpnew(MetaEntry *me, char *s) +{ + int n; + uchar *p; + + p = me->p; + + p += 6; + n = U16GET(p); + p += 2; + + assert(n + 8 < me->size); + + while(n > 0) { + if(*s == 0) + return 1; + if(*p < (uchar)*s) + return -1; + if(*p > (uchar)*s) + return 1; + p++; + s++; + n--; + } + return -(*s != 0); +} + +static int +offsetcmp(const void *s0, const void *s1) +{ + const MetaChunk *mc0, *mc1; + + mc0 = s0; + mc1 = s1; + if(mc0->offset < mc1->offset) + return -1; + if(mc0->offset > mc1->offset) + return 1; + return 0; +} + +static MetaChunk * +metachunks(MetaBlock *mb) +{ + MetaChunk *mc; + int oo, o, n, i; + uchar *p; + + mc = vtmalloc(mb->nindex*sizeof(MetaChunk)); + p = mb->buf + MetaHeaderSize; + for(i = 0; i<mb->nindex; i++) { + mc[i].offset = U16GET(p); + mc[i].size = U16GET(p+2); + mc[i].index = i; + p += MetaIndexSize; + } + + qsort(mc, mb->nindex, sizeof(MetaChunk), offsetcmp); + + /* check block looks ok */ + oo = MetaHeaderSize + mb->maxindex*MetaIndexSize; + o = oo; + n = 0; + for(i=0; i<mb->nindex; i++) { + o = mc[i].offset; + n = mc[i].size; + if(o < oo) + goto Err; + oo += n; + } + if(o+n <= mb->size) + goto Err; + if(mb->size - oo != mb->free) + goto Err; + + return mc; +Err: + vtfree(mc); + return nil; +} + +static void +mbcompact(MetaBlock *mb, MetaChunk *mc) +{ + int oo, o, n, i; + + oo = MetaHeaderSize + mb->maxindex*MetaIndexSize; + + for(i=0; i<mb->nindex; i++) { + o = mc[i].offset; + n = mc[i].size; + if(o != oo) { + memmove(mb->buf + oo, mb->buf + o, n); + U16PUT(mb->buf + MetaHeaderSize + mc[i].index*MetaIndexSize, oo); + } + oo += n; + } + + mb->size = oo; + mb->free = 0; +} + +uchar * +mballoc(MetaBlock *mb, int n) +{ + int i, o; + MetaChunk *mc; + + /* off the end */ + if(mb->maxsize - mb->size >= n) + return mb->buf + mb->size; + + /* check if possible */ + if(mb->maxsize - mb->size + mb->free < n) + return nil; + + mc = metachunks(mb); + + /* look for hole */ + o = MetaHeaderSize + mb->maxindex*MetaIndexSize; + for(i=0; i<mb->nindex; i++) { + if(mc[i].offset - o >= n) { + vtfree(mc); + return mb->buf + o; + } + o = mc[i].offset + mc[i].size; + } + + if(mb->maxsize - o >= n) { + vtfree(mc); + return mb->buf + o; + } + + /* compact and return off the end */ + mbcompact(mb, mc); + vtfree(mc); + + assert(mb->maxsize - mb->size >= n); + return mb->buf + mb->size; +} + +int +vdsize(VacDir *dir, int version) +{ + int n; + + if(version < 8 || version > 9) + sysfatal("bad version %d in vdpack", version); + + /* constant part */ + n = 4 + /* magic */ + 2 + /* version */ + 4 + /* entry */ + 8 + /* qid */ + 4 + /* mtime */ + 4 + /* mcount */ + 4 + /* ctime */ + 4 + /* atime */ + 4 + /* mode */ + 0; + + if(version == 9){ + n += 4 + /* gen */ + 4 + /* mentry */ + 4 + /* mgen */ + 0; + } + + /* strings */ + n += 2 + strlen(dir->elem); + n += 2 + strlen(dir->uid); + n += 2 + strlen(dir->gid); + n += 2 + strlen(dir->mid); + + /* optional sections */ + if(version < 9 && dir->plan9) { + n += 3 + /* option header */ + 8 + /* path */ + 4; /* version */ + } + if(dir->qidspace) { + n += 3 + /* option header */ + 8 + /* qid offset */ + 8; /* qid max */ + } + if(version < 9 && dir->gen) { + n += 3 + /* option header */ + 4; /* gen */ + } + + return n; +} + +void +vdpack(VacDir *dir, MetaEntry *me, int version) +{ + uchar *p; + ulong t32; + + if(version < 8 || version > 9) + sysfatal("bad version %d in vdpack", version); + + p = me->p; + + U32PUT(p, DirMagic); + U16PUT(p+4, version); /* version */ + p += 6; + + p += stringpack(dir->elem, p); + + U32PUT(p, dir->entry); + p += 4; + + if(version == 9){ + U32PUT(p, dir->gen); + U32PUT(p+4, dir->mentry); + U32PUT(p+8, dir->mgen); + p += 12; + } + + U64PUT(p, dir->qid, t32); + p += 8; + + p += stringpack(dir->uid, p); + p += stringpack(dir->gid, p); + p += stringpack(dir->mid, p); + + U32PUT(p, dir->mtime); + U32PUT(p+4, dir->mcount); + U32PUT(p+8, dir->ctime); + U32PUT(p+12, dir->atime); + U32PUT(p+16, dir->mode); + p += 5*4; + + if(dir->plan9 && version < 9) { + U8PUT(p, DirPlan9Entry); + U16PUT(p+1, 8+4); + p += 3; + U64PUT(p, dir->p9path, t32); + U32PUT(p+8, dir->p9version); + p += 12; + } + + if(dir->qidspace) { + U8PUT(p, DirQidSpaceEntry); + U16PUT(p+1, 2*8); + p += 3; + U64PUT(p, dir->qidoffset, t32); + U64PUT(p+8, dir->qidmax, t32); + p += 16; + } + + if(dir->gen && version < 9) { + U8PUT(p, DirGenEntry); + U16PUT(p+1, 4); + p += 3; + U32PUT(p, dir->gen); + p += 4; + } + + assert(p == me->p + me->size); +} + +int +vdunpack(VacDir *dir, MetaEntry *me) +{ + int t, nn, n, version; + uchar *p; + + p = me->p; + n = me->size; + + memset(dir, 0, sizeof(VacDir)); + + /* magic */ + if(n < 4 || U32GET(p) != DirMagic) + goto Err; + p += 4; + n -= 4; + + /* version */ + if(n < 2) + goto Err; + version = U16GET(p); + if(version < 7 || version > 9) + goto Err; + p += 2; + n -= 2; + + /* elem */ + if(stringunpack(&dir->elem, &p, &n) < 0) + goto Err; + + /* entry */ + if(n < 4) + goto Err; + dir->entry = U32GET(p); + p += 4; + n -= 4; + + if(version < 9) { + dir->gen = 0; + dir->mentry = dir->entry+1; + dir->mgen = 0; + } else { + if(n < 3*4) + goto Err; + dir->gen = U32GET(p); + dir->mentry = U32GET(p+4); + dir->mgen = U32GET(p+8); + p += 3*4; + n -= 3*4; + } + + /* size is gotten from DirEntry */ + + /* qid */ + if(n < 8) + goto Err; + dir->qid = U64GET(p); + p += 8; + n -= 8; + + /* skip replacement */ + if(version == 7) { + if(n < VtScoreSize) + goto Err; + p += VtScoreSize; + n -= VtScoreSize; + } + + /* uid */ + if(stringunpack(&dir->uid, &p, &n) < 0) + goto Err; + + /* gid */ + if(stringunpack(&dir->gid, &p, &n) < 0) + goto Err; + + /* mid */ + if(stringunpack(&dir->mid, &p, &n) < 0) + goto Err; + + if(n < 5*4) + goto Err; + dir->mtime = U32GET(p); + dir->mcount = U32GET(p+4); + dir->ctime = U32GET(p+8); + dir->atime = U32GET(p+12); + dir->mode = U32GET(p+16); + p += 5*4; + n -= 5*4; + + /* optional meta data */ + while(n > 0) { + if(n < 3) + goto Err; + t = p[0]; + nn = U16GET(p+1); + p += 3; + n -= 3; + if(n < nn) + goto Err; + switch(t) { + case DirPlan9Entry: + /* not valid in version >= 9 */ + if(version >= 9) + break; + if(dir->plan9 || nn != 12) + goto Err; + dir->plan9 = 1; + dir->p9path = U64GET(p); + dir->p9version = U32GET(p+8); + if(dir->mcount == 0) + dir->mcount = dir->p9version; + break; + case DirGenEntry: + /* not valid in version >= 9 */ + if(version >= 9) + break; + break; + case DirQidSpaceEntry: + if(dir->qidspace || nn != 16) + goto Err; + dir->qidspace = 1; + dir->qidoffset = U64GET(p); + dir->qidmax = U64GET(p+8); + break; + } + p += nn; + n -= nn; + } + + if(p != me->p + me->size) + goto Err; + + return 0; +Err: + werrstr(EBadMeta); + vdcleanup(dir); + return -1; +} + +void +vdcleanup(VacDir *dir) +{ + vtfree(dir->elem); + dir->elem = nil; + vtfree(dir->uid); + dir->uid = nil; + vtfree(dir->gid); + dir->gid = nil; + vtfree(dir->mid); + dir->mid = nil; +} + +void +vdcopy(VacDir *dst, VacDir *src) +{ + *dst = *src; + dst->elem = vtstrdup(dst->elem); + dst->uid = vtstrdup(dst->uid); + dst->gid = vtstrdup(dst->gid); + dst->mid = vtstrdup(dst->mid); +} + +int +mbsearch(MetaBlock *mb, char *elem, int *ri, MetaEntry *me) +{ + int i; + int b, t, x; + + /* binary search within block */ + b = 0; + t = mb->nindex; + while(b < t) { + i = (b+t)>>1; + if(meunpack(me, mb, i) < 0) + return 0; + if(mb->unbotch) + x = mecmpnew(me, elem); + else + x = mecmp(me, elem); + + if(x == 0) { + *ri = i; + return 1; + } + + if(x < 0) + b = i+1; + else /* x > 0 */ + t = i; + } + + assert(b == t); + + *ri = b; /* b is the index to insert this entry */ + memset(me, 0, sizeof(*me)); + + return -1; +} + +void +mbinit(MetaBlock *mb, uchar *p, int n, int entries) +{ + memset(mb, 0, sizeof(MetaBlock)); + mb->maxsize = n; + mb->buf = p; + mb->maxindex = entries; + mb->size = MetaHeaderSize + mb->maxindex*MetaIndexSize; +} + +int +mbresize(MetaBlock *mb, MetaEntry *me, int n) +{ + uchar *p, *ep; + + /* easy case */ + if(n <= me->size){ + me->size = n; + return 0; + } + + /* try and expand entry */ + + p = me->p + me->size; + ep = mb->buf + mb->maxsize; + while(p < ep && *p == 0) + p++; + if(n <= p - me->p){ + me->size = n; + return 0; + } + + p = mballoc(mb, n); + if(p != nil){ + me->p = p; + me->size = n; + return 0; + } + + return -1; +} diff --git a/sys/src/cmd/vac/stdinc.h b/sys/src/cmd/vac/stdinc.h new file mode 100755 index 000000000..070c56b3f --- /dev/null +++ b/sys/src/cmd/vac/stdinc.h @@ -0,0 +1,8 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ctype.h> +#include <thread.h> +#include <venti.h> +#include <libsec.h> +#include <regexp.h> diff --git a/sys/src/cmd/vac/testinc.c b/sys/src/cmd/vac/testinc.c new file mode 100755 index 000000000..0cd431434 --- /dev/null +++ b/sys/src/cmd/vac/testinc.c @@ -0,0 +1,31 @@ +#include "stdinc.h" +#include "vac.h" +#include "dat.h" +#include "fns.h" +#include "error.h" + +void +threadmain(int argc, char **argv) +{ + Biobuf b; + char *p; + + ARGBEGIN{ + default: + goto usage; + }ARGEND + + if(argc != 1){ + usage: + fprint(2, "usage: testinc includefile\n"); + threadexitsall("usage"); + } + + loadexcludefile(argv[0]); + Binit(&b, 0, OREAD); + while((p = Brdline(&b, '\n')) != nil){ + p[Blinelen(&b)-1] = 0; + print("%d %s\n", includefile(p), p); + } + threadexitsall(0); +} diff --git a/sys/src/cmd/vac/unvac.c b/sys/src/cmd/vac/unvac.c new file mode 100755 index 000000000..b197defb7 --- /dev/null +++ b/sys/src/cmd/vac/unvac.c @@ -0,0 +1,337 @@ +#include "stdinc.h" +#include <fcall.h> /* dirmodefmt */ +#include "vac.h" + +#pragma varargck type "t" ulong + +VacFs *fs; +int tostdout; +int diff; +int nwant; +char **want; +int *found; +int chatty; +VtConn *conn; +int errors; +int settimes; +int table; + +int mtimefmt(Fmt*); +void unvac(VacFile*, char*, VacDir*); + +void +usage(void) +{ + fprint(2, "usage: unvac [-TVcdtv] [-h host] file.vac [file ...]\n"); + threadexitsall("usage"); +} + +struct +{ + vlong data; + vlong skipdata; +} stats; + +void +threadmain(int argc, char *argv[]) +{ + int i, printstats; + char *host; + VacFile *f; + + fmtinstall('H', encodefmt); + fmtinstall('V', vtscorefmt); + fmtinstall('F', vtfcallfmt); + fmtinstall('t', mtimefmt); + fmtinstall('M', dirmodefmt); + + host = nil; + printstats = 0; + + ARGBEGIN{ + case 'T': + settimes = 1; + break; + case 'V': + chattyventi = 1; + break; + case 'c': + tostdout++; + break; + case 'd': + diff++; + break; + case 'h': + host = EARGF(usage()); + break; + case 's': + printstats++; + break; + case 't': + table++; + break; + case 'v': + chatty++; + break; + default: + usage(); + }ARGEND + + if(argc < 1) + usage(); + + if(tostdout && diff){ + fprint(2, "cannot use -c with -d\n"); + usage(); + } + + conn = vtdial(host); + if(conn == nil) + sysfatal("could not connect to server: %r"); + + if(vtconnect(conn) < 0) + sysfatal("vtconnect: %r"); + + fs = vacfsopen(conn, argv[0], VtOREAD, 128); + if(fs == nil) + sysfatal("vacfsopen: %r"); + + nwant = argc-1; + want = argv+1; + found = vtmallocz(nwant*sizeof found[0]); + + if((f = vacfsgetroot(fs)) == nil) + sysfatal("vacfsgetroot: %r"); + + unvac(f, nil, nil); + for(i=0; i<nwant; i++){ + if(want[i] && !found[i]){ + fprint(2, "warning: didn't find %s\n", want[i]); + errors++; + } + } + if(errors) + threadexitsall("errors"); + if(printstats) + fprint(2, "%lld bytes read, %lld bytes skipped\n", + stats.data, stats.skipdata); + threadexitsall(0); +} + +int +writen(int fd, char *buf, int n) +{ + int m; + int oldn; + + oldn = n; + while(n > 0){ + m = write(fd, buf, n); + if(m <= 0) + return -1; + buf += m; + n -= m; + } + return oldn; +} + +int +wantfile(char *name) +{ + int i, namelen, n; + + if(nwant == 0) + return 1; + + namelen = strlen(name); + for(i=0; i<nwant; i++){ + if(want[i] == nil) + continue; + n = strlen(want[i]); + if(n < namelen && name[n] == '/' && memcmp(name, want[i], n) == 0) + return 1; + if(namelen < n && want[i][namelen] == '/' && memcmp(want[i], name, namelen) == 0) + return 1; + if(n == namelen && memcmp(name, want[i], n) == 0){ + found[i] = 1; + return 1; + } + } + return 0; +} + +void +unvac(VacFile *f, char *name, VacDir *vdir) +{ + static char buf[65536]; + int fd, n, m, bsize; + ulong mode, mode9; + char *newname; + char *what; + vlong off; + Dir d, *dp; + VacDirEnum *vde; + VacDir newvdir; + VacFile *newf; + + if(vdir) + mode = vdir->mode; + else + mode = vacfilegetmode(f); + + if(vdir){ + if(table){ + if(chatty){ + mode9 = vdir->mode&0777; + if(mode&ModeDir) + mode9 |= DMDIR; + if(mode&ModeAppend) + mode9 |= DMAPPEND; + if(mode&ModeExclusive) + mode9 |= DMEXCL; + print("%M %-10s %-10s %11lld %t %s\n", + mode9, vdir->uid, vdir->gid, vdir->size, + vdir->mtime, name); + }else + print("%s%s\n", name, (mode&ModeDir) ? "/" : ""); + } + else if(chatty) + fprint(2, "%s%s\n", name, (mode&ModeDir) ? "/" : ""); + } + + if(mode&(ModeDevice|ModeLink|ModeNamedPipe|ModeExclusive)){ + if(table) + return; + if(mode&ModeDevice) + what = "device"; + else if(mode&ModeLink) + what = "link"; + else if(mode&ModeNamedPipe) + what = "named pipe"; + else if(mode&ModeExclusive) + what = "lock"; + else + what = "unknown type of file"; + fprint(2, "warning: ignoring %s %s\n", what, name); + return; + } + + if(mode&ModeDir){ + if((vde = vdeopen(f)) == nil){ + fprint(2, "vdeopen %s: %r", name); + errors++; + return; + } + if(!table && !tostdout && vdir){ + // create directory + if((dp = dirstat(name)) == nil){ + if((fd = create(name, OREAD, DMDIR|(mode&0777))) < 0){ + fprint(2, "mkdir %s: %r\n", name); + vdeclose(vde); + } + close(fd); + }else{ + if(!(dp->mode&DMDIR)){ + fprint(2, "%s already exists and is not a directory\n", name); + errors++; + free(dp); + vdeclose(vde); + return; + } + free(dp); + } + } + while(vderead(vde, &newvdir) > 0){ + if(name == nil) + newname = newvdir.elem; + else + newname = smprint("%s/%s", name, newvdir.elem); + if(wantfile(newname)){ + if((newf = vacfilewalk(f, newvdir.elem)) == nil){ + fprint(2, "walk %s: %r\n", name); + errors++; + }else if(newf == f){ + fprint(2, "walk loop: %s\n", newname); + vacfiledecref(newf); + }else{ + unvac(newf, newname, &newvdir); + vacfiledecref(newf); + } + } + if(newname != newvdir.elem) + free(newname); + vdcleanup(&newvdir); + } + vdeclose(vde); + }else{ + if(!table){ + off = 0; + if(tostdout) + fd = dup(1, -1); + else if(diff && (fd = open(name, ORDWR)) >= 0){ + bsize = vacfiledsize(f); + while((n = readn(fd, buf, bsize)) > 0){ + if(sha1matches(f, off/bsize, (uchar*)buf, n)){ + off += n; + stats.skipdata += n; + continue; + } + seek(fd, off, 0); + if((m = vacfileread(f, buf, n, off)) < 0) + break; + if(writen(fd, buf, m) != m){ + fprint(2, "write %s: %r\n", name); + goto Err; + } + off += m; + stats.data += m; + if(m < n){ + nulldir(&d); + d.length = off; + if(dirfwstat(fd, &d) < 0){ + fprint(2, "dirfwstat %s: %r\n", name); + goto Err; + } + break; + } + } + } + else if((fd = create(name, OWRITE, mode&0777)) < 0){ + fprint(2, "create %s: %r\n", name); + errors++; + return; + } + while((n = vacfileread(f, buf, sizeof buf, off)) > 0){ + if(writen(fd, buf, n) != n){ + fprint(2, "write %s: %r\n", name); + Err: + errors++; + close(fd); + remove(name); + return; + } + off += n; + stats.data += n; + } + close(fd); + } + } + if(vdir && settimes && !tostdout){ + nulldir(&d); + d.mtime = vdir->mtime; + if(dirwstat(name, &d) < 0) + fprint(2, "warning: setting mtime on %s: %r", name); + } +} + +int +mtimefmt(Fmt *f) +{ + Tm *tm; + + tm = localtime(va_arg(f->args, ulong)); + fmtprint(f, "%04d-%02d-%02d %02d:%02d", + tm->year+1900, tm->mon+1, tm->mday, + tm->hour, tm->min); + return 0; +} diff --git a/sys/src/cmd/vac/vac.c b/sys/src/cmd/vac/vac.c new file mode 100755 index 000000000..5a8bae7d7 --- /dev/null +++ b/sys/src/cmd/vac/vac.c @@ -0,0 +1,715 @@ +#include "stdinc.h" +#include "vac.h" +#include "dat.h" +#include "fns.h" + +// TODO: qids + +void +usage(void) +{ + fprint(2, "vac [-imqsv] [-a archive.vac] [-b bsize] [-d old.vac] [-f new.vac] [-e exclude]... [-h host] file...\n"); + threadexitsall("usage"); +} + +enum +{ + BlockSize = 8*1024, +}; + +struct +{ + int nfile; + int ndir; + vlong data; + vlong skipdata; + int skipfiles; +} stats; + +int qdiff; +int merge; +int verbose; +char *host; +VtConn *z; +VacFs *fs; +char *archivefile; +char *vacfile; + +int vacmerge(VacFile*, char*); +void vac(VacFile*, VacFile*, char*, Dir*); +void vacstdin(VacFile*, char*); +VacFile *recentarchive(VacFs*, char*); + +static u64int unittoull(char*); +static void warn(char *fmt, ...); +static void removevacfile(void); + +void +threadmain(int argc, char **argv) +{ + int i, j, fd, n, printstats; + Dir *d; + char *s; + uvlong u; + VacFile *f, *fdiff; + VacFs *fsdiff; + int blocksize; + int outfd; + char *stdinname; + char *diffvac; + uvlong qid; + + + fmtinstall('F', vtfcallfmt); + fmtinstall('H', encodefmt); + fmtinstall('V', vtscorefmt); + + blocksize = BlockSize; + stdinname = nil; + printstats = 0; + fsdiff = nil; + diffvac = nil; + + ARGBEGIN{ + case 'V': + chattyventi++; + break; + case 'a': + archivefile = EARGF(usage()); + break; + case 'b': + u = unittoull(EARGF(usage())); + if(u < 512) + u = 512; + if(u > VtMaxLumpSize) + u = VtMaxLumpSize; + blocksize = u; + break; + case 'd': + diffvac = EARGF(usage()); + break; + case 'e': + excludepattern(EARGF(usage())); + break; + case 'f': + vacfile = EARGF(usage()); + break; + case 'h': + host = EARGF(usage()); + break; + case 'i': + stdinname = EARGF(usage()); + break; + case 'm': + merge++; + break; + case 'q': + qdiff++; + break; + case 's': + printstats++; + break; + case 'v': + verbose++; + break; + case 'x': + loadexcludefile(EARGF(usage())); + break; + default: + usage(); + }ARGEND + + if(argc == 0 && !stdinname) + usage(); + + if(archivefile && (vacfile || diffvac)){ + fprint(2, "cannot use -a with -f, -d\n"); + usage(); + } + + z = vtdial(host); + if(z == nil) + sysfatal("could not connect to server: %r"); + if(vtconnect(z) < 0) + sysfatal("vtconnect: %r"); + + // Setup: + // fs is the output vac file system + // f is directory in output vac to write new files + // fdiff is corresponding directory in existing vac + if(archivefile){ + VacFile *fp; + char yyyy[5]; + char mmdd[10]; + char oldpath[40]; + Tm tm; + + fdiff = nil; + if((outfd = open(archivefile, ORDWR)) < 0){ + if(access(archivefile, 0) >= 0) + sysfatal("open %s: %r", archivefile); + if((outfd = create(archivefile, OWRITE, 0666)) < 0) + sysfatal("create %s: %r", archivefile); + atexit(removevacfile); // because it is new + if((fs = vacfscreate(z, blocksize, 512)) == nil) + sysfatal("vacfscreate: %r"); + }else{ + if((fs = vacfsopen(z, archivefile, VtORDWR, 512)) == nil) + sysfatal("vacfsopen %s: %r", archivefile); + if((fdiff = recentarchive(fs, oldpath)) != nil){ + if(verbose) + fprint(2, "diff %s\n", oldpath); + }else + if(verbose) + fprint(2, "no recent archive to diff against\n"); + } + + // Create yyyy/mmdd. + tm = *localtime(time(0)); + snprint(yyyy, sizeof yyyy, "%04d", tm.year+1900); + fp = vacfsgetroot(fs); + if((f = vacfilewalk(fp, yyyy)) == nil + && (f = vacfilecreate(fp, yyyy, ModeDir|0555)) == nil) + sysfatal("vacfscreate %s: %r", yyyy); + vacfiledecref(fp); + fp = f; + + snprint(mmdd, sizeof mmdd, "%02d%02d", tm.mon+1, tm.mday); + n = 0; + while((f = vacfilewalk(fp, mmdd)) != nil){ + vacfiledecref(f); + n++; + snprint(mmdd+4, sizeof mmdd-4, ".%d", n); + } + f = vacfilecreate(fp, mmdd, ModeDir|0555); + if(f == nil) + sysfatal("vacfscreate %s/%s: %r", yyyy, mmdd); + vacfiledecref(fp); + + if(verbose) + fprint(2, "archive %s/%s\n", yyyy, mmdd); + }else{ + if(vacfile == nil) + outfd = 1; + else if((outfd = create(vacfile, OWRITE, 0666)) < 0) + sysfatal("create %s: %r", vacfile); + atexit(removevacfile); + if((fs = vacfscreate(z, blocksize, 512)) == nil) + sysfatal("vacfscreate: %r"); + f = vacfsgetroot(fs); + + fdiff = nil; + if(diffvac){ + if((fsdiff = vacfsopen(z, diffvac, VtOREAD, 128)) == nil) + warn("vacfsopen %s: %r", diffvac); + else + fdiff = vacfsgetroot(fsdiff); + } + } + + if(stdinname) + vacstdin(f, stdinname); + for(i=0; i<argc; i++){ + // We can't use / and . and .. and ../.. as valid archive + // names, so expand to the list of files in the directory. + if(argv[i][0] == 0){ + warn("empty string given as command-line argument"); + continue; + } + cleanname(argv[i]); + if(strcmp(argv[i], "/") == 0 + || strcmp(argv[i], ".") == 0 + || strcmp(argv[i], "..") == 0 + || (strlen(argv[i]) > 3 && strcmp(argv[i]+strlen(argv[i])-3, "/..") == 0)){ + if((fd = open(argv[i], OREAD)) < 0){ + warn("open %s: %r", argv[i]); + continue; + } + while((n = dirread(fd, &d)) > 0){ + for(j=0; j<n; j++){ + s = vtmalloc(strlen(argv[i])+1+strlen(d[j].name)+1); + strcpy(s, argv[i]); + strcat(s, "/"); + strcat(s, d[j].name); + cleanname(s); + vac(f, fdiff, s, &d[j]); + } + free(d); + } + close(fd); + continue; + } + if((d = dirstat(argv[i])) == nil){ + warn("stat %s: %r", argv[i]); + continue; + } + vac(f, fdiff, argv[i], d); + free(d); + } + if(fdiff) + vacfiledecref(fdiff); + + /* + * Record the maximum qid so that vacs can be merged + * without introducing overlapping qids. Older versions + * of vac arranged that the root would have the largest + * qid in the file system, but we can't do that anymore + * (the root gets created first!). + */ + if(_vacfsnextqid(fs, &qid) >= 0) + vacfilesetqidspace(f, 0, qid); + vacfiledecref(f); + + /* + * Copy fsdiff's root block score into fs's slot for that, + * so that vacfssync will copy it into root.prev for us. + * Just nice documentation, no effect. + */ + if(fsdiff) + memmove(fs->score, fsdiff->score, VtScoreSize); + if(vacfssync(fs) < 0) + fprint(2, "vacfssync: %r\n"); + + fprint(outfd, "vac:%V\n", fs->score); + atexitdont(removevacfile); + vacfsclose(fs); + vthangup(z); + + if(printstats){ + fprint(2, + "%d files, %d files skipped, %d directories\n" + "%lld data bytes written, %lld data bytes skipped\n", + stats.nfile, stats.skipfiles, stats.ndir, stats.data, stats.skipdata); + dup(2, 1); + packetstats(); + } + threadexitsall(0); +} + +VacFile* +recentarchive(VacFs *fs, char *path) +{ + VacFile *fp, *f; + VacDirEnum *de; + VacDir vd; + char buf[10]; + int year, mmdd, nn, n, n1; + char *p; + + fp = vacfsgetroot(fs); + de = vdeopen(fp); + year = 0; + if(de){ + for(; vderead(de, &vd) > 0; vdcleanup(&vd)){ + if(strlen(vd.elem) != 4) + continue; + if((n = strtol(vd.elem, &p, 10)) < 1900 || *p != 0) + continue; + if(year < n) + year = n; + } + } + vdeclose(de); + if(year == 0){ + vacfiledecref(fp); + return nil; + } + snprint(buf, sizeof buf, "%04d", year); + if((f = vacfilewalk(fp, buf)) == nil){ + fprint(2, "warning: dirread %s but cannot walk", buf); + vacfiledecref(fp); + return nil; + } + fp = f; + + de = vdeopen(fp); + mmdd = 0; + nn = 0; + if(de){ + for(; vderead(de, &vd) > 0; vdcleanup(&vd)){ + if(strlen(vd.elem) < 4) + continue; + if((n = strtol(vd.elem, &p, 10)) < 100 || n > 1231 || p != vd.elem+4) + continue; + if(*p == '.'){ + if(p[1] == '0' || (n1 = strtol(p+1, &p, 10)) == 0 || *p != 0) + continue; + }else{ + if(*p != 0) + continue; + n1 = 0; + } + if(n < mmdd || (n == mmdd && n1 < nn)) + continue; + mmdd = n; + nn = n1; + } + } + vdeclose(de); + if(mmdd == 0){ + vacfiledecref(fp); + return nil; + } + if(nn == 0) + snprint(buf, sizeof buf, "%04d", mmdd); + else + snprint(buf, sizeof buf, "%04d.%d", mmdd, nn); + if((f = vacfilewalk(fp, buf)) == nil){ + fprint(2, "warning: dirread %s but cannot walk", buf); + vacfiledecref(fp); + return nil; + } + vacfiledecref(fp); + + sprint(path, "%04d/%s", year, buf); + return f; +} + +static void +removevacfile(void) +{ + if(vacfile) + remove(vacfile); +} + +void +plan9tovacdir(VacDir *vd, Dir *dir) +{ + memset(vd, 0, sizeof *vd); + + vd->elem = dir->name; + vd->uid = dir->uid; + vd->gid = dir->gid; + vd->mid = dir->muid; + if(vd->mid == nil) + vd->mid = ""; + vd->mtime = dir->mtime; + vd->mcount = 0; + vd->ctime = dir->mtime; /* ctime: not available on plan 9 */ + vd->atime = dir->atime; + vd->size = dir->length; + + vd->mode = dir->mode & 0777; + if(dir->mode & DMDIR) + vd->mode |= ModeDir; + if(dir->mode & DMAPPEND) + vd->mode |= ModeAppend; + if(dir->mode & DMEXCL) + vd->mode |= ModeExclusive; + + vd->plan9 = 1; + vd->p9path = dir->qid.path; + vd->p9version = dir->qid.vers; +} + + +/* + * Archive the file named name, which has stat info d, + * into the vac directory fp (p = parent). + * + * If we're doing a vac -d against another archive, the + * equivalent directory to fp in that archive is diffp. + */ +void +vac(VacFile *fp, VacFile *diffp, char *name, Dir *d) +{ + char *elem, *s; + static char buf[65536]; + int fd, i, n, bsize; + vlong off; + Dir *dk; // kids + VacDir vd, vddiff; + VacFile *f, *fdiff; + VtEntry e; + + if(!includefile(name)){ + warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : ""); + return; + } + + if(d->mode&DMDIR) + stats.ndir++; + else + stats.nfile++; + + if(merge && vacmerge(fp, name) >= 0) + return; + + if(verbose) + fprint(2, "%s%s\n", name, (d->mode&DMDIR) ? "/" : ""); + + if((fd = open(name, OREAD)) < 0){ + warn("open %s: %r", name); + return; + } + + elem = strrchr(name, '/'); + if(elem) + elem++; + else + elem = name; + + plan9tovacdir(&vd, d); + if((f = vacfilecreate(fp, elem, vd.mode)) == nil){ + warn("vacfilecreate %s: %r", name); + return; + } + if(diffp) + fdiff = vacfilewalk(diffp, elem); + else + fdiff = nil; + + if(vacfilesetdir(f, &vd) < 0) + warn("vacfilesetdir %s: %r", name); + + if(d->mode&DMDIR){ + while((n = dirread(fd, &dk)) > 0){ + for(i=0; i<n; i++){ + s = vtmalloc(strlen(name)+1+strlen(dk[i].name)+1); + strcpy(s, name); + strcat(s, "/"); + strcat(s, dk[i].name); + vac(f, fdiff, s, &dk[i]); + free(s); + } + free(dk); + } + }else{ + off = 0; + bsize = fs->bsize; + if(fdiff){ + /* + * Copy fdiff's contents into f by moving the score. + * We'll diff and update below. + */ + if(vacfilegetentries(fdiff, &e, nil) >= 0) + if(vacfilesetentries(f, &e, nil) >= 0){ + bsize = e.dsize; + + /* + * Or if -q is set, and the metadata looks the same, + * don't even bother reading the file. + */ + if(qdiff && vacfilegetdir(fdiff, &vddiff) >= 0){ + if(vddiff.mtime == vd.mtime) + if(vddiff.size == vd.size) + if(!vddiff.plan9 || (/* vddiff.p9path == vd.p9path && */ vddiff.p9version == vd.p9version)){ + stats.skipfiles++; + stats.nfile--; + vdcleanup(&vddiff); + goto Out; + } + + /* + * Skip over presumably-unchanged prefix + * of an append-only file. + */ + if(vd.mode&ModeAppend) + if(vddiff.size < vd.size) + if(vddiff.plan9 && vd.plan9) + if(vddiff.p9path == vd.p9path){ + off = vd.size/bsize*bsize; + if(seek(fd, off, 0) >= 0) + stats.skipdata += off; + else{ + seek(fd, 0, 0); // paranoia + off = 0; + } + } + + vdcleanup(&vddiff); + // XXX different verbose chatty prints for kaminsky? + } + } + } + if(qdiff && verbose) + fprint(2, "+%s\n", name); + while((n = readn(fd, buf, bsize)) > 0){ + if(fdiff && sha1matches(f, off/bsize, (uchar*)buf, n)){ + off += n; + stats.skipdata += n; + continue; + } + if(vacfilewrite(f, buf, n, off) < 0){ + warn("venti write %s: %r", name); + goto Out; + } + stats.data += n; + off += n; + } + /* + * Since we started with fdiff's contents, + * set the size in case fdiff was bigger. + */ + if(fdiff && vacfilesetsize(f, off) < 0) + warn("vtfilesetsize %s: %r", name); + } + +Out: + vacfileflush(f, 1); + vacfiledecref(f); + if(fdiff) + vacfiledecref(fdiff); + close(fd); +} + +void +vacstdin(VacFile *fp, char *name) +{ + vlong off; + VacFile *f; + static char buf[8192]; + int n; + + if((f = vacfilecreate(fp, name, 0666)) == nil){ + warn("vacfilecreate %s: %r", name); + return; + } + + off = 0; + while((n = read(0, buf, sizeof buf)) > 0){ + if(vacfilewrite(f, buf, n, off) < 0){ + warn("venti write %s: %r", name); + vacfiledecref(f); + return; + } + off += n; + } + vacfileflush(f, 1); + vacfiledecref(f); +} + +/* + * fp is the directory we're writing. + * mp is the directory whose contents we're merging in. + * d is the directory entry of the file from mp that we want to add to fp. + * vacfile is the name of the .vac file, for error messages. + * offset is the qid that qid==0 in mp should correspond to. + * max is the maximum qid we expect to see (not really needed). + */ +int +vacmergefile(VacFile *fp, VacFile *mp, VacDir *d, char *vacfile, + vlong offset, vlong max) +{ + VtEntry ed, em; + VacFile *mf; + VacFile *f; + + mf = vacfilewalk(mp, d->elem); + if(mf == nil){ + warn("could not walk %s in %s", d->elem, vacfile); + return -1; + } + if(vacfilegetentries(mf, &ed, &em) < 0){ + warn("could not get entries for %s in %s", d->elem, vacfile); + vacfiledecref(mf); + return -1; + } + + if((f = vacfilecreate(fp, d->elem, d->mode)) == nil){ + warn("vacfilecreate %s: %r", d->elem); + vacfiledecref(mf); + return -1; + } + if(d->qidspace){ + d->qidoffset += offset; + d->qidmax += offset; + }else{ + d->qidspace = 1; + d->qidoffset = offset; + d->qidmax = max; + } + if(vacfilesetdir(f, d) < 0 + || vacfilesetentries(f, &ed, &em) < 0 + || vacfilesetqidspace(f, d->qidoffset, d->qidmax) < 0){ + warn("vacmergefile %s: %r", d->elem); + vacfiledecref(mf); + vacfiledecref(f); + return -1; + } + + vacfiledecref(mf); + vacfiledecref(f); + return 0; +} + +int +vacmerge(VacFile *fp, char *name) +{ + VacFs *mfs; + VacDir vd; + VacDirEnum *de; + VacFile *mp; + uvlong maxqid, offset; + + if(strlen(name) < 4 || strcmp(name+strlen(name)-4, ".vac") != 0) + return -1; + if((mfs = vacfsopen(z, name, VtOREAD, 100)) == nil) + return -1; + if(verbose) + fprint(2, "merging %s\n", name); + + mp = vacfsgetroot(mfs); + de = vdeopen(mp); + if(de){ + offset = 0; + if(vacfsgetmaxqid(mfs, &maxqid) >= 0){ + _vacfsnextqid(fs, &offset); + vacfsjumpqid(fs, maxqid+1); + } + while(vderead(de, &vd) > 0){ + if(vd.qid > maxqid){ + warn("vacmerge %s: maxqid=%lld but %s has %lld", + name, maxqid, vd.elem, vd.qid); + vacfsjumpqid(fs, vd.qid - maxqid); + maxqid = vd.qid; + } + vacmergefile(fp, mp, &vd, name, + offset, maxqid); + vdcleanup(&vd); + } + vdeclose(de); + } + vacfiledecref(mp); + vacfsclose(mfs); + return 0; +} + +#define TWID64 ((u64int)~(u64int)0) + +static u64int +unittoull(char *s) +{ + char *es; + u64int n; + + if(s == nil) + return TWID64; + n = strtoul(s, &es, 0); + if(*es == 'k' || *es == 'K'){ + n *= 1024; + es++; + }else if(*es == 'm' || *es == 'M'){ + n *= 1024*1024; + es++; + }else if(*es == 'g' || *es == 'G'){ + n *= 1024*1024*1024; + es++; + } + if(*es != '\0') + return TWID64; + return n; +} + +static void +warn(char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + fprint(2, "vac: "); + vfprint(2, fmt, arg); + fprint(2, "\n"); + va_end(arg); +} + diff --git a/sys/src/cmd/vac/vac.h b/sys/src/cmd/vac/vac.h new file mode 100755 index 000000000..9888e04fe --- /dev/null +++ b/sys/src/cmd/vac/vac.h @@ -0,0 +1,145 @@ +typedef struct VacFs VacFs; +typedef struct VacDir VacDir; +typedef struct VacFile VacFile; +typedef struct VacDirEnum VacDirEnum; + +#pragma incomplete VacFile +#pragma incomplete VacDirEnum + +/* + * Mode bits + */ +enum +{ + ModeOtherExec = (1<<0), + ModeOtherWrite = (1<<1), + ModeOtherRead = (1<<2), + ModeGroupExec = (1<<3), + ModeGroupWrite = (1<<4), + ModeGroupRead = (1<<5), + ModeOwnerExec = (1<<6), + ModeOwnerWrite = (1<<7), + ModeOwnerRead = (1<<8), + ModeSticky = (1<<9), + ModeSetUid = (1<<10), + ModeSetGid = (1<<11), + ModeAppend = (1<<12), /* append only file */ + ModeExclusive = (1<<13), /* lock file - plan 9 */ + ModeLink = (1<<14), /* sym link */ + ModeDir = (1<<15), /* duplicate of DirEntry */ + ModeHidden = (1<<16), /* MS-DOS */ + ModeSystem = (1<<17), /* MS-DOS */ + ModeArchive = (1<<18), /* MS-DOS */ + ModeTemporary = (1<<19), /* MS-DOS */ + ModeSnapshot = (1<<20), /* read only snapshot */ + ModeDevice = (1<<21), /* Unix device */ + ModeNamedPipe = (1<<22) /* Unix named pipe */ +}; + +enum +{ + MetaMagic = 0x5656fc79, + MetaHeaderSize = 12, + MetaIndexSize = 4, + IndexEntrySize = 8, + DirMagic = 0x1c4d9072 +}; + +enum +{ + DirPlan9Entry = 1, /* not valid in version >= 9 */ + DirNTEntry, /* not valid in version >= 9 */ + DirQidSpaceEntry, + DirGenEntry /* not valid in version >= 9 */ +}; + +struct VacDir +{ + char *elem; /* path element */ + ulong entry; /* entry in directory for data */ + ulong gen; /* generation of data entry */ + ulong mentry; /* entry in directory for meta */ + ulong mgen; /* generation of meta entry */ + uvlong size; /* size of file */ + uvlong qid; /* unique file id */ + + char *uid; /* owner id */ + char *gid; /* group id */ + char *mid; /* last modified by */ + ulong mtime; /* last modified time */ + ulong mcount; /* number of modifications: can wrap! */ + ulong ctime; /* directory entry last changed */ + ulong atime; /* last time accessed */ + ulong mode; /* various mode bits */ + + /* plan 9 */ + int plan9; + uvlong p9path; + ulong p9version; + + /* sub space of qid */ + int qidspace; + uvlong qidoffset; /* qid offset */ + uvlong qidmax; /* qid maximum */ +}; + +struct VacFs +{ + char name[128]; + uchar score[VtScoreSize]; + VacFile *root; + VtConn *z; + int mode; + int bsize; + uvlong qid; + VtCache *cache; +}; + +VacFs *vacfsopen(VtConn *z, char *file, int mode, int ncache); +VacFs *vacfsopenscore(VtConn *z, u8int *score, int mode, int ncache); +VacFs *vacfscreate(VtConn *z, int bsize, int ncache); +void vacfsclose(VacFs *fs); +int vacfssync(VacFs *fs); +int vacfssnapshot(VacFs *fs, char *src, char *dst); +int vacfsgetscore(VacFs *fs, u8int *score); +int vacfsgetmaxqid(VacFs*, uvlong*); +void vacfsjumpqid(VacFs*, uvlong); + +VacFile *vacfsgetroot(VacFs *fs); +VacFile *vacfileopen(VacFs *fs, char *path); +VacFile *vacfilecreate(VacFile *file, char *elem, ulong perm); +VacFile *vacfilewalk(VacFile *file, char *elem); +int vacfileremove(VacFile *file); +int vacfileread(VacFile *file, void *buf, int n, vlong offset); +int vacfileblockscore(VacFile *file, u32int, u8int*); +int vacfilewrite(VacFile *file, void *buf, int n, vlong offset); +uvlong vacfilegetid(VacFile *file); +ulong vacfilegetmcount(VacFile *file); +int vacfileisdir(VacFile *file); +int vacfileisroot(VacFile *file); +ulong vacfilegetmode(VacFile *file); +int vacfilegetsize(VacFile *file, uvlong *size); +int vacfilegetdir(VacFile *file, VacDir *dir); +int vacfilesetdir(VacFile *file, VacDir *dir); +VacFile *vacfilegetparent(VacFile *file); +int vacfileflush(VacFile*, int); +VacFile *vacfileincref(VacFile*); +int vacfiledecref(VacFile*); +int vacfilesetsize(VacFile *f, uvlong size); + +int vacfilegetentries(VacFile *f, VtEntry *e, VtEntry *me); +int vacfilesetentries(VacFile *f, VtEntry *e, VtEntry *me); + +void vdcleanup(VacDir *dir); +void vdcopy(VacDir *dst, VacDir *src); +int vacfilesetqidspace(VacFile*, u64int, u64int); +uvlong vacfilegetqidoffset(VacFile*); + +VacDirEnum *vdeopen(VacFile*); +int vderead(VacDirEnum*, VacDir *); +void vdeclose(VacDirEnum*); +int vdeunread(VacDirEnum*); + +int vacfiledsize(VacFile *f); +int sha1matches(VacFile *f, ulong b, uchar *buf, int n); + diff --git a/sys/src/cmd/vac/vacfs.c b/sys/src/cmd/vac/vacfs.c new file mode 100755 index 000000000..e4697c92a --- /dev/null +++ b/sys/src/cmd/vac/vacfs.c @@ -0,0 +1,788 @@ +#include "stdinc.h" +#include <fcall.h> +#include "vac.h" + +#define convM2Su(a, b, c, d) convM2S(a, b, c) +#define convS2Mu(a, b, c, d) convS2M(a, b, c) +#define convM2Du(a, b, c, d) convM2D(a, b, c) +#define convD2Mu(a, b, c, d) convD2M(a, b, c) + +typedef struct Fid Fid; + +enum +{ + Stacksize = 320 * 1024, /* was 32K */ + OPERM = 0x3 /* mask of all permission types in open mode */ +}; + +struct Fid +{ + short busy; + short open; + int fid; + char *user; + Qid qid; + VacFile *file; + VacDirEnum *vde; + Fid *next; +}; + +enum +{ + Pexec = 1, + Pwrite = 2, + Pread = 4, + Pother = 1, + Pgroup = 8, + Powner = 64 +}; + +Fid *fids; +uchar *data; +int mfd[2]; +int srvfd = -1; +char *user; +uchar mdata[8192+IOHDRSZ]; +int messagesize = sizeof mdata; +Fcall rhdr; +Fcall thdr; +VacFs *fs; +VtConn *conn; +int noperm; +int dotu; +char *defmnt; + +Fid * newfid(int); +void error(char*); +void io(void); +void vacshutdown(void); +void usage(void); +int perm(Fid*, int); +int permf(VacFile*, char*, int); +ulong getl(void *p); +void init(char*, char*, long, int); +int vacdirread(Fid *f, char *p, long off, long cnt); +int vacstat(VacFile *parent, VacDir *vd, uchar *p, int np); +void srv(void* a); + + +char *rflush(Fid*), *rversion(Fid*), + *rauth(Fid*), *rattach(Fid*), *rwalk(Fid*), + *ropen(Fid*), *rcreate(Fid*), + *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), + *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*); + +char *(*fcalls[Tmax])(Fid*); + +void +initfcalls(void) +{ + fcalls[Tflush]= rflush; + fcalls[Tversion]= rversion; + fcalls[Tattach]= rattach; + fcalls[Tauth]= rauth; + fcalls[Twalk]= rwalk; + fcalls[Topen]= ropen; + fcalls[Tcreate]= rcreate; + fcalls[Tread]= rread; + fcalls[Twrite]= rwrite; + fcalls[Tclunk]= rclunk; + fcalls[Tremove]= rremove; + fcalls[Tstat]= rstat; + fcalls[Twstat]= rwstat; +} + +char Eperm[] = "permission denied"; +char Enotdir[] = "not a directory"; +char Enotexist[] = "file does not exist"; +char Einuse[] = "file in use"; +char Eexist[] = "file exists"; +char Enotowner[] = "not owner"; +char Eisopen[] = "file already open for I/O"; +char Excl[] = "exclusive use file already open"; +char Ename[] = "illegal name"; +char Erdonly[] = "read only file system"; +char Eio[] = "i/o error"; +char Eempty[] = "directory is not empty"; +char Emode[] = "illegal mode"; + +int dflag; + +void +notifyf(void *a, char *s) +{ + USED(a); + if(strncmp(s, "interrupt", 9) == 0) + noted(NCONT); + noted(NDFLT); +} + +void +threadmain(int argc, char *argv[]) +{ + char *defsrv, *srvname; + int p[2], fd; + int stdio; + char *host = nil; + long ncache; + + stdio = 0; + ncache = 256; + fmtinstall('H', encodefmt); + fmtinstall('V', vtscorefmt); + fmtinstall('F', vtfcallfmt); + + defmnt = nil; + defsrv = nil; + ARGBEGIN{ + case 'd': + fmtinstall('F', fcallfmt); + dflag = 1; + break; + case 'c': + ncache = atoi(EARGF(usage())); + break; + case 'i': + defmnt = nil; + stdio = 1; + mfd[0] = 0; + mfd[1] = 1; + break; + case 'h': + host = EARGF(usage()); + break; + case 'S': + defsrv = EARGF(usage()); + break; + case 's': + defsrv = "vacfs"; + break; + case 'm': + defmnt = EARGF(usage()); + break; + case 'p': + noperm = 1; + break; + case 'V': + chattyventi = 1; + break; + default: + usage(); + }ARGEND + + if(argc != 1) + usage(); + + if(defsrv == nil && defmnt == nil && !stdio) + defmnt = "/n/vac"; + if(stdio && defmnt) + sysfatal("cannot use -m with -i"); + + initfcalls(); + + notify(notifyf); + user = getuser(); + + conn = vtdial(host); + if(conn == nil) + sysfatal("could not connect to server: %r"); + + if(vtconnect(conn) < 0) + sysfatal("vtconnect: %r"); + + fs = vacfsopen(conn, argv[0], VtOREAD, ncache); + if(fs == nil) + sysfatal("vacfsopen: %r"); + + if(!stdio){ + if(pipe(p) < 0) + sysfatal("pipe failed: %r"); + mfd[0] = p[0]; + mfd[1] = p[0]; + srvfd = p[1]; + } + + procrfork(srv, 0, Stacksize, RFFDG|RFNAMEG|RFNOTEG); + + if(!stdio){ + close(p[0]); + if(defsrv){ + srvname = smprint("/srv/%s", defsrv); + fd = create(srvname, OWRITE|ORCLOSE, 0666); + if(fd < 0) + sysfatal("create %s: %r", srvname); + if(fprint(fd, "%d", srvfd) < 0) + sysfatal("write %s: %r", srvname); + free(srvname); + } + if(defmnt){ + if(mount(srvfd, -1, defmnt, MREPL|MCREATE, "") < 0) + sysfatal("mount %s: %r", defmnt); + } + } + threadexits(0); +} + +void +srv(void *a) +{ + USED(a); + io(); + vacshutdown(); +} + +void +usage(void) +{ + fprint(2, "usage: %s [-sd] [-h host] [-c ncache] [-m mountpoint] vacfile\n", argv0); + threadexitsall("usage"); +} + +char* +rversion(Fid *unused) +{ + Fid *f; + + USED(unused); + + for(f = fids; f; f = f->next) + if(f->busy) + rclunk(f); + + if(rhdr.msize < 256) + return vtstrdup("version: message size too small"); + messagesize = rhdr.msize; + if(messagesize > sizeof mdata) + messagesize = sizeof mdata; + thdr.msize = messagesize; + if(strncmp(rhdr.version, "9P2000", 6) != 0) + return vtstrdup("unrecognized 9P version"); + thdr.version = "9P2000"; + if(strncmp(rhdr.version, "9P2000.u", 8) == 0){ + dotu = 1; + thdr.version = "9P2000.u"; + } + return nil; +} + +char* +rflush(Fid *f) +{ + USED(f); + return 0; +} + +char* +rauth(Fid *f) +{ + USED(f); + return vtstrdup("vacfs: authentication not required"); +} + +char* +rattach(Fid *f) +{ + /* no authentication for the momment */ + VacFile *file; + char err[80]; + + file = vacfsgetroot(fs); + if(file == nil) { + rerrstr(err, sizeof err); + return vtstrdup(err); + } + + f->busy = 1; + f->file = file; + f->qid.path = vacfilegetid(f->file); + f->qid.vers = 0; + f->qid.type = QTDIR; + thdr.qid = f->qid; + if(rhdr.uname[0]) + f->user = vtstrdup(rhdr.uname); + else + f->user = "none"; + return 0; +} + +char* +rwalk(Fid *f) +{ + VacFile *file, *nfile; + Fid *nf; + int nqid, nwname; + Qid qid; + char *err = nil; + + if(f->busy == 0) + return Enotexist; + nf = nil; + if(rhdr.fid != rhdr.newfid){ + if(f->open) + return vtstrdup(Eisopen); + if(f->busy == 0) + return vtstrdup(Enotexist); + nf = newfid(rhdr.newfid); + if(nf->busy) + return vtstrdup(Eisopen); + nf->busy = 1; + nf->open = 0; + nf->qid = f->qid; + nf->file = vacfileincref(f->file); + nf->user = vtstrdup(f->user); + f = nf; + } + + nwname = rhdr.nwname; + + /* easy case */ + if(nwname == 0) { + thdr.nwqid = 0; + return 0; + } + + file = f->file; + vacfileincref(file); + qid = f->qid; + + for(nqid = 0; nqid < nwname; nqid++){ + if((qid.type & QTDIR) == 0){ + err = Enotdir; + break; + } + if(!permf(file, f->user, Pexec)) { + err = Eperm; + break; + } + nfile = vacfilewalk(file, rhdr.wname[nqid]); + if(nfile == nil) + break; + vacfiledecref(file); + file = nfile; + qid.type = QTFILE; + if(vacfileisdir(file)) + qid.type = QTDIR; + qid.vers = vacfilegetmcount(file); + qid.path = vacfilegetid(file); + thdr.wqid[nqid] = qid; + } + + thdr.nwqid = nqid; + + if(nqid == nwname){ + /* success */ + f->qid = thdr.wqid[nqid-1]; + vacfiledecref(f->file); + f->file = file; + return 0; + } + + vacfiledecref(file); + if(nf != nil) + rclunk(nf); + + /* only error on the first element */ + if(nqid == 0) + return vtstrdup(err); + + return 0; +} + +char * +ropen(Fid *f) +{ + int mode, trunc; + + if(f->open) + return vtstrdup(Eisopen); + if(!f->busy) + return vtstrdup(Enotexist); + + mode = rhdr.mode; + thdr.iounit = messagesize - IOHDRSZ; + if(f->qid.type & QTDIR){ + if(mode != OREAD) + return vtstrdup(Eperm); + if(!perm(f, Pread)) + return vtstrdup(Eperm); + thdr.qid = f->qid; + f->vde = nil; + f->open = 1; + return 0; + } + if(mode & ORCLOSE) + return vtstrdup(Erdonly); + trunc = mode & OTRUNC; + mode &= OPERM; + if(mode==OWRITE || mode==ORDWR || trunc) + if(!perm(f, Pwrite)) + return vtstrdup(Eperm); + if(mode==OREAD || mode==ORDWR) + if(!perm(f, Pread)) + return vtstrdup(Eperm); + if(mode==OEXEC) + if(!perm(f, Pexec)) + return vtstrdup(Eperm); + thdr.qid = f->qid; + thdr.iounit = messagesize - IOHDRSZ; + f->open = 1; + return 0; +} + +char* +rcreate(Fid* fid) +{ + VacFile *vf; + ulong mode; + + if(fid->open) + return vtstrdup(Eisopen); + if(!fid->busy) + return vtstrdup(Enotexist); + if(fs->mode & ModeSnapshot) + return vtstrdup(Erdonly); + vf = fid->file; + if(!vacfileisdir(vf)) + return vtstrdup(Enotdir); + if(!permf(vf, fid->user, Pwrite)) + return vtstrdup(Eperm); + + mode = rhdr.perm & 0777; + + if(rhdr.perm & DMDIR){ + if((rhdr.mode & OTRUNC) || (rhdr.perm & DMAPPEND)) + return vtstrdup(Emode); + switch(rhdr.mode & OPERM){ + default: + return vtstrdup(Emode); + case OEXEC: + case OREAD: + break; + case OWRITE: + case ORDWR: + return vtstrdup(Eperm); + } + mode |= ModeDir; + } + vf = vacfilecreate(vf, rhdr.name, mode); + if(vf == nil) { + char err[80]; + rerrstr(err, sizeof err); + + return vtstrdup(err); + } + + vacfiledecref(fid->file); + + fid->file = vf; + fid->qid.type = QTFILE; + if(vacfileisdir(vf)) + fid->qid.type = QTDIR; + fid->qid.vers = vacfilegetmcount(vf); + fid->qid.path = vacfilegetid(vf); + + thdr.qid = fid->qid; + thdr.iounit = messagesize - IOHDRSZ; + + return 0; +} + +char* +rread(Fid *f) +{ + char *buf; + vlong off; + int cnt; + VacFile *vf; + char err[80]; + int n; + + if(!f->busy) + return vtstrdup(Enotexist); + vf = f->file; + thdr.count = 0; + off = rhdr.offset; + buf = thdr.data; + cnt = rhdr.count; + if(f->qid.type & QTDIR) + n = vacdirread(f, buf, off, cnt); + else if(vacfilegetmode(f->file)&ModeDevice) + return vtstrdup("device"); + else if(vacfilegetmode(f->file)&ModeLink) + return vtstrdup("symbolic link"); + else if(vacfilegetmode(f->file)&ModeNamedPipe) + return vtstrdup("named pipe"); + else + n = vacfileread(vf, buf, cnt, off); + if(n < 0) { + rerrstr(err, sizeof err); + return vtstrdup(err); + } + thdr.count = n; + return 0; +} + +char* +rwrite(Fid *f) +{ + USED(f); + return vtstrdup(Erdonly); +} + +char * +rclunk(Fid *f) +{ + f->busy = 0; + f->open = 0; + vtfree(f->user); + f->user = nil; + if(f->file) + vacfiledecref(f->file); + f->file = nil; + vdeclose(f->vde); + f->vde = nil; + return 0; +} + +char * +rremove(Fid *f) +{ + VacFile *vf, *vfp; + char errbuf[80]; + char *err = nil; + + if(!f->busy) + return vtstrdup(Enotexist); + vf = f->file; + vfp = vacfilegetparent(vf); + + if(!permf(vfp, f->user, Pwrite)) { + err = Eperm; + goto Exit; + } + + if(!vacfileremove(vf)) { + rerrstr(errbuf, sizeof errbuf); + err = errbuf; + } + +Exit: + vacfiledecref(vfp); + rclunk(f); + return vtstrdup(err); +} + +char * +rstat(Fid *f) +{ + VacDir dir; + static uchar statbuf[1024]; + VacFile *parent; + + if(!f->busy) + return vtstrdup(Enotexist); + parent = vacfilegetparent(f->file); + vacfilegetdir(f->file, &dir); + thdr.stat = statbuf; + thdr.nstat = vacstat(parent, &dir, thdr.stat, sizeof statbuf); + vdcleanup(&dir); + vacfiledecref(parent); + return 0; +} + +char * +rwstat(Fid *f) +{ + if(!f->busy) + return vtstrdup(Enotexist); + return vtstrdup(Erdonly); +} + +int +vacstat(VacFile *parent, VacDir *vd, uchar *p, int np) +{ + int ret; + Dir dir; + + memset(&dir, 0, sizeof(dir)); + + dir.qid.path = vd->qid + vacfilegetqidoffset(parent); + if(vd->qidspace) + dir.qid.path += vd->qidoffset; + dir.qid.vers = vd->mcount; + dir.mode = vd->mode & 0777; + if(vd->mode & ModeAppend){ + dir.qid.type |= QTAPPEND; + dir.mode |= DMAPPEND; + } + if(vd->mode & ModeExclusive){ + dir.qid.type |= QTEXCL; + dir.mode |= DMEXCL; + } + if(vd->mode & ModeDir){ + dir.qid.type |= QTDIR; + dir.mode |= DMDIR; + } + + + dir.atime = vd->atime; + dir.mtime = vd->mtime; + dir.length = vd->size; + + dir.name = vd->elem; + dir.uid = vd->uid; + dir.gid = vd->gid; + dir.muid = vd->mid; + + ret = convD2Mu(&dir, p, np, dotu); + return ret; +} + +int +vacdirread(Fid *f, char *p, long off, long cnt) +{ + int i, n, nb; + VacDir vd; + + /* + * special case of rewinding a directory + * otherwise ignore the offset + */ + if(off == 0 && f->vde){ + vdeclose(f->vde); + f->vde = nil; + } + + if(f->vde == nil){ + f->vde = vdeopen(f->file); + if(f->vde == nil) + return -1; + } + + for(nb = 0; nb < cnt; nb += n) { + i = vderead(f->vde, &vd); + if(i < 0) + return -1; + if(i == 0) + break; + n = vacstat(f->file, &vd, (uchar*)p, cnt-nb); + if(n <= BIT16SZ) { + vdeunread(f->vde); + break; + } + vdcleanup(&vd); + p += n; + } + return nb; +} + +Fid * +newfid(int fid) +{ + Fid *f, *ff; + + ff = 0; + for(f = fids; f; f = f->next) + if(f->fid == fid) + return f; + else if(!ff && !f->busy) + ff = f; + if(ff){ + ff->fid = fid; + return ff; + } + f = vtmallocz(sizeof *f); + f->fid = fid; + f->next = fids; + fids = f; + return f; +} + +void +io(void) +{ + char *err; + int n; + + for(;;){ + n = read9pmsg(mfd[0], mdata, sizeof mdata); + if(n <= 0) + break; + if(convM2Su(mdata, n, &rhdr, dotu) != n) + sysfatal("convM2S conversion error"); + + if(dflag) + fprint(2, "vacfs:<-%F\n", &rhdr); + + thdr.data = (char*)mdata + IOHDRSZ; + if(!fcalls[rhdr.type]) + err = "bad fcall type"; + else + err = (*fcalls[rhdr.type])(newfid(rhdr.fid)); + if(err){ + thdr.type = Rerror; + thdr.ename = err; + }else{ + thdr.type = rhdr.type + 1; + thdr.fid = rhdr.fid; + } + thdr.tag = rhdr.tag; + if(dflag) + fprint(2, "vacfs:->%F\n", &thdr); + n = convS2Mu(&thdr, mdata, messagesize, dotu); + if(n <= BIT16SZ) + sysfatal("convS2Mu conversion error"); + if(err) + vtfree(err); + + if(write(mfd[1], mdata, n) != n) + sysfatal("mount write: %r"); + } +} + +int +permf(VacFile *vf, char *user, int p) +{ + VacDir dir; + ulong perm; + + if(vacfilegetdir(vf, &dir)) + return 0; + perm = dir.mode & 0777; + + if(noperm) + goto Good; + if((p*Pother) & perm) + goto Good; + if(strcmp(user, dir.gid)==0 && ((p*Pgroup) & perm)) + goto Good; + if(strcmp(user, dir.uid)==0 && ((p*Powner) & perm)) + goto Good; + vdcleanup(&dir); + return 0; +Good: + vdcleanup(&dir); + return 1; +} + +int +perm(Fid *f, int p) +{ + return permf(f->file, f->user, p); +} + +void +vacshutdown(void) +{ + Fid *f; + + for(f = fids; f; f = f->next) { + if(!f->busy) + continue; + rclunk(f); + } + + vacfsclose(fs); + vthangup(conn); +} + |