summaryrefslogtreecommitdiff
path: root/sys/src/cmd/fossil/file.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/fossil/file.c
downloadplan9front-e5888a1ffdae813d7575f5fb02275c6bb07e5199.tar.xz
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/fossil/file.c')
-rwxr-xr-xsys/src/cmd/fossil/file.c1842
1 files changed, 1842 insertions, 0 deletions
diff --git a/sys/src/cmd/fossil/file.c b/sys/src/cmd/fossil/file.c
new file mode 100755
index 000000000..cb96ed197
--- /dev/null
+++ b/sys/src/cmd/fossil/file.c
@@ -0,0 +1,1842 @@
+#include "stdinc.h"
+#include "9.h" /* for consPrint */
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+/*
+ * locking order is upwards. A thread can hold the lock for a File
+ * and then acquire the lock of its parent
+ */
+
+struct File {
+ Fs *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 meta data */
+
+ DirEntry dir; /* meta data for this file, including component name */
+
+ File *up; /* parent file (directory) */
+ File *next; /* sibling */
+
+ /* data for file */
+ VtLock *lk; /* lock for the following */
+ Source *source;
+ Source *msource; /* for directories: meta data for children */
+ File *down; /* children */
+
+ int mode;
+ int issnapshot;
+};
+
+static int fileMetaFlush2(File*, char*);
+static u32int fileMetaAlloc(File*, DirEntry*, u32int);
+static int fileRLock(File*);
+static void fileRUnlock(File*);
+static int fileLock(File*);
+static void fileUnlock(File*);
+static void fileMetaLock(File*);
+static void fileMetaUnlock(File*);
+static void fileRAccess(File*);
+static void fileWAccess(File*, char*);
+
+static File *
+fileAlloc(Fs *fs)
+{
+ File *f;
+
+ f = vtMemAllocZ(sizeof(File));
+ f->lk = vtLockAlloc();
+ f->ref = 1;
+ f->fs = fs;
+ f->boff = NilBlock;
+ f->mode = fs->mode;
+ return f;
+}
+
+static void
+fileFree(File *f)
+{
+ sourceClose(f->source);
+ vtLockFree(f->lk);
+ sourceClose(f->msource);
+ deCleanup(&f->dir);
+
+ memset(f, ~0, sizeof(File));
+ vtMemFree(f);
+}
+
+/*
+ * the file is locked already
+ * f->msource is unlocked
+ */
+static File *
+dirLookup(File *f, char *elem)
+{
+ int i;
+ MetaBlock mb;
+ MetaEntry me;
+ Block *b;
+ Source *meta;
+ File *ff;
+ u32int bo, nb;
+
+ meta = f->msource;
+ b = nil;
+ if(!sourceLock(meta, -1))
+ return nil;
+ nb = (sourceGetSize(meta)+meta->dsize-1)/meta->dsize;
+ for(bo=0; bo<nb; bo++){
+ b = sourceBlock(meta, bo, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, meta->dsize))
+ goto Err;
+ if(mbSearch(&mb, elem, &i, &me)){
+ ff = fileAlloc(f->fs);
+ if(!deUnpack(&ff->dir, &me)){
+ fileFree(ff);
+ goto Err;
+ }
+ sourceUnlock(meta);
+ blockPut(b);
+ ff->boff = bo;
+ ff->mode = f->mode;
+ ff->issnapshot = f->issnapshot;
+ return ff;
+ }
+
+ blockPut(b);
+ b = nil;
+ }
+ vtSetError(ENoFile);
+ /* fall through */
+Err:
+ sourceUnlock(meta);
+ blockPut(b);
+ return nil;
+}
+
+File *
+fileRoot(Source *r)
+{
+ Block *b;
+ Source *r0, *r1, *r2;
+ MetaBlock mb;
+ MetaEntry me;
+ File *root, *mr;
+ Fs *fs;
+
+ b = nil;
+ root = nil;
+ mr = nil;
+ r1 = nil;
+ r2 = nil;
+
+ fs = r->fs;
+ if(!sourceLock(r, -1))
+ return nil;
+ r0 = sourceOpen(r, 0, fs->mode, 0);
+ if(r0 == nil)
+ goto Err;
+ r1 = sourceOpen(r, 1, fs->mode, 0);
+ if(r1 == nil)
+ goto Err;
+ r2 = sourceOpen(r, 2, fs->mode, 0);
+ if(r2 == nil)
+ goto Err;
+
+ mr = fileAlloc(fs);
+ mr->msource = r2;
+ r2 = nil;
+
+ root = fileAlloc(fs);
+ root->boff = 0;
+ root->up = mr;
+ root->source = r0;
+ r0->file = root; /* point back to source */
+ r0 = nil;
+ root->msource = r1;
+ r1 = nil;
+
+ mr->down = root;
+
+ if(!sourceLock(mr->msource, -1))
+ goto Err;
+ b = sourceBlock(mr->msource, 0, OReadOnly);
+ sourceUnlock(mr->msource);
+ if(b == nil)
+ goto Err;
+
+ if(!mbUnpack(&mb, b->data, mr->msource->dsize))
+ goto Err;
+
+ meUnpack(&me, &mb, 0);
+ if(!deUnpack(&root->dir, &me))
+ goto Err;
+ blockPut(b);
+ sourceUnlock(r);
+ fileRAccess(root);
+
+ return root;
+Err:
+ blockPut(b);
+ if(r0)
+ sourceClose(r0);
+ if(r1)
+ sourceClose(r1);
+ if(r2)
+ sourceClose(r2);
+ if(mr)
+ fileFree(mr);
+ if(root)
+ fileFree(root);
+ sourceUnlock(r);
+
+ return nil;
+}
+
+static Source *
+fileOpenSource(File *f, u32int offset, u32int gen, int dir, uint mode,
+ int issnapshot)
+{
+ char *rname, *fname;
+ Source *r;
+
+ if(!sourceLock(f->source, mode))
+ return nil;
+ r = sourceOpen(f->source, offset, mode, issnapshot);
+ sourceUnlock(f->source);
+ if(r == nil)
+ return nil;
+ if(r->gen != gen){
+ vtSetError(ERemoved);
+ goto Err;
+ }
+ if(r->dir != dir && r->mode != -1){
+ /* this hasn't been as useful as we hoped it would be. */
+ rname = sourceName(r);
+ fname = fileName(f);
+ consPrint("%s: source %s for file %s: fileOpenSource: "
+ "dir mismatch %d %d\n",
+ f->source->fs->name, rname, fname, r->dir, dir);
+ free(rname);
+ free(fname);
+
+ vtSetError(EBadMeta);
+ goto Err;
+ }
+ return r;
+Err:
+ sourceClose(r);
+ return nil;
+}
+
+File *
+_fileWalk(File *f, char *elem, int partial)
+{
+ File *ff;
+
+ fileRAccess(f);
+
+ if(elem[0] == 0){
+ vtSetError(EBadPath);
+ return nil;
+ }
+
+ if(!fileIsDir(f)){
+ vtSetError(ENotDir);
+ return nil;
+ }
+
+ if(strcmp(elem, ".") == 0){
+ return fileIncRef(f);
+ }
+
+ if(strcmp(elem, "..") == 0){
+ if(fileIsRoot(f))
+ return fileIncRef(f);
+ return fileIncRef(f->up);
+ }
+
+ if(!fileLock(f))
+ 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 = OReadOnly;
+ ff->issnapshot = 1;
+ }
+
+ 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->issnapshot);
+ ff->msource = fileOpenSource(f, ff->dir.mentry, ff->dir.mgen,
+ 0, ff->mode, ff->issnapshot);
+ if(ff->source == nil || ff->msource == nil)
+ goto Err;
+ }else{
+ ff->source = fileOpenSource(f, ff->dir.entry, ff->dir.gen,
+ 0, ff->mode, ff->issnapshot);
+ if(ff->source == nil)
+ goto Err;
+ }
+
+ /* link in and up parent ref count */
+ if (ff->source)
+ ff->source->file = ff; /* point back */
+ ff->next = f->down;
+ f->down = ff;
+ ff->up = f;
+ fileIncRef(f);
+Exit:
+ fileUnlock(f);
+ return ff;
+Err:
+ fileUnlock(f);
+ if(ff != nil)
+ fileDecRef(ff);
+ return nil;
+}
+
+File *
+fileWalk(File *f, char *elem)
+{
+ return _fileWalk(f, elem, 0);
+}
+
+File *
+_fileOpen(Fs *fs, char *path, int partial)
+{
+ File *f, *ff;
+ char *p, elem[VtMaxStringSize], *opath;
+ int n;
+
+ f = fs->file;
+ fileIncRef(f);
+ opath = path;
+ while(*path != 0){
+ for(p = path; *p && *p != '/'; p++)
+ ;
+ n = p - path;
+ if(n > 0){
+ if(n > VtMaxStringSize){
+ vtSetError("%s: element too long", EBadPath);
+ goto Err;
+ }
+ memmove(elem, path, n);
+ elem[n] = 0;
+ ff = _fileWalk(f, elem, partial && *p=='\0');
+ if(ff == nil){
+ vtSetError("%.*s: %R", utfnlen(opath, p-opath),
+ opath);
+ goto Err;
+ }
+ fileDecRef(f);
+ f = ff;
+ }
+ if(*p == '/')
+ p++;
+ path = p;
+ }
+ return f;
+Err:
+ fileDecRef(f);
+ return nil;
+}
+
+File*
+fileOpen(Fs *fs, char *path)
+{
+ return _fileOpen(fs, path, 0);
+}
+
+static void
+fileSetTmp(File *f, int istmp)
+{
+ int i;
+ Entry e;
+ Source *r;
+
+ for(i=0; i<2; i++){
+ if(i==0)
+ r = f->source;
+ else
+ r = f->msource;
+ if(r == nil)
+ continue;
+ if(!sourceGetEntry(r, &e)){
+ fprint(2, "sourceGetEntry failed (cannot happen): %r\n");
+ continue;
+ }
+ if(istmp)
+ e.flags |= VtEntryNoArchive;
+ else
+ e.flags &= ~VtEntryNoArchive;
+ if(!sourceSetEntry(r, &e)){
+ fprint(2, "sourceSetEntry failed (cannot happen): %r\n");
+ continue;
+ }
+ }
+}
+
+File *
+fileCreate(File *f, char *elem, ulong mode, char *uid)
+{
+ File *ff;
+ DirEntry *dir;
+ Source *pr, *r, *mr;
+ int isdir;
+
+ if(!fileLock(f))
+ 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;
+ vtSetError(EExists);
+ goto Err1;
+ }
+ }
+
+ ff = dirLookup(f, elem);
+ if(ff != nil){
+ vtSetError(EExists);
+ goto Err1;
+ }
+
+ pr = f->source;
+ if(pr->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err1;
+ }
+
+ if(!sourceLock2(f->source, f->msource, -1))
+ goto Err1;
+
+ ff = fileAlloc(f->fs);
+ isdir = mode & ModeDir;
+
+ r = sourceCreate(pr, pr->dsize, isdir, 0);
+ if(r == nil)
+ goto Err;
+ if(isdir){
+ mr = sourceCreate(pr, pr->dsize, 0, r->offset);
+ 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(!fsNextQid(f->fs, &dir->qid))
+ 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;
+
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+
+ ff->source = r;
+ r->file = ff; /* point back */
+ ff->msource = mr;
+
+ if(mode&ModeTemporary){
+ if(!sourceLock2(r, mr, -1))
+ goto Err1;
+ fileSetTmp(ff, 1);
+ sourceUnlock(r);
+ if(mr)
+ sourceUnlock(mr);
+ }
+
+ /* committed */
+
+ /* link in and up parent ref count */
+ ff->next = f->down;
+ f->down = ff;
+ ff->up = f;
+ fileIncRef(f);
+
+ fileWAccess(f, uid);
+
+ fileUnlock(f);
+ return ff;
+
+Err:
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+Err1:
+ if(r){
+ sourceLock(r, -1);
+ sourceRemove(r);
+ }
+ if(mr){
+ sourceLock(mr, -1);
+ sourceRemove(mr);
+ }
+ if(ff)
+ fileDecRef(ff);
+ fileUnlock(f);
+ return 0;
+}
+
+int
+fileRead(File *f, void *buf, int cnt, vlong offset)
+{
+ Source *s;
+ uvlong size;
+ u32int bn;
+ int off, dsize, n, nn;
+ Block *b;
+ uchar *p;
+
+if(0)fprint(2, "fileRead: %s %d, %lld\n", f->dir.elem, cnt, offset);
+
+ if(!fileRLock(f))
+ return -1;
+
+ if(offset < 0){
+ vtSetError(EBadOffset);
+ goto Err1;
+ }
+
+ fileRAccess(f);
+
+ if(!sourceLock(f->source, OReadOnly))
+ goto Err1;
+
+ s = f->source;
+ dsize = s->dsize;
+ size = sourceGetSize(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 = sourceBlock(s, bn, OReadOnly);
+ 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;
+ blockPut(b);
+ }
+ sourceUnlock(s);
+ fileRUnlock(f);
+ return p-(uchar*)buf;
+
+Err:
+ sourceUnlock(s);
+Err1:
+ fileRUnlock(f);
+ return -1;
+}
+
+/*
+ * Changes the file block bn to be the given block score.
+ * Very sneaky. Only used by flfmt.
+ */
+int
+fileMapBlock(File *f, ulong bn, uchar score[VtScoreSize], ulong tag)
+{
+ Block *b;
+ Entry e;
+ Source *s;
+
+ if(!fileLock(f))
+ return 0;
+
+ s = nil;
+ if(f->dir.mode & ModeDir){
+ vtSetError(ENotFile);
+ goto Err;
+ }
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err;
+ }
+
+ if(!sourceLock(f->source, -1))
+ goto Err;
+
+ s = f->source;
+ b = _sourceBlock(s, bn, OReadWrite, 1, tag);
+ if(b == nil)
+ goto Err;
+
+ if(!sourceGetEntry(s, &e))
+ 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;
+ entryPack(&e, b->data, f->source->offset % f->source->epb);
+ }else
+ memmove(b->data + (bn%(e.psize/VtScoreSize))*VtScoreSize, score, VtScoreSize);
+ blockDirty(b);
+ blockPut(b);
+ sourceUnlock(s);
+ fileUnlock(f);
+ return 1;
+
+Err:
+ if(s)
+ sourceUnlock(s);
+ fileUnlock(f);
+ return 0;
+}
+
+int
+fileSetSize(File *f, uvlong size)
+{
+ int r;
+
+ if(!fileLock(f))
+ return 0;
+ r = 0;
+ if(f->dir.mode & ModeDir){
+ vtSetError(ENotFile);
+ goto Err;
+ }
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err;
+ }
+ if(!sourceLock(f->source, -1))
+ goto Err;
+ r = sourceSetSize(f->source, size);
+ sourceUnlock(f->source);
+Err:
+ fileUnlock(f);
+ return r;
+}
+
+int
+fileWrite(File *f, void *buf, int cnt, vlong offset, char *uid)
+{
+ Source *s;
+ ulong bn;
+ int off, dsize, n;
+ Block *b;
+ uchar *p;
+ vlong eof;
+
+if(0)fprint(2, "fileWrite: %s %d, %lld\n", f->dir.elem, cnt, offset);
+
+ if(!fileLock(f))
+ return -1;
+
+ s = nil;
+ if(f->dir.mode & ModeDir){
+ vtSetError(ENotFile);
+ goto Err;
+ }
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err;
+ }
+ if(offset < 0){
+ vtSetError(EBadOffset);
+ goto Err;
+ }
+
+ fileWAccess(f, uid);
+
+ if(!sourceLock(f->source, -1))
+ goto Err;
+ s = f->source;
+ dsize = s->dsize;
+
+ eof = sourceGetSize(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 = sourceBlock(s, bn, n<dsize?OReadWrite:OOverWrite);
+ if(b == nil){
+ if(offset > eof)
+ sourceSetSize(s, offset);
+ goto Err;
+ }
+ memmove(b->data+off, p, n);
+ off = 0;
+ cnt -= n;
+ p += n;
+ offset += n;
+ bn++;
+ blockDirty(b);
+ blockPut(b);
+ }
+ if(offset > eof && !sourceSetSize(s, offset))
+ goto Err;
+ sourceUnlock(s);
+ fileUnlock(f);
+ return p-(uchar*)buf;
+Err:
+ if(s)
+ sourceUnlock(s);
+ fileUnlock(f);
+ return -1;
+}
+
+int
+fileGetDir(File *f, DirEntry *dir)
+{
+ if(!fileRLock(f))
+ return 0;
+
+ fileMetaLock(f);
+ deCopy(dir, &f->dir);
+ fileMetaUnlock(f);
+
+ if(!fileIsDir(f)){
+ if(!sourceLock(f->source, OReadOnly)){
+ fileRUnlock(f);
+ return 0;
+ }
+ dir->size = sourceGetSize(f->source);
+ sourceUnlock(f->source);
+ }
+ fileRUnlock(f);
+
+ return 1;
+}
+
+int
+fileTruncate(File *f, char *uid)
+{
+ if(fileIsDir(f)){
+ vtSetError(ENotFile);
+ return 0;
+ }
+
+ if(!fileLock(f))
+ return 0;
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ fileUnlock(f);
+ return 0;
+ }
+ if(!sourceLock(f->source, -1)){
+ fileUnlock(f);
+ return 0;
+ }
+ if(!sourceTruncate(f->source)){
+ sourceUnlock(f->source);
+ fileUnlock(f);
+ return 0;
+ }
+ sourceUnlock(f->source);
+ fileUnlock(f);
+
+ fileWAccess(f, uid);
+
+ return 1;
+}
+
+int
+fileSetDir(File *f, DirEntry *dir, char *uid)
+{
+ File *ff;
+ char *oelem;
+ u32int mask;
+ u64int size;
+
+ /* can not set permissions for the root */
+ if(fileIsRoot(f)){
+ vtSetError(ERoot);
+ return 0;
+ }
+
+ if(!fileLock(f))
+ return 0;
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ fileUnlock(f);
+ return 0;
+ }
+
+ 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){
+ vtSetError(EExists);
+ goto Err;
+ }
+ }
+
+ ff = dirLookup(f->up, dir->elem);
+ if(ff != nil){
+ fileDecRef(ff);
+ vtSetError(EExists);
+ goto Err;
+ }
+ }
+
+ if(!sourceLock2(f->source, f->msource, -1))
+ goto Err;
+ if(!fileIsDir(f)){
+ size = sourceGetSize(f->source);
+ if(size != dir->size){
+ if(!sourceSetSize(f->source, dir->size)){
+ sourceUnlock(f->source);
+ if(f->msource)
+ sourceUnlock(f->msource);
+ goto Err;
+ }
+ /* commited to changing it now */
+ }
+ }
+ /* commited to changing it now */
+ if((f->dir.mode&ModeTemporary) != (dir->mode&ModeTemporary))
+ fileSetTmp(f, dir->mode&ModeTemporary);
+ sourceUnlock(f->source);
+ if(f->msource)
+ sourceUnlock(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){
+ vtMemFree(f->dir.uid);
+ f->dir.uid = vtStrDup(dir->uid);
+ }
+
+ if(strcmp(f->dir.gid, dir->gid) != 0){
+ vtMemFree(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);
+ vtMemFree(oelem);
+
+ fileMetaUnlock(f);
+ fileUnlock(f);
+
+ fileWAccess(f->up, uid);
+
+ return 1;
+Err:
+ fileMetaUnlock(f);
+ fileUnlock(f);
+ return 0;
+}
+
+int
+fileSetQidSpace(File *f, u64int offset, u64int max)
+{
+ int ret;
+
+ if(!fileLock(f))
+ return 0;
+ fileMetaLock(f);
+ f->dir.qidSpace = 1;
+ f->dir.qidOffset = offset;
+ f->dir.qidMax = max;
+ ret = fileMetaFlush2(f, nil)>=0;
+ fileMetaUnlock(f);
+ fileUnlock(f);
+ return ret;
+}
+
+
+uvlong
+fileGetId(File *f)
+{
+ /* immutable */
+ return f->dir.qid;
+}
+
+ulong
+fileGetMcount(File *f)
+{
+ ulong mcount;
+
+ fileMetaLock(f);
+ mcount = f->dir.mcount;
+ fileMetaUnlock(f);
+ return mcount;
+}
+
+ulong
+fileGetMode(File *f)
+{
+ ulong mode;
+
+ fileMetaLock(f);
+ mode = f->dir.mode;
+ fileMetaUnlock(f);
+ return mode;
+}
+
+int
+fileIsDir(File *f)
+{
+ /* immutable */
+ return (f->dir.mode & ModeDir) != 0;
+}
+
+int
+fileIsRoot(File *f)
+{
+ return f == f->fs->file;
+}
+
+int
+fileIsRoFs(File *f)
+{
+ return f->fs->mode == OReadOnly;
+}
+
+int
+fileGetSize(File *f, uvlong *size)
+{
+ if(!fileRLock(f))
+ return 0;
+ if(!sourceLock(f->source, OReadOnly)){
+ fileRUnlock(f);
+ return 0;
+ }
+ *size = sourceGetSize(f->source);
+ sourceUnlock(f->source);
+ fileRUnlock(f);
+
+ return 1;
+}
+
+int
+fileMetaFlush(File *f, int rec)
+{
+ File **kids, *p;
+ int nkids;
+ int i, rv;
+
+ fileMetaLock(f);
+ rv = fileMetaFlush2(f, nil);
+ fileMetaUnlock(f);
+
+ if(!rec || !fileIsDir(f))
+ return rv;
+
+ if(!fileLock(f))
+ return rv;
+ nkids = 0;
+ for(p=f->down; p; p=p->next)
+ nkids++;
+ kids = vtMemAlloc(nkids*sizeof(File*));
+ i = 0;
+ for(p=f->down; p; p=p->next){
+ kids[i++] = p;
+ p->ref++;
+ }
+ fileUnlock(f);
+
+ for(i=0; i<nkids; i++){
+ rv |= fileMetaFlush(kids[i], 1);
+ fileDecRef(kids[i]);
+ }
+ vtMemFree(kids);
+ return rv;
+}
+
+/* assumes metaLock is held */
+static int
+fileMetaFlush2(File *f, char *oelem)
+{
+ File *fp;
+ Block *b, *bb;
+ MetaBlock mb;
+ MetaEntry me, me2;
+ int i, n;
+ u32int boff;
+
+ if(!f->dirty)
+ return 0;
+
+ if(oelem == nil)
+ oelem = f->dir.elem;
+
+//print("fileMetaFlush %s->%s\n", oelem, f->dir.elem);
+
+ fp = f->up;
+
+ if(!sourceLock(fp->msource, -1))
+ return -1;
+ /* can happen if source is clri'ed out from under us */
+ if(f->boff == NilBlock)
+ goto Err1;
+ b = sourceBlock(fp->msource, f->boff, OReadWrite);
+ if(b == nil)
+ goto Err1;
+
+ if(!mbUnpack(&mb, b->data, fp->msource->dsize))
+ goto Err;
+ if(!mbSearch(&mb, oelem, &i, &me))
+ goto Err;
+
+ n = deSize(&f->dir);
+if(0)fprint(2, "old size %d new size %d\n", me.size, n);
+
+ if(mbResize(&mb, &me, n)){
+ /* fits in the block */
+ mbDelete(&mb, i);
+ if(strcmp(f->dir.elem, oelem) != 0)
+ mbSearch(&mb, f->dir.elem, &i, &me2);
+ dePack(&f->dir, &me);
+ mbInsert(&mb, i, &me);
+ mbPack(&mb);
+ blockDirty(b);
+ blockPut(b);
+ sourceUnlock(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);
+ blockDirty(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 = sourceBlock(fp->msource, f->boff, OReadWrite);
+ mbDelete(&mb, i);
+ mbPack(&mb);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+ blockDirty(b);
+ blockPut(b);
+ sourceUnlock(fp->msource);
+
+ f->dirty = 0;
+
+ return 1;
+
+Err:
+ blockPut(b);
+Err1:
+ sourceUnlock(fp->msource);
+ return -1;
+}
+
+static int
+fileMetaRemove(File *f, char *uid)
+{
+ Block *b;
+ MetaBlock mb;
+ MetaEntry me;
+ int i;
+ File *up;
+
+ up = f->up;
+
+ fileWAccess(up, uid);
+
+ fileMetaLock(f);
+
+ sourceLock(up->msource, OReadWrite);
+ b = sourceBlock(up->msource, f->boff, OReadWrite);
+ if(b == nil)
+ goto Err;
+
+ if(!mbUnpack(&mb, b->data, up->msource->dsize))
+{
+fprint(2, "U\n");
+ goto Err;
+}
+ if(!mbSearch(&mb, f->dir.elem, &i, &me))
+{
+fprint(2, "S\n");
+ goto Err;
+}
+ mbDelete(&mb, i);
+ mbPack(&mb);
+ sourceUnlock(up->msource);
+
+ blockDirty(b);
+ blockPut(b);
+
+ f->removed = 1;
+ f->boff = NilBlock;
+ f->dirty = 0;
+
+ fileMetaUnlock(f);
+ return 1;
+
+Err:
+ sourceUnlock(up->msource);
+ blockPut(b);
+ fileMetaUnlock(f);
+ return 0;
+}
+
+/* assume file is locked, assume f->msource is locked */
+static int
+fileCheckEmpty(File *f)
+{
+ u32int i, n;
+ Block *b;
+ MetaBlock mb;
+ Source *r;
+
+ r = f->msource;
+ n = (sourceGetSize(r)+r->dsize-1)/r->dsize;
+ for(i=0; i<n; i++){
+ b = sourceBlock(r, i, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, r->dsize))
+ goto Err;
+ if(mb.nindex > 0){
+ vtSetError(ENotEmpty);
+ goto Err;
+ }
+ blockPut(b);
+ }
+ return 1;
+Err:
+ blockPut(b);
+ return 0;
+}
+
+int
+fileRemove(File *f, char *uid)
+{
+ File *ff;
+
+ /* can not remove the root */
+ if(fileIsRoot(f)){
+ vtSetError(ERoot);
+ return 0;
+ }
+
+ if(!fileLock(f))
+ return 0;
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err1;
+ }
+ if(!sourceLock2(f->source, f->msource, -1))
+ goto Err1;
+ if(fileIsDir(f) && !fileCheckEmpty(f))
+ goto Err;
+
+ for(ff=f->down; ff; ff=ff->next)
+ assert(ff->removed);
+
+ sourceRemove(f->source);
+ f->source->file = nil; /* erase back pointer */
+ f->source = nil;
+ if(f->msource){
+ sourceRemove(f->msource);
+ f->msource = nil;
+ }
+
+ fileUnlock(f);
+
+ if(!fileMetaRemove(f, uid))
+ return 0;
+
+ return 1;
+
+Err:
+ sourceUnlock(f->source);
+ if(f->msource)
+ sourceUnlock(f->msource);
+Err1:
+ fileUnlock(f);
+ return 0;
+}
+
+static int
+clri(File *f, char *uid)
+{
+ int r;
+
+ if(f == nil)
+ return 0;
+ if(f->up->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ fileDecRef(f);
+ return 0;
+ }
+ r = fileMetaRemove(f, uid);
+ fileDecRef(f);
+ return r;
+}
+
+int
+fileClriPath(Fs *fs, char *path, char *uid)
+{
+ return clri(_fileOpen(fs, path, 1), uid);
+}
+
+int
+fileClri(File *dir, char *elem, char *uid)
+{
+ return clri(_fileWalk(dir, elem, 1), uid);
+}
+
+File *
+fileIncRef(File *vf)
+{
+ fileMetaLock(vf);
+ assert(vf->ref > 0);
+ vf->ref++;
+ fileMetaUnlock(vf);
+ return vf;
+}
+
+int
+fileDecRef(File *f)
+{
+ File *p, *q, **qq;
+
+ if(f->up == nil){
+ /* never linked in */
+ assert(f->ref == 1);
+ fileFree(f);
+ return 1;
+ }
+
+ fileMetaLock(f);
+ f->ref--;
+ if(f->ref > 0){
+ fileMetaUnlock(f);
+ return 0;
+ }
+ 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);
+
+ fileDecRef(p);
+ return 1;
+}
+
+File *
+fileGetParent(File *f)
+{
+ if(fileIsRoot(f))
+ return fileIncRef(f);
+ return fileIncRef(f->up);
+}
+
+DirEntryEnum *
+deeOpen(File *f)
+{
+ DirEntryEnum *dee;
+ File *p;
+
+ if(!fileIsDir(f)){
+ vtSetError(ENotDir);
+ fileDecRef(f);
+ return nil;
+ }
+
+ /* flush out meta data */
+ if(!fileLock(f))
+ return nil;
+ for(p=f->down; p; p=p->next)
+ fileMetaFlush2(p, nil);
+ fileUnlock(f);
+
+ dee = vtMemAllocZ(sizeof(DirEntryEnum));
+ dee->file = fileIncRef(f);
+
+ return dee;
+}
+
+static int
+dirEntrySize(Source *s, ulong elem, ulong gen, uvlong *size)
+{
+ Block *b;
+ ulong bn;
+ Entry e;
+ int epb;
+
+ epb = s->dsize/VtEntrySize;
+ bn = elem/epb;
+ elem -= bn*epb;
+
+ b = sourceBlock(s, bn, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!entryUnpack(&e, b->data, elem))
+ goto Err;
+
+ /* hanging entries are returned as zero size */
+ if(!(e.flags & VtEntryActive) || e.gen != gen)
+ *size = 0;
+ else
+ *size = e.size;
+ blockPut(b);
+ return 1;
+
+Err:
+ blockPut(b);
+ return 0;
+}
+
+static int
+deeFill(DirEntryEnum *dee)
+{
+ int i, n;
+ Source *meta, *source;
+ MetaBlock mb;
+ MetaEntry me;
+ File *f;
+ Block *b;
+ DirEntry *de;
+
+ /* clean up first */
+ for(i=dee->i; i<dee->n; i++)
+ deCleanup(dee->buf+i);
+ vtMemFree(dee->buf);
+ dee->buf = nil;
+ dee->i = 0;
+ dee->n = 0;
+
+ f = dee->file;
+
+ source = f->source;
+ meta = f->msource;
+
+ b = sourceBlock(meta, dee->boff, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, meta->dsize))
+ goto Err;
+
+ n = mb.nindex;
+ dee->buf = vtMemAlloc(n * sizeof(DirEntry));
+
+ for(i=0; i<n; i++){
+ de = dee->buf + i;
+ meUnpack(&me, &mb, i);
+ if(!deUnpack(de, &me))
+ goto Err;
+ dee->n++;
+ if(!(de->mode & ModeDir))
+ if(!dirEntrySize(source, de->entry, de->gen, &de->size))
+ goto Err;
+ }
+ dee->boff++;
+ blockPut(b);
+ return 1;
+Err:
+ blockPut(b);
+ return 0;
+}
+
+int
+deeRead(DirEntryEnum *dee, DirEntry *de)
+{
+ int ret, didread;
+ File *f;
+ u32int nb;
+
+ if(dee == nil){
+ vtSetError("cannot happen in deeRead");
+ return -1;
+ }
+
+ f = dee->file;
+ if(!fileRLock(f))
+ return -1;
+
+ if(!sourceLock2(f->source, f->msource, OReadOnly)){
+ fileRUnlock(f);
+ return -1;
+ }
+
+ nb = (sourceGetSize(f->msource)+f->msource->dsize-1)/f->msource->dsize;
+
+ didread = 0;
+ while(dee->i >= dee->n){
+ if(dee->boff >= nb){
+ ret = 0;
+ goto Return;
+ }
+ didread = 1;
+ if(!deeFill(dee)){
+ ret = -1;
+ goto Return;
+ }
+ }
+
+ memmove(de, dee->buf + dee->i, sizeof(DirEntry));
+ dee->i++;
+ ret = 1;
+
+Return:
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+ fileRUnlock(f);
+
+ if(didread)
+ fileRAccess(f);
+ return ret;
+}
+
+void
+deeClose(DirEntryEnum *dee)
+{
+ int i;
+ if(dee == nil)
+ return;
+ for(i=dee->i; i<dee->n; i++)
+ deCleanup(dee->buf+i);
+ vtMemFree(dee->buf);
+ fileDecRef(dee->file);
+ vtMemFree(dee);
+}
+
+/*
+ * caller must lock f->source and f->msource
+ * caller must NOT lock the source and msource
+ * referenced by dir.
+ */
+static u32int
+fileMetaAlloc(File *f, DirEntry *dir, u32int start)
+{
+ u32int nb, bo;
+ Block *b, *bb;
+ MetaBlock mb;
+ int nn;
+ uchar *p;
+ int i, n, epb;
+ MetaEntry me;
+ Source *s, *ms;
+
+ s = f->source;
+ ms = f->msource;
+
+ n = deSize(dir);
+ nb = (sourceGetSize(ms)+ms->dsize-1)/ms->dsize;
+ b = nil;
+ if(start > nb)
+ start = nb;
+ for(bo=start; bo<nb; bo++){
+ b = sourceBlock(ms, bo, OReadWrite);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, ms->dsize))
+ goto Err;
+ nn = (mb.maxsize*FullPercentage/100) - mb.size + mb.free;
+ if(n <= nn && mb.nindex < mb.maxindex)
+ break;
+ blockPut(b);
+ b = nil;
+ }
+
+ /* add block to meta file */
+ if(b == nil){
+ b = sourceBlock(ms, bo, OReadWrite);
+ if(b == nil)
+ goto Err;
+ sourceSetSize(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);
+ blockDirty(b);
+ vtSetError(EBadMeta);
+ goto Err;
+ }
+
+ mbSearch(&mb, dir->elem, &i, &me);
+ assert(me.p == nil);
+ me.p = p;
+ me.size = n;
+ dePack(dir, &me);
+ mbInsert(&mb, i, &me);
+ mbPack(&mb);
+
+ /* meta block depends on super block for qid ... */
+ bb = cacheLocal(b->c, PartSuper, 0, OReadOnly);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+
+ /* ... and one or two dir entries */
+ epb = s->dsize/VtEntrySize;
+ bb = sourceBlock(s, dir->entry/epb, OReadOnly);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+ if(dir->mode & ModeDir){
+ bb = sourceBlock(s, dir->mentry/epb, OReadOnly);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+ }
+
+ blockDirty(b);
+ blockPut(b);
+ return bo;
+Err:
+ blockPut(b);
+ return NilBlock;
+}
+
+static int
+chkSource(File *f)
+{
+ if(f->partial)
+ return 1;
+
+ if(f->source == nil || (f->dir.mode & ModeDir) && f->msource == nil){
+ vtSetError(ERemoved);
+ return 0;
+ }
+ return 1;
+}
+
+static int
+fileRLock(File *f)
+{
+ assert(!vtCanLock(f->fs->elk));
+ vtRLock(f->lk);
+ if(!chkSource(f)){
+ fileRUnlock(f);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+fileRUnlock(File *f)
+{
+ vtRUnlock(f->lk);
+}
+
+static int
+fileLock(File *f)
+{
+ assert(!vtCanLock(f->fs->elk));
+ vtLock(f->lk);
+ if(!chkSource(f)){
+ fileUnlock(f);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+fileUnlock(File *f)
+{
+ vtUnlock(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(File *f)
+{
+if(f->up == nil)
+fprint(2, "f->elem = %s\n", f->dir.elem);
+ assert(f->up != nil);
+ assert(!vtCanLock(f->fs->elk));
+ vtLock(f->up->lk);
+}
+
+static void
+fileMetaUnlock(File *f)
+{
+ vtUnlock(f->up->lk);
+}
+
+/*
+ * f->source and f->msource must NOT be locked.
+ * see fileMetaLock.
+ */
+static void
+fileRAccess(File* f)
+{
+ if(f->mode == OReadOnly)
+ 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(File* f, char *mid)
+{
+ if(f->mode == OReadOnly)
+ return;
+
+ fileMetaLock(f);
+ f->dir.atime = f->dir.mtime = time(0L);
+ if(strcmp(f->dir.mid, mid) != 0){
+ vtMemFree(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);
+*/
+}
+
+static int
+getEntry(Source *r, Entry *e, int checkepoch)
+{
+ u32int epoch;
+ Block *b;
+
+ if(r == nil){
+ memset(&e, 0, sizeof e);
+ return 1;
+ }
+
+ b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadOnly);
+ if(b == nil)
+ return 0;
+ if(!entryUnpack(e, b->data, r->offset % r->epb)){
+ blockPut(b);
+ return 0;
+ }
+ epoch = b->l.epoch;
+ blockPut(b);
+
+ if(checkepoch){
+ b = cacheGlobal(r->fs->cache, e->score, entryType(e), e->tag, OReadOnly);
+ if(b){
+ if(b->l.epoch >= epoch)
+ fprint(2, "warning: entry %p epoch not older %#.8ux/%d %V/%d in getEntry\n",
+ r, b->addr, b->l.epoch, r->score, epoch);
+ blockPut(b);
+ }
+ }
+
+ return 1;
+}
+
+static int
+setEntry(Source *r, Entry *e)
+{
+ Block *b;
+ Entry oe;
+
+ b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadWrite);
+ 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)){
+ blockPut(b);
+ return 0;
+ }
+ e->gen = oe.gen;
+ entryPack(e, b->data, r->offset % r->epb);
+
+ /* BUG b should depend on the entry pointer */
+
+ blockDirty(b);
+ blockPut(b);
+ return 1;
+}
+
+/* assumes hold elk */
+int
+fileSnapshot(File *dst, File *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(File *f, Entry *e, Entry *ee)
+{
+ if(!getEntry(f->source, e, 0)
+ || !getEntry(f->msource, ee, 0))
+ return 0;
+ return 1;
+}
+
+/*
+ * Walk down to the block(s) containing the Entries
+ * for f->source and f->msource, copying as we go.
+ */
+int
+fileWalkSources(File *f)
+{
+ if(f->mode == OReadOnly){
+ fprint(2, "readonly in fileWalkSources\n");
+ return 1;
+ }
+ if(!sourceLock2(f->source, f->msource, OReadWrite)){
+ fprint(2, "sourceLock2 failed in fileWalkSources\n");
+ return 0;
+ }
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+ return 1;
+}
+
+/*
+ * convert File* to full path name in malloced string.
+ * this hasn't been as useful as we hoped it would be.
+ */
+char *
+fileName(File *f)
+{
+ char *name, *pname;
+ File *p;
+ static char root[] = "/";
+
+ if (f == nil)
+ return strdup("/**GOK**");
+
+ p = fileGetParent(f);
+ if (p == f)
+ name = strdup(root);
+ else {
+ pname = fileName(p);
+ if (strcmp(pname, root) == 0)
+ name = smprint("/%s", f->dir.elem);
+ else
+ name = smprint("%s/%s", pname, f->dir.elem);
+ free(pname);
+ }
+ fileDecRef(p);
+ return name;
+}