#include <u.h>
#include <libc.h>
#include <fcall.h>
#include "compat.h"
#include "error.h"
typedef struct Fid Fid;
typedef struct Export Export;
typedef struct Exq Exq;
typedef struct Exwork Exwork;
enum
{
Nfidhash = 32,
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;
while(waserror())
fprint(2, "exslave %d errored out of loop -- heading back in!\n", getpid());
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);
rendclearintr();
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;
}
|