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/file.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/vac/file.c')
-rwxr-xr-x | sys/src/cmd/vac/file.c | 2099 |
1 files changed, 2099 insertions, 0 deletions
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; +} + |