#include <9pm/u.h> #include <9pm/libc.h> #include <9pm/fcall.h> #include <9pm/thread.h> #include <9pm/9p.h> static char Ebadattach[] = "unknown specifier in attach"; static char Ebadoffset[] = "bad offset"; static char Ebadcount[] = "bad count"; static char Ebotch[] = "9P protocol botch"; static char Ecreatenondir[] = "create in non-directory"; static char Edupfid[] = "duplicate fid"; static char Eduptag[] = "duplicate tag"; static char Eisdir[] = "is a directory"; static char Enocreate[] = "create prohibited"; static char Enomem[] = "out of memory"; static char Enoremove[] = "remove prohibited"; static char Enostat[] = "stat prohibited"; static char Enotfound[] = "file not found"; static char Enowrite[] = "write prohibited"; static char Enowstat[] = "wstat prohibited"; static char Eperm[] = "permission denied"; static char Eunknownfid[] = "unknown fid"; static char Ebaddir[] = "bad directory in wstat"; static char Ewalknodir[] = "walk in non-directory"; static void setfcallerror(Fcall *f, char *err) { f->ename = err; f->type = Rerror; } static void changemsize(Srv *srv, int msize) { if(srv->rbuf && srv->wbuf && srv->msize == msize) return; qlock(&srv->rlock); qlock(&srv->wlock); srv->msize = msize; free(srv->rbuf); free(srv->wbuf); srv->rbuf = emalloc9p(msize); srv->wbuf = emalloc9p(msize); if(srv->rbuf==nil || srv->wbuf==nil) sysfatal("out of memory"); /* BUG */ qunlock(&srv->rlock); qunlock(&srv->wlock); } static Req* getreq(Srv *s) { long n; uchar *buf; Fcall f; Req *r; qlock(&s->rlock); if((n = read9pmsg(s->infd, s->rbuf, s->msize)) <= 0){ qunlock(&s->rlock); return nil; } buf = emalloc9p(n); memmove(buf, s->rbuf, n); qunlock(&s->rlock); if(convM2S(buf, n, &f) != n){ free(buf); return nil; } if((r=allocreq(s->rpool, f.tag)) == nil){ /* duplicate tag: cons up a fake Req */ r = emalloc9p(sizeof *r); incref(&r->ref); r->tag = f.tag; r->ifcall = f; r->error = Eduptag; r->buf = buf; r->responded = 0; r->type = 0; r->srv = s; r->pool = nil; if(chatty9p) fprint(2, "<-%d- %F: dup tag\n", s->infd, &f); return r; } r->srv = s; r->responded = 0; r->buf = buf; r->ifcall = f; memset(&r->ofcall, 0, sizeof r->ofcall); r->type = r->ifcall.type; if(chatty9p) if(r->error) fprint(2, "<-%d- %F: %s\n", s->infd, &r->ifcall, r->error); else fprint(2, "<-%d- %F\n", s->infd, &r->ifcall); return r; } static void filewalk(Req *r) { int i; File *f; f = r->fid->file; assert(f != nil); incref(&f->ref); for(i=0; iifcall.nwname; i++) if(f = walkfile(f, r->ifcall.wname[i])) r->ofcall.wqid[i] = f->dir.qid; else break; r->ofcall.nwqid = i; if(f){ r->newfid->file = f; r->newfid->qid = r->newfid->file->dir.qid; } respond(r, nil); } static void conswalk(Req *r, char *(*clone)(Fid*, Fid*), char *(*walk1)(Fid*, char*, Qid*)) { int i; char *e; if(r->fid == r->newfid && r->ifcall.nwname > 1){ respond(r, "lib9p: unused documented feature not implemented"); return; } if(r->fid != r->newfid){ r->newfid->qid = r->fid->qid; if(clone && (e = clone(r->fid, r->newfid))){ respond(r, e); return; } } e = nil; for(i=0; iifcall.nwname; i++){ if(e = walk1(r->newfid, r->ifcall.wname[i], &r->ofcall.wqid[i])) break; r->newfid->qid = r->ofcall.wqid[i]; } r->ofcall.nwqid = i; if(e && i==0) respond(r, e); else respond(r, nil); } void srv(Srv *srv) { int o, p; Req *r; char e[ERRMAX]; fmtinstall('D', dirconv); fmtinstall('F', fcallconv); if(srv->fpool == nil) srv->fpool = allocfidpool(srv->destroyfid); if(srv->rpool == nil) srv->rpool = allocreqpool(srv->destroyreq); if(srv->msize == 0) srv->msize = 8192+IOHDRSZ; changemsize(srv, srv->msize); srv->fpool->srv = srv; srv->rpool->srv = srv; while(r = getreq(srv)){ if(r->error){ respond(r, r->error); continue; } switch(r->type){ default: respond(r, "unknown message"); break; case Tversion: if(strncmp(r->ifcall.version, "9P", 2) != 0){ r->ofcall.version = "unknown"; respond(r, nil); break; } r->ofcall.version = "9P2000"; r->ofcall.msize = r->ifcall.msize; respond(r, nil); break; case Tauth: if((r->afid = allocfid(srv->fpool, r->ifcall.afid)) == nil){ respond(r, Edupfid); break; } if(srv->auth) srv->auth(r); else{ snprint(e, sizeof e, "%s: authentication not required", argv0); respond(r, e); } break; case Tattach: if((r->fid = allocfid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Edupfid); break; } r->afid = nil; if(r->ifcall.afid != NOFID && (r->afid = allocfid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Edupfid); break; } r->fid->uid = estrdup9p(r->ifcall.uname); if(srv->tree){ r->fid->file = srv->tree->root; r->ofcall.qid = r->fid->file->dir.qid; r->fid->qid = r->ofcall.qid; } if(srv->attach) srv->attach(r); else respond(r, nil); break; case Tflush: r->oldreq = lookupreq(srv->rpool, r->ifcall.oldtag); if(r->oldreq == nil || r->oldreq == r) respond(r, nil); else if(srv->flush) srv->flush(r); else sysfatal("outstanding message but flush not provided"); break; case Twalk: if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Eunknownfid); break; } if(r->fid->omode != -1){ respond(r, "cannot clone open fid"); break; } if(r->ifcall.nwname && !(r->fid->qid.type&QTDIR)){ respond(r, Ewalknodir); break; } if(r->ifcall.fid != r->ifcall.newfid){ if((r->newfid = allocfid(srv->fpool, r->ifcall.newfid)) == nil){ respond(r, Edupfid); break; } r->newfid->uid = estrdup9p(r->fid->uid); }else{ incref(&r->fid->ref); r->newfid = r->fid; } if(r->fid->file){ filewalk(r); }else if(srv->walk1) conswalk(r, srv->clone, srv->walk1); else if(srv->walk) srv->walk(r); else sysfatal("no walk function, no file trees"); break; case Topen: if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Eunknownfid); break; } if(r->fid->omode != -1){ respond(r, Ebotch); break; } if((r->fid->qid.type&QTDIR) && (r->ifcall.mode&~ORCLOSE) != OREAD){ respond(r, Eisdir); break; } r->ofcall.qid = r->fid->qid; switch(r->ifcall.mode&3){ default: assert(0); case OREAD: p = AREAD; break; case OWRITE: p = AWRITE; break; case ORDWR: p = AREAD|AWRITE; break; case OEXEC: p = AEXEC; break; } if(r->ifcall.mode&OTRUNC) p |= AWRITE; if((r->fid->qid.type&QTDIR) && p!=AREAD){ respond(r, Eperm); break; } if(r->fid->file){ if(!hasperm(r->fid->file, r->fid->uid, p)){ respond(r, Eperm); break; } /* BUG RACE */ if((r->ifcall.mode&ORCLOSE) && !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){ respond(r, Eperm); break; } r->ofcall.qid = r->fid->file->dir.qid; if((r->ofcall.qid.type&QTDIR) && (r->fid->rdir = opendirfile(r->fid->file)) == nil){ respond(r, "opendirfile failed"); break; } } if(srv->open) srv->open(r); else respond(r, nil); break; case Tcreate: if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil) respond(r, Eunknownfid); else if(r->fid->omode != -1) respond(r, Ebotch); else if(!(r->fid->qid.type&QTDIR)) respond(r, Ecreatenondir); else if(r->fid->file && !hasperm(r->fid->file, r->fid->uid, AWRITE)) respond(r, Eperm); else if(srv->create) srv->create(r); else respond(r, Enocreate); break; case Tread: if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Eunknownfid); break; } if(r->ifcall.count < 0){ respond(r, Ebotch); break; } if(r->ifcall.offset < 0 || ((r->fid->qid.type&QTDIR) && r->ifcall.offset != 0 && r->ifcall.offset != r->fid->diroffset)){ respond(r, Ebadoffset); break; } if(r->ifcall.count > srv->msize - IOHDRSZ) r->ifcall.count = srv->msize - IOHDRSZ; if((r->rbuf = emalloc9p(r->ifcall.count)) == nil){ respond(r, Enomem); break; } r->ofcall.data = r->rbuf; o = r->fid->omode & 3; if(o != OREAD && o != ORDWR && o != OEXEC){ respond(r, Ebotch); break; } if((r->fid->qid.type&QTDIR) && r->fid->file){ r->ofcall.count = readdirfile(r->fid->rdir, r->rbuf, r->ifcall.count); respond(r, nil); break; } if(srv->read) srv->read(r); else respond(r, "no srv->read"); break; case Twrite: if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Eunknownfid); break; } if(r->ifcall.count < 0){ respond(r, Ebotch); break; } if(r->ifcall.offset < 0){ respond(r, Ebotch); break; } if(r->ifcall.count > srv->msize - IOHDRSZ) r->ifcall.count = srv->msize - IOHDRSZ; o = r->fid->omode & 3; if(o != OWRITE && o != ORDWR){ respond(r, Ebotch); break; } if(srv->write) srv->write(r); else respond(r, "no srv->write"); break; case Tclunk: if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil) respond(r, Eunknownfid); else respond(r, nil); break; case Tremove: if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Eunknownfid); break; } /* BUG RACE */ if(r->fid->file && !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){ respond(r, Eperm); break; } if(srv->remove) srv->remove(r); else respond(r, r->fid->file ? nil : Enoremove); break; case Tstat: if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){ respond(r, Eunknownfid); break; } if(r->fid->file){ r->d = r->fid->file->dir; if(r->d.name) r->d.name = estrdup9p(r->d.name); if(r->d.uid) r->d.uid = estrdup9p(r->d.uid); if(r->d.gid) r->d.gid = estrdup9p(r->d.gid); if(r->d.muid) r->d.muid = estrdup9p(r->d.muid); } if(srv->stat) srv->stat(r); else if(r->fid->file) respond(r, nil); else respond(r, Enostat); break; case Twstat: if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil) respond(r, Eunknownfid); else if(srv->wstat){ if(convM2D(r->ifcall.stat, r->ifcall.nstat, &r->d, (char*)r->ifcall.stat) != r->ifcall.nstat) respond(r, Ebaddir); else srv->wstat(r); }else respond(r, Enowstat); break; } } } void respond(Req *r, char *error) { int m, n; Req *or; uchar *statbuf, tmp[BIT16SZ]; char errbuf[ERRMAX]; Srv *srv; srv = r->srv; assert(srv != nil); assert(r->responded == 0); r->responded = 1; switch(r->ifcall.type){ default: assert(0); case Tversion: assert(error == nil); changemsize(srv, r->ofcall.msize); break; case Tauth: if(error){ if(r->afid) closefid(removefid(srv->fpool, r->afid->fid)); break; } break; case Tattach: if(error){ if(r->fid) closefid(removefid(srv->fpool, r->fid->fid)); break; } break; case Tflush: assert(error == nil); if(r->oldreq){ if(or = removereq(r->pool, r->oldreq->tag)){ assert(or == r->oldreq); if(or->ifcall.type == Twalk && or->ifcall.fid != or->ifcall.newfid) closefid(removefid(srv->fpool, or->newfid->fid)); closereq(or); } closereq(r->oldreq); } r->oldreq = nil; break; case Twalk: if(error || r->ofcall.nwqid < r->ifcall.nwname){ if(r->ifcall.fid != r->ifcall.newfid && r->newfid) closefid(removefid(srv->fpool, r->newfid->fid)); if (r->ofcall.nwqid==0){ if(error==nil && r->ifcall.nwname!=0) error = Enotfound; }else error = nil; // No error on partial walks break; }else{ if(r->ofcall.nwqid == 0){ /* Just a clone */ r->newfid->qid = r->fid->qid; }else{ /* if file trees are in use, filewalk took care of the rest */ r->newfid->qid = r->ofcall.wqid[r->ofcall.nwqid-1]; } } break; case Topen: if(error){ break; } r->fid->omode = r->ifcall.mode; r->fid->qid = r->ofcall.qid; if(r->ofcall.qid.type&QTDIR) r->fid->diroffset = 0; break; case Tcreate: if(error){ break; } r->fid->omode = r->ifcall.mode; r->fid->qid = r->ofcall.qid; break; case Tread: if(error==nil && (r->fid->qid.type&QTDIR)) r->fid->diroffset += r->ofcall.count; break; case Twrite: if(error) break; if(r->fid->file) r->fid->file->dir.qid.vers++; break; case Tclunk: break; case Tremove: if(error) break; if(r->fid->file){ if(removefile(r->fid->file) < 0){ snprint(errbuf, ERRMAX, "remove %s: %r", r->fid->file->dir.name); error = errbuf; } r->fid->file = nil; } break; case Tstat: if(error) break; if(convD2M(&r->d, tmp, BIT16SZ) != BIT16SZ){ error = "convD2M(_,_,BIT16SZ) did not return BIT16SZ"; break; } n = GBIT16(tmp)+BIT16SZ; statbuf = emalloc9p(n); if(statbuf == nil){ error = "out of memory"; break; } r->ofcall.nstat = convD2M(&r->d, statbuf, n); r->ofcall.stat = statbuf; if(r->ofcall.nstat < 0){ error = "convD2M fails"; break; } break; case Twstat: break; } r->ofcall.tag = r->ifcall.tag; r->ofcall.type = r->ifcall.type+1; if(error) setfcallerror(&r->ofcall, error); if(chatty9p) fprint(2, "-%d-> %F\n", srv->outfd, &r->ofcall); qlock(&srv->wlock); n = convS2M(&r->ofcall, srv->wbuf, srv->msize); if(n <= 0){ fprint(2, "n = %d %F\n", n, &r->ofcall); abort(); } assert(n > 2); m = write(srv->outfd, srv->wbuf, n); if(m != n) sysfatal("lib9p srv: write %d returned %d on fd %d: %r", n, m, srv->outfd); qunlock(&srv->wlock); if(r->pool){ /* not a fake */ closereq(removereq(r->pool, r->ifcall.tag)); closereq(r); }else free(r); } int postfd(char *name, int pfd) { int fd; char buf[80]; snprint(buf, sizeof buf, "/srv/%s", name); if(chatty9p) fprint(2, "postfd %s\n", buf); fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600); if(fd < 0){ if(chatty9p) fprint(2, "create fails: %r\n"); return -1; } if(fprint(fd, "%d", pfd) < 0){ if(chatty9p) fprint(2, "write fails: %r\n"); close(fd); return -1; } if(chatty9p) fprint(2, "postfd successful\n"); return 0; }