#include <u.h>
#include <libc.h>
#include <bio.h>
#include <regexp.h>
#include <thread.h>
#include <auth.h>
#include <fcall.h>
#include <plumb.h>
#include "plumber.h"
enum
{
Stack = 8*1024
};
typedef struct Dirtab Dirtab;
typedef struct Fid Fid;
typedef struct Sendreq Sendreq;
struct Dirtab
{
char *name;
uchar type;
uint qid;
uint perm;
};
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;
};
struct /* needed because incref() doesn't return value */
{
Lock;
int ref;
} rulesref;
enum
{
DEBUG = 0,
NDIR = 4,
Nhash = 16,
Qdir = 0,
Qrules = 1,
Qsend = 2,
};
static Dirtab dir[NDIR] =
{
{ ".", QTDIR, Qdir, 0500|DMDIR },
{ "rules", QTFILE, Qrules, 0600 },
{ "send", QTFILE, Qsend, 0200 },
{ nil, 0, 0, 0 },
};
static int ndir = 3;
static int srvfd;
static int srvclosefd; /* rock for end of pipe to close */
static int clockfd;
static int clock;
static Fid *fids[Nhash];
static QLock readlock;
static QLock queue;
static char srvfile[128];
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*) =
{
[Tflush] = fsysflush,
[Tversion] = fsysversion,
[Tauth] = fsysauth,
[Tattach] = fsysattach,
[Twalk] = fsyswalk,
[Topen] = fsysopen,
[Tcreate] = fsyscreate,
[Tread] = fsysread,
[Twrite] = fsyswrite,
[Tclunk] = fsysclunk,
[Tremove] = fsysremove,
[Tstat] = fsysstat,
[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";
static ulong
getclock(void)
{
char buf[32];
seek(clockfd, 0, 0);
read(clockfd, buf, sizeof buf);
return atoi(buf);
}
void
startfsys(void)
{
int p[2], fd;
fmtinstall('F', fcallfmt);
clockfd = open("/dev/time", OREAD|OCEXEC);
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];
srvclosefd = p[1];
sprint(srvfile, "/srv/plumb.%s.%d", user, getpid());
if(putenv("plumbsrv", srvfile) < 0)
error("can't write $plumbsrv: %r");
fd = create(srvfile, OWRITE|OCEXEC|ORCLOSE, 0600);
if(fd < 0)
error("can't create /srv file: %r");
if(fprint(fd, "%d", p[1]) <= 0)
error("can't write /srv/file: %r");
/* leave fd open; ORCLOSE will take care of it */
/* fsys proc runs at another NS, that does not
* have /mnt/plumb/send mounted on it, to avoid
* deadlock
*/
procrfork(fsysproc, nil, Stack, RFFDG|RFNAMEG);
close(p[0]);
if(mount(p[1], -1, "/mnt/plumb", MBEFORE, "") < 0)
error("can't mount /mnt/plumb: %r");
close(p[1]);
}
static int
alarmed(void *, char *s)
{
if(strcmp(s, "alarm") == 0)
return 1;
return 0;
}
static void
fsysproc(void*)
{
int n;
Fcall *t;
Fid *f;
uchar *buf;
threadnotify(alarmed, 1);
close(srvclosefd);
srvclosefd = -1;
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");
}
if(readlock.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] == nil)
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 char*
sendreq(Plumbmsg* msg)
{
char *pack; /* plumbpack()ed message */
int npack; /* length of pack */
char* e = nil;
int fd, nw;
char pf[80];
pack = plumbpack(msg, &npack);
if (debug)
fprint(2, "netplumb: to %s [%.*s]\n\n", msg->dst, npack, pack);
seprint(pf, pf+sizeof(pf), "/mnt/plumb/%s", msg->dst);
fd = open(pf, OWRITE);
if (fd < 0){
e = "can't open port";
goto Ret;
}
nw = write(fd, pack, npack);
close(fd);
if (nw != npack)
e = "write to port failed";
Ret:
free(pack);
return e;
}
/* We don't have the ports. Therefore, we don't know for how long
* to hold a message for a client that is still starting up.
* We try once soon after the client process has been started, and
* another time in a while. Then we discard the message if we fail.
*/
static void
holdproc(void* a)
{
Plumbmsg* m = a;
threadsetname("holdproc");
sleep(100);
if (sendreq(m) != nil){
sleep(1000);
sendreq(m);
}
plumbfree(m);
threadexits(nil);
}
char*
mdispose(Plumbmsg *m, Ruleset *rs, Exec *e)
{
char *err;
qlock(&queue);
if(m->dst==nil || m->dst[0]=='\0'){
err = Enoport;
//fprint(2, "mdispose rs %p\n", rs);
if(rs != nil)
err = startup(rs, e);
} else {
if(err = sendreq(m))
err = startup(rs, e);
}
if (e != nil && e->holdforclient)
proccreate(holdproc, m, 8*1024);
else
plumbfree(m);
freeexec(e);
qunlock(&queue);
return err;
}
static void
dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs, Exec *e)
{
char *err;
err = mdispose(m, rs, e);
fsysrespond(t, buf, err);
free(t);
}
static Fcall*
fsysversion(Fcall *t, uchar *buf, 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*)
{
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*)
{
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);
if(rulesref.ref++ != 0){
rulesref.ref--;
unlock(&rulesref);
fsysrespond(t, buf, Einuse);
return t;
}
unlock(&rulesref);
}
if(clearrules){
writerules(nil, 0);
rules[0] = nil;
}
t->qid = f->qid;
t->iounit = 0;
qlock(&queue);
f->mode = mode;
f->open = 1;
qunlock(&queue);
fsysrespond(t, buf, nil);
return t;
Deny:
fsysrespond(t, buf, Eperm);
return t;
}
static Fcall*
fsyscreate(Fcall *t, uchar *buf, 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);
fsysrespond(t, buf, "internal error: unknown read port");
return t;
}
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*)
{
fsysrespond(t, buf, Eperm);
return t;
}
static Fcall*
fsysremove(Fcall *t, uchar *buf, Fid*)
{
fsysrespond(t, buf, Eperm);
return t;
}
static Fcall*
fsysclunk(Fcall *t, uchar *buf, Fid *f)
{
Dirtab *d;
qlock(&queue);
if(f->open){
d = f->dir;
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);
rulesref.ref--;
unlock(&rulesref);
}
}
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;
}
|