| #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); |
| } |
| |
| if(strcmp(f->dir.mid, dir->mid) != 0){ |
| vtfree(f->dir.mid); |
| f->dir.mid = vtstrdup(dir->mid); |
| } |
| |
| 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, ulong cachemem, int mode) |
| { |
| VacFs *fs; |
| |
| fs = vtmallocz(sizeof(VacFs)); |
| fs->z = z; |
| fs->bsize = bsize; |
| fs->mode = mode; |
| fs->cache = vtcachealloc(z, cachemem); |
| 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, ulong cachemem) |
| { |
| int fd; |
| uchar score[VtScoreSize]; |
| char *prefix; |
| |
| if(vtparsescore(file, &prefix, score) >= 0){ |
| if(prefix == nil || 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); |
| } |
| if(debug) fprint(2, "vacfsopen %V\n", score); |
| return vacfsopenscore(z, score, mode, cachemem); |
| } |
| |
| VacFs* |
| vacfsopenscore(VtConn *z, u8int *score, int mode, ulong cachemem) |
| { |
| 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) { |
| if(debug) fprint(2, "read %r\n"); |
| return nil; |
| } |
| if(n != VtRootSize){ |
| werrstr("vtread on root too short"); |
| if(debug) fprint(2, "size %d\n", n); |
| return nil; |
| } |
| |
| if(vtrootunpack(&rt, buf) < 0) { |
| if(debug) fprint(2, "unpack: %r\n"); |
| return nil; |
| } |
| |
| if(strcmp(rt.type, "vac") != 0) { |
| if(debug) fprint(2, "bad type %s\n", rt.type); |
| werrstr("not a vac root"); |
| return nil; |
| } |
| |
| fs = vacfsalloc(z, rt.blocksize, cachemem, mode); |
| memmove(fs->score, score, VtScoreSize); |
| fs->mode = mode; |
| |
| memmove(e.score, rt.score, VtScoreSize); |
| e.gen = 0; |
| |
| // Don't waste cache memory on pointer blocks |
| // when rt.blocksize is large. |
| e.psize = (rt.blocksize/VtEntrySize)*VtEntrySize; |
| if(e.psize > 60000) |
| e.psize = (60000/VtEntrySize)*VtEntrySize; |
| |
| e.dsize = rt.blocksize; |
| if(debug) fprint(2, "openscore %d psize %d dsize %d\n", (int)rt.blocksize, (int)e.psize, (int)e.dsize); |
| 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, ulong cachemem) |
| { |
| VacFs *fs; |
| VtFile *f; |
| uchar buf[VtEntrySize], metascore[VtScoreSize]; |
| VtEntry e; |
| VtBlock *b; |
| MetaBlock mb; |
| VacDir vd; |
| MetaEntry me; |
| int psize; |
| |
| if((fs = vacfsalloc(z, bsize, cachemem, VtORDWR)) == nil) |
| return nil; |
| |
| /* |
| * Fake up an empty vac fs. |
| */ |
| psize = bsize/VtScoreSize*VtScoreSize; |
| if(psize > 60000) |
| psize = 60000/VtScoreSize*VtScoreSize; |
| if(debug) fprint(2, "create bsize %d psize %d\n", bsize, psize); |
| |
| f = vtfilecreateroot(fs->cache, psize, bsize, VtDirType); |
| if(f == nil) |
| sysfatal("vtfilecreateroot: %r"); |
| vtfilelock(f, VtORDWR); |
| |
| /* Write metablock containing root directory VacDir. */ |
| b = vtcacheallocblock(fs->cache, VtDataType, bsize); |
| mbinit(&mb, b->data, bsize, bsize/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; |
| vtentrypack(&e, buf, 0); |
| vtfilewrite(f, buf, VtEntrySize, VtEntrySize); |
| |
| /* Third entry: metadata stream with root directory. */ |
| memmove(e.score, metascore, VtScoreSize); |
| e.size = bsize; |
| 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; |
| } |
| |