ref: 39f18c9d88f52a22373790dec5721fa3521d3f00
dir: /sys/src/cmd/vac/file.c/
#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, 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(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); } 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; }