|  | #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 */ | 
|  | RWLock	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 = vtmallocz(sizeof(File)); | 
|  | f->ref = 1; | 
|  | f->fs = fs; | 
|  | f->boff = NilBlock; | 
|  | f->mode = fs->mode; | 
|  | return f; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fileFree(File *f) | 
|  | { | 
|  | sourceClose(f->source); | 
|  | sourceClose(f->msource); | 
|  | deCleanup(&f->dir); | 
|  |  | 
|  | memset(f, ~0, sizeof(File)); | 
|  | vtfree(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; | 
|  | } | 
|  | werrstr(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){ | 
|  | werrstr(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); | 
|  |  | 
|  | werrstr(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){ | 
|  | werrstr(EBadPath); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if(!fileIsDir(f)){ | 
|  | werrstr(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){ | 
|  | 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; | 
|  | } | 
|  | 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; | 
|  | werrstr(EExists); | 
|  | goto Err1; | 
|  | } | 
|  | } | 
|  |  | 
|  | ff = dirLookup(f, elem); | 
|  | if(ff != nil){ | 
|  | werrstr(EExists); | 
|  | goto Err1; | 
|  | } | 
|  |  | 
|  | pr = f->source; | 
|  | if(pr->mode != OReadWrite){ | 
|  | werrstr(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){ | 
|  | werrstr(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){ | 
|  | werrstr(ENotFile); | 
|  | goto Err; | 
|  | } | 
|  |  | 
|  | if(f->source->mode != OReadWrite){ | 
|  | werrstr(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){ | 
|  | werrstr(ENotFile); | 
|  | goto Err; | 
|  | } | 
|  | if(f->source->mode != OReadWrite){ | 
|  | werrstr(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){ | 
|  | werrstr(ENotFile); | 
|  | goto Err; | 
|  | } | 
|  |  | 
|  | if(f->source->mode != OReadWrite){ | 
|  | werrstr(EReadOnly); | 
|  | goto Err; | 
|  | } | 
|  | if(offset < 0){ | 
|  | werrstr(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)){ | 
|  | werrstr(ENotFile); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if(!fileLock(f)) | 
|  | return 0; | 
|  |  | 
|  | if(f->source->mode != OReadWrite){ | 
|  | werrstr(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)){ | 
|  | werrstr(ERoot); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if(!fileLock(f)) | 
|  | return 0; | 
|  |  | 
|  | if(f->source->mode != OReadWrite){ | 
|  | werrstr(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){ | 
|  | werrstr(EExists); | 
|  | goto Err; | 
|  | } | 
|  | } | 
|  |  | 
|  | ff = dirLookup(f->up, dir->elem); | 
|  | if(ff != nil){ | 
|  | fileDecRef(ff); | 
|  | werrstr(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){ | 
|  | 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 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 | 
|  | fileIsAppend(File *f) | 
|  | { | 
|  | return (f->dir.mode & ModeAppend) != 0; | 
|  | } | 
|  |  | 
|  | int | 
|  | fileIsExclusive(File *f) | 
|  | { | 
|  | return (f->dir.mode & ModeExclusive) != 0; | 
|  | } | 
|  |  | 
|  | int | 
|  | fileIsTemporary(File *f) | 
|  | { | 
|  | return (f->dir.mode & ModeTemporary) != 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 = vtmalloc(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]); | 
|  | } | 
|  | vtfree(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){ | 
|  | werrstr(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)){ | 
|  | werrstr(ERoot); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if(!fileLock(f)) | 
|  | return 0; | 
|  |  | 
|  | if(f->source->mode != OReadWrite){ | 
|  | werrstr(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){ | 
|  | werrstr(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)){ | 
|  | werrstr(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 = vtmallocz(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); | 
|  | vtfree(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 = vtmalloc(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){ | 
|  | werrstr("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); | 
|  | vtfree(dee->buf); | 
|  | fileDecRef(dee->file); | 
|  | vtfree(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); | 
|  | werrstr(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){ | 
|  | werrstr(ERemoved); | 
|  | return 0; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static int | 
|  | fileRLock(File *f) | 
|  | { | 
|  | assert(!canwlock(&f->fs->elk)); | 
|  | rlock(&f->lk); | 
|  | if(!chkSource(f)){ | 
|  | fileRUnlock(f); | 
|  | return 0; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fileRUnlock(File *f) | 
|  | { | 
|  | runlock(&f->lk); | 
|  | } | 
|  |  | 
|  | static int | 
|  | fileLock(File *f) | 
|  | { | 
|  | assert(!canwlock(&f->fs->elk)); | 
|  | wlock(&f->lk); | 
|  | if(!chkSource(f)){ | 
|  | fileUnlock(f); | 
|  | return 0; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fileUnlock(File *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(File *f) | 
|  | { | 
|  | if(f->up == nil) | 
|  | fprint(2, "f->elem = %s\n", f->dir.elem); | 
|  | assert(f->up != nil); | 
|  | assert(!canwlock(&f->fs->elk)); | 
|  | wlock(&f->up->lk); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fileMetaUnlock(File *f) | 
|  | { | 
|  | wunlock(&f->up->lk); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * f->source and f->msource must NOT be locked. | 
|  | * see fileMetaLock. | 
|  | */ | 
|  | static void | 
|  | fileRAccess(File* f) | 
|  | { | 
|  | if(f->mode == OReadOnly || f->fs->noatimeupd) | 
|  | 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){ | 
|  | 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); | 
|  | */ | 
|  | } | 
|  |  | 
|  | 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 vtstrdup("/**GOK**"); | 
|  |  | 
|  | p = fileGetParent(f); | 
|  | if (p == f) | 
|  | name = vtstrdup(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; | 
|  | } |