ref: 6bb619c8db2867ddd9cd19c0aec05065f5ee0cae
dir: /emu/port/exportfs.c/
#include "dat.h" #include "fns.h" #include "error.h" #include "kernel.h" typedef struct Fid Fid; typedef struct Export Export; typedef struct Exq Exq; enum { Nfidhash = 32, MAXFDATA = 8192, MAXRPCDEF = IOHDRSZ+MAXFDATA, /* initial/default */ MAXRPCMAX = IOHDRSZ+64*1024, /* most every allowed */ MSGHDRSZ = BIT32SZ+BIT8SZ+BIT16SZ }; struct Export { Lock l; Ref r; Exq* work; Lock fidlock; Fid* fid[Nfidhash]; Uqidtab uqids; Chan* io; Chan* root; Pgrp* pgrp; Egrp* egrp; Fgrp* fgrp; int async; int readonly; int uid; int gid; int msize; char* user; }; struct Fid { Fid* next; Fid** last; Chan* chan; int fid; int ref; /* fcalls using the fid; locked by Export.Lock */ vlong offset; /* last offset used (within directory) */ int attached; /* fid attached or cloned but not clunked */ Uqid* qid; /* generated qid */ }; struct Exq { Lock l; int busy; /* fcall in progress */ int finished; /* will do no more work on this request or flushes */ Exq* next; int shut; /* has been noted for shutdown */ Exq* flush; /* queued flush requests */ Exq* flusht; /* tail of flush queue */ Export* export; Proc* slave; Fcall in, out; uchar* buf; int bsize; }; struct { Lock l; QLock qwait; Rendez rwait; Exq *head; /* work waiting for a slave */ Exq *tail; }exq; static void exshutdown(Export*); static int exflushed(Export*, Exq*); static void exslave(void*); static void exfree(Export*); static void exfreeq(Exq*); static void exportproc(void*); static void exreply(Exq*, char*); static int exisroot(Export*, Chan*); static char* Exversion(Export*, Fcall*, Fcall*); static char* Exauth(Export*, Fcall*, Fcall*); static char* Exattach(Export*, Fcall*, Fcall*); static char* Exclunk(Export*, Fcall*, Fcall*); static char* Excreate(Export*, Fcall*, Fcall*); static char* Exopen(Export*, Fcall*, Fcall*); static char* Exread(Export*, Fcall*, Fcall*); static char* Exremove(Export*, Fcall*, Fcall*); static char* Exstat(Export*, Fcall*, Fcall*); static char* Exwalk(Export*, Fcall*, Fcall*); static char* Exwrite(Export*, Fcall*, Fcall*); static char* Exwstat(Export*, Fcall*, Fcall*); static char *(*fcalls[Tmax])(Export*, Fcall*, Fcall*); static char Enofid[] = "no such fid"; static char Eseekdir[] = "can't seek on a directory"; static char Eopen[] = "walk of open fid"; static char Emode[] = "open/create -- unknown mode"; static char Edupfid[] = "fid in use"; static char Eaccess[] = "read/write -- not open in suitable mode"; static char Ecount[] = "read/write -- count too big"; extern int exdebug; int export(int fd, char *dir, int async) { Chan *c, *dc; Pgrp *pg; Egrp *eg; Export *fs; if(waserror()) return -1; c = fdtochan(up->env->fgrp, fd, ORDWR, 1, 1); poperror(); if(waserror()){ cclose(c); return -1; } dc = namec(dir, Atodir, 0, 0); poperror(); fs = malloc(sizeof(Export)); if(fs == nil){ cclose(c); cclose(dc); error(Enomem); } fs->r.ref = 1; pg = up->env->pgrp; fs->pgrp = pg; incref(&pg->r); eg = up->env->egrp; fs->egrp = eg; incref(&eg->r); fs->fgrp = newfgrp(nil); fs->uid = up->env->uid; fs->gid = up->env->gid; kstrdup(&fs->user, up->env->user); fs->root = dc; fs->io = c; uqidinit(&fs->uqids); fs->msize = 0; c->flag |= CMSG; fs->async = async; if(async){ if(waserror()) return -1; kproc("exportfs", exportproc, fs, 0); poperror(); }else exportproc(fs); return 0; } static void exportinit(void) { lock(&exq.l); if(fcalls[Tversion] != nil) { unlock(&exq.l); return; } fcalls[Tversion] = Exversion; 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[Twstat] = Exwstat; unlock(&exq.l); } static int exisroot(Export *fs, Chan *c) { return eqchan(fs->root, c, 1); } static int exreadn(Chan *c, void *buf, int n) { int nr, t; if(waserror()) return -1; for(nr = 0; nr < n;){ t = devtab[c->type]->read(c, (char*)buf+nr, n-nr, 0); if(t <= 0) break; nr += t; } poperror(); return nr; } static int exreadmsg(Chan *c, void *a, uint n) { int m, len; uchar *buf; buf = a; m = exreadn(c, buf, BIT32SZ); if(m < BIT32SZ){ if(m < 0) return -1; return 0; } len = GBIT32(buf); if(len <= BIT32SZ || len > n){ kwerrstr("bad length in Styx message header"); return -1; } len -= BIT32SZ; m = exreadn(c, buf+BIT32SZ, len); if(m < len){ if(m < 0) return -1; return 0; } return BIT32SZ+m; } static void exportproc(void *a) { Exq *q; int async, msize; int n, type; Export *fs = a; exportinit(); for(;;){ msize = fs->msize; if(msize == 0) msize = MAXRPCDEF; for(n=0;; n++){ /* we don't use smalloc, to avoid memset */ q = mallocz(sizeof(*q)+msize, 0); if(q != nil || n > 6000) break; if(n%600 == 0) print("exportproc %d: waiting for memory (%d) for request\n", up->pid, msize); osenter(); osmillisleep(100); osleave(); } if(q == nil){ kwerrstr("out of memory: read request"); n = -1; break; } memset(q, 0, sizeof(*q)); q->buf = (uchar*)q + sizeof(*q); q->bsize = msize; n = exreadmsg(fs->io, q->buf, msize); /* TO DO: avoid copy */ if(n <= 0) break; if(convM2S(q->buf, n, &q->in) != n){ kwerrstr("bad T-message"); n = -1; break; } type = q->in.type; if(type < Tversion || type >= Tmax || type&1 || type == Terror){ kwerrstr("invalid T-message type %d", type); n = -1; break; } if(exdebug) print("export %d <- %F\n", up->pid, &q->in); q->out.type = type+1; q->out.tag = q->in.tag; q->export = fs; incref(&fs->r); if(fs->readonly){ switch(type){ case Topen: if((q->in.mode & (ORCLOSE|OTRUNC|3)) == OREAD) break; /* FALL THROUGH */ case Tcreate: case Twrite: case Tremove: case Twstat: q->out.type = Rerror; q->out.ename = "file system read only"; exreply(q, "exportproc"); exfreeq(q); continue; } } if(q->in.type == Tflush){ if(exflushed(fs, q)){ /* not yet started or not found (flush arrived after reply); we reply */ if(exdebug) print("export: flush %d\n", q->in.oldtag); exreply(q, "exportproc"); exfreeq(q); } continue; } 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) kproc("exslave", exslave, nil, 0); Wakeup(&exq.rwait); } if(exdebug){ if(n < 0) print("exportproc %d shut down: %s\n", up->pid, up->env->errstr); else print("exportproc %d shut down\n", up->pid); } free(q); exshutdown(fs); async = fs->async; exfree(fs); if(async) pexit("mount shut down", 0); } static int exflushed(Export *fs, Exq *fq) { Exq *q, **last; ulong pid; /* not yet started? */ lock(&exq.l); for(last = &exq.head; (q = *last) != nil; last = &q->next) if(q->export == fs && q->in.tag == fq->in.oldtag){ *last = q->next; unlock(&exq.l); /* not yet started: discard, and Rflush */ exfreeq(q); return 1; } unlock(&exq.l); /* tricky case: in progress */ lock(&fs->l); for(q = fs->work; q != nil; q = q->next) if(q->in.tag == fq->in.oldtag){ pid = 0; lock(&q->l); if(q->finished){ /* slave replied and emptied its flush queue; we can Rflush now */ unlock(&q->l); return 1; } /* append to slave's flush queue */ fq->next = nil; if(q->flush != nil) q->flusht->next = fq; else q->flush = fq; q->flusht = fq; if(q->busy){ pid = q->slave->pid; swiproc(q->slave, 0); } unlock(&q->l); unlock(&fs->l); if(exdebug && pid) print("export: swiproc %ld to flush %d\n", pid, fq->in.oldtag); return 0; } unlock(&fs->l); /* not found */ return 1; } static void exfreeq(Exq *q) { Exq *fq; while((fq = q->flush) != nil){ q->flush = fq->next; exfree(fq->export); free(fq); } exfree(q->export); free(q); } static void exshutdown(Export *fs) { Exq *q, **last; /* work not started */ lock(&exq.l); for(last = &exq.head; (q = *last) != nil;) if(q->export == fs){ *last = q->next; exfreeq(q); }else last = &q->next; unlock(&exq.l); /* tell slaves to abandon work in progress */ lock(&fs->l); while((q = fs->work) != nil){ fs->work = q->next; lock(&q->l); q->shut = 1; swiproc(q->slave, 0); /* whether busy or not */ unlock(&q->l); } unlock(&fs->l); } 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; f->attached = 0; if(f->ref == 0) { if(f->chan != nil) cclose(f->chan); freeuqid(&fs->uqids, f->qid); free(f); } else print("exfreefids: busy fid\n"); } } } static void exfree(Export *fs) { if(exdebug) print("export p/s %d free %p ref %ld\n", up->pid, fs, fs->r.ref); if(decref(&fs->r) != 0) return; closepgrp(fs->pgrp); closeegrp(fs->egrp); closefgrp(fs->fgrp); cclose(fs->root); cclose(fs->io); exfreefids(fs); free(fs->user); free(fs); } static int exwork(void *a) { USED(a); return exq.head != nil; } static void exslave(void *a) { Export *fs; Exq *q, *t, *fq, **last; char *err; USED(a); for(;;){ qlock(&exq.qwait); if(waserror()){ qunlock(&exq.qwait); continue; } Sleep(&exq.rwait, exwork, nil); poperror(); lock(&exq.l); q = exq.head; if(q == nil) { unlock(&exq.l); qunlock(&exq.qwait); continue; } exq.head = q->next; qunlock(&exq.qwait); /* * put the job on the work queue before it's * visible as off of the head queue, so it's always * findable for flushes and shutdown */ notkilled(); q->slave = up; q->busy = 1; /* fcall in progress: interruptible */ fs = q->export; lock(&fs->l); q->next = fs->work; fs->work = q; unlock(&fs->l); unlock(&exq.l); up->env->pgrp = q->export->pgrp; up->env->egrp = q->export->egrp; up->env->fgrp = q->export->fgrp; up->env->uid = q->export->uid; up->env->gid = q->export->gid; kstrdup(&up->env->user, q->export->user); if(exdebug > 1) print("exslave %d dispatch %F\n", up->pid, &q->in); if(waserror()){ print("exslave %d err %s\n", up->pid, up->env->errstr); /* shouldn't happen */ err = up->env->errstr; }else{ if(q->in.type >= Tmax || !fcalls[q->in.type]){ snprint(up->genbuf, sizeof(up->genbuf), "unknown message: %F", &q->in); err = up->genbuf; }else{ switch(q->in.type){ case Tread: q->out.data = (char*)q->buf + IOHDRSZ; break; case Tstat: q->out.stat = q->buf + MSGHDRSZ + BIT16SZ; /* leaves it just where we want it */ q->out.nstat = q->bsize-(MSGHDRSZ+BIT16SZ); break; } err = (*fcalls[q->in.type])(fs, &q->in, &q->out); } poperror(); } /* * if the fcall completed without error we must reply, * even if a flush is pending (because the underlying server * might have changed state), unless the export has shut down completely. * must also reply to each flush in order, and only after the original reply (if sent). */ lock(&q->l); notkilled(); q->busy = 0; /* operation complete */ if(!q->shut){ if(q->flush == nil || err == nil){ unlock(&q->l); q->out.type = q->in.type+1; q->out.tag = q->in.tag; if(err){ q->out.type = Rerror; q->out.ename = err; } exreply(q, "exslave"); lock(&q->l); } while((fq = q->flush) != nil && !q->shut){ q->flush = fq->next; unlock(&q->l); exreply(fq, "exslave"); exfreeq(fq); lock(&q->l); } } q->finished = 1; /* promise not to send any more */ unlock(&q->l); lock(&fs->l); for(last = &fs->work; (t = *last) != nil; last = &t->next) if(t == q){ *last = q->next; break; } unlock(&fs->l); notkilled(); exfreeq(q); } } static void exreply(Exq *q, char *who) { Export *fs; Fcall *r; int n; fs = q->export; r = &q->out; n = convS2M(r, q->buf, q->bsize); if(n == 0){ r->type = Rerror; if(fs->msize == 0) r->ename = "Tversion not seen"; else r->ename = "failed to convert R-message"; n = convS2M(r, q->buf, q->bsize); } if(exdebug) print("%s %d -> %F\n", who, up->pid, r); if(!waserror()){ devtab[fs->io->type]->write(fs->io, q->buf, n, 0); poperror(); } } static int exiounit(Export *fs, Chan *c) { int iounit; iounit = fs->msize-IOHDRSZ; if(c->iounit != 0 && c->iounit < fs->msize) iounit = c->iounit; return iounit; } static Fid* Exmkfid(Export *fs, ulong fid) { ulong h; Fid *f, *nf; nf = malloc(sizeof(Fid)); if(nf == nil) return nil; lock(&fs->fidlock); h = fid % Nfidhash; for(f = fs->fid[h]; f != nil; f = f->next){ if(f->fid == fid){ unlock(&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->offset = 0; nf->chan = nil; nf->qid = nil; unlock(&fs->fidlock); return nf; } static Fid* Exgetfid(Export *fs, ulong fid) { Fid *f; ulong h; lock(&fs->fidlock); h = fid % Nfidhash; for(f = fs->fid[h]; f; f = f->next) { if(f->fid == fid){ if(f->attached == 0) break; f->ref++; unlock(&fs->fidlock); return f; } } unlock(&fs->fidlock); return nil; } static void Exputfid(Export *fs, Fid *f) { Chan *c; lock(&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; unlock(&fs->fidlock); freeuqid(&fs->uqids, f->qid); free(f); if(c != nil) cclose(c); return; } unlock(&fs->fidlock); } static Chan* exmount(Chan *c, Mhead **mp, int doname) { struct {Chan *nc;} nc; Cname *oname; nc.nc = nil; if((c->flag & COPEN) == 0 && findmount(&nc.nc, mp, c->type, c->dev, c->qid)){ if(waserror()){ cclose(nc.nc); nexterror(); } nc.nc = cunique(nc.nc); poperror(); if(doname){ oname = c->name; incref(&oname->r); cnameclose(nc.nc->name); nc.nc->name = oname; } return nc.nc; } incref(&c->r); return c; } static char* Exversion(Export *fs, Fcall *t, Fcall *r) { char *p; static char version[] = VERSION9P; int iounit; r->msize = t->msize; if(r->msize > MAXRPCMAX) r->msize = MAXRPCMAX; iounit = fs->io->iounit; if(iounit != 0 && iounit > 64 && iounit < r->msize) r->msize = iounit; if(r->msize < 64) return "message size too small"; if(0) print("msgsize=%d\n", r->msize); if((p = strchr(t->version, '.')) != nil) *p = 0; if(strncmp(t->version, "9P", 2) ==0 && strcmp(version, t->version) <= 0){ r->version = version; fs->msize = r->msize; }else r->version = "unknown"; return nil; } static char* Exauth(Export *fs, Fcall *t, Fcall *r) { USED(fs); USED(t); USED(r); return "authentication not required"; } static char* Exattach(Export *fs, Fcall *t, Fcall *r) { Fid *f; f = Exmkfid(fs, t->fid); if(f == nil) return Edupfid; if(waserror()){ f->attached = 0; Exputfid(fs, f); return up->env->errstr; } f->chan = cclone(fs->root); f->qid = uqidalloc(&fs->uqids, f->chan); poperror(); r->qid = mkuqid(f->chan, f->qid); Exputfid(fs, f); return nil; } static char* Exclunk(Export *fs, Fcall *t, Fcall *r) { Fid *f; USED(r); f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; f->attached = 0; Exputfid(fs, f); return nil; } static int safewalk(Chan **cp, char **names, int nnames, int nomount, int *nerror) { int r; /* walk can raise error */ if(waserror()) return -1; r = walk(cp, names, nnames, nomount, nerror); poperror(); return r; } static char* Exwalk(Export *fs, Fcall *t, Fcall *r) { Fid *f, *nf; Chan *c; char *name; Uqid *qid; int i; f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; if(f->chan->flag & COPEN){ Exputfid(fs, f); return Eopen; } if(waserror()) return up->env->errstr; c = cclone(f->chan); poperror(); qid = f->qid; incref(&qid->r); r->nwqid = 0; if(t->nwname > 0){ for(i=0; i<t->nwname; i++){ name = t->wname[i]; if(!exisroot(fs, c) || *name != '\0' && strcmp(name, "..") != 0){ if(safewalk(&c, &name, 1, 0, nil) < 0){ /* leave the original state on error */ cclose(c); freeuqid(&fs->uqids, qid); Exputfid(fs, f); if(i == 0) return up->env->errstr; return nil; } freeuqid(&fs->uqids, qid); qid = uqidalloc(&fs->uqids, c); } r->wqid[r->nwqid++] = mkuqid(c, qid); } } if(t->newfid != t->fid){ nf = Exmkfid(fs, t->newfid); if(nf == nil){ cclose(c); freeuqid(&fs->uqids, qid); Exputfid(fs, f); return Edupfid; } nf->chan = c; nf->qid = qid; Exputfid(fs, nf); }else{ cclose(f->chan); f->chan = c; freeuqid(&fs->uqids, f->qid); f->qid = qid; } Exputfid(fs, f); return nil; } static char* Exopen(Export *fs, Fcall *t, Fcall *r) { Fid *f; Chan *c; Uqid *qid; Mhead *m; f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; if(f->chan->flag & COPEN){ Exputfid(fs, f); return Emode; } m = nil; c = exmount(f->chan, &m, 1); if(waserror()){ cclose(c); Exputfid(fs, f); return up->env->errstr; } /* only save the mount head if it's a multiple element union */ if(m && m->mount && m->mount->next) c->umh = m; else putmhead(m); c = devtab[c->type]->open(c, t->mode); if(t->mode & ORCLOSE) c->flag |= CRCLOSE; qid = uqidalloc(&fs->uqids, c); poperror(); freeuqid(&fs->uqids, f->qid); cclose(f->chan); f->chan = c; f->qid = qid; f->offset = 0; r->qid = mkuqid(c, f->qid); r->iounit = exiounit(fs, c); Exputfid(fs, f); return nil; } static char* Excreate(Export *fs, Fcall *t, Fcall *r) { Fid *f; volatile struct {Chan *c;} c, dc; Cname *oname; Uqid *qid; Mhead *m; f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; if(f->chan->flag & COPEN){ Exputfid(fs, f); return Emode; } if(waserror()){ Exputfid(fs, f); return up->env->errstr; } validname(t->name, 0); if(t->name[0] == '.' && (t->name[1] == '\0' || t->name[1] == '.' && t->name[2] == '\0')) error(Efilename); /* underlying server should check, but stop it here */ m = nil; c.c = exmount(f->chan, &m, 1); if(waserror()){ cclose(c.c); if(m != nil) putmhead(m); nexterror(); } if(m != nil){ oname = c.c->name; incref(&oname->r); if(waserror()){ cnameclose(oname); nexterror(); } dc.c = createdir(c.c, m); if(waserror()){ cclose(dc.c); nexterror(); } c.c = cunique(dc.c); poperror(); cnameclose(c.c->name); poperror(); c.c->name = oname; } devtab[c.c->type]->create(c.c, t->name, t->mode, t->perm); c.c->name = addelem(c.c->name, t->name); if(t->mode & ORCLOSE) c.c->flag |= CRCLOSE; qid = uqidalloc(&fs->uqids, c.c); poperror(); if(m != nil) putmhead(m); poperror(); cclose(f->chan); f->chan = c.c; freeuqid(&fs->uqids, f->qid); f->qid = qid; r->qid = mkuqid(c.c, f->qid); r->iounit = exiounit(fs, c.c); Exputfid(fs, f); return nil; } static char* Exread(Export *fs, Fcall *t, Fcall *r) { Fid *f; Chan *c; Lock *cl; vlong off; int dir, n, seek; f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; if(waserror()) { Exputfid(fs, f); return up->env->errstr; } c = f->chan; if((c->flag & COPEN) == 0) error(Emode); if(c->mode != OREAD && c->mode != ORDWR) error(Eaccess); if(t->count < 0 || t->count > fs->msize-IOHDRSZ) error(Ecount); if(t->offset < 0) error(Enegoff); dir = c->qid.type & QTDIR; if(dir && t->offset != f->offset){ if(t->offset != 0) error(Eseekdir); f->offset = 0; c->uri = 0; c->dri = 0; } for(;;){ n = t->count; seek = 0; off = t->offset; if(dir && f->offset != off){ off = f->offset; n = t->offset - off; if(n > MAXFDATA) n = MAXFDATA; seek = 1; } if(dir && c->umh != nil){ if(0) print("union read %d uri %d dri %d\n", seek, c->uri, c->dri); n = unionread(c, r->data, n); } else{ cl = &c->l; lock(cl); c->offset = off; unlock(cl); n = devtab[c->type]->read(c, r->data, n, off); lock(cl); c->offset += n; unlock(cl); } f->offset = off + n; if(n == 0 || !seek) break; } r->count = n; poperror(); Exputfid(fs, f); return nil; } static char* Exwrite(Export *fs, Fcall *t, Fcall *r) { Fid *f; Chan *c; f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->env->errstr; } c = f->chan; if((c->flag & COPEN) == 0) error(Emode); if(c->mode != OWRITE && c->mode != ORDWR) error(Eaccess); if(c->qid.type & QTDIR) error(Eisdir); if(t->count < 0 || t->count > fs->msize-IOHDRSZ) error(Ecount); if(t->offset < 0) error(Enegoff); r->count = devtab[c->type]->write(c, t->data, t->count, t->offset); poperror(); Exputfid(fs, f); return nil; } static char* Exstat(Export *fs, Fcall *t, Fcall *r) { Fid *f; Chan *c; int n; f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; c = exmount(f->chan, nil, 1); if(waserror()){ cclose(c); Exputfid(fs, f); return up->env->errstr; } n = devtab[c->type]->stat(c, r->stat, r->nstat); if(n <= BIT16SZ) error(Eshortstat); r->nstat = n; poperror(); /* TO DO: need to change qid */ cclose(c); Exputfid(fs, f); return nil; } static char* Exwstat(Export *fs, Fcall *t, Fcall *r) { Fid *f; Chan *c; USED(r); f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->env->errstr; } validstat(t->stat, t->nstat); /* check name */ c = exmount(f->chan, nil, 0); if(waserror()){ cclose(c); nexterror(); } devtab[c->type]->wstat(c, t->stat, t->nstat); poperror(); cclose(c); poperror(); Exputfid(fs, f); return nil; } static char* Exremove(Export *fs, Fcall *t, Fcall *r) { Fid *f; Chan *c; USED(r); f = Exgetfid(fs, t->fid); if(f == nil) return Enofid; if(waserror()){ f->attached = 0; Exputfid(fs, f); return up->env->errstr; } c = exmount(f->chan, nil, 0); if(waserror()){ c->type = 0; /* see below */ cclose(c); nexterror(); } devtab[c->type]->remove(c); poperror(); 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; cclose(c); f->attached = 0; Exputfid(fs, f); return nil; }