plan9fox/sys/src/cmd/vnc/exportfs.c
cinap_lenrek a0d4c5e208 make error handling in 9p service loops consistent
when we get eof, stop the loop immidiately and do not
rely on the read to eventually return an error.

when convM2S() fails to decode the message, error out
and stop the loop. there is no point in continuing.
2016-07-24 02:21:32 +02:00

805 lines
13 KiB
C

#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)
break;
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);
}
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 < 256)
return "version: message size too small";
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;
}