| #include <u.h> |
| #include <libc.h> |
| #include <fcall.h> |
| #include <thread.h> |
| #include <9p.h> |
| |
| /* |
| * To avoid deadlock, the following rules must be followed. |
| * Always lock child then parent, never parent then child. |
| * If holding the free file lock, do not lock any Files. |
| */ |
| struct Filelist { |
| File *f; |
| Filelist *link; |
| }; |
| |
| static QLock filelk; |
| static File *freefilelist; |
| |
| static File* |
| allocfile(void) |
| { |
| int i, a; |
| File *f; |
| enum { N = 16 }; |
| |
| qlock(&filelk); |
| if(freefilelist == nil){ |
| f = emalloc9p(N*sizeof(*f)); |
| for(i=0; i<N-1; i++) |
| f[i].aux = &f[i+1]; |
| f[N-1].aux = nil; |
| f[0].allocd = 1; |
| freefilelist = f; |
| } |
| |
| f = freefilelist; |
| freefilelist = f->aux; |
| qunlock(&filelk); |
| |
| a = f->allocd; |
| memset(f, 0, sizeof *f); |
| f->allocd = a; |
| return f; |
| } |
| |
| static void |
| freefile(File *f) |
| { |
| Filelist *fl, *flnext; |
| |
| for(fl=f->filelist; fl; fl=flnext){ |
| flnext = fl->link; |
| assert(fl->f == nil); |
| free(fl); |
| } |
| |
| free(f->dir.name); |
| free(f->dir.uid); |
| free(f->dir.gid); |
| free(f->dir.muid); |
| qlock(&filelk); |
| assert(f->ref.ref == 0); |
| f->aux = freefilelist; |
| freefilelist = f; |
| qunlock(&filelk); |
| } |
| |
| void |
| closefile(File *f) |
| { |
| if(decref(&f->ref) == 0){ |
| f->tree->destroy(f); |
| freefile(f); |
| } |
| } |
| |
| static void |
| nop(File *f) |
| { |
| USED(f); |
| } |
| |
| int |
| removefile(File *f) |
| { |
| File *fp; |
| Filelist *fl; |
| |
| fp = f->parent; |
| if(fp == nil){ |
| werrstr("no parent"); |
| closefile(f); |
| return -1; |
| } |
| |
| if(fp == f){ |
| werrstr("cannot remove root"); |
| closefile(f); |
| return -1; |
| } |
| |
| wlock(&fp->rwlock); |
| wlock(&f->rwlock); |
| if(f->nchild != 0){ |
| werrstr("has children"); |
| wunlock(&f->rwlock); |
| wunlock(&fp->rwlock); |
| closefile(f); |
| return -1; |
| } |
| |
| if(f->parent != fp){ |
| werrstr("parent changed underfoot"); |
| wunlock(&f->rwlock); |
| wunlock(&fp->rwlock); |
| closefile(f); |
| return -1; |
| } |
| |
| for(fl=fp->filelist; fl; fl=fl->link) |
| if(fl->f == f) |
| break; |
| assert(fl != nil && fl->f == f); |
| |
| fl->f = nil; |
| fp->nchild--; |
| f->parent = nil; |
| wunlock(&fp->rwlock); |
| wunlock(&f->rwlock); |
| |
| closefile(fp); /* reference from child */ |
| closefile(f); /* reference from tree */ |
| closefile(f); |
| return 0; |
| } |
| |
| File* |
| createfile(File *fp, char *name, char *uid, ulong perm, void *aux) |
| { |
| File *f; |
| Filelist *fl, *freel; |
| Tree *t; |
| |
| if((fp->dir.qid.type&QTDIR) == 0){ |
| werrstr("create in non-directory"); |
| return nil; |
| } |
| |
| freel = nil; |
| wlock(&fp->rwlock); |
| for(fl=fp->filelist; fl; fl=fl->link){ |
| if(fl->f == nil) |
| freel = fl; |
| else if(strcmp(fl->f->dir.name, name) == 0){ |
| wunlock(&fp->rwlock); |
| werrstr("file already exists"); |
| return nil; |
| } |
| } |
| |
| if(freel == nil){ |
| freel = emalloc9p(sizeof *freel); |
| freel->link = fp->filelist; |
| fp->filelist = freel; |
| } |
| |
| f = allocfile(); |
| f->dir.name = estrdup9p(name); |
| f->dir.uid = estrdup9p(uid ? uid : fp->dir.uid); |
| f->dir.gid = estrdup9p(fp->dir.gid); |
| f->dir.muid = estrdup9p(uid ? uid : "unknown"); |
| f->aux = aux; |
| f->dir.mode = perm; |
| |
| t = fp->tree; |
| lock(&t->genlock); |
| f->dir.qid.path = t->qidgen++; |
| unlock(&t->genlock); |
| if(perm & DMDIR) |
| f->dir.qid.type |= QTDIR; |
| if(perm & DMAPPEND) |
| f->dir.qid.type |= QTAPPEND; |
| if(perm & DMEXCL) |
| f->dir.qid.type |= QTEXCL; |
| |
| f->dir.mode = perm; |
| f->dir.atime = f->dir.mtime = time(0); |
| f->dir.length = 0; |
| f->parent = fp; |
| incref(&fp->ref); |
| f->tree = fp->tree; |
| |
| incref(&f->ref); /* being returned */ |
| incref(&f->ref); /* for the tree */ |
| freel->f = f; |
| fp->nchild++; |
| wunlock(&fp->rwlock); |
| |
| return f; |
| } |
| |
| static File* |
| walkfile1(File *dir, char *elem) |
| { |
| File *fp; |
| Filelist *fl; |
| |
| rlock(&dir->rwlock); |
| if(strcmp(elem, "..") == 0){ |
| fp = dir->parent; |
| incref(&fp->ref); |
| runlock(&dir->rwlock); |
| closefile(dir); |
| return fp; |
| } |
| |
| fp = nil; |
| for(fl=dir->filelist; fl; fl=fl->link) |
| if(fl->f && strcmp(fl->f->dir.name, elem)==0){ |
| fp = fl->f; |
| incref(&fp->ref); |
| break; |
| } |
| |
| runlock(&dir->rwlock); |
| closefile(dir); |
| return fp; |
| } |
| |
| File* |
| walkfile(File *f, char *path) |
| { |
| char *os, *s, *nexts; |
| |
| if(strchr(path, '/') == nil) |
| return walkfile1(f, path); /* avoid malloc */ |
| |
| os = s = estrdup9p(path); |
| for(; *s; s=nexts){ |
| if(nexts = strchr(s, '/')) |
| *nexts++ = '\0'; |
| else |
| nexts = s+strlen(s); |
| f = walkfile1(f, s); |
| if(f == nil) |
| break; |
| } |
| free(os); |
| return f; |
| } |
| |
| static Qid |
| mkqid(vlong path, long vers, int type) |
| { |
| Qid q; |
| |
| q.path = path; |
| q.vers = vers; |
| q.type = type; |
| return q; |
| } |
| |
| |
| Tree* |
| alloctree(char *uid, char *gid, ulong mode, void (*destroy)(File*)) |
| { |
| char *muid; |
| Tree *t; |
| File *f; |
| |
| t = emalloc9p(sizeof *t); |
| f = allocfile(); |
| f->dir.name = estrdup9p("/"); |
| if(uid == nil){ |
| if(uid = getuser()) |
| uid = estrdup9p(uid); |
| } |
| if(uid == nil) |
| uid = estrdup9p("none"); |
| else |
| uid = estrdup9p(uid); |
| |
| if(gid == nil) |
| gid = estrdup9p(uid); |
| else |
| gid = estrdup9p(gid); |
| |
| muid = estrdup9p(uid); |
| |
| f->dir.qid = mkqid(0, 0, QTDIR); |
| f->dir.length = 0; |
| f->dir.atime = f->dir.mtime = time(0); |
| f->dir.mode = DMDIR | mode; |
| f->tree = t; |
| f->parent = f; |
| f->dir.uid = uid; |
| f->dir.gid = gid; |
| f->dir.muid = muid; |
| |
| incref(&f->ref); |
| t->root = f; |
| t->qidgen = 0; |
| t->dirqidgen = 1; |
| if(destroy == nil) |
| destroy = nop; |
| t->destroy = destroy; |
| |
| return t; |
| } |
| |
| static void |
| _freefiles(File *f) |
| { |
| Filelist *fl, *flnext; |
| |
| for(fl=f->filelist; fl; fl=flnext){ |
| flnext = fl->link; |
| _freefiles(fl->f); |
| free(fl); |
| } |
| |
| f->tree->destroy(f); |
| freefile(f); |
| } |
| |
| void |
| freetree(Tree *t) |
| { |
| _freefiles(t->root); |
| free(t); |
| } |
| |
| struct Readdir { |
| Filelist *fl; |
| }; |
| |
| Readdir* |
| opendirfile(File *dir) |
| { |
| Readdir *r; |
| |
| rlock(&dir->rwlock); |
| if((dir->dir.mode & DMDIR)==0){ |
| runlock(&dir->rwlock); |
| return nil; |
| } |
| r = emalloc9p(sizeof(*r)); |
| |
| /* |
| * This reference won't go away while we're using it |
| * since we are dir->rdir. |
| */ |
| r->fl = dir->filelist; |
| runlock(&dir->rwlock); |
| return r; |
| } |
| |
| long |
| readdirfile(Readdir *r, uchar *buf, long n) |
| { |
| long x, m; |
| Filelist *fl; |
| |
| for(fl=r->fl, m=0; fl && m+2<=n; fl=fl->link, m+=x){ |
| if(fl->f == nil) |
| x = 0; |
| else if((x=convD2M(&fl->f->dir, buf+m, n-m)) <= BIT16SZ) |
| break; |
| } |
| r->fl = fl; |
| return m; |
| } |
| |
| void |
| closedirfile(Readdir *r) |
| { |
| free(r); |
| } |