123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831 |
- #include <u.h>
- #include <libc.h>
- #include <auth.h>
- #include <fcall.h>
- #include <thread.h>
- #include <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);
- 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);
- for(i=0; i<r->ifcall.nwname; i++)
- if(f = walkfile(f, r->ifcall.wname[i]))
- r->ofcall.wqid[i] = f->qid;
- else
- break;
- r->ofcall.nwqid = i;
- if(f){
- r->newfid->file = f;
- r->newfid->qid = r->newfid->file->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*, Req *r)
- {
- if(strncmp(r->ifcall.version, "9P", 2) != 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;
- /* BUG? incref(r->fid->file) ??? */
- r->ofcall.qid = r->fid->file->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->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(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->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(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)
- 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->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*, char*)
- {
- }
- 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->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 < 0){
- 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*, char*)
- {
- }
- 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;
- 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);
- 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(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);
- if(r->pool) /* not a fake */
- closereq(removereq(r->pool, r->ifcall.tag));
- 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);
- qlock(&r->lk); /* no one will add flushes now */
- r->responded = 1;
- qunlock(&r->lk);
- for(i=0; i<r->nflush; i++)
- respond(r->flush[i], nil);
- free(r->flush);
- 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;
- }
|