#include <9pm/u.h> #include <9pm/libc.h> #include <9pm/fcall.h> #include <9pm/thread.h> #include <9pm/ns.h> enum { DBG9p = (1<<0), DBGproc = (1<<1), DBGflush = (1<<2), excheck = 0 }; static int exdebug = 0; typedef struct Fid Fid; typedef struct Qidt Qidt; typedef struct Export Export; typedef struct Exq Exq; enum { Nfidhash = 32, /* power of 2 */ Nqidhash = 32, /* power of 2 */ PATHHI = 0x7fffffff, MAXRPC = IOHDRSZ+8192, }; struct Export { struct { Lock l; int ref; } r; Exq* work; QLock fidlock; Fid* fid[Nfidhash]; Qidt* qidt[Nqidhash]; Qidt* qidp[Nqidhash]; Chan* io; Chan* root; Fgrp* fgrp; Pgrp* pgrp; int async; int version; char *user; Queue *q; }; struct Fid { Fid* next; Fid** last; Chan* chan; int fid; Qidt *qidt; /* claim to Qid */ int ref; /* fcalls using the fid; locked by Export.Lock */ int attached; /* fid attached or cloned but not clunked */ }; struct Qidt { Qidt* next; Qidt** last; Qidt* pnext; Qidt** plast; int ref; int type; int dev; ulong path; /* orig */ ulong uniqpath; /* allocated */ }; struct Exq { Lock lk; int nointr; int noresponse; /* don't respond to this one */ Exq* next; int shut; /* has been noted for shutdown */ Export* export; void* slave; Fcall rpc; uchar *buf; int buflen; Block *b; }; struct { Lock l; QLock qwait; Rendez rwait; Exq *head; /* work waiting for a slave */ Exq *tail; }exq; struct { Lock l; Exq* free; } exqalloc; struct { Lock l; Fid* free; } fidalloc; struct { Lock l; Qidt* free; int path; } qidalloc; static void exshutdown(Export*); static void exflush(Export*, int, int); static void exslave(void*); static void exfree(Export*); static void exportproc(void*); static int exportsrv(Export*); static char* Exversion(Export*, Fcall*); static char* Exattach(Export*, Fcall*); static char* Exclunk(Export*, Fcall*); static char* Excreate(Export*, Fcall*); static char* Exopen(Export*, Fcall*); static char* Exread(Export*, Fcall*); static char* Exremove(Export*, Fcall*); static char* Exauth(Export*, Fcall*); static char* Exstat(Export*, Fcall*); static char* Exwalk(Export*, Fcall*); static char* Exwrite(Export*, Fcall*); static char* Exwstat(Export*, Fcall*); static char *(*fcalls[Tmax])(Export*, Fcall*); static char Enofid[] = "no such fid"; static char Eseekdir[] = "can't seek on a directory"; static char Ereaddir[] = "unaligned read of a directory"; static char Eversion[] = "Bad 9P2000 version"; static int excloses; static int exopens; static int collisions; static int exqs; static int fids; static int qidts; static int slaves; int exportfs(int fd, int async) { Chan *c; Pgrp *pg; Export *fs; Mhead *mh; if(waserror()) return -1; c = fdtochan(up->fgrp, fd, ORDWR, 1, 1); poperror(); c->flag |= CMSG; fs = malloc(sizeof(Export)); fs->r.ref = 1; kstrdup(&fs->user, pm_conf.eve); fs->fgrp = dupfgrp(nil); pg = up->pgrp; fs->pgrp = pg; incref(&pg->ref); fs->root = up->slash; incref(&fs->root->ref); mh = nil; domount(&fs->root, &mh); fs->io = c; if(fs->io->iounit == 0) fs->io->iounit = MAXRPC; /* reasonable default; may change in Exversion */ fs->async = async; fs->q = qopen(10*MAXRPC, 0, nil, nil); if(async) nsaddproc("exportfs", exportproc, fs, 0); else return exportsrv(fs); return 0; } static void exqfree(Exq *e) { uchar *p; p = nil; lock(&exqalloc.l); e->next = exqalloc.free; exqalloc.free = e; if(e->buflen > 2*MAXRPC) { p = e->buf; e->buf = nil; e->buflen = 0; } unlock(&exqalloc.l); free(p); } static void freefid(Fid *f) { lock(&fidalloc.l); f->next = fidalloc.free; fidalloc.free = f; unlock(&fidalloc.l); } static void freeqid(Qidt *q) { lock(&qidalloc.l); q->next = qidalloc.free; qidalloc.free = q; unlock(&qidalloc.l); } static void exportinit(void) { lock(&exq.l); if(fcalls[Tauth] != nil) { unlock(&exq.l); return; } fmtinstall('F', fcallconv); fcalls[Tauth] = Exauth; fcalls[Tattach] = Exattach; fcalls[Twalk] = Exwalk; fcalls[Topen] = Exopen; fcalls[Tcreate] = Excreate; fcalls[Tread] = Exread; fcalls[Twrite] = Exwrite; fcalls[Tclunk] = Exclunk; fcalls[Tremove] = Exremove; fcalls[Tstat] = Exstat; fcalls[Tversion] = Exversion; fcalls[Twstat] = Exwstat; unlock(&exq.l); } static int exrpcread(Export *fs, Exq *q) { int i, t, len, hlen; Block *b, **l, *nb; q->rpc.type = 0; q->rpc.tag = 0; /* read at least length, type, and tag and pullup to a single block */ while(qlen(fs->q) < BIT32SZ+BIT8SZ+BIT16SZ){ b = devtab[fs->io->type]->bread(fs->io, 2*MAXRPC, 0); if(b == nil) return -1; if(BLEN(b) == 0) { /* EOF */ freeb(b); return -1; } qaddlist(fs->q, b); } nb = pullupqueue(fs->q, BIT32SZ+BIT8SZ+BIT16SZ); len = GBIT32(nb->rp); /* read in the rest of the message */ while(qlen(fs->q) < len){ b = devtab[fs->io->type]->bread(fs->io, 2*MAXRPC, 0); if(b == nil) return -1; qaddlist(fs->q, b); } /* pullup the header (i.e. everything except data) */ t = nb->rp[BIT32SZ]; switch(t){ case Twrite: hlen = BIT32SZ+BIT8SZ+BIT16SZ+BIT32SZ+BIT64SZ+BIT32SZ; break; default: hlen = len; break; } nb = pullupqueue(fs->q, hlen); if(convM2S(nb->rp, len, &q->rpc) <= 0){ /* bad message, dump it */ print("exrpcread: convM2S failed\n"); qdiscard(fs->q, len); return -1; } /* hang the data off of the Exq struct */ l = &q->b; *l = nil; do { b = qremove(fs->q); if(hlen > 0){ b->rp += hlen; len -= hlen; hlen = 0; } i = BLEN(b); if(i <= len){ len -= i; *l = b; l = &(b->next); } else { /* split block and put unused bit back */ nb = allocb(i-len); memmove(nb->wp, b->rp+len, i-len); b->wp = b->rp+len; nb->wp += i-len; qputback(fs->q, nb); *l = b; return 0; } }while(len > 0); return 0; } static void exportproc(void *a) { Export *fs = a; if(exportsrv(fs) < 0) iprint("exportsrv error: %r\n"); pexit("mount shut down", 0); } static int exportsrv(Export *fs) { Exq *q; int n; exportinit(); exopens++; for(;;){ lock(&exqalloc.l); q = exqalloc.free; if(q != nil) exqalloc.free = q->next; unlock(&exqalloc.l); if(q == nil) { exqs++; q = malloc(sizeof(Exq)); if(q == nil) { werrstr(Enomem); goto bad; } } if(q->buflen < fs->io->iounit) { q->buflen = fs->io->iounit; q->buf = realloc(q->buf, q->buflen); } if(waserror()) goto bad; n = exrpcread(fs, q); poperror(); if(n < 0) goto bad; if(exdebug&DBG9p) iprint("export %d <- %F\n", getpid(), &q->rpc); switch(q->rpc.type){ case Tflush: exflush(fs, q->rpc.tag, q->rpc.oldtag); exqfree(q); continue; case Twrite: q->b = bl2mem(q->buf, q->b, q->rpc.count); q->rpc.data = (char*)q->buf; break; } q->export = fs; lock(&fs->r.l); fs->r.ref++; unlock(&fs->r.l); lock(&exq.l); if(exq.head == nil) exq.head = q; else exq.tail->next = q; q->next = nil; exq.tail = q; unlock(&exq.l); if(exq.qwait.head == nil) { n = nsaddproc("exportfs", exslave, nil, 0); if(exdebug&DBGproc) iprint("launch export (pid=%ux)\n", n); } rendwakeup(&exq.rwait); } bad: if(q != nil) exqfree(q); exshutdown(fs); exfree(fs); if(excheck) { iprint("exportfs:\n"); iprint("%d opens, %d closes\n", exopens, excloses); iprint("%d slaves, %d qid collisions\n", slaves, collisions); iprint("%d e %d f %d q\n", exqs, fids, qidts); } return -1; } static void exflush(Export *fs, int flushtag, int tag) { Exq *q, **last; int n; Fcall fc; char buf[MAXRPC]; /* hasn't been started? */ lock(&exq.l); last = &exq.head; for(q = exq.head; q != nil; q = q->next){ if(q->export == fs && q->rpc.tag == tag){ *last = q->next; unlock(&exq.l); exfree(fs); exqfree(q); goto Respond; } last = &q->next; } unlock(&exq.l); /* in progress? */ lock(&fs->r.l); for(q = fs->work; q != nil; q = q->next){ if(q->rpc.tag == tag && !q->noresponse){ lock(&q->lk); q->noresponse = 1; if(!q->nointr) swiproc(q->slave, 0); unlock(&q->lk); unlock(&fs->r.l); goto Respond; return; } } unlock(&fs->r.l); if(exdebug&DBGflush) iprint("exflush: did not find rpc: %d\n", tag); Respond: fc.type = Rflush; fc.tag = flushtag; n = convS2M(&fc, (uchar*)buf, sizeof buf); if(exdebug&DBGflush) iprint("exflush -> %F\n", &fc); if(!waserror()){ devtab[fs->io->type]->_write(fs->io, buf, n, 0); poperror(); } } static void exshutdown(Export *fs) { Exq *q, **last; lock(&exq.l); last = &exq.head; for(q = exq.head; q != nil; q = *last){ if(q->export == fs){ *last = q->next; exfree(fs); exqfree(q); continue; } last = &q->next; } unlock(&exq.l); rescan: lock(&fs->r.l); for(q = fs->work; q != nil; q = q->next) { if(!q->shut) { q->shut = 1; unlock(&fs->r.l); swiproc(q->slave, 0); goto rescan; } } unlock(&fs->r.l); } static void decrqid(Qidt *q) { if(q == nil) return; q->ref--; if(q->ref > 0) return; *q->last = q->next; if(q->next != nil) q->next->last = q->last; *q->plast = q->pnext; if(q->pnext != nil) q->pnext->plast = q->plast; freeqid(q); } static void exfreefids(Export *fs) { Fid *f, *n; int i; for(i = 0; i < Nfidhash; i++){ for(f = fs->fid[i]; f != nil; f = n){ n = f->next; if(excheck && f->ref != 0) print("exfree ref\n"); if(f->chan != nil) cclose(f->chan); freefid(f); } } } static void exfreeqids(Export *fs) { Qidt *q, *n; int i; for(i = 0; i < Nqidhash; i++){ for(q = fs->qidt[i]; q != nil; q = n){ n = q->next; freeqid(q); } } } static void exfree(Export *fs) { lock(&fs->r.l); if(--fs->r.ref != 0){ unlock(&fs->r.l); return; } unlock(&fs->r.l); closefgrp(fs->fgrp); closepgrp(fs->pgrp); cclose(fs->root); cclose(fs->io); exfreefids(fs); exfreeqids(fs); qfree(fs->q); free(fs); excloses++; } static int exwork(void *a) { USED(a); return exq.head != nil; } static void exslave(void *a) { Export *volatile fs; Exq *volatile q, *t, **last; char *volatile err; int n; USED(a); slaves++; if(exdebug&DBGproc) iprint("new exslave %d\n", getpid()); for(;;){ if(exdebug&DBGproc) iprint("exslave %d waiting for work\n", getpid()); qlock(&exq.qwait); rendsleep(&exq.rwait, exwork, nil); lock(&exq.l); q = exq.head; if(q == nil) { unlock(&exq.l); qunlock(&exq.qwait); continue; } exq.head = q->next; q->slave = up; unlock(&exq.l); qunlock(&exq.qwait); q->noresponse = 0; q->nointr = 0; fs = q->export; lock(&fs->r.l); q->next = fs->work; fs->work = q; unlock(&fs->r.l); up->fgrp = q->export->fgrp; up->pgrp = q->export->pgrp; // kstrdup(&up->user, q->export->user); if(exdebug&DBGproc) iprint("exslave %d dispatched %F\n", getpid(), &q->rpc); if(waserror()){ iprint("exslave err %r\n"); err = up->err; goto Err; } if(q->rpc.type >= Tmax || !fcalls[q->rpc.type]) err = "bad fcall type"; else err = (*fcalls[q->rpc.type])(fs, &q->rpc); poperror(); Err:; freeblist(q->b); lock(&fs->r.l); notkilled(); last = &fs->work; for(t = fs->work; t != nil; t = t->next){ if(t == q){ *last = q->next; break; } last = &t->next; } if(q->shut) { unlock(&fs->r.l); exfree(q->export); exqfree(q); continue; } unlock(&fs->r.l); q->rpc.type++; if(err){ q->rpc.type = Rerror; q->rpc.ename = err; } n = convS2M(&q->rpc, q->buf, q->buflen); if(n < 0) panic("bad message type in exslave"); if(exdebug&DBG9p) iprint("exslave %d -> %F\n", getpid(), &q->rpc); switch(q->rpc.type) { case Rread: free(q->rpc.data); break; case Rstat: free(q->rpc.stat); break; } lock(&q->lk); if(q->noresponse == 0){ q->nointr = 1; up->killed = 0; if(!waserror()){ devtab[fs->io->type]->_write(fs->io, q->buf, n, 0); poperror(); } } unlock(&q->lk); /* * exflush might set noresponse at this point, but * setting noresponse means don't send a response now; * it's okay that we sent a response already. */ if(exdebug&DBGproc) iprint("exslave %d written %d\n", getpid(), q->rpc.tag); exfree(q->export); exqfree(q); } iprint("exslave shut down"); pexit("exslave shut down", 0); } static int pathhash(uvlong vpath) { ulong path; path = (ulong)vpath ^ (ulong)(vpath>>32); return (path^(path>>8)^(path>>16)^(path>>24)) & (Nqidhash-1); } static Qid Exrmtqid(Export *fs, Fid *f) { Qid r; Chan *c; Qidt *q; uvlong path; int h0, h1; c = f->chan; r.vers = c->qid.vers; r.type = c->qid.type; path = c->qid.path; h0 = pathhash(path); qlock(&fs->fidlock); decrqid(f->qidt); f->qidt = nil; /* qidt hashes (type, dev, path) */ for(q = fs->qidt[h0]; q != nil; q = q->next) { if(q->type==c->type && q->dev==c->dev && q->path==path) { q->ref++; f->qidt = q; qunlock(&fs->fidlock); r.path = q->uniqpath; return r; } } h1 = h0; for (;;) { /* qidp hashes uniqpath */ for(q = fs->qidp[h1]; q != nil; q = q->next) if(q->uniqpath == path) break; if(q == nil) break; collisions++; lock(&qidalloc.l); path = qidalloc.path; if(path == 0) path = PATHHI; qidalloc.path = path - 1; unlock(&qidalloc.l); h1 = pathhash(path); } r.path = path; lock(&qidalloc.l); q = qidalloc.free; if(q != nil) qidalloc.free = q->next; unlock(&qidalloc.l); if(q == nil) { qidts++; q = malloc(sizeof(Qidt)); if(q == nil) { qunlock(&fs->fidlock); return r; } } q->next = fs->qidt[h0]; if(q->next != nil) q->next->last = &q->next; q->last = &fs->qidt[h0]; fs->qidt[h0] = q; q->pnext = fs->qidp[h1]; if(q->pnext != nil) q->pnext->plast = &q->pnext; q->plast = &fs->qidp[h1]; fs->qidp[h1] = q; q->ref = 1; q->type = c->type; q->dev = c->dev; q->path = c->qid.path; q->uniqpath = path; f->qidt = q; qunlock(&fs->fidlock); return r; } static Fid* Exmkfid(Export *fs, int fid) { ulong h; Fid *f, *nf; lock(&fidalloc.l); nf = fidalloc.free; if(nf != nil) fidalloc.free = nf->next; unlock(&fidalloc.l); if(nf == nil) { fids++; nf = malloc(sizeof(Fid)); if(nf == nil) return nil; } qlock(&fs->fidlock); h = fid & (Nfidhash-1); for(f = fs->fid[h]; f != nil; f = f->next){ if(f->fid == fid){ qunlock(&fs->fidlock); free(nf); return nil; } } nf->next = fs->fid[h]; if(nf->next != nil) nf->next->last = &nf->next; nf->last = &fs->fid[h]; fs->fid[h] = nf; nf->fid = fid; nf->ref = 1; nf->attached = 1; nf->chan = nil; nf->qidt = nil; qunlock(&fs->fidlock); return nf; } static Fid* Exgetfid(Export *fs, int fid) { Fid *f; ulong h; qlock(&fs->fidlock); h = fid & (Nfidhash-1); for(f = fs->fid[h]; f; f = f->next) { if(f->fid == fid){ if(f->attached == 0) break; f->ref++; qunlock(&fs->fidlock); return f; } } qunlock(&fs->fidlock); return nil; } static void Exputfid(Export *fs, Fid *f) { Chan *c; qlock(&fs->fidlock); f->ref--; if(f->ref == 0 && f->attached == 0){ c = f->chan; f->chan = nil; *f->last = f->next; if(f->next != nil) f->next->last = f->last; decrqid(f->qidt); qunlock(&fs->fidlock); if(c != nil) cclose(c); freefid(f); return; } qunlock(&fs->fidlock); } static char* Exversion(Export *e, Fcall *rpc) { e->io->iounit = rpc->msize; if(strncmp(rpc->version, "9P2000", 6) != 0) return Eversion; return nil; } static char* Exauth(Export *e, Fcall *rpc) { USED(e); USED(rpc); return "authentication not supported"; } static char* Exattach(Export *fs, Fcall *rpc) { Fid *f; f = Exmkfid(fs, rpc->fid); if(f == nil) return Einuse; if(waserror()){ f->attached = 0; Exputfid(fs, f); return up->err; } f->chan = cclone(fs->root); poperror(); rpc->qid = Exrmtqid(fs, f); Exputfid(fs, f); return nil; } static Fid* exclonefid(Export *fs, Fid *f, int new) { Fid *n; n = Exmkfid(fs, new); if(n == nil) { n = Exgetfid(fs, new); if(n == nil) return nil; n->attached = 0; Exputfid(fs, n); n = Exmkfid(fs, new); if(n == nil) return nil; } if(waserror()) return nil; n->chan = cclone(f->chan); poperror(); return n; } static char* Exwalk(Export *fs, Fcall *rpc) { char *e; Chan *c; Fid *f, *nf; int i, nwqid; Qid wqid[MAXWELEM]; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; nf = nil; if(rpc->newfid != rpc->fid){ nf = exclonefid(fs, f, rpc->newfid); f = nf; if(f == nil) panic("Exwalk"); } nwqid = 0; e = nil; c = f->chan; for(i = 0; i < rpc->nwname; i++){ if(i == MAXWELEM){ e = "Too many path elements"; break; } if(waserror()){ e = up->err; break; } if(walk(&c, &rpc->wname[i], 1, 0) < 0) { poperror(); e = Enonexist; c = nil; break; } poperror(); wqid[nwqid++] = c->qid; } f->chan = c; if(nf != nil && (e != nil || nwqid != rpc->nwname)) { nf->attached = 0; Exputfid(fs, nf); } Exputfid(fs, f); if(e != nil) return e; rpc->qid = Exrmtqid(fs, f); rpc->nwqid = nwqid; memmove(rpc->wqid, wqid, nwqid*sizeof(Qid)); return nil; } static char* Exclunk(Export *fs, Fcall *rpc) { Fid *f; f = Exgetfid(fs, rpc->fid); if(f != nil){ f->attached = 0; Exputfid(fs, f); } return nil; } static char* Exopen(Export *fs, Fcall *rpc) { Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->err; } c = f->chan; c = devtab[c->type]->_open(c, rpc->mode); poperror(); f->chan = c; rpc->qid = Exrmtqid(fs, f); rpc->iounit = c->iounit; Exputfid(fs, f); return nil; } extern Chan* cunique(Chan*); extern int findmount(Chan *volatile *, Mhead *volatile *, int, int, Qid); extern Chan* createdir(Chan*, Mhead*); static char* Excreate(Export *fs, Fcall *rpc) { Fid *f; Mhead *volatile m; Chan *c, *volatile cnew; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->err; } c = f->chan; m = nil; cnew = nil; /* is this assignment necessary? */ if(!waserror()){ /* try create */ if(findmount(&cnew, &m, c->type, c->dev, c->qid)) cnew = createdir(cnew, m); else{ cnew = c; incref(&cnew->ref); } cnew = cunique(cnew); devtab[cnew->type]->_create(cnew, rpc->name, rpc->mode&~(OEXCL|OCEXEC), rpc->perm); poperror(); if(rpc->mode & OCEXEC) cnew->flag |= CCEXEC; if(rpc->mode & ORCLOSE) cnew->flag |= CRCLOSE; if(m) putmhead(m); cclose(c); c = cnew; }else{ /* create failed */ cclose(cnew); if(m) putmhead(m); if(rpc->mode & OEXCL) nexterror(); if(walk(&c, &rpc->name, 1, 0) < 0) nexterror(); rpc->mode |= OTRUNC; c = devtab[c->type]->_open(c, rpc->mode); } poperror(); f->chan = c; rpc->qid = Exrmtqid(fs, f); rpc->iounit = c->iounit; Exputfid(fs, f); return nil; } static char* Exread(Export *fs, Fcall *rpc) { Fid *f; Chan *c; Lock *cl; int n, dir, count, seeking, maxread; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; n = rpc->count; maxread = fs->io->iounit-IOHDRSZ; if(n > maxread) n = maxread; c = f->chan; dir = c->qid.type & QTDIR; if(dir){ if(rpc->offset < c->offset){ Exputfid(fs, f); return Eseekdir; } } rpc->data = malloc(n); if(rpc->data == nil) return Enomem; if(waserror()) { Exputfid(fs, f); return up->err; } do{ seeking = 0; if(dir && rpc->offset > c->offset){ count = rpc->offset - c->offset; if(count > maxread) count = maxread; seeking = 1; }else if(dir && rpc->offset < c->offset){ return Eseekdir; }else{ count = n; c->offset = rpc->offset; } if(dir && c->umh != nil) count = unionread(c, rpc->data, count); else { count = devtab[c->type]->_read(c, rpc->data, count, c->offset); lock(&c->lk); c->offset += count; unlock(&c->lk); } }while(count > 0 && seeking); rpc->count = count; poperror(); Exputfid(fs, f); return nil; } static char* Exwrite(Export *fs, Fcall *rpc) { Fid *f; Chan *c; int n, maxwrite; n = rpc->count; maxwrite = fs->io->iounit-IOHDRSZ; if(n > maxwrite) n = maxwrite; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->err; } c = f->chan; if(c->qid.type & QTDIR) error(Eisdir); rpc->count = devtab[c->type]->_write(c, rpc->data, n, rpc->offset); poperror(); Exputfid(fs, f); return nil; } static char* Exstat(Export *fs, Fcall *rpc) { int n; Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->err; } c = f->chan; n = fs->io->iounit; rpc->stat = malloc(n); rpc->nstat = devtab[c->type]->_stat(c, rpc->stat, n); poperror(); Exputfid(fs, f); return nil; } static char* Exwstat(Export *fs, Fcall *rpc) { Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->err; } c = f->chan; devtab[c->type]->_wstat(c, rpc->stat, rpc->nstat); poperror(); Exputfid(fs, f); return nil; } static char* Exremove(Export *fs, Fcall *rpc) { Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ f->attached = 0; Exputfid(fs, f); return up->err; } c = f->chan; devtab[c->type]->_remove(c); poperror(); /* * chan is already clunked by remove. * however, we need to recover the chan, * and follow sysremove's lead in making it point to root. */ c->type = 0; f->attached = 0; Exputfid(fs, f); return nil; }