blob: 515426748efe2b15b33895daa6603a9fc559c261 [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <regexp.h>
#include <thread.h>
#include <fcall.h>
#include <plumb.h>
#include "plumber.h"
enum
{
Stack = 32*1024
};
typedef struct Dirtab Dirtab;
typedef struct Fid Fid;
typedef struct Holdq Holdq;
typedef struct Readreq Readreq;
typedef struct Sendreq Sendreq;
struct Dirtab
{
char *name;
uchar type;
uint qid;
uint perm;
int nopen; /* #fids open on this port */
Fid *fopen;
Holdq *holdq;
Readreq *readq;
Sendreq *sendq;
};
struct Fid
{
int fid;
int busy;
int open;
int mode;
Qid qid;
Dirtab *dir;
long offset; /* zeroed at beginning of each message, read or write */
char *writebuf; /* partial message written so far; offset tells how much */
Fid *next;
Fid *nextopen;
};
struct Readreq
{
Fid *fid;
Fcall *fcall;
uchar *buf;
Readreq *next;
};
struct Sendreq
{
int nfid; /* number of fids that should receive this message */
int nleft; /* number left that haven't received it */
Fid **fid; /* fid[nfid] */
Plumbmsg *msg;
char *pack; /* plumbpack()ed message */
int npack; /* length of pack */
Sendreq *next;
};
struct Holdq
{
Plumbmsg *msg;
Holdq *next;
};
struct /* needed because incref() doesn't return value */
{
Lock lk;
int ref;
} rulesref;
enum
{
NDIR = 50,
Nhash = 16,
Qdir = 0,
Qrules = 1,
Qsend = 2,
Qport = 3,
NQID = Qport
};
static Dirtab dir[NDIR] =
{
{ ".", QTDIR, Qdir, 0500|DMDIR },
{ "rules", QTFILE, Qrules, 0600 },
{ "send", QTFILE, Qsend, 0200 }
};
static int ndir = NQID;
static int srvfd;
#define clock plumbclock /* SunOS name clash */
static int clock;
static Fid *fids[Nhash];
static QLock readlock;
static QLock queue;
static int messagesize = 8192+IOHDRSZ; /* good start */
static void fsysproc(void*);
static void fsysrespond(Fcall*, uchar*, char*);
static Fid* newfid(int);
static Fcall* fsysflush(Fcall*, uchar*, Fid*);
static Fcall* fsysversion(Fcall*, uchar*, Fid*);
static Fcall* fsysauth(Fcall*, uchar*, Fid*);
static Fcall* fsysattach(Fcall*, uchar*, Fid*);
static Fcall* fsyswalk(Fcall*, uchar*, Fid*);
static Fcall* fsysopen(Fcall*, uchar*, Fid*);
static Fcall* fsyscreate(Fcall*, uchar*, Fid*);
static Fcall* fsysread(Fcall*, uchar*, Fid*);
static Fcall* fsyswrite(Fcall*, uchar*, Fid*);
static Fcall* fsysclunk(Fcall*, uchar*, Fid*);
static Fcall* fsysremove(Fcall*, uchar*, Fid*);
static Fcall* fsysstat(Fcall*, uchar*, Fid*);
static Fcall* fsyswstat(Fcall*, uchar*, Fid*);
Fcall* (*fcall[Tmax])(Fcall*, uchar*, Fid*);
static void
initfcall(void)
{
fcall[Tflush] = fsysflush;
fcall[Tversion] = fsysversion;
fcall[Tauth] = fsysauth;
fcall[Tattach] = fsysattach;
fcall[Twalk] = fsyswalk;
fcall[Topen] = fsysopen;
fcall[Tcreate] = fsyscreate;
fcall[Tread] = fsysread;
fcall[Twrite] = fsyswrite;
fcall[Tclunk] = fsysclunk;
fcall[Tremove]= fsysremove;
fcall[Tstat] = fsysstat;
fcall[Twstat] = fsyswstat;
}
char Ebadfcall[] = "bad fcall type";
char Eperm[] = "permission denied";
char Enomem[] = "malloc failed for buffer";
char Enotdir[] = "not a directory";
char Enoexist[] = "plumb file does not exist";
char Eisdir[] = "file is a directory";
char Ebadmsg[] = "bad plumb message format";
char Enosuchport[] ="no such plumb port";
char Enoport[] = "couldn't find destination for message";
char Einuse[] = "file already open";
/*
* Add new port. A no-op if port already exists or is the null string
*/
void
addport(char *port)
{
int i;
if(port == nil)
return;
for(i=NQID; i<ndir; i++)
if(strcmp(port, dir[i].name) == 0)
return;
if(i == NDIR){
fprint(2, "plumb: too many ports; max %d\n", NDIR);
return;
}
ndir++;
dir[i].name = estrdup(port);
dir[i].qid = i;
dir[i].perm = 0400;
nports++;
ports = erealloc(ports, nports*sizeof(char*));
ports[nports-1] = dir[i].name;
}
static ulong
getclock(void)
{
return time(0);
}
void
startfsys(void)
{
int p[2];
fmtinstall('F', fcallfmt);
clock = getclock();
if(pipe(p) < 0)
error("can't create pipe: %r");
/* 0 will be server end, 1 will be client end */
srvfd = p[0];
if(post9pservice(p[1], "plumb", nil) < 0)
sysfatal("post9pservice plumb: %r");
close(p[1]);
proccreate(fsysproc, nil, Stack);
}
static void
fsysproc(void *v)
{
int n;
Fcall *t;
Fid *f;
uchar *buf;
USED(v);
initfcall();
t = nil;
for(;;){
buf = malloc(messagesize); /* avoid memset of emalloc */
if(buf == nil)
error("malloc failed: %r");
qlock(&readlock);
n = read9pmsg(srvfd, buf, messagesize);
if(n <= 0){
if(n < 0)
error("i/o error on server channel");
threadexitsall("unmounted");
}
/*
* can give false positive (create an extra fsysproc) once in a while,
* but no false negatives, so good enough. once we have one extra
* we'll never have more.
*/
if(readlock.waiting.head == nil) /* no other processes waiting to read; start one */
proccreate(fsysproc, nil, Stack);
qunlock(&readlock);
if(t == nil)
t = emalloc(sizeof(Fcall));
if(convM2S(buf, n, t) != n)
error("convert error in convM2S");
if(debug)
fprint(2, "<= %F\n", t);
if(fcall[t->type] == 0)
fsysrespond(t, buf, Ebadfcall);
else{
if(t->type==Tversion || t->type==Tauth)
f = nil;
else
f = newfid(t->fid);
t = (*fcall[t->type])(t, buf, f);
}
}
}
static void
fsysrespond(Fcall *t, uchar *buf, char *err)
{
int n;
if(err){
t->type = Rerror;
t->ename = err;
}else
t->type++;
if(buf == nil)
buf = emalloc(messagesize);
n = convS2M(t, buf, messagesize);
if(n < 0)
error("convert error in convS2M");
if(write(srvfd, buf, n) != n)
error("write error in respond");
if(debug)
fprint(2, "=> %F\n", t);
free(buf);
}
static
Fid*
newfid(int fid)
{
Fid *f, *ff, **fh;
qlock(&queue);
ff = nil;
fh = &fids[fid&(Nhash-1)];
for(f=*fh; f; f=f->next)
if(f->fid == fid)
goto Return;
else if(ff==nil && !f->busy)
ff = f;
if(ff){
ff->fid = fid;
f = ff;
goto Return;
}
f = emalloc(sizeof *f);
f->fid = fid;
f->next = *fh;
*fh = f;
Return:
qunlock(&queue);
return f;
}
static uint
dostat(Dirtab *dir, uchar *buf, uint nbuf, uint clock)
{
Dir d;
d.qid.type = dir->type;
d.qid.path = dir->qid;
d.qid.vers = 0;
d.mode = dir->perm;
d.length = 0; /* would be nice to do better */
d.name = dir->name;
d.uid = user;
d.gid = user;
d.muid = user;
d.atime = clock;
d.mtime = clock;
return convD2M(&d, buf, nbuf);
}
static void
queuesend(Dirtab *d, Plumbmsg *m)
{
Sendreq *s, *t;
Fid *f;
int i;
s = emalloc(sizeof(Sendreq));
s->nfid = d->nopen;
s->nleft = s->nfid;
s->fid = emalloc(s->nfid*sizeof(Fid*));
i = 0;
/* build array of fids open on this channel */
for(f=d->fopen; f!=nil; f=f->nextopen)
s->fid[i++] = f;
s->msg = m;
s->next = nil;
/* link to end of queue; drainqueue() searches in sender order so this implements a FIFO */
for(t=d->sendq; t!=nil; t=t->next)
if(t->next == nil)
break;
if(t == nil)
d->sendq = s;
else
t->next = s;
}
static void
queueread(Dirtab *d, Fcall *t, uchar *buf, Fid *f)
{
Readreq *r;
r = emalloc(sizeof(Readreq));
r->fcall = t;
r->buf = buf;
r->fid = f;
r->next = d->readq;
d->readq = r;
}
static void
drainqueue(Dirtab *d)
{
Readreq *r, *nextr, *prevr;
Sendreq *s, *nexts, *prevs;
int i, n;
prevs = nil;
for(s=d->sendq; s!=nil; s=nexts){
nexts = s->next;
for(i=0; i<s->nfid; i++){
prevr = nil;
for(r=d->readq; r!=nil; r=nextr){
nextr = r->next;
if(r->fid == s->fid[i]){
/* pack the message if necessary */
if(s->pack == nil)
s->pack = plumbpack(s->msg, &s->npack);
/* exchange the stuff... */
r->fcall->data = s->pack+r->fid->offset;
n = s->npack - r->fid->offset;
if(n > messagesize-IOHDRSZ)
n = messagesize-IOHDRSZ;
if(n > r->fcall->count)
n = r->fcall->count;
r->fcall->count = n;
fsysrespond(r->fcall, r->buf, nil);
r->fid->offset += n;
if(r->fid->offset >= s->npack){
/* message transferred; delete this fid from send queue */
r->fid->offset = 0;
s->fid[i] = nil;
s->nleft--;
}
/* delete read request from queue */
if(prevr)
prevr->next = r->next;
else
d->readq = r->next;
free(r->fcall);
free(r);
break;
}else
prevr = r;
}
}
/* if no fids left, delete this send from queue */
if(s->nleft == 0){
free(s->fid);
plumbfree(s->msg);
free(s->pack);
if(prevs)
prevs->next = s->next;
else
d->sendq = s->next;
free(s);
}else
prevs = s;
}
}
/* can't flush a send because they are always answered synchronously */
static void
flushqueue(Dirtab *d, int oldtag)
{
Readreq *r, *prevr;
prevr = nil;
for(r=d->readq; r!=nil; r=r->next){
if(oldtag == r->fcall->tag){
/* delete read request from queue */
if(prevr)
prevr->next = r->next;
else
d->readq = r->next;
free(r->fcall);
free(r->buf);
free(r);
return;
}
prevr = r;
}
}
/* remove messages awaiting delivery to now-closing fid */
static void
removesenders(Dirtab *d, Fid *fid)
{
Sendreq *s, *nexts, *prevs;
int i;
prevs = nil;
for(s=d->sendq; s!=nil; s=nexts){
nexts = s->next;
for(i=0; i<s->nfid; i++)
if(fid == s->fid[i]){
/* delete this fid from send queue */
s->fid[i] = nil;
s->nleft--;
break;
}
/* if no fids left, delete this send from queue */
if(s->nleft == 0){
free(s->fid);
plumbfree(s->msg);
free(s->pack);
if(prevs)
prevs->next = s->next;
else
d->sendq = s->next;
free(s);
}else
prevs = s;
}
}
static void
hold(Plumbmsg *m, Dirtab *d)
{
Holdq *h, *q;
h = emalloc(sizeof(Holdq));
h->msg = m;
/* add to end of queue */
if(d->holdq == nil)
d->holdq = h;
else{
for(q=d->holdq; q->next!=nil; q=q->next)
;
q->next = h;
}
}
static void
queueheld(Dirtab *d)
{
Holdq *h;
while(d->holdq != nil){
h = d->holdq;
d->holdq = h->next;
queuesend(d, h->msg);
/* no need to drain queue because we know no-one is reading yet */
free(h);
}
}
static void
dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs, Exec *e)
{
int i;
char *err;
qlock(&queue);
err = nil;
if(m->dst==nil || m->dst[0]=='\0'){
err = Enoport;
if(rs != nil)
err = startup(rs, e);
plumbfree(m);
}else
for(i=NQID; i<ndir; i++)
if(strcmp(m->dst, dir[i].name) == 0){
if(dir[i].nopen == 0){
err = startup(rs, e);
if(e!=nil && e->holdforclient)
hold(m, &dir[i]);
else
plumbfree(m);
}else{
queuesend(&dir[i], m);
drainqueue(&dir[i]);
}
break;
}
freeexec(e);
qunlock(&queue);
fsysrespond(t, buf, err);
free(t);
}
static Fcall*
fsysversion(Fcall *t, uchar *buf, Fid *fid)
{
USED(fid);
if(t->msize < 256){
fsysrespond(t, buf, "version: message size too small");
return t;
}
if(t->msize < messagesize)
messagesize = t->msize;
t->msize = messagesize;
if(strncmp(t->version, "9P2000", 6) != 0){
fsysrespond(t, buf, "unrecognized 9P version");
return t;
}
t->version = "9P2000";
fsysrespond(t, buf, nil);
return t;
}
static Fcall*
fsysauth(Fcall *t, uchar *buf, Fid *fid)
{
USED(fid);
fsysrespond(t, buf, "plumber: authentication not required");
return t;
}
static Fcall*
fsysattach(Fcall *t, uchar *buf, Fid *f)
{
Fcall out;
/*
if(strcmp(t->uname, user) != 0){
fsysrespond(&out, buf, Eperm);
return t;
}
*/
f->busy = 1;
f->open = 0;
f->qid.type = QTDIR;
f->qid.path = Qdir;
f->qid.vers = 0;
f->dir = dir;
memset(&out, 0, sizeof(Fcall));
out.type = t->type;
out.tag = t->tag;
out.fid = f->fid;
out.qid = f->qid;
fsysrespond(&out, buf, nil);
return t;
}
static Fcall*
fsysflush(Fcall *t, uchar *buf, Fid *fid)
{
int i;
USED(fid);
qlock(&queue);
for(i=NQID; i<ndir; i++)
flushqueue(&dir[i], t->oldtag);
qunlock(&queue);
fsysrespond(t, buf, nil);
return t;
}
static Fcall*
fsyswalk(Fcall *t, uchar *buf, Fid *f)
{
Fcall out;
Fid *nf;
ulong path;
Dirtab *d, *dir;
Qid q;
int i;
uchar type;
char *err;
if(f->open){
fsysrespond(t, buf, "clone of an open fid");
return t;
}
nf = nil;
if(t->fid != t->newfid){
nf = newfid(t->newfid);
if(nf->busy){
fsysrespond(t, buf, "clone to a busy fid");
return t;
}
nf->busy = 1;
nf->open = 0;
nf->dir = f->dir;
nf->qid = f->qid;
f = nf; /* walk f */
}
out.nwqid = 0;
err = nil;
dir = f->dir;
q = f->qid;
if(t->nwname > 0){
for(i=0; i<t->nwname; i++){
if((q.type & QTDIR) == 0){
err = Enotdir;
break;
}
if(strcmp(t->wname[i], "..") == 0){
type = QTDIR;
path = Qdir;
Accept:
q.type = type;
q.vers = 0;
q.path = path;
out.wqid[out.nwqid++] = q;
continue;
}
d = dir;
d++; /* skip '.' */
for(; d->name; d++)
if(strcmp(t->wname[i], d->name) == 0){
type = d->type;
path = d->qid;
dir = d;
goto Accept;
}
err = Enoexist;
break;
}
}
out.type = t->type;
out.tag = t->tag;
if(err!=nil || out.nwqid<t->nwname){
if(nf)
nf->busy = 0;
}else if(out.nwqid == t->nwname){
f->qid = q;
f->dir = dir;
}
fsysrespond(&out, buf, err);
return t;
}
static Fcall*
fsysopen(Fcall *t, uchar *buf, Fid *f)
{
int m, clearrules, mode;
clearrules = 0;
if(t->mode & OTRUNC){
if(f->qid.path != Qrules)
goto Deny;
clearrules = 1;
}
/* can't truncate anything, so just disregard */
mode = t->mode & ~(OTRUNC|OCEXEC);
/* can't execute or remove anything */
if(mode==OEXEC || (mode&ORCLOSE))
goto Deny;
switch(mode){
default:
goto Deny;
case OREAD:
m = 0400;
break;
case OWRITE:
m = 0200;
break;
case ORDWR:
m = 0600;
break;
}
if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
goto Deny;
if(f->qid.path==Qrules && (mode==OWRITE || mode==ORDWR)){
lock(&rulesref.lk);
if(rulesref.ref++ != 0){
rulesref.ref--;
unlock(&rulesref.lk);
fsysrespond(t, buf, Einuse);
return t;
}
unlock(&rulesref.lk);
}
if(clearrules){
writerules(nil, 0);
rules[0] = nil;
}
t->qid = f->qid;
t->iounit = 0;
qlock(&queue);
f->mode = mode;
f->open = 1;
f->dir->nopen++;
f->nextopen = f->dir->fopen;
f->dir->fopen = f;
queueheld(f->dir);
qunlock(&queue);
fsysrespond(t, buf, nil);
return t;
Deny:
fsysrespond(t, buf, Eperm);
return t;
}
static Fcall*
fsyscreate(Fcall *t, uchar *buf, Fid *fid)
{
USED(fid);
fsysrespond(t, buf, Eperm);
return t;
}
static Fcall*
fsysreadrules(Fcall *t, uchar *buf)
{
char *p;
int n;
p = printrules();
n = strlen(p);
t->data = p;
if(t->offset >= n)
t->count = 0;
else{
t->data = p+t->offset;
if(t->offset+t->count > n)
t->count = n-t->offset;
}
fsysrespond(t, buf, nil);
free(p);
return t;
}
static Fcall*
fsysread(Fcall *t, uchar *buf, Fid *f)
{
uchar *b;
int i, n, o, e;
uint len;
Dirtab *d;
uint clock;
if(f->qid.path != Qdir){
if(f->qid.path == Qrules)
return fsysreadrules(t, buf);
/* read from port */
if(f->qid.path < NQID){
fsysrespond(t, buf, "internal error: unknown read port");
return t;
}
qlock(&queue);
queueread(f->dir, t, buf, f);
drainqueue(f->dir);
qunlock(&queue);
return nil;
}
o = t->offset;
e = t->offset+t->count;
clock = getclock();
b = malloc(messagesize-IOHDRSZ);
if(b == nil){
fsysrespond(t, buf, Enomem);
return t;
}
n = 0;
d = dir;
d++; /* first entry is '.' */
for(i=0; d->name!=nil && i<e; i+=len){
len = dostat(d, b+n, messagesize-IOHDRSZ-n, clock);
if(len <= BIT16SZ)
break;
if(i >= o)
n += len;
d++;
}
t->data = (char*)b;
t->count = n;
fsysrespond(t, buf, nil);
free(b);
return t;
}
static Fcall*
fsyswrite(Fcall *t, uchar *buf, Fid *f)
{
Plumbmsg *m;
int i, n;
long count;
char *data;
Exec *e;
switch((int)f->qid.path){
case Qdir:
fsysrespond(t, buf, Eisdir);
return t;
case Qrules:
clock = getclock();
fsysrespond(t, buf, writerules(t->data, t->count));
return t;
case Qsend:
if(f->offset == 0){
data = t->data;
count = t->count;
}else{
/* partial message already assembled */
f->writebuf = erealloc(f->writebuf, f->offset + t->count);
memmove(f->writebuf+f->offset, t->data, t->count);
data = f->writebuf;
count = f->offset+t->count;
}
m = plumbunpackpartial(data, count, &n);
if(m == nil){
if(n == 0){
f->offset = 0;
free(f->writebuf);
f->writebuf = nil;
fsysrespond(t, buf, Ebadmsg);
return t;
}
/* can read more... */
if(f->offset == 0){
f->writebuf = emalloc(t->count);
memmove(f->writebuf, t->data, t->count);
}
/* else buffer has already been grown */
f->offset += t->count;
fsysrespond(t, buf, nil);
return t;
}
/* release partial buffer */
f->offset = 0;
free(f->writebuf);
f->writebuf = nil;
for(i=0; rules[i]; i++)
if((e=matchruleset(m, rules[i])) != nil){
dispose(t, buf, m, rules[i], e);
return nil;
}
if(m->dst != nil){
dispose(t, buf, m, nil, nil);
return nil;
}
fsysrespond(t, buf, "no matching plumb rule");
return t;
}
fsysrespond(t, buf, "internal error: write to unknown file");
return t;
}
static Fcall*
fsysstat(Fcall *t, uchar *buf, Fid *f)
{
t->stat = emalloc(messagesize-IOHDRSZ);
t->nstat = dostat(f->dir, t->stat, messagesize-IOHDRSZ, clock);
fsysrespond(t, buf, nil);
free(t->stat);
t->stat = nil;
return t;
}
static Fcall*
fsyswstat(Fcall *t, uchar *buf, Fid *fid)
{
USED(fid);
fsysrespond(t, buf, Eperm);
return t;
}
static Fcall*
fsysremove(Fcall *t, uchar *buf, Fid *fid)
{
USED(fid);
fsysrespond(t, buf, Eperm);
return t;
}
static Fcall*
fsysclunk(Fcall *t, uchar *buf, Fid *f)
{
Fid *prev, *p;
Dirtab *d;
qlock(&queue);
if(f->open){
d = f->dir;
d->nopen--;
if(d->qid==Qrules && (f->mode==OWRITE || f->mode==ORDWR)){
/*
* just to be sure last rule is parsed; error messages will be lost, though,
* unless last write ended with a blank line
*/
writerules(nil, 0);
lock(&rulesref.lk);
rulesref.ref--;
unlock(&rulesref.lk);
}
prev = nil;
for(p=d->fopen; p; p=p->nextopen){
if(p == f){
if(prev)
prev->nextopen = f->nextopen;
else
d->fopen = f->nextopen;
removesenders(d, f);
break;
}
prev = p;
}
}
f->busy = 0;
f->open = 0;
f->offset = 0;
if(f->writebuf != nil){
free(f->writebuf);
f->writebuf = nil;
}
qunlock(&queue);
fsysrespond(t, buf, nil);
return t;
}