summaryrefslogtreecommitdiff
path: root/sys/src/cmd/execnet
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/execnet
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/execnet')
-rwxr-xr-xsys/src/cmd/execnet/client.c432
-rwxr-xr-xsys/src/cmd/execnet/dat.h63
-rwxr-xr-xsys/src/cmd/execnet/fs.c451
-rwxr-xr-xsys/src/cmd/execnet/main.c51
-rwxr-xr-xsys/src/cmd/execnet/mkfile20
-rwxr-xr-xsys/src/cmd/execnet/note.c144
6 files changed, 1161 insertions, 0 deletions
diff --git a/sys/src/cmd/execnet/client.c b/sys/src/cmd/execnet/client.c
new file mode 100755
index 000000000..57414966d
--- /dev/null
+++ b/sys/src/cmd/execnet/client.c
@@ -0,0 +1,432 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+int nclient;
+Client **client;
+#define Zmsg ((Msg*)~0)
+char nocmd[] = "";
+
+static void readthread(void*);
+static void writethread(void*);
+static void kickwriter(Client*);
+
+int
+newclient(void)
+{
+ int i;
+ Client *c;
+
+ for(i=0; i<nclient; i++)
+ if(client[i]->ref==0 && !client[i]->moribund)
+ return i;
+
+ c = emalloc(sizeof(Client));
+ c->writerkick = chancreate(sizeof(void*), 1);
+ c->execpid = chancreate(sizeof(ulong), 0);
+ c->cmd = nocmd;
+
+ c->readerproc = ioproc();
+ c->writerproc = ioproc();
+ c->num = nclient;
+ if(nclient%16 == 0)
+ client = erealloc(client, (nclient+16)*sizeof(client[0]));
+ client[nclient++] = c;
+ return nclient-1;
+}
+
+void
+die(Client *c)
+{
+ Msg *m, *next;
+ Req *r, *rnext;
+
+ c->moribund = 1;
+ kickwriter(c);
+ iointerrupt(c->readerproc);
+ iointerrupt(c->writerproc);
+ if(--c->activethread == 0){
+ if(c->cmd != nocmd){
+ free(c->cmd);
+ c->cmd = nocmd;
+ }
+ c->pid = 0;
+ c->moribund = 0;
+ c->status = Closed;
+ for(m=c->mq; m && m != Zmsg; m=next){
+ next = m->link;
+ free(m);
+ }
+ c->mq = nil;
+ if(c->rq != nil){
+ for(r=c->rq; r; r=rnext){
+ rnext = r->aux;
+ respond(r, "hangup");
+ }
+ c->rq = nil;
+ }
+ if(c->wq != nil){
+ for(r=c->wq; r; r=rnext){
+ rnext = r->aux;
+ respond(r, "hangup");
+ }
+ c->wq = nil;
+ }
+ c->rq = nil;
+ c->wq = nil;
+ c->emq = nil;
+ c->erq = nil;
+ c->ewq = nil;
+ }
+}
+
+void
+closeclient(Client *c)
+{
+ if(--c->ref == 0){
+ if(c->pid > 0)
+ postnote(PNPROC, c->pid, "kill");
+ c->status = Hangup;
+ close(c->fd[0]);
+ c->fd[0] = c->fd[1] = -1;
+ c->moribund = 1;
+ kickwriter(c);
+ iointerrupt(c->readerproc);
+ iointerrupt(c->writerproc);
+ c->activethread++;
+ die(c);
+ }
+}
+
+void
+queuerdreq(Client *c, Req *r)
+{
+ if(c->rq==nil)
+ c->erq = &c->rq;
+ *c->erq = r;
+ r->aux = nil;
+ c->erq = (Req**)&r->aux;
+}
+
+void
+queuewrreq(Client *c, Req *r)
+{
+ if(c->wq==nil)
+ c->ewq = &c->wq;
+ *c->ewq = r;
+ r->aux = nil;
+ c->ewq = (Req**)&r->aux;
+}
+
+void
+queuemsg(Client *c, Msg *m)
+{
+ if(c->mq==nil)
+ c->emq = &c->mq;
+ *c->emq = m;
+ if(m != Zmsg){
+ m->link = nil;
+ c->emq = (Msg**)&m->link;
+ }else
+ c->emq = nil;
+}
+
+void
+matchmsgs(Client *c)
+{
+ Req *r;
+ Msg *m;
+ int n, rm;
+
+ while(c->rq && c->mq){
+ r = c->rq;
+ c->rq = r->aux;
+
+ rm = 0;
+ m = c->mq;
+ if(m == Zmsg){
+ respond(r, "execnet: no more data");
+ break;
+ }
+ n = r->ifcall.count;
+ if(n >= m->ep - m->rp){
+ n = m->ep - m->rp;
+ c->mq = m->link;
+ rm = 1;
+ }
+ if(n)
+ memmove(r->ofcall.data, m->rp, n);
+ if(rm)
+ free(m);
+ else
+ m->rp += n;
+ r->ofcall.count = n;
+ respond(r, nil);
+ }
+}
+
+void
+findrdreq(Client *c, Req *r)
+{
+ Req **l;
+
+ for(l=&c->rq; *l; l=(Req**)&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ if(*l == nil)
+ c->erq = l;
+ respond(r, "flushed");
+ break;
+ }
+ }
+}
+
+void
+findwrreq(Client *c, Req *r)
+{
+ Req **l;
+
+ for(l=&c->wq; *l; l=(Req**)&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ if(*l == nil)
+ c->ewq = l;
+ respond(r, "flushed");
+ return;
+ }
+ }
+}
+
+void
+dataread(Req *r, Client *c)
+{
+ queuerdreq(c, r);
+ matchmsgs(c);
+}
+
+static void
+readthread(void *a)
+{
+ uchar *buf;
+ int n;
+ Client *c;
+ Ioproc *io;
+ Msg *m;
+ char tmp[32];
+
+ c = a;
+ snprint(tmp, sizeof tmp, "read%d", c->num);
+ threadsetname(tmp);
+
+ buf = emalloc(8192);
+ io = c->readerproc;
+ while((n = ioread(io, c->fd[0], buf, 8192)) >= 0){
+ m = emalloc(sizeof(Msg)+n);
+ m->rp = (uchar*)&m[1];
+ m->ep = m->rp + n;
+ if(n)
+ memmove(m->rp, buf, n);
+ queuemsg(c, m);
+ matchmsgs(c);
+ }
+ queuemsg(c, Zmsg);
+ free(buf);
+ die(c);
+}
+
+static void
+kickwriter(Client *c)
+{
+ nbsendp(c->writerkick, nil);
+}
+
+void
+clientflush(Req *or, Client *c)
+{
+ if(or->ifcall.type == Tread)
+ findrdreq(c, or);
+ else{
+ if(c->execreq == or){
+ c->execreq = nil;
+ iointerrupt(c->writerproc);
+ }
+ findwrreq(c, or);
+ if(c->curw == or){
+ c->curw = nil;
+ iointerrupt(c->writerproc);
+ kickwriter(c);
+ }
+ }
+}
+
+void
+datawrite(Req *r, Client *c)
+{
+ queuewrreq(c, r);
+ kickwriter(c);
+}
+
+static void
+writethread(void *a)
+{
+ char e[ERRMAX];
+ uchar *buf;
+ int n;
+ Ioproc *io;
+ Req *r;
+ Client *c;
+ char tmp[32];
+
+ c = a;
+ snprint(tmp, sizeof tmp, "write%d", c->num);
+ threadsetname(tmp);
+
+ buf = emalloc(8192);
+ io = c->writerproc;
+ for(;;){
+ while(c->wq == nil){
+ if(c->moribund)
+ goto Out;
+ recvp(c->writerkick);
+ if(c->moribund)
+ goto Out;
+ }
+ r = c->wq;
+ c->wq = r->aux;
+ c->curw = r;
+ n = iowrite(io, c->fd[1], r->ifcall.data, r->ifcall.count);
+ if(chatty9p)
+ fprint(2, "io->write returns %d\n", n);
+ if(n >= 0){
+ r->ofcall.count = n;
+ respond(r, nil);
+ }else{
+ rerrstr(e, sizeof e);
+ respond(r, e);
+ }
+ }
+Out:
+ free(buf);
+ die(c);
+}
+
+static void
+execproc(void *a)
+{
+ int i, fd;
+ Client *c;
+ char tmp[32];
+
+ c = a;
+ snprint(tmp, sizeof tmp, "execproc%d", c->num);
+ threadsetname(tmp);
+ if(pipe(c->fd) < 0){
+ rerrstr(c->err, sizeof c->err);
+ sendul(c->execpid, -1);
+ return;
+ }
+ rfork(RFFDG);
+ fd = c->fd[1];
+ close(c->fd[0]);
+ dup(fd, 0);
+ dup(fd, 1);
+ for(i=3; i<100; i++) /* should do better */
+ close(i);
+ strcpy(c->err, "exec failed");
+ procexecl(c->execpid, "/bin/rc", "rc", "-c", c->cmd, nil);
+}
+
+static void
+execthread(void *a)
+{
+ Client *c;
+ int p;
+ char tmp[32];
+
+ c = a;
+ snprint(tmp, sizeof tmp, "exec%d", c->num);
+ threadsetname(tmp);
+ c->execpid = chancreate(sizeof(ulong), 0);
+ proccreate(execproc, c, STACK);
+ p = recvul(c->execpid);
+ chanfree(c->execpid);
+ c->execpid = nil;
+ close(c->fd[1]);
+ c->fd[1] = c->fd[0];
+ if(p != -1){
+ c->pid = p;
+ c->activethread = 2;
+ threadcreate(readthread, c, STACK);
+ threadcreate(writethread, c, STACK);
+ if(c->execreq)
+ respond(c->execreq, nil);
+ }else{
+ if(c->execreq)
+ respond(c->execreq, c->err);
+ }
+}
+
+void
+ctlwrite(Req *r, Client *c)
+{
+ char *f[3], *s, *p;
+ int nf;
+
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = '\0';
+
+ f[0] = s;
+ p = strchr(s, ' ');
+ if(p == nil)
+ nf = 1;
+ else{
+ *p++ = '\0';
+ f[1] = p;
+ nf = 2;
+ }
+
+ if(f[0][0] == '\0'){
+ free(s);
+ respond(r, nil);
+ return;
+ }
+
+ r->ofcall.count = r->ifcall.count;
+ if(strcmp(f[0], "hangup") == 0){
+ if(c->pid == 0){
+ respond(r, "connection already hung up");
+ goto Out;
+ }
+ postnote(PNPROC, c->pid, "kill");
+ respond(r, nil);
+ goto Out;
+ }
+
+ if(strcmp(f[0], "connect") == 0){
+ if(c->cmd != nocmd){
+ respond(r, "already have connection");
+ goto Out;
+ }
+ if(nf == 1){
+ respond(r, "need argument to connect");
+ goto Out;
+ }
+ c->status = Exec;
+ if(p = strrchr(f[1], '!'))
+ *p = '\0';
+ c->cmd = emalloc(4+1+strlen(f[1])+1);
+ strcpy(c->cmd, "exec ");
+ strcat(c->cmd, f[1]);
+ c->execreq = r;
+ threadcreate(execthread, c, STACK);
+ goto Out;
+ }
+
+ respond(r, "bad or inappropriate control message");
+Out:
+ free(s);
+}
diff --git a/sys/src/cmd/execnet/dat.h b/sys/src/cmd/execnet/dat.h
new file mode 100755
index 000000000..8c9c17db3
--- /dev/null
+++ b/sys/src/cmd/execnet/dat.h
@@ -0,0 +1,63 @@
+typedef struct Msg Msg;
+struct Msg
+{
+ Msg *link;
+ uchar *rp;
+ uchar *ep;
+};
+
+typedef struct Client Client;
+struct Client
+{
+ int moribund;
+ int activethread;
+ int num;
+ int ref;
+ int status;
+ int pid;
+ char *cmd;
+ int fd[2];
+ char err[ERRMAX];
+
+ Req *execreq;
+ Channel *execpid;
+
+ Req *rq, **erq; /* reading */
+ Msg *mq, **emq;
+ Ioproc *readerproc;
+
+ Channel *writerkick;
+ Req *wq, **ewq; /* writing */
+ Req *curw; /* currently writing */
+ Ioproc *writerproc; /* writing */
+};
+extern int nclient;
+extern Client **client;
+extern void dataread(Req*, Client*);
+extern int newclient(void);
+extern void closeclient(Client*);
+extern void datawrite(Req*, Client*);
+extern void ctlwrite(Req*, Client*);
+extern void clientflush(Req*, Client*);
+
+#define emalloc emalloc9p
+#define estrdup estrdup9p
+#define erealloc erealloc9p
+
+extern Srv fs;
+extern void initfs(void);
+extern void setexecname(char*);
+
+enum
+{
+ STACK = 8192,
+};
+
+enum /* Client.status */
+{
+ Closed,
+ Exec,
+ Established,
+ Hangup,
+};
+
diff --git a/sys/src/cmd/execnet/fs.c b/sys/src/cmd/execnet/fs.c
new file mode 100755
index 000000000..7f734358b
--- /dev/null
+++ b/sys/src/cmd/execnet/fs.c
@@ -0,0 +1,451 @@
+/*
+ * ``Exec'' network device. Mounted on net, provides /net/exec.
+ *
+ * exec protocol directory
+ * n connection directory
+ * ctl control messages (like connect)
+ * data data
+ * err errors
+ * local local address (pid of command)
+ * remote remote address (command)
+ * status status
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+int fsdebug;
+
+enum
+{
+ Qroot,
+ Qexec,
+ Qclone,
+ Qn,
+ Qctl,
+ Qdata,
+ Qlocal,
+ Qremote,
+ Qstatus,
+};
+
+#define PATH(type, n) ((type)|((n)<<8))
+#define TYPE(path) ((int)(path) & 0xFF)
+#define NUM(path) ((uint)(path)>>8)
+
+typedef struct Tab Tab;
+struct Tab
+{
+ char *name;
+ ulong mode;
+};
+
+Tab tab[] =
+{
+ "/", DMDIR|0555,
+ "exec", DMDIR|0555,
+ "clone", 0666,
+ nil, DMDIR|0555,
+ "ctl", 0666,
+ "data", 0666,
+ "local", 0444,
+ "remote", 0444,
+ "status", 0444,
+};
+
+void
+setexecname(char *s)
+{
+ tab[Qexec].name = s;
+}
+
+ulong time0;
+
+static void
+fillstat(Dir *d, ulong path)
+{
+ Tab *t;
+ int type;
+ char buf[32];
+
+ memset(d, 0, sizeof(*d));
+ d->uid = estrdup("exec");
+ d->gid = estrdup("exec");
+ d->qid.path = path;
+ d->atime = d->mtime = time0;
+ d->length = 0;
+
+ type = TYPE(path);
+ t = &tab[type];
+ if(t->name)
+ d->name = estrdup(t->name);
+ else{
+ snprint(buf, sizeof buf, "%ud", NUM(path));
+ d->name = estrdup(buf);
+ }
+ d->qid.type = t->mode>>24;
+ d->mode = t->mode;
+}
+
+static void
+fsstat(Req *r)
+{
+ fillstat(&r->d, r->fid->qid.path);
+ respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void*)
+{
+ if(i < 1){
+ fillstat(d, PATH(Qexec, 0));
+ return 0;
+ }
+ return -1;
+}
+
+static int
+execgen(int i, Dir *d, void*)
+{
+ if(i < 1){
+ fillstat(d, PATH(Qclone, 0));
+ return 0;
+ }
+ i -= 1;
+
+ if(i < nclient){
+ fillstat(d, PATH(Qn, i));
+ return 0;
+ }
+ return -1;
+}
+
+static int
+conngen(int i, Dir *d, void *aux)
+{
+ Client *c;
+
+ c = aux;
+ i += Qn+1;
+ if(i <= Qstatus){
+ fillstat(d, PATH(i, c->num));
+ return 0;
+ }
+ return -1;
+}
+
+char *statusstr[] =
+{
+ "Closed",
+ "Exec",
+ "Established",
+ "Hangup",
+};
+
+static void
+fsread(Req *r)
+{
+ char e[ERRMAX], *s;
+ ulong path;
+
+ path = r->fid->qid.path;
+ switch(TYPE(path)){
+ default:
+ snprint(e, sizeof e, "bug in execnet path=%lux", path);
+ respond(r, e);
+ break;
+
+ case Qroot:
+ dirread9p(r, rootgen, nil);
+ respond(r, nil);
+ break;
+
+ case Qexec:
+ dirread9p(r, execgen, nil);
+ respond(r, nil);
+ break;
+
+ case Qn:
+ dirread9p(r, conngen, client[NUM(path)]);
+ respond(r, nil);
+ break;
+
+ case Qctl:
+ snprint(e, sizeof e, "%ud", NUM(path));
+ readstr(r, e);
+ respond(r, nil);
+ break;
+
+ case Qdata:
+ dataread(r, client[NUM(path)]);
+ break;
+
+ case Qlocal:
+ snprint(e, sizeof e, "%d", client[NUM(path)]->pid);
+ readstr(r, e);
+ respond(r, nil);
+ break;
+
+ case Qremote:
+ s = client[NUM(path)]->cmd;
+ if(strlen(s) >= 5) /* "exec " */
+ readstr(r, s+5);
+ else
+ readstr(r, s);
+ respond(r, nil);
+ break;
+
+ case Qstatus:
+ readstr(r, statusstr[client[NUM(path)]->status]);
+ respond(r, nil);
+ break;
+ }
+}
+
+static void
+fswrite(Req *r)
+{
+ char e[ERRMAX];
+ ulong path;
+
+ path = r->fid->qid.path;
+ switch(TYPE(path)){
+ default:
+ snprint(e, sizeof e, "bug in execnet path=%lux", path);
+ respond(r, e);
+ break;
+
+ case Qctl:
+ ctlwrite(r, client[NUM(path)]);
+ break;
+
+ case Qdata:
+ datawrite(r, client[NUM(path)]);
+ break;
+ }
+}
+
+
+static void
+fsflush(Req *r)
+{
+ ulong path;
+ Req *or;
+
+ for(or=r; or->ifcall.type==Tflush; or=or->oldreq)
+ ;
+
+ if(or->ifcall.type != Tread && or->ifcall.type != Twrite)
+ abort();
+
+ path = or->fid->qid.path;
+ if(TYPE(path) != Qdata)
+ abort();
+
+ clientflush(or, client[NUM(path)]);
+ respond(r, nil);
+}
+
+static void
+fsattach(Req *r)
+{
+ if(r->ifcall.aname && r->ifcall.aname[0]){
+ respond(r, "invalid attach specifier");
+ return;
+ }
+ r->fid->qid.path = PATH(Qroot, 0);
+ r->fid->qid.type = QTDIR;
+ r->fid->qid.vers = 0;
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ char buf[32];
+ int i, n;
+ ulong path;
+
+ if(!(fid->qid.type&QTDIR))
+ return "walk in non-directory";
+
+ path = fid->qid.path;
+ if(strcmp(name, "..") == 0){
+ switch(TYPE(path)){
+ case Qn:
+ qid->path = PATH(Qexec, 0);
+ qid->type = QTDIR;
+ return nil;
+ case Qroot:
+ case Qexec:
+ qid->path = PATH(Qroot, 0);
+ qid->type = QTDIR;
+ return nil;
+ default:
+ return "bug in fswalk1";
+ }
+ }
+
+ i = TYPE(path)+1;
+ for(; i<nelem(tab); i++){
+ if(i==Qn){
+ n = atoi(name);
+ snprint(buf, sizeof buf, "%d", n);
+ if(n < nclient && strcmp(buf, name) == 0){
+ qid->path = PATH(Qn, n);
+ qid->type = QTDIR;
+ return nil;
+ }
+ break;
+ }
+ if(strcmp(tab[i].name, name) == 0){
+ qid->path = PATH(i, NUM(path));
+ qid->type = tab[i].mode>>24;
+ return nil;
+ }
+ if(tab[i].mode&DMDIR)
+ break;
+ }
+ return "directory entry not found";
+}
+
+static void
+fsopen(Req *r)
+{
+ static int need[4] = { 4, 2, 6, 1 };
+ ulong path;
+ int n;
+ Tab *t;
+
+ /*
+ * lib9p already handles the blatantly obvious.
+ * we just have to enforce the permissions we have set.
+ */
+ path = r->fid->qid.path;
+ t = &tab[TYPE(path)];
+ n = need[r->ifcall.mode&3];
+ if((n&t->mode) != n){
+ respond(r, "permission denied");
+ return;
+ }
+
+ switch(TYPE(path)){
+ case Qclone:
+ n = newclient();
+ path = PATH(Qctl, n);
+ r->fid->qid.path = path;
+ r->ofcall.qid.path = path;
+ if(fsdebug)
+ fprint(2, "open clone => path=%lux\n", path);
+ t = &tab[Qctl];
+ /* fall through */
+ default:
+ if(t-tab >= Qn)
+ client[NUM(path)]->ref++;
+ respond(r, nil);
+ break;
+ }
+}
+
+Channel *cclunk;
+Channel *cclunkwait;
+Channel *creq;
+Channel *creqwait;
+
+static void
+fsthread(void*)
+{
+ ulong path;
+ Alt a[3];
+ Fid *fid;
+ Req *r;
+
+ threadsetname("fsthread");
+
+ a[0].op = CHANRCV;
+ a[0].c = cclunk;
+ a[0].v = &fid;
+ a[1].op = CHANRCV;
+ a[1].c = creq;
+ a[1].v = &r;
+ a[2].op = CHANEND;
+
+ for(;;){
+ switch(alt(a)){
+ case 0:
+ path = fid->qid.path;
+ if(fid->omode != -1 && TYPE(path) >= Qn)
+ closeclient(client[NUM(path)]);
+ sendp(cclunkwait, nil);
+ break;
+ case 1:
+ switch(r->ifcall.type){
+ case Tattach:
+ fsattach(r);
+ break;
+ case Topen:
+ fsopen(r);
+ break;
+ case Tread:
+ fsread(r);
+ break;
+ case Twrite:
+ fswrite(r);
+ break;
+ case Tstat:
+ fsstat(r);
+ break;
+ case Tflush:
+ fsflush(r);
+ break;
+ default:
+ respond(r, "bug in fsthread");
+ break;
+ }
+ sendp(creqwait, 0);
+ break;
+ }
+ }
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+ sendp(cclunk, fid);
+ recvp(cclunkwait);
+}
+
+static void
+fssend(Req *r)
+{
+ sendp(creq, r);
+ recvp(creqwait); /* avoids need to deal with spurious flushes */
+}
+
+void
+initfs(void)
+{
+ time0 = time(0);
+ creq = chancreate(sizeof(void*), 0);
+ creqwait = chancreate(sizeof(void*), 0);
+ cclunk = chancreate(sizeof(void*), 0);
+ cclunkwait = chancreate(sizeof(void*), 0);
+ procrfork(fsthread, nil, STACK, RFNAMEG);
+}
+
+Srv fs =
+{
+.attach= fssend,
+.destroyfid= fsdestroyfid,
+.walk1= fswalk1,
+.open= fssend,
+.read= fssend,
+.write= fssend,
+.stat= fssend,
+.flush= fssend,
+};
diff --git a/sys/src/cmd/execnet/main.c b/sys/src/cmd/execnet/main.c
new file mode 100755
index 000000000..e24f63ccc
--- /dev/null
+++ b/sys/src/cmd/execnet/main.c
@@ -0,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: execnet [-n exec] [/net]\n");
+ exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ char *net;
+
+//extern long _threaddebuglevel;
+//_threaddebuglevel = 1<<20; /* DBGNOTE */
+
+ rfork(RFNOTEG);
+ ARGBEGIN{
+ case 'D':
+ chatty9p++;
+ break;
+ case 'n':
+ setexecname(EARGF(usage()));
+ break;
+ }ARGEND
+
+ switch(argc){
+ default:
+ usage();
+ case 0:
+ net = "/net";
+ break;
+ case 1:
+ net = argv[0];
+ break;
+ }
+
+ quotefmtinstall();
+
+ initfs();
+ threadpostmountsrv(&fs, nil, net, MBEFORE);
+ threadexits(nil);
+}
+
+
diff --git a/sys/src/cmd/execnet/mkfile b/sys/src/cmd/execnet/mkfile
new file mode 100755
index 000000000..10724b83f
--- /dev/null
+++ b/sys/src/cmd/execnet/mkfile
@@ -0,0 +1,20 @@
+</$objtype/mkfile
+
+TARG=execnet
+
+OFILES=\
+ client.$O\
+ fs.$O\
+ main.$O\
+ note.$O\
+
+HFILES=dat.h
+
+BIN=/$objtype/bin
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/386/bin/%}\
+
+</sys/src/cmd/mkone
diff --git a/sys/src/cmd/execnet/note.c b/sys/src/cmd/execnet/note.c
new file mode 100755
index 000000000..be968f50b
--- /dev/null
+++ b/sys/src/cmd/execnet/note.c
@@ -0,0 +1,144 @@
+/* BUG BUG BUG */
+
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "/sys/src/libthread/threadimpl.h"
+
+int _threadnopasser;
+
+#define NFN 33
+#define ERRLEN 48
+typedef struct Note Note;
+struct Note
+{
+ Lock inuse;
+ Proc *proc; /* recipient */
+ char s[ERRMAX]; /* arg2 */
+};
+
+static Note notes[128];
+static Note *enotes = notes+nelem(notes);
+static int (*onnote[NFN])(void*, char*);
+static int onnotepid[NFN];
+static Lock onnotelock;
+
+int
+threadnotify(int (*f)(void*, char*), int in)
+{
+ int i, topid;
+ int (*from)(void*, char*), (*to)(void*, char*);
+
+ if(in){
+ from = nil;
+ to = f;
+ topid = _threadgetproc()->pid;
+ }else{
+ from = f;
+ to = nil;
+ topid = 0;
+ }
+ lock(&onnotelock);
+ for(i=0; i<NFN; i++)
+ if(onnote[i]==from){
+ onnote[i] = to;
+ onnotepid[i] = topid;
+ break;
+ }
+ unlock(&onnotelock);
+ return i<NFN;
+}
+
+static void
+delayednotes(Proc *p, void *v)
+{
+ int i;
+ Note *n;
+ int (*fn)(void*, char*);
+
+ if(!p->pending)
+ return;
+
+ p->pending = 0;
+ for(n=notes; n<enotes; n++){
+ if(n->proc == p){
+ for(i=0; i<NFN; i++){
+ if(onnotepid[i]!=p->pid || (fn = onnote[i])==nil)
+ continue;
+ if((*fn)(v, n->s))
+ break;
+ }
+ if(i==NFN){
+ _threaddebug(DBGNOTE, "Unhandled note %s, proc %p\n", n->s, p);
+ if(v != nil)
+ noted(NDFLT);
+ else if(strncmp(n->s, "sys:", 4)==0)
+ abort();
+ threadexitsall(n->s);
+ }
+ n->proc = nil;
+ unlock(&n->inuse);
+ }
+ }
+}
+
+void
+_threadnote(void *v, char *s)
+{
+ Proc *p;
+ Note *n;
+
+ _threaddebug(DBGNOTE, "Got note %s", s);
+// if(strncmp(s, "sys:", 4) == 0)
+// noted(NDFLT);
+
+// if(_threadexitsallstatus){
+// _threaddebug(DBGNOTE, "Threadexitsallstatus = '%s'\n", _threadexitsallstatus);
+// _exits(_threadexitsallstatus);
+// }
+
+ if(strcmp(s, "threadint")==0)
+ noted(NCONT);
+
+ p = _threadgetproc();
+ if(p == nil)
+ noted(NDFLT);
+
+ for(n=notes; n<enotes; n++)
+ if(canlock(&n->inuse))
+ break;
+ if(n==enotes)
+ sysfatal("libthread: too many delayed notes");
+ utfecpy(n->s, n->s+ERRMAX, s);
+ n->proc = p;
+ p->pending = 1;
+ if(!p->splhi)
+ delayednotes(p, v);
+ noted(NCONT);
+}
+
+int
+_procsplhi(void)
+{
+ int s;
+ Proc *p;
+
+ p = _threadgetproc();
+ s = p->splhi;
+ p->splhi = 1;
+ return s;
+}
+
+void
+_procsplx(int s)
+{
+ Proc *p;
+
+ p = _threadgetproc();
+ p->splhi = s;
+ if(s)
+ return;
+ if(p->pending)
+ delayednotes(p, nil);
+}
+