#include "stdinc.h" #include "vac.h" #include "dat.h" #include "fns.h" #include "error.h" #define debug 0 /* * locking order is upwards. A thread can hold the lock for a VacFile * and then acquire the lock of its parent */ 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; }; static int filelock(VacFile*); static u32int filemetaalloc(VacFile*, VacDir*, u32int); static int filemetaflush2(VacFile*, char*); static void filemetalock(VacFile*); static void filemetaunlock(VacFile*); static void fileraccess(VacFile*); static int filerlock(VacFile*); static void filerunlock(VacFile*); static void fileunlock(VacFile*); static void filewaccess(VacFile*, char*); void mbinit(MetaBlock*, u8int*, uint, uint); int mbsearch(MetaBlock*, char*, int*, MetaEntry*); int mbresize(MetaBlock*, MetaEntry*, int); VacFile *vdlookup(VacFile*, char*); 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); } /* * the file is locked already * f->msource is unlocked */ static VacFile* dirlookup(VacFile *f, char *elem) { int i; MetaBlock mb; MetaEntry me; VtBlock *b; VtFile *meta; VacFile *ff; u32int bo, nb; meta = f->msource; b = nil; if(vtfilelock(meta, -1) < 0) return nil; nb = (vtfilegetsize(meta)+meta->dsize-1)/meta->dsize; for(bo=0; bodata, meta->dsize) < 0) goto Err; if(mbsearch(&mb, elem, &i, &me) >= 0){ ff = filealloc(f->fs); if(vdunpack(&ff->dir, &me) < 0){ filefree(ff); goto Err; } 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; } 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; if(vtfilelock(mr->msource, -1) < 0) goto Err; b = vtfileblock(mr->msource, 0, VtOREAD); vtfileunlock(mr->msource); if(b == nil) goto Err; if(mbunpack(&mb, b->data, mr->msource->dsize) < 0) goto Err; meunpack(&me, &mb, 0); if(vdunpack(&root->dir, &me) < 0) goto Err; vtblockput(b); vtfileunlock(r); fileraccess(root); return root; Err: vtblockput(b); if(r0) vtfileclose(r0); if(r1) vtfileclose(r1); if(r2) vtfileclose(r2); if(mr) filefree(mr); if(root) filefree(root); vtfileunlock(r); return nil; } static VtFile * fileopensource(VacFile *f, u32int offset, u32int gen, int dir, uint mode) { VtFile *r; if(vtfilelock(f->source, mode) < 0) return nil; r = vtfileopen(f->source, offset, mode); vtfileunlock(f->source); if(r == nil) return nil; if(r->gen != gen){ werrstr(ERemoved); goto Err; } if(r->dir != dir && r->mode != -1){ fprint(2, "fileopensource: dir mismatch %d %d\n", r->dir, dir); werrstr(EBadMeta); goto Err; } return r; Err: vtfileclose(r); return nil; } VacFile* _filewalk(VacFile *f, char *elem, int partial) { VacFile *ff; fileraccess(f); 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){ if(vacfileisroot(f)) return vacfileincref(f); return vacfileincref(f->up); } 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(partial){ /* * Do nothing. We're opening this file only so we can clri it. * Usually the sources can't be opened, hence we won't even bother. * Be VERY careful with the returned file. If you hand it to a routine * expecting ff->source and/or ff->msource to be non-nil, we're * likely to dereference nil. FileClri should be the only routine * setting partial. */ ff->partial = 1; }else 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 Err; }else{ ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 0, ff->mode); if(ff->source == nil) goto Err; } /* link in and up parent ref count */ ff->next = f->down; f->down = ff; ff->up = f; vacfileincref(f); Exit: fileunlock(f); return ff; Err: fileunlock(f); if(ff != nil) vacfiledecref(ff); return nil; } VacFile* vacfilewalk(VacFile *f, char *elem) { return _filewalk(f, elem, 0); } VacFile* _fileopen(VacFs *fs, char *path, int partial) { 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 = _filewalk(f, elem, partial && *p=='\0'); 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; } VacFile* vacfileopen(VacFs *fs, char *path) { return _fileopen(fs, path, 0); } #if 0 static void filesettmp(VacFile *f, int istmp) { int i; VtEntry e; VtFile *r; for(i=0; i<2; i++){ if(i==0) r = f->source; else r = f->msource; if(r == nil) continue; if(vtfilegetentry(r, &e) < 0){ fprint(2, "vtfilegetentry failed (cannot happen): %r\n"); continue; } if(istmp) e.flags |= VtEntryNoArchive; else e.flags &= ~VtEntryNoArchive; if(vtfilesetentry(r, &e) < 0){ fprint(2, "vtfilesetentry failed (cannot happen): %r\n"); continue; } } } #endif VacFile* vacfilecreate(VacFile *f, char *elem, ulong mode, char *uid) { VacFile *ff; VacDir *dir; VtFile *pr, *r, *mr; int isdir; if(filelock(f) < 0) return nil; r = nil; mr = nil; for(ff = f->down; ff; ff=ff->next){ if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){ ff = nil; werrstr(EExists); goto Err1; } } ff = dirlookup(f, elem); if(ff != nil){ werrstr(EExists); goto Err1; } pr = f->source; if(pr->mode != VtORDWR){ werrstr(EReadOnly); goto Err1; } if(vtfilelock2(f->source, f->msource, -1) < 0) goto Err1; ff = filealloc(f->fs); isdir = mode & ModeDir; r = vtfilecreate(pr, pr->psize, pr->dsize, isdir ? VtDirType : VtDataType); if(r == nil) goto Err; if(isdir){ mr = vtfilecreate(pr, pr->psize, pr->dsize, VtDataType); if(mr == nil) goto Err; } dir = &ff->dir; dir->elem = vtstrdup(elem); dir->entry = r->offset; dir->gen = r->gen; if(isdir){ dir->mentry = mr->offset; dir->mgen = mr->gen; } dir->size = 0; if(_vacfsnextqid(f->fs, &dir->qid) < 0) goto Err; dir->uid = vtstrdup(uid); dir->gid = vtstrdup(f->dir.gid); dir->mid = vtstrdup(uid); dir->mtime = time(0L); dir->mcount = 0; dir->ctime = dir->mtime; dir->atime = dir->mtime; dir->mode = mode; ff->boff = filemetaalloc(f, dir, 0); if(ff->boff == NilBlock) goto Err; vtfileunlock(f->source); vtfileunlock(f->msource); ff->source = r; ff->msource = mr; #if 0 if(mode&ModeTemporary){ if(vtfilelock2(r, mr, -1) < 0) goto Err1; filesettmp(ff, 1); vtfileunlock(r); if(mr) vtfileunlock(mr); } #endif /* committed */ /* link in and up parent ref count */ ff->next = f->down; f->down = ff; ff->up = f; vacfileincref(f); filewaccess(f, uid); fileunlock(f); return ff; Err: vtfileunlock(f->source); vtfileunlock(f->msource); Err1: if(r){ vtfilelock(r, -1); vtfileremove(r); } if(mr){ vtfilelock(mr, -1); vtfileremove(mr); } if(ff) vacfiledecref(ff); fileunlock(f); return nil; } int vacfileblockscore(VacFile *f, u32int bn, u8int *score) { VtFile *s; uvlong size; int dsize, ret; ret = -1; if(filerlock(f) < 0) return -1; fileraccess(f); if(vtfilelock(f->source, VtOREAD) < 0) goto out; s = f->source; dsize = s->dsize; size = vtfilegetsize(s); if((uvlong)bn*dsize >= size) goto out; ret = vtfileblockscore(f->source, bn, score); out: vtfileunlock(f->source); filerunlock(f); return ret; } int vacfileread(VacFile *f, void *buf, int cnt, vlong offset) { VtFile *s; uvlong size; u32int bn; int off, dsize, n, nn; VtBlock *b; uchar *p; if(0)fprint(2, "fileRead: %s %d, %lld\n", f->dir.elem, cnt, offset); if(filerlock(f) < 0) return -1; if(offset < 0){ werrstr(EBadOffset); goto Err1; } fileraccess(f); if(vtfilelock(f->source, VtOREAD) < 0) goto Err1; s = f->source; dsize = s->dsize; size = vtfilegetsize(s); if(offset >= size) offset = size; if(cnt > size-offset) cnt = size-offset; bn = offset/dsize; off = offset%dsize; p = buf; while(cnt > 0){ b = vtfileblock(s, bn, OREAD); if(b == nil) goto Err; n = cnt; if(n > dsize-off) n = dsize-off; nn = dsize-off; if(nn > n) nn = n; memmove(p, b->data+off, nn); memset(p+nn, 0, nn-n); off = 0; bn++; cnt -= n; p += n; vtblockput(b); } vtfileunlock(s); filerunlock(f); return p-(uchar*)buf; Err: vtfileunlock(s); Err1: filerunlock(f); return -1; } #if 0 /* * Changes the file block bn to be the given block score. * Very sneaky. Only used by flfmt. */ int filemapblock(VacFile *f, ulong bn, uchar score[VtScoreSize], ulong tag) { VtBlock *b; VtEntry e; VtFile *s; if(filelock(f) < 0) return -1; s = nil; if(f->dir.mode & ModeDir){ werrstr(ENotFile); goto Err; } if(f->source->mode != VtORDWR){ werrstr(EReadOnly); goto Err; } if(vtfilelock(f->source, -1) < 0) goto Err; s = f->source; b = _vtfileblock(s, bn, VtORDWR, 1, tag); if(b == nil) goto Err; if(vtfilegetentry(s, &e) < 0) goto Err; if(b->l.type == BtDir){ memmove(e.score, score, VtScoreSize); assert(e.tag == tag || e.tag == 0); e.tag = tag; e.flags |= VtEntryLocal; vtentrypack(&e, b->data, f->source->offset % f->source->epb); }else memmove(b->data + (bn%(e.psize/VtScoreSize))*VtScoreSize, score, VtScoreSize); /* vtblockdirty(b); */ vtblockput(b); vtfileunlock(s); fileunlock(f); return 0; Err: if(s) vtfileunlock(s); fileunlock(f); return -1; } #endif int vacfilesetsize(VacFile *f, uvlong size) { int r; if(filelock(f) < 0) return -1; r = 0; if(f->dir.mode & ModeDir){ werrstr(ENotFile); goto Err; } if(f->source->mode != VtORDWR){ werrstr(EReadOnly); goto Err; } if(vtfilelock(f->source, -1) < 0) goto Err; r = vtfilesetsize(f->source, size); vtfileunlock(f->source); Err: fileunlock(f); return r; } int filewrite(VacFile *f, void *buf, int cnt, vlong offset, char *uid) { VtFile *s; ulong bn; int off, dsize, n; VtBlock *b; uchar *p; vlong eof; if(0)fprint(2, "fileWrite: %s %d, %lld\n", f->dir.elem, cnt, offset); if(filelock(f) < 0) return -1; s = nil; if(f->dir.mode & ModeDir){ werrstr(ENotFile); goto Err; } if(f->source->mode != VtORDWR){ werrstr(EReadOnly); goto Err; } if(offset < 0){ werrstr(EBadOffset); goto Err; } filewaccess(f, uid); if(vtfilelock(f->source, -1) < 0) goto Err; s = f->source; dsize = s->dsize; eof = vtfilegetsize(s); if(f->dir.mode & ModeAppend) offset = eof; bn = offset/dsize; off = offset%dsize; p = buf; while(cnt > 0){ n = cnt; if(n > dsize-off) n = dsize-off; b = vtfileblock(s, bn, n eof) vtfilesetsize(s, offset); goto Err; } memmove(b->data+off, p, n); off = 0; cnt -= n; p += n; offset += n; bn++; /* vtblockdirty(b); */ vtblockput(b); } if(offset > eof && vtfilesetsize(s, offset) < 0) goto Err; vtfileunlock(s); fileunlock(f); return p-(uchar*)buf; Err: if(s) vtfileunlock(s); fileunlock(f); return -1; } int vacfilegetdir(VacFile *f, VacDir *dir) { if(filerlock(f) < 0) return -1; filemetalock(f); vdcopy(dir, &f->dir); filemetaunlock(f); if(vacfileisdir(f) < 0){ if(vtfilelock(f->source, VtOREAD) < 0){ filerunlock(f); return -1; } dir->size = vtfilegetsize(f->source); vtfileunlock(f->source); } filerunlock(f); return 0; } int vacfiletruncate(VacFile *f, char *uid) { if(vacfileisdir(f)){ werrstr(ENotFile); return -1; } if(filelock(f) < 0) return -1; if(f->source->mode != VtORDWR){ werrstr(EReadOnly); fileunlock(f); return -1; } if(vtfilelock(f->source, -1) < 0){ fileunlock(f); return -1; } if(vtfiletruncate(f->source) < 0){ vtfileunlock(f->source); fileunlock(f); return -1; } vtfileunlock(f->source); fileunlock(f); filewaccess(f, uid); return 0; } int vacfilesetdir(VacFile *f, VacDir *dir, char *uid) { 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; if(f->source->mode != VtORDWR){ werrstr(EReadOnly); fileunlock(f); return -1; } filemetalock(f); /* 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; } } 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; } /* commited to changing it now */ } } /* commited to changing it now */ #if 0 if((f->dir.mode&ModeTemporary) != (dir->mode&ModeTemporary)) filesettmp(f, dir->mode&ModeTemporary); #endif 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; /*fprint(2, "mode %x %x ", f->dir.mode, dir->mode); */ mask = ~(ModeDir|ModeSnapshot); f->dir.mode &= ~mask; f->dir.mode |= mask & dir->mode; f->dirty = 1; /*fprint(2, "->%x\n", f->dir.mode); */ filemetaflush2(f, oelem); vtfree(oelem); filemetaunlock(f); fileunlock(f); filewaccess(f->up, uid); return 0; Err: filemetaunlock(f); fileunlock(f); return -1; } int vacfilesetqidspace(VacFile *f, u64int offset, u64int max) { int ret; if(filelock(f) < 0) return -1; filemetalock(f); f->dir.qidspace = 1; f->dir.qidoffset = offset; f->dir.qidmax = max; ret = filemetaflush2(f, nil); filemetaunlock(f); fileunlock(f); return ret; } uvlong vacfilegetid(VacFile *f) { /* immutable */ return f->dir.qid; } 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; } int vacfilegetsize(VacFile *f, uvlong *size) { if(filerlock(f) < 0) return 0; if(vtfilelock(f->source, VtOREAD) < 0){ filerunlock(f); return -1; } *size = vtfilegetsize(f->source); vtfileunlock(f->source); filerunlock(f); return 0; } int vacfilegetvtentry(VacFile *f, VtEntry *e) { if(filerlock(f) < 0) return 0; if(vtfilelock(f->source, VtOREAD) < 0){ filerunlock(f); return -1; } vtfilegetentry(f->source, e); vtfileunlock(f->source); filerunlock(f); return 0; } void vacfilemetaflush(VacFile *f, int rec) { VacFile **kids, *p; int nkids; int i; filemetalock(f); filemetaflush2(f, nil); filemetaunlock(f); if(!rec || !vacfileisdir(f)) return; if(filelock(f) < 0) return; 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++; } fileunlock(f); for(i=0; idirty) return 0; if(oelem == nil) oelem = f->dir.elem; fp = f->up; if(vtfilelock(fp->msource, -1) < 0) return 0; /* 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; n = vdsize(&f->dir); if(0)fprint(2, "old size %d new size %d\n", me.size, n); if(mbresize(&mb, &me, n) >= 0){ /* fits in the block */ mbdelete(&mb, i, &me); if(strcmp(f->dir.elem, oelem) != 0) mbsearch(&mb, f->dir.elem, &i, &me2); vdpack(&f->dir, &me); mbinsert(&mb, i, &me); mbpack(&mb); /* vtblockdirty(b); */ vtblockput(b); vtfileunlock(fp->msource); f->dirty = 0; return -1; } /* * moving entry to another block * it is feasible for the fs to crash leaving two copies * of the directory entry. This is just too much work to * fix. Given that entries are only allocated in a block that * is less than PercentageFull, most modifications of meta data * will fit within the block. i.e. this code should almost * never be executed. */ boff = filemetaalloc(fp, &f->dir, f->boff+1); if(boff == NilBlock){ /* mbResize might have modified block */ mbpack(&mb); /* vtblockdirty(b); */ goto Err; } fprint(2, "fileMetaFlush moving entry from %ud -> %ud\n", f->boff, boff); f->boff = boff; /* make sure deletion goes to disk after new entry */ bb = vtfileblock(fp->msource, f->boff, VtORDWR); mbdelete(&mb, i, &me); mbpack(&mb); /* blockDependency(b, bb, -1, nil, nil); */ vtblockput(bb); /* vtblockdirty(b); */ vtblockput(b); vtfileunlock(fp->msource); f->dirty = 0; return 0; Err: vtblockput(b); Err1: vtfileunlock(fp->msource); return -1; } static int filemetaremove(VacFile *f, char *uid) { VtBlock *b; MetaBlock mb; MetaEntry me; int i; VacFile *up; b = nil; up = f->up; filewaccess(up, uid); filemetalock(f); if(vtfilelock(up->msource, VtORDWR) < 0) goto Err; b = vtfileblock(up->msource, f->boff, VtORDWR); if(b == nil) goto Err; if(mbunpack(&mb, b->data, up->msource->dsize) < 0) goto Err; if(mbsearch(&mb, f->dir.elem, &i, &me) < 0) goto Err; mbdelete(&mb, i, &me); mbpack(&mb); vtfileunlock(up->msource); /* vtblockdirty(b); */ vtblockput(b); f->removed = 1; f->boff = NilBlock; f->dirty = 0; filemetaunlock(f); return 0; Err: vtfileunlock(up->msource); vtblockput(b); filemetaunlock(f); return -1; } /* assume file is locked, assume f->msource is locked */ static int filecheckempty(VacFile *f) { u32int i, n; VtBlock *b; MetaBlock mb; VtFile *r; r = f->msource; n = (vtfilegetsize(r)+r->dsize-1)/r->dsize; for(i=0; idata, r->dsize) < 0) goto Err; if(mb.nindex > 0){ werrstr(ENotEmpty); goto Err; } vtblockput(b); } return 0; Err: vtblockput(b); return -1; } int vacfileremove(VacFile *f, char *muid) { VacFile *ff; /* can not 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, muid) < 0) return -1; return 0; Err: vtfileunlock(f->source); if(f->msource) vtfileunlock(f->msource); Err1: fileunlock(f); return -1; } static int clri(VacFile *f, char *uid) { int r; if(f == nil) return -1; if(f->up->source->mode != VtORDWR){ werrstr(EReadOnly); vacfiledecref(f); return -1; } r = filemetaremove(f, uid); vacfiledecref(f); return r; } int vacfileclripath(VacFs *fs, char *path, char *uid) { return clri(_fileopen(fs, path, 1), uid); } int vacfileclri(VacFile *dir, char *elem, char *uid) { return clri(_filewalk(dir, elem, 1), uid); } 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); filemetaflush2(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; } VacFile* vacfilegetparent(VacFile *f) { if(vacfileisroot(f)) return vacfileincref(f); return vacfileincref(f->up); } int vacfilewrite(VacFile *file, void *buf, int n, vlong offset, char *muid) { werrstr("read only file system"); return -1; } VacDirEnum* vdeopen(VacFile *f) { VacDirEnum *vde; VacFile *p; if(!vacfileisdir(f)){ werrstr(ENotDir); return nil; } /* flush out meta data */ if(filelock(f) < 0) return nil; for(p=f->down; p; p=p->next) filemetaflush2(p, nil); fileunlock(f); vde = vtmallocz(sizeof(VacDirEnum)); vde->file = vacfileincref(f); return vde; } static int direntrysize(VtFile *s, ulong elem, ulong gen, uvlong *size) { VtBlock *b; ulong bn; VtEntry e; int epb; epb = s->dsize/VtEntrySize; bn = elem/epb; elem -= bn*epb; b = vtfileblock(s, bn, VtOREAD); if(b == nil) goto Err; if(vtentryunpack(&e, b->data, elem) < 0) goto Err; /* hanging 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; } static int vdefill(VacDirEnum *vde) { int i, n; VtFile *meta, *source; MetaBlock mb; MetaEntry me; VacFile *f; VtBlock *b; VacDir *de; /* clean up first */ for(i=vde->i; in; i++) vdcleanup(vde->buf+i); vtfree(vde->buf); vde->buf = nil; vde->i = 0; vde->n = 0; f = vde->file; source = f->source; meta = f->msource; b = vtfileblock(meta, vde->boff, VtOREAD); if(b == nil) goto Err; if(mbunpack(&mb, b->data, meta->dsize) < 0) goto Err; n = mb.nindex; vde->buf = vtmalloc(n * sizeof(VacDir)); for(i=0; ibuf + i; meunpack(&me, &mb, i); if(vdunpack(de, &me) < 0) goto Err; vde->n++; if(!(de->mode & ModeDir)) if(direntrysize(source, de->entry, de->gen, &de->size) < 0) goto Err; } vde->boff++; vtblockput(b); return 0; Err: vtblockput(b); return -1; } int vderead(VacDirEnum *vde, VacDir *de) { int ret, didread; 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; didread = 0; while(vde->i >= vde->n){ if(vde->boff >= nb){ ret = 0; goto Return; } didread = 1; 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); if(didread) fileraccess(f); return ret; } void vdeclose(VacDirEnum *vde) { int i; if(vde == nil) return; for(i=vde->i; in; i++) vdcleanup(vde->buf+i); vtfree(vde->buf); vacfiledecref(vde->file); vtfree(vde); } /* * caller must lock f->source and f->msource * caller must NOT lock the source and msource * referenced by dir. */ static u32int filemetaalloc(VacFile *f, VacDir *dir, u32int start) { u32int nb, bo; VtBlock *b; MetaBlock mb; int nn; uchar *p; int i, n; MetaEntry me; VtFile *s, *ms; s = f->source; ms = f->msource; n = vdsize(dir); nb = (vtfilegetsize(ms)+ms->dsize-1)/ms->dsize; b = nil; if(start > nb) start = nb; for(bo=start; bodata, ms->dsize) < 0) goto Err; nn = (mb.maxsize*FullPercentage/100) - mb.size + mb.free; if(n <= nn && mb.nindex < mb.maxindex) break; vtblockput(b); b = nil; } /* add block to meta file */ if(b == nil){ b = vtfileblock(ms, bo, VtORDWR); if(b == nil) goto Err; vtfilesetsize(ms, (nb+1)*ms->dsize); mbinit(&mb, b->data, ms->dsize, ms->dsize/BytesPerEntry); } p = mballoc(&mb, n); if(p == nil){ /* mbAlloc might have changed block */ mbpack(&mb); /* vtblockdirty(b); */ werrstr(EBadMeta); goto Err; } mbsearch(&mb, dir->elem, &i, &me); assert(me.p == nil); me.p = p; me.size = n; vdpack(dir, &me); mbinsert(&mb, i, &me); mbpack(&mb); #ifdef notdef /* XXX */ /* meta block depends on super block for qid ... */ bb = cacheLocal(b->c, PartSuper, 0, VtOREAD); blockDependency(b, bb, -1, nil, nil); vtblockput(bb); /* ... and one or two dir entries */ epb = s->dsize/VtEntrySize; bb = vtfileblock(s, dir->entry/epb, VtOREAD); blockDependency(b, bb, -1, nil, nil); vtblockput(bb); if(dir->mode & ModeDir){ bb = sourceBlock(s, dir->mentry/epb, VtOREAD); blockDependency(b, bb, -1, nil, nil); vtblockput(bb); } #endif /* vtblockdirty(b); */ vtblockput(b); return bo; Err: vtblockput(b); return NilBlock; } 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 filerlock(VacFile *f) { /* assert(!canwlock(&f->fs->elk)); */ rlock(&f->lk); if(chksource(f) < 0){ runlock(&f->lk); return -1; } return 0; } static void filerunlock(VacFile *f) { runlock(&f->lk); } static int filelock(VacFile *f) { /* assert(!canwlock(&f->fs->elk)); */ wlock(&f->lk); if(chksource(f) < 0){ wunlock(&f->lk); return -1; } return 0; } static void fileunlock(VacFile *f) { wunlock(&f->lk); } /* * f->source and f->msource must NOT be locked. * fileMetaFlush locks the fileMeta and then the source (in fileMetaFlush2). * We have to respect that ordering. */ static void filemetalock(VacFile *f) { assert(f->up != nil); /* assert(!canwlock(&f->fs->elk)); */ wlock(&f->up->lk); } static void filemetaunlock(VacFile *f) { wunlock(&f->up->lk); } /* * f->source and f->msource must NOT be locked. * see filemetalock. */ static void fileraccess(VacFile* f) { if(f->mode == VtOREAD) return; filemetalock(f); f->dir.atime = time(0L); f->dirty = 1; filemetaunlock(f); } /* * f->source and f->msource must NOT be locked. * see filemetalock. */ static void filewaccess(VacFile* f, char *mid) { if(f->mode == VtOREAD) return; filemetalock(f); f->dir.atime = f->dir.mtime = time(0L); if(strcmp(f->dir.mid, mid) != 0){ vtfree(f->dir.mid); f->dir.mid = vtstrdup(mid); } f->dir.mcount++; f->dirty = 1; filemetaunlock(f); /*RSC: let's try this */ /*presotto - lets not if(f->up) filewaccess(f->up, mid); */ } #if 0 static void markCopied(Block *b) { VtBlock *lb; Label l; if(globalToLocal(b->score) == NilBlock) return; if(!(b->l.state & BsCopied)){ /* * We need to record that there are now pointers in * b that are not unique to b. We do this by marking * b as copied. Since we don't return the label block, * the caller can't get the dependencies right. So we have * to flush the block ourselves. This is a rare occurrence. */ l = b->l; l.state |= BsCopied; lb = _blockSetLabel(b, &l); WriteAgain: while(!blockWrite(lb)){ fprint(2, "getEntry: could not write label block\n"); sleep(10*1000); } while(lb->iostate != BioClean && lb->iostate != BioDirty){ assert(lb->iostate == BioWriting); vtSleep(lb->ioready); } if(lb->iostate == BioDirty) goto WriteAgain; vtblockput(lb); } } static int getEntry(VtFile *r, Entry *e, int mark) { Block *b; if(r == nil){ memset(&e, 0, sizeof e); return 1; } b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, VtOREAD); if(b == nil) return 0; if(!entryUnpack(e, b->data, r->offset % r->epb)){ vtblockput(b); return 0; } if(mark) markCopied(b); vtblockput(b); return 1; } static int setEntry(Source *r, Entry *e) { Block *b; Entry oe; b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, VtORDWR); if(0) fprint(2, "setEntry: b %#ux %d score=%V\n", b->addr, r->offset % r->epb, e->score); if(b == nil) return 0; if(!entryUnpack(&oe, b->data, r->offset % r->epb)){ vtblockput(b); return 0; } e->gen = oe.gen; entryPack(e, b->data, r->offset % r->epb); /* BUG b should depend on the entry pointer */ markCopied(b); /* vtblockdirty(b); */ vtblockput(b); return 1; } /* assumes hold elk */ int fileSnapshot(VacFile *dst, VacFile *src, u32int epoch, int doarchive) { Entry e, ee; /* add link to snapshot */ if(!getEntry(src->source, &e, 1) || !getEntry(src->msource, &ee, 1)) return 0; e.snap = epoch; e.archive = doarchive; ee.snap = epoch; ee.archive = doarchive; if(!setEntry(dst->source, &e) || !setEntry(dst->msource, &ee)) return 0; return 1; } int fileGetSources(VacFile *f, Entry *e, Entry *ee, int mark) { if(!getEntry(f->source, e, mark) || !getEntry(f->msource, ee, mark)) return 0; return 1; } int fileWalkSources(VacFile *f) { if(f->mode == VtOREAD) return 1; if(!sourceLock2(f->source, f->msource, VtORDWR)) return 0; vtfileunlock(f->source); vtfileunlock(f->msource); return 1; } #endif