blob: d12e35223f1a06411f4f886feaca3810a0de2bbd [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
int chatty9p;
/* 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);
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+1); /* +1 for NUL in swrite */
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; i<r->ifcall.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);
}
void
walkandclone(Req *r, char *(*walk1)(Fid*, char*, void*), char *(*clone)(Fid*, Fid*, void*), void *arg)
{
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, arg))){
respond(r, e);
return;
}
}
e = nil;
for(i=0; i<r->ifcall.nwname; i++){
if(e = walk1(r->newfid, r->ifcall.wname[i], arg))
break;
r->ofcall.wqid[i] = r->newfid->qid;
}
r->ofcall.nwqid = i;
if(e && i==0)
respond(r, e);
else
respond(r, nil);
}
static void
sversion(Srv *srv, Req *r)
{
USED(srv);
if(strncmp(r->ifcall.version, "9P2000", 6) != 0){
r->ofcall.version = "unknown";
respond(r, nil);
return;
}
r->ofcall.version = "9P2000";
r->ofcall.msize = r->ifcall.msize;
respond(r, nil);
}
static void
rversion(Req *r, char *error)
{
assert(error == nil);
changemsize(r->srv, r->ofcall.msize);
}
static void
sauth(Srv *srv, Req *r)
{
char e[ERRMAX];
if((r->afid = allocfid(srv->fpool, r->ifcall.afid)) == nil){
respond(r, Edupfid);
return;
}
if(srv->auth)
srv->auth(r);
else{
snprint(e, sizeof e, "%s: authentication not required", argv0);
respond(r, e);
}
}
static void
rauth(Req *r, char *error)
{
if(error && r->afid)
closefid(removefid(r->srv->fpool, r->afid->fid));
}
static void
sattach(Srv *srv, Req *r)
{
if((r->fid = allocfid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Edupfid);
return;
}
r->afid = nil;
if(r->ifcall.afid != NOFID && (r->afid = lookupfid(srv->fpool, r->ifcall.afid)) == nil){
respond(r, Eunknownfid);
return;
}
r->fid->uid = estrdup9p(r->ifcall.uname);
if(srv->tree){
r->fid->file = srv->tree->root;
incref(&r->fid->file->ref);
r->ofcall.qid = r->fid->file->dir.qid;
r->fid->qid = r->ofcall.qid;
}
if(srv->attach)
srv->attach(r);
else
respond(r, nil);
return;
}
static void
rattach(Req *r, char *error)
{
if(error && r->fid)
closefid(removefid(r->srv->fpool, r->fid->fid));
}
static void
sflush(Srv *srv, Req *r)
{
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
respond(r, nil);
}
static int
rflush(Req *r, char *error)
{
Req *or;
assert(error == nil);
or = r->oldreq;
if(or){
qlock(&or->lk);
if(or->responded == 0){
or->flush = erealloc9p(or->flush, (or->nflush+1)*sizeof(or->flush[0]));
or->flush[or->nflush++] = r;
qunlock(&or->lk);
return -1; /* delay response until or is responded */
}
qunlock(&or->lk);
closereq(or);
}
r->oldreq = nil;
return 0;
}
static char*
oldwalk1(Fid *fid, char *name, void *arg)
{
char *e;
Qid qid;
Srv *srv;
srv = arg;
e = srv->walk1(fid, name, &qid);
if(e)
return e;
fid->qid = qid;
return nil;
}
static char*
oldclone(Fid *fid, Fid *newfid, void *arg)
{
Srv *srv;
srv = arg;
if(srv->clone == nil)
return nil;
return srv->clone(fid, newfid);
}
static void
swalk(Srv *srv, Req *r)
{
if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Eunknownfid);
return;
}
if(r->fid->omode != -1){
respond(r, "cannot clone open fid");
return;
}
if(r->ifcall.nwname && !(r->fid->qid.type&QTDIR)){
respond(r, Ewalknodir);
return;
}
if(r->ifcall.fid != r->ifcall.newfid){
if((r->newfid = allocfid(srv->fpool, r->ifcall.newfid)) == nil){
respond(r, Edupfid);
return;
}
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)
walkandclone(r, oldwalk1, oldclone, srv);
else if(srv->walk)
srv->walk(r);
else
sysfatal("no walk function, no file trees");
}
static void
rwalk(Req *r, char *error)
{
if(error || r->ofcall.nwqid < r->ifcall.nwname){
if(r->ifcall.fid != r->ifcall.newfid && r->newfid)
closefid(removefid(r->srv->fpool, r->newfid->fid));
if (r->ofcall.nwqid==0){
if(error==nil && r->ifcall.nwname!=0)
r->error = Enotfound;
}else
r->error = nil; /* No error on partial walks */
}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];
}
}
}
static void
sopen(Srv *srv, Req *r)
{
int p;
if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Eunknownfid);
return;
}
if(r->fid->omode != -1){
respond(r, Ebotch);
return;
}
if((r->fid->qid.type&QTDIR) && (r->ifcall.mode&~ORCLOSE) != OREAD){
respond(r, Eisdir);
return;
}
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);
return;
}
if(r->fid->file){
if(!hasperm(r->fid->file, r->fid->uid, p)){
respond(r, Eperm);
return;
}
/* BUG RACE */
if((r->ifcall.mode&ORCLOSE)
&& !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
respond(r, Eperm);
return;
}
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");
return;
}
}
if(srv->open)
srv->open(r);
else
respond(r, nil);
}
static void
ropen(Req *r, char *error)
{
char errbuf[ERRMAX];
if(error)
return;
if(chatty9p){
snprint(errbuf, sizeof errbuf, "fid mode is 0x%ux\n", r->ifcall.mode);
write(2, errbuf, strlen(errbuf));
}
r->fid->omode = r->ifcall.mode;
r->fid->qid = r->ofcall.qid;
if(r->ofcall.qid.type&QTDIR)
r->fid->diroffset = 0;
}
static void
screate(Srv *srv, Req *r)
{
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);
}
static void
rcreate(Req *r, char *error)
{
if(error)
return;
r->fid->omode = r->ifcall.mode;
r->fid->qid = r->ofcall.qid;
}
static void
sread(Srv *srv, Req *r)
{
int o;
if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Eunknownfid);
return;
}
if((int32)r->ifcall.count < 0){
respond(r, Ebotch);
return;
}
if(r->ifcall.offset < 0
|| ((r->fid->qid.type&QTDIR) && r->ifcall.offset != 0 && r->ifcall.offset != r->fid->diroffset)){
respond(r, Ebadoffset);
return;
}
if(r->ifcall.count > srv->msize - IOHDRSZ)
r->ifcall.count = srv->msize - IOHDRSZ;
r->rbuf = emalloc9p(r->ifcall.count);
r->ofcall.data = r->rbuf;
o = r->fid->omode & 3;
if(o != OREAD && o != ORDWR && o != OEXEC){
respond(r, Ebotch);
return;
}
if((r->fid->qid.type&QTDIR) && r->fid->file){
r->ofcall.count = readdirfile(r->fid->rdir, r->rbuf, r->ifcall.count);
respond(r, nil);
return;
}
if(srv->read)
srv->read(r);
else
respond(r, "no srv->read");
}
static void
rread(Req *r, char *error)
{
if(error==nil && (r->fid->qid.type&QTDIR))
r->fid->diroffset = r->ifcall.offset + r->ofcall.count;
}
static void
swrite(Srv *srv, Req *r)
{
int o;
char e[ERRMAX];
if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Eunknownfid);
return;
}
if((int32)r->ifcall.count < 0){
respond(r, Ebotch);
return;
}
if(r->ifcall.offset < 0){
respond(r, Ebotch);
return;
}
if(r->ifcall.count > srv->msize - IOHDRSZ)
r->ifcall.count = srv->msize - IOHDRSZ;
o = r->fid->omode & 3;
if(o != OWRITE && o != ORDWR){
snprint(e, sizeof e, "write on fid with open mode 0x%ux", r->fid->omode);
respond(r, e);
return;
}
if(srv->write){
r->ifcall.data[r->ifcall.count] = 0; /* enough room - see getreq */
srv->write(r);
}else
respond(r, "no srv->write");
}
static void
rwrite(Req *r, char *error)
{
if(error)
return;
if(r->fid->file)
r->fid->file->dir.qid.vers++;
}
static void
sclunk(Srv *srv, Req *r)
{
if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil)
respond(r, Eunknownfid);
else
respond(r, nil);
}
static void
rclunk(Req *r, char *msg)
{
USED(r);
USED(msg);
}
static void
sremove(Srv *srv, Req *r)
{
if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Eunknownfid);
return;
}
/* BUG RACE */
if(r->fid->file && !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
respond(r, Eperm);
return;
}
if(srv->remove)
srv->remove(r);
else
respond(r, r->fid->file ? nil : Enoremove);
}
static void
rremove(Req *r, char *error, char *errbuf)
{
if(error)
return;
if(r->fid->file){
if(removefile(r->fid->file) < 0){
snprint(errbuf, ERRMAX, "remove %s: %r",
r->fid->file->dir.name);
r->error = errbuf;
}
r->fid->file = nil;
}
}
static void
sstat(Srv *srv, Req *r)
{
if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Eunknownfid);
return;
}
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);
}
static void
rstat(Req *r, char *error)
{
int n;
uchar *statbuf;
uchar tmp[BIT16SZ];
if(error)
return;
if(convD2M(&r->d, tmp, BIT16SZ) != BIT16SZ){
r->error = "convD2M(_,_,BIT16SZ) did not return BIT16SZ";
return;
}
n = GBIT16(tmp)+BIT16SZ;
statbuf = emalloc9p(n);
if(statbuf == nil){
r->error = "out of memory";
return;
}
r->ofcall.nstat = convD2M(&r->d, statbuf, n);
r->ofcall.stat = statbuf; /* freed in closereq */
if(r->ofcall.nstat <= BIT16SZ){
r->error = "convD2M fails";
free(statbuf);
return;
}
}
static void
swstat(Srv *srv, Req *r)
{
if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
respond(r, Eunknownfid);
return;
}
if(srv->wstat == nil){
respond(r, Enowstat);
return;
}
if(convM2D(r->ifcall.stat, r->ifcall.nstat, &r->d, (char*)r->ifcall.stat) != r->ifcall.nstat){
respond(r, Ebaddir);
return;
}
if((ushort)~r->d.type){
respond(r, "wstat -- attempt to change type");
return;
}
if((uint)~r->d.dev){
respond(r, "wstat -- attempt to change dev");
return;
}
if((uchar)~r->d.qid.type || (ulong)~r->d.qid.vers || (uvlong)~r->d.qid.path){
respond(r, "wstat -- attempt to change qid");
return;
}
if(r->d.muid && r->d.muid[0]){
respond(r, "wstat -- attempt to change muid");
return;
}
if((ulong)~r->d.mode && ((r->d.mode&DMDIR)>>24) != (r->fid->qid.type&QTDIR)){
respond(r, "wstat -- attempt to change DMDIR bit");
return;
}
srv->wstat(r);
}
static void
rwstat(Req *r, char *msg)
{
USED(r);
USED(msg);
}
void
srv(Srv *srv)
{
Req *r;
fmtinstall('D', dirfmt);
fmtinstall('F', fcallfmt);
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;
if(srv->start)
srv->start(srv);
while(r = getreq(srv)){
if(r->error){
respond(r, r->error);
continue;
}
switch(r->ifcall.type){
default:
respond(r, "unknown message");
break;
case Tversion: sversion(srv, r); break;
case Tauth: sauth(srv, r); break;
case Tattach: sattach(srv, r); break;
case Tflush: sflush(srv, r); break;
case Twalk: swalk(srv, r); break;
case Topen: sopen(srv, r); break;
case Tcreate: screate(srv, r); break;
case Tread: sread(srv, r); break;
case Twrite: swrite(srv, r); break;
case Tclunk: sclunk(srv, r); break;
case Tremove: sremove(srv, r); break;
case Tstat: sstat(srv, r); break;
case Twstat: swstat(srv, r); break;
}
}
if(srv->end)
srv->end(srv);
}
void
respond(Req *r, char *error)
{
int i, m, n;
char errbuf[ERRMAX];
Srv *srv;
srv = r->srv;
assert(srv != nil);
if(r->responded){
assert(r->pool);
goto free;
}
assert(r->responded == 0);
r->error = error;
switch(r->ifcall.type){
default:
assert(0);
/*
* Flush is special. If the handler says so, we return
* without further processing. Respond will be called
* again once it is safe.
*/
case Tflush:
if(rflush(r, error)<0)
return;
break;
case Tversion: rversion(r, error); break;
case Tauth: rauth(r, error); break;
case Tattach: rattach(r, error); break;
case Twalk: rwalk(r, error); break;
case Topen: ropen(r, error); break;
case Tcreate: rcreate(r, error); break;
case Tread: rread(r, error); break;
case Twrite: rwrite(r, error); break;
case Tclunk: rclunk(r, error); break;
case Tremove: rremove(r, error, errbuf); break;
case Tstat: rstat(r, error); break;
case Twstat: rwstat(r, error); break;
}
r->ofcall.tag = r->ifcall.tag;
r->ofcall.type = r->ifcall.type+1;
if(r->error)
setfcallerror(&r->ofcall, r->error);
if(srv->fake)
return;
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);
/*
* There is a race here - we must remove the entry before
* the write, so that if the client is very fast and reuses the
* tag, the read loop won't think it is still in use.
*
* By removing the entry before the write, we open up a
* race with incoming Tflush messages. Specifically, an
* incoming Tflush might not see r even though it has not
* yet been responded to. It would then send an Rflush
* immediately, potentially before we do the write. This can't
* happen because we already old srv->wlock, so nothing
* is going out on the wire before this write.
*/
if(r->pool) /* not a fake */
closereq(removereq(r->pool, r->ifcall.tag));
qlock(&r->lk);
r->responded = 1;
if(r->pool)
if(r->ref.ref == 1+r->nflush)
if(r->fid){
/*
* There are no references other than in our r->flush array,
* so no one else should be accessing r concurrently.
* Close the fid now, before responding to the message.
*
* If the client is behaving (there are no outstanding T-messages
* that reference r->fid) and the message is a Tclunk or Tremove,
* then this closefid will call destroyfid.
*
* This means destroyfid can't piddle around
* indefinitely (we're holding srv->wlock!), but it provides
* for tighter semantics as to when destroyfid is called.
*
* LANL has observed cases where waiting until after the write
* can delay a closefid on a Twrite for many 9P transactions,
* so that a handful of transactions can happen including a Tclunk
* and a Topen, and the original fid will still not be destroyed.
*/
closefid(r->fid);
r->fid = nil;
}
qunlock(&r->lk);
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);
free:
qlock(&r->lk); /* no one will add flushes now */
for(i=0; i<r->nflush; i++){
r->flush[i]->oldreq = nil; /* so it doesn't try to lock us! */
respond(r->flush[i], nil);
}
free(r->flush);
r->nflush = 0;
r->flush = nil;
qunlock(&r->lk);
if(r->pool)
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;
}