#include #include #include #include "compat.h" #include "error.h" typedef struct Fid Fid; typedef struct Export Export; typedef struct Exq Exq; typedef struct Exwork Exwork; enum { Nfidhash = 1, Maxfdata = 8192, Maxrpc = IOHDRSZ + Maxfdata, }; struct Export { Ref r; Exq* work; Lock fidlock; Fid* fid[Nfidhash]; int io; /* fd to read/write */ int iounit; int nroots; Chan **roots; }; struct Fid { Fid* next; Fid** last; Chan* chan; long offset; int fid; int ref; /* fcalls using the fid; locked by Export.Lock */ int attached; /* fid attached or cloned but not clunked */ }; struct Exq { Lock lk; int responding; /* writing out reply message */ 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[Maxrpc]; }; struct Exwork { Lock l; int ref; int nwaiters; /* queue of slaves waiting for work */ QLock qwait; Rendez rwait; Exq *head; /* work waiting for a slave */ Exq *tail; }; Exwork exq; static void exshutdown(Export*); static void exflush(Export*, int, int); static void exslave(void*); static void exfree(Export*); static void exportproc(Export*); static char* Exattach(Export*, Fcall*, uchar*); static char* Exauth(Export*, Fcall*, uchar*); static char* Exclunk(Export*, Fcall*, uchar*); static char* Excreate(Export*, Fcall*, uchar*); static char* Exversion(Export*, Fcall*, uchar*); static char* Exopen(Export*, Fcall*, uchar*); static char* Exread(Export*, Fcall*, uchar*); static char* Exremove(Export*, Fcall*, uchar*); static char* Exsession(Export*, Fcall*, uchar*); static char* Exstat(Export*, Fcall*, uchar*); static char* Exwalk(Export*, Fcall*, uchar*); static char* Exwrite(Export*, Fcall*, uchar*); static char* Exwstat(Export*, Fcall*, uchar*); static char *(*fcalls[Tmax])(Export*, Fcall*, uchar*); static char Enofid[] = "no such fid"; static char Eseekdir[] = "can't seek on a directory"; static char Ereaddir[] = "unaligned read of a directory"; static int exdebug = 0; int sysexport(int fd, Chan **roots, int nroots) { Export *fs; fs = smalloc(sizeof(Export)); fs->r.ref = 1; fs->io = fd; fs->roots = roots; fs->nroots = nroots; exportproc(fs); return 0; } static void exportinit(void) { lock(&exq.l); exq.ref++; if(fcalls[Tversion] != nil) { unlock(&exq.l); return; } fmtinstall('F', fcallfmt); 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 void exportproc(Export *fs) { Exq *q; int n, ed; exportinit(); ed = errdepth(-1); for(;;){ errdepth(ed); q = smalloc(sizeof(Exq)); n = read9pmsg(fs->io, q->buf, Maxrpc); if(n <= 0 || convM2S(q->buf, n, &q->rpc) != n) goto bad; if(exdebug) print("export %d <- %F\n", getpid(), &q->rpc); if(q->rpc.type == Tflush){ exflush(fs, q->rpc.tag, q->rpc.oldtag); free(q); continue; } q->export = fs; incref(&fs->r); lock(&exq.l); if(exq.head == nil) exq.head = q; else exq.tail->next = q; q->next = nil; exq.tail = q; n = exq.nwaiters; if(n) exq.nwaiters = n - 1; unlock(&exq.l); if(!n) kproc("exportfs", exslave, nil); rendwakeup(&exq.rwait); } bad: free(q); if(exdebug) fprint(2, "export proc shutting down: %r\n"); exshutdown(fs); exfree(fs); } static void exflush(Export *fs, int flushtag, int tag) { Exq *q, **last; Fcall fc; uchar buf[Maxrpc]; int n; /* 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); free(q); goto Respond; } last = &q->next; } unlock(&exq.l); /* in progress? */ lock(&fs->r); for(q = fs->work; q != nil; q = q->next){ if(q->rpc.tag == tag){ lock(&q->lk); q->noresponse = 1; if(!q->responding) rendintr(q->slave); unlock(&q->lk); break; } } unlock(&fs->r); Respond: fc.type = Rflush; fc.tag = flushtag; n = convS2M(&fc, buf, Maxrpc); if(n == 0) panic("convS2M error on write"); if(write(fs->io, buf, n) != n) panic("mount write"); } 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); free(q); continue; } last = &q->next; } /* * cleanly shut down the slaves if this is the last fs around */ exq.ref--; if(!exq.ref) rendwakeup(&exq.rwait); unlock(&exq.l); /* * kick any sleepers */ lock(&fs->r); for(q = fs->work; q != nil; q = q->next){ lock(&q->lk); q->noresponse = 1; if(!q->responding) rendintr(q->slave); unlock(&q->lk); } unlock(&fs->r); } static void exfree(Export *fs) { Fid *f, *n; int i; if(decref(&fs->r) != 0) return; for(i = 0; i < Nfidhash; i++){ for(f = fs->fid[i]; f != nil; f = n){ if(f->chan != nil) cclose(f->chan); n = f->next; free(f); } } free(fs); } static int exwork(void *) { int work; lock(&exq.l); work = exq.head != nil || !exq.ref; unlock(&exq.l); return work; } static void exslave(void *) { Export *fs; Exq *q, *t, **last; char *volatile err; int n, ed; ed = errdepth(-1); for(;;){ errdepth(ed); qlock(&exq.qwait); if(waserror()){ qunlock(&exq.qwait); nexterror(); } rendsleep(&exq.rwait, exwork, nil); lock(&exq.l); if(!exq.ref){ unlock(&exq.l); poperror(); qunlock(&exq.qwait); break; } q = exq.head; if(q == nil){ unlock(&exq.l); poperror(); qunlock(&exq.qwait); continue; } exq.head = q->next; if(exq.head == nil) exq.tail = nil; poperror(); 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 */ q->slave = up; q->noresponse = 0; q->responding = 0; rendclearintr(); fs = q->export; lock(&fs->r); q->next = fs->work; fs->work = q; unlock(&fs->r); unlock(&exq.l); if(exdebug > 1) print("exslave dispatch %d %F\n", getpid(), &q->rpc); if(waserror()){ print("exslave err %r\n"); err = up->error; }else{ if(q->rpc.type >= Tmax || !fcalls[q->rpc.type]) err = "bad fcall type"; else err = (*fcalls[q->rpc.type])(fs, &q->rpc, &q->buf[IOHDRSZ]); poperror(); } q->rpc.type++; if(err){ q->rpc.type = Rerror; q->rpc.ename = err; } n = convS2M(&q->rpc, q->buf, Maxrpc); if(exdebug) print("exslave %d -> %F\n", getpid(), &q->rpc); lock(&q->lk); if(!q->noresponse){ q->responding = 1; unlock(&q->lk); write(fs->io, q->buf, n); }else 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 > 1) print("exslave %d written %d\n", getpid(), q->rpc.tag); lock(&fs->r); last = &fs->work; for(t = fs->work; t != nil; t = t->next){ if(t == q){ *last = q->next; break; } last = &t->next; } unlock(&fs->r); exfree(q->export); free(q); lock(&exq.l); exq.nwaiters++; unlock(&exq.l); } if(exdebug) fprint(2, "export slaveshutting down\n"); kexit(); } Fid* Exmkfid(Export *fs, int fid) { ulong h; Fid *f, *nf; nf = mallocz(sizeof(Fid), 1); 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; unlock(&fs->fidlock); return nf; } Fid* Exgetfid(Export *fs, int 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; } void Exputfid(Export *fs, Fid *f) { lock(&fs->fidlock); f->ref--; if(f->ref == 0 && f->attached == 0){ if(f->chan != nil) cclose(f->chan); f->chan = nil; *f->last = f->next; if(f->next != nil) f->next->last = f->last; unlock(&fs->fidlock); free(f); return; } unlock(&fs->fidlock); } static char* Exversion(Export *fs, Fcall *rpc, uchar *) { if(rpc->msize > Maxrpc) rpc->msize = Maxrpc; if(strncmp(rpc->version, "9P", 2) != 0){ rpc->version = "unknown"; return nil; } fs->iounit = rpc->msize - IOHDRSZ; rpc->version = "9P2000"; return nil; } static char* Exauth(Export *, Fcall *, uchar *) { return "vnc: authentication not required"; } static char* Exattach(Export *fs, Fcall *rpc, uchar *) { Fid *f; int w; w = 0; if(rpc->aname != nil) w = strtol(rpc->aname, nil, 10); if(w < 0 || w > fs->nroots) error(Ebadspec); f = Exmkfid(fs, rpc->fid); if(f == nil) return Einuse; if(waserror()){ f->attached = 0; Exputfid(fs, f); return up->error; } f->chan = cclone(fs->roots[w]); poperror(); rpc->qid = f->chan->qid; Exputfid(fs, f); return nil; } static char* Exclunk(Export *fs, Fcall *rpc, uchar *) { Fid *f; f = Exgetfid(fs, rpc->fid); if(f != nil){ f->attached = 0; Exputfid(fs, f); } return nil; } static char* Exwalk(Export *fs, Fcall *rpc, uchar *) { Fid *volatile f, *volatile nf; Walkqid *wq; Chan *c; int i, nwname; int volatile isnew; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; nf = nil; if(waserror()){ Exputfid(fs, f); if(nf != nil) Exputfid(fs, nf); return up->error; } /* * optional clone, but don't attach it until the walk succeeds. */ if(rpc->fid != rpc->newfid){ nf = Exmkfid(fs, rpc->newfid); if(nf == nil) error(Einuse); nf->attached = 0; isnew = 1; }else{ nf = Exgetfid(fs, rpc->fid); isnew = 0; } /* * let the device do the work */ c = f->chan; nwname = rpc->nwname; wq = (*devtab[c->type]->walk)(c, nf->chan, rpc->wname, nwname); if(wq == nil) error(Enonexist); poperror(); /* * copy qid array */ for(i = 0; i < wq->nqid; i++) rpc->wqid[i] = wq->qid[i]; rpc->nwqid = wq->nqid; /* * update the channel if everything walked correctly. */ if(isnew && wq->nqid == nwname){ nf->chan = wq->clone; nf->attached = 1; } free(wq); Exputfid(fs, f); Exputfid(fs, nf); return nil; } static char* Exopen(Export *fs, Fcall *rpc, uchar *) { Fid *volatile f; Chan *c; int iou; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->error; } c = f->chan; c = (*devtab[c->type]->open)(c, rpc->mode); poperror(); f->chan = c; f->offset = 0; rpc->qid = f->chan->qid; iou = f->chan->iounit; if(iou > fs->iounit) iou = fs->iounit; rpc->iounit = iou; Exputfid(fs, f); return nil; } static char* Excreate(Export *fs, Fcall *rpc, uchar *) { Fid *f; Chan *c; int iou; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->error; } c = f->chan; (*devtab[c->type]->create)(c, rpc->name, rpc->mode, rpc->perm); poperror(); f->chan = c; rpc->qid = f->chan->qid; iou = f->chan->iounit; if(iou > fs->iounit) iou = fs->iounit; rpc->iounit = iou; Exputfid(fs, f); return nil; } static char* Exread(Export *fs, Fcall *rpc, uchar *buf) { Fid *f; Chan *c; long off; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; c = f->chan; if(waserror()) { Exputfid(fs, f); return up->error; } rpc->data = (char*)buf; off = rpc->offset; c->offset = off; rpc->count = (*devtab[c->type]->read)(c, rpc->data, rpc->count, off); poperror(); Exputfid(fs, f); return nil; } static char* Exwrite(Export *fs, Fcall *rpc, uchar *) { Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->error; } c = f->chan; if(c->qid.type & QTDIR) error(Eisdir); rpc->count = (*devtab[c->type]->write)(c, rpc->data, rpc->count, rpc->offset); poperror(); Exputfid(fs, f); return nil; } static char* Exstat(Export *fs, Fcall *rpc, uchar *buf) { Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->error; } c = f->chan; rpc->stat = buf; rpc->nstat = (*devtab[c->type]->stat)(c, rpc->stat, Maxrpc); poperror(); Exputfid(fs, f); return nil; } static char* Exwstat(Export *fs, Fcall *rpc, uchar *) { Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->error; } 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, uchar *) { Fid *f; Chan *c; f = Exgetfid(fs, rpc->fid); if(f == nil) return Enofid; if(waserror()){ Exputfid(fs, f); return up->error; } 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 to point to root. */ c->type = 0; f->attached = 0; Exputfid(fs, f); return nil; }