shithub: ridefs

Download patch

ref: e259f9d582ce2cb4ee427efe0f366504794b5095
parent: e283e9f2622b7df01c315adf41325cbf1e087727
author: B. Wilson <x@wilsonb.com>
date: Sun Jul 13 09:43:03 EDT 2025

Make text non-blocking and expose server event queue

--- a/ridefs.c
+++ b/ridefs.c
@@ -6,27 +6,57 @@
 #include <stdio.h>
 #include <json.h>
 
-
+typedef struct Evfifo Evfifo;
+typedef struct Qfile Qfile;
 typedef struct Client Client;
+typedef struct Rmsg Rmsg;
 typedef struct Rfid Rfid;
 
+struct Evfifo {
+	int in, out;
+	int open;
+	int pid;      /* event handler */
+};
+
+struct Qfile {
+	RWLock;
+	int fd;
+};
+
 struct Client {
 	Ref;
+	QLock;
 
+	/* config */
 	char *addr;
 
-	/* tcp i/o */
-	QLock rl, wl;
-	Req *rreq, *wreq;
-	int rpid, wpid;
-	int fd, cfd;
+	/* internal */
+	Evfifo ev;
+	Qfile qtext, tdata, tctl;
+	char *qctl, *qinfo;
+};
 
-	/* buffers */
-	char *qinfo, *qctl;
-	char *r;      /* response */
-	int rn, roff;
+enum {
+	RHandshake,
+	THandshake,
+	TIdentify,
+	RIdentify,
+	TExecute,
+	RAppendSessionOutput,
+	RSetPromptType,
+	CMDCOUNT
 };
 
+static char *cmdtab[] = {
+	"SupportedProtocols",
+	"UsingProtocol",
+	"Identify",
+	"ReplyIdentify",
+	"Execute",
+	"AppendSessionOutput",
+	"SetPromptType",
+};
+
 enum {
 	Qroot,
 		Qrctl,
@@ -33,25 +63,30 @@
 		Qclone,
 		Qclient,
 			Qctl,
-			Qdata,
+			Qtext,
 			Qinfo,
+			Qevent,
 	QCOUNT
 };
 
 static char *nametab[] = {
-	"/",
+	".",
 		"ctl",
 		"clone",
 		nil,
 			"ctl",
-			"data",
+			"text",
 			"info",
+			"event",
 };
 
 struct Rfid {
 	Ref;
+	RWLock *l;
+	void (*unlock)(RWLock*);
 	int kind;
 	int client;
+	int pid;
 };
 
 #define VERSION 0
@@ -61,7 +96,6 @@
 static char *defport;
 static char *user;
 static char *qrctl;
-static long bufsz;
 static long time0;
 static uint nclients;
 static int debug;
@@ -103,13 +137,14 @@
 
 int
 genqrctl(void){
-	snprintf(qrctl, bufsz,
-		"version %i\n"
-		"bufsz %u\n"
-		"nclients %u\n"
-		"debug %i\n"
+	if(qrctl)
+		free(qrctl);
+	qrctl = smprint(
+		"version %d\n"
+		"nclients %d\n"
+		"debug %d\n"
 		"defport %s\n",
-		VERSION, bufsz, nclients, debug,defport);
+		VERSION, nclients, debug, defport);
 
 	return strlen(qrctl);
 }
@@ -119,8 +154,10 @@
 	Client *c;
 
 	c = clientref(client);
-	snprintf(c->qctl, bufsz,
-		"%i\n"
+	if(c->qctl)
+		free(c->qctl);
+	c->qctl = smprint(
+		"%d\n"
 		"connect %s\n",
 		client, c->addr);
 
@@ -140,10 +177,6 @@
 	c = &cpool[i];
 
 	incref(c);
-	c->r = ecalloc(bufsz);
-	c->rn = -1;
-	c->qctl = ecalloc(bufsz);
-	c->qinfo = ecalloc(bufsz);
 	genqctl(i);
 
 	return i;
@@ -154,14 +187,17 @@
 	Client *c;
 
 	c = clientref(i);
-	if(c == nil || decref(c))
+	if(decref(c))
 		return;
-		
-	if(c->fd) close(c->fd);
-	if(c->cfd) close(c->cfd);
+
+	if(c->ev.in) close(c->ev.in);
+	if(c->ev.out) close(c->ev.out);
+	if(c->ev.pid > 0) postnote(PNPROC, c->ev.pid, "kill");
+	if(c->qtext.fd) close(c->qtext.fd);
+	if(c->tdata.fd) close(c->tdata.fd);
+	if(c->tctl.fd) close(c->tctl.fd);
 	if(c->qctl) free(c->qctl);
 	if(c->qinfo) free(c->qinfo);
-	if(c->r) free(c->r);
 
 	memset(c, 0, sizeof(*c));
 }
@@ -180,61 +216,135 @@
 	}
 }
 
+char*
+jsonesc(char *s){
+	char c, *b;
+	int i, sz;
+
+	sz = 32;
+	b = ecalloc(32);
+	for(i = 0, c = s[i]; c != '\0'; c = s[++i]){
+		if(i == sz-7){ sz *= 2; b = realloc(b, sz); }
+		if(c >= 0 && c < 32 || c == '"' || c == '\\')
+			sprintf(&b[i], "\\u%04x", c);
+		else
+			b[i] = c;
+	}
+
+	return b;
+}
+
 long
-writemsg(int fd, void *pld, long n){
-	char *r;
-	int len;
+writecmd(int fd, int cmd, ...){
+	va_list v;
+	char *j, *s;
+	long n;
+	char **strv;
+	int i, strc;
 
-	len = n+8;
-	r = ecalloc(len);
-	r[0] = 24>>len & 0xff;
-	r[1] = 16>>len & 0xff;
-	r[2] =  8>>len & 0xff;
-	r[3] =     len & 0xff;
-	r[4] = 'R';
-	r[5] = 'I';
-	r[6] = 'D';
-	r[7] = 'E';
-	memcpy(&r[8], pld, n);
-	write(fd, r, len);
+	j = nil;
+	strc = 0;
+	strv = nil;
+	va_start(v, cmd);
+	switch(cmd){
+	case THandshake:
+		j = vsmprint("%d", v);
+		break;
+	case TIdentify:
+		j = vsmprint("{\"apiVersion\":%d,\"identity\":%d}", v);
+		break;
+	case TExecute:
+		strc = 1;
+		strv = ecalloc(strc * sizeof(*strv));
+		strv[0] = jsonesc(va_arg(v, char*));
+		j = smprint("{\"text\":\"%s\",\"trace\":%d}", strv[0], va_arg(v, int));
+		break;
+	}
+	va_end(v);
 
+	switch(cmd){
+	case THandshake: s = smprint("%s=%s", cmdtab[cmd], j); break;
+	default: s = smprint("[\"%s\",%s]", cmdtab[cmd], j); break;
+	}
+
+	n = 8 + strlen(s);
+	fprint(fd, "%c%c%c%cRIDE%s",
+		(char)(24>>n & 0xff),
+		(char)(16>>n & 0xff),
+		(char)(8>>n & 0xff),
+		(char)(n & 0xff),
+		s);
+
+	if(debug)
+		fprint(2, "ridefs: T: %s\n", s);
+	for(i = 0; i < strc; i++)
+		free(strv[i]);
+	if(strv) free(strv);
+	free(j);
+	free(s);
 	return n;
 }
 
 long
-readmsg(int fd, void **pld){
-	ulong len;
-	int e;
-	char buf[9];
+readcmd(int fd, int *cmd, JSON **arg){
+	JSON *j, *v;
+	char *pld, buf[9];
+	long n;
+	int i, ret;
 
-	if(0 > (e = readn(fd, buf, 8)))
-		return e;
-
+	if((n = readn(fd, buf, 8)) < 0)
+		return n;
 	buf[9] = '\0';
-	if(0 != (e = strcmp(&buf[4], "RIDE")))
-		return e;
+	if((n = strcmp(&buf[4], "RIDE")) != 0)
+		return n;
 
-	len  = buf[0]<<24 & 0xff000000;
-	len |= buf[1]<<16 & 0x00ff0000;
-	len |= buf[2]<<8  & 0x0000ff00;
-	len |= buf[3]     & 0x000000ff;
-	len -= 8; /* len + magic */
-	len += 1; /* trailing string null */
-	*pld = ecalloc(len);
-	if(0 > (e = readn(fd, *pld, len-1))){
-		free(*pld);
-		return e;
+	n  = buf[0]<<24 & 0xff000000;
+	n |= buf[1]<<16 & 0x00ff0000;
+	n |= buf[2]<<8  & 0x0000ff00;
+	n |= buf[3]     & 0x000000ff;
+	n -= 8; /* len + magic */
+	pld = ecalloc(n+32); /* XXX: normalization overhead */
+	if((n = readn(fd, pld, n)) < 0)
+		return n;
+
+	/* Normalize bespoke handshake payload to JSON */
+	if((j = jsonparse(pld)) == nil)
+	if(sscanf(pld, "SupportedProtocols=%i", &i) == 1){
+		sprintf(pld, "[\"SupportedProtocols\",%i]", i);
+		j = jsonparse(pld);
 	}
+	free(pld);
 
-	return len;
+	ret = -1;
+	if(j->t != JSONArray || j->first == nil)
+		goto end;
+	v = j->first->val;
+	if(v->t != JSONString)
+		goto end;
+	for(*cmd = 0; *cmd < CMDCOUNT; (*cmd)++)
+		if(strcmp(v->s, cmdtab[*cmd]) == 0)
+			break;
+	if(*cmd == CMDCOUNT)
+		goto end;
+
+	if(debug)
+		fprint(2, "ridefs: R: %J\n", j);
+	*arg = j->first->next->val;
+	j->first->next->val = nil;
+	ret = n;
+
+	end:
+	jsonfree(j);
+	return ret;
 }
 
-char *
+char*
 rideinit(char *addr, int *fd, int *cfd, char **info){
-	int i;
+	int i, cmd;
 	char *b;
-	JSON *j, *v;
+	JSON *j;
 	JSONEl *d;
+	FILE *f;
 
 	b = netmkaddr(addr, "tcp", defport);
 	if(debug)
@@ -242,47 +352,87 @@
 	if((*fd = dial(b, nil, nil, cfd)) < 0)
 		return "failed to dial";
 
-	if(0 > readmsg(*fd, &b))
+	if(readcmd(*fd, &cmd, &j) < 0)
 		return "failed to read handshake";
-	if(0 != strcmp(b, "SupportedProtocols=2"))
+	if(cmd != RHandshake || j->n != 2)
 		return "unrecognized protocol";
-	free(b);
-
-	b = "UsingProtocol=2";
-	if(0 > writemsg(*fd, b, strlen(b)))
+	if(writecmd(*fd, THandshake, 2) < 0)
 		return "failed to write handshake";
-		
-	b = "[\"Identify\",{\"apiVersion\":1,\"identity\":1}]";
-	if(0 > writemsg(*fd, b, strlen(b)))
+	jsonfree(j);
+
+	if(writecmd(*fd, TIdentify, 1, 1) < 0)
 		return "failed to send identification message";
+	if(readcmd(*fd, &cmd, &j) < 0 || cmd != RIdentify)
+		return "failed to receive identification reply";
 
-	if(0 > readmsg(*fd, &b))
-		return "failed to receive identification message";
-	if(nil == (j = jsonparse(b)))
-		return "failed to parse identification message";
-	free(b);
-	if(j == nil || j->t != JSONArray || nil == j->first)
-		return "unrecognized reply";
-	v = j->first->val;
-	if(v->t != JSONString || 0 != strcmp(v->s, "ReplyIdentify"))
-		return "unexpected identification reply";
-	if(nil == (d = j->first->next) || d->val->t != JSONObject)
-		return "malformed identification reply";
-
-	for(i = 0, d = d->val->first; d != nil; d = d->next){
+	f = sopenw();
+	for(i = 0, d = j->first; d != nil; d = d->next){
 		switch(d->val->t){
 		case JSONBool:
 		case JSONNumber:
-			i += snprintf(*info+i, bufsz-i, "%s %i\n", d->name, d->val->n); break;
+			i += fprintf(f, "%s %lld\n", d->name, d->val->n); break;
 		case JSONString:
-			i += snprintf(*info+i, bufsz-i, "%s %s\n", d->name, d->val->s); break;
+			i += fprintf(f, "%s %s\n", d->name, d->val->s); break;
 		}
 	}
-
+	*info = estrdup(f->buf);
+	fclose(f);
 	jsonfree(j);
+
 	return nil;
 }
 
+void
+handlemsgs(int in, Evfifo *ev, Qfile *out){
+	JSON *j, *v;
+	char *s;
+	int cmd;
+	vlong off;
+
+	while(1){
+		s = nil;
+		readcmd(in, &cmd, &j);
+		switch(cmd){
+		case RAppendSessionOutput:
+			v = jsonbyname(j, "type");
+			switch((int)v->n){
+			case 11: /* echoed input */
+			case 14: /* input line */
+				goto end;
+				break;
+			}
+			v = jsonbyname(j, "result");
+			s = v->s;
+			break;
+		case RSetPromptType:
+			v = jsonbyname(j, "type");
+			switch((int)v->n){
+			case 0: break;                    /* no prompt */
+			case 1: s = "      "; break;      /* normal prompt */
+			case 2: s = "⎕:\n      "; break;  /* ⎕ input */
+			case 3: break;                    /* line editor */
+			case 4: s = ""; break;            /* ⍞ input */
+			case 5: break;                    /* unforeseen */
+			}
+			break;
+		}
+
+		if(s == nil)
+			continue;
+
+		off = seek(out->fd, 0, 1);
+		wlock(out);
+		fprint(out->fd, s);
+		wunlock(out);
+
+		if(ev->open > 0)
+			fprint(ev->in, "t%ld %lld\n", strlen(s), off);
+
+		end:
+		jsonfree(j);
+	}
+}
+
 long
 writeqrctl(char *b, long){
 	char *s;
@@ -290,8 +440,6 @@
 
 	sprintf(sep, " \t\n\f\r\v");
 	for(s = strtok(b, sep); s != nil; s = strtok(nil, sep)){
-		if(strcmp(s, "bufsz") == 0)
-			bufsz = strtoul(strtok(nil, sep), nil, 0);
 		if(strcmp(s, "nclients") == 0)
 			nclients = strtoul(strtok(nil, sep), nil, 0);
 		if(strcmp(s, "debug") == 0)
@@ -326,6 +474,7 @@
 void
 mkdirent(Dir *d, int kind, int client){
 	Client *c;
+	Dir *dir;
 	char *nm;
 
 	memset(d, 0, sizeof(*d));
@@ -347,22 +496,28 @@
 
 	c = clientref(client);
 	switch(kind){
-	case Qrctl:   d->mode = 0666; break;
+	case Qrctl: d->mode = 0666; break;
 	case Qctl:
-	case Qdata:     d->mode = 0666; break;
+	case Qtext: d->mode = 0666; break;
 	}
 
 	switch(kind){
 	case Qrctl: d->length = strlen(qrctl); break;
 	case Qctl: d->length = strlen(c->qctl); break;
-	case Qdata: d->length = 0; break;
 	case Qinfo: d->length = strlen(c->qinfo); break;
+	case Qtext:
+		if(c->qtext.fd){
+			dir = dirfstat(c->qtext.fd);
+			d->length = dir->length;
+			free(dir);
+		} else
+			d->length = 0;
+		break;
 	}
 }
 
 int
 genqroot(int i, Dir *d, void*){
-	static int n;
 	int j;
 
 	i += Qroot + 1;
@@ -370,12 +525,9 @@
 		mkdirent(d, i, -1);
 	} else {
 		i -= Qclient;
-		if(i == 0)
-			n = 0;
-		for(j = n; j < nclients && cpool[j].ref == 0; j++);
+		for(j = i; j < nclients && cpool[j].ref == 0; j++);
 		if(j == nclients)
 			return -1;
-		n++;
 		mkdirent(d, Qclient, j);
 	}
 
@@ -385,12 +537,20 @@
 int
 genqclient(int i, Dir *d, void *aux){
 	int client;
+	Client *c;
 
 	client = (vlong)aux;
+	c = clientref(client);
+
 	i += Qclient + 1;
-	if(i >= QCOUNT)
+	if(i >= Qtext && c->addr == nil) i++;
+	if(i >= Qinfo && c->qinfo == nil) i++;
+	if(i >= Qevent && c->ev.in == 0) i++;
+	if(i == QCOUNT)
 		return -1;
+
 	mkdirent(d, i, client);
+
 	return 0;
 }
 
@@ -397,12 +557,20 @@
 static void
 fsdestroyfid(Fid *fid){
 	Rfid *f;
+	Client *c;
 
-	f = fid->aux;
-	if(f == nil)
+	if((f = fid->aux) == nil)
 		return;
 
-	rmclient(f->client);
+	if(c = clientref(f->client))
+		switch(f->kind){
+		case Qevent: c->ev.open -= c->ev.open ? 1 : 0; break;
+		}
+
+	if(c)
+		rmclient(f->client);
+
+
 	free(f);
 }
 
@@ -438,6 +606,8 @@
 	char *err;
 	Rfid *f;
 	Client *c;
+	char *s;
+	int ppid;
 
 	f = r->fid->aux;
 	c = clientref(f->client);
@@ -444,51 +614,52 @@
 	err = nil;
 	switch(f->kind){
 	case Qclone:
-		if((f->client = mkclient()) == -1){
-			respond(r, "reached client limit");
-			return;
-		}
+		if((f->client = mkclient()) < 0)
+			err = "reached client limit";
+		if(err)
+			break;
 
 		f->kind = Qctl;
 
 		mkqid(&r->ofcall.qid, f->kind, f->client);
 		r->fid->qid = r->ofcall.qid;
-
-		respond(r, nil);
 		break;
-	case Qdata:
-		if(nil == c->addr){
-			respond(r, "no server set");
-			return;
-		}
-
-		qlock(&c->wl);
-		incref(&r->ref);
-		c->wreq = r;
-		switch(c->wpid = rfork(RFPROC|RFNOWAIT|RFMEM)){
+	case Qtext:
+		ppid = getpid();
+		switch(f->pid = rfork(RFPROC|RFNOWAIT|RFMEM)){
 		case 0:
-			if(!c->fd)
-				err = rideinit(c->addr, &c->fd, &c->cfd, &c->qinfo);
-			c->wpid = 0;
-			c->wreq = nil;
-			decref(&r->ref);
+			f->l = &c->qtext;
+			f->unlock = wunlock;
+			wlock(f->l);
+			if(c->tdata.fd == 0){
+				if(err = rideinit(c->addr, &c->tdata.fd, &c->tctl.fd, &c->qinfo))
+					goto done;
+				switch(c->ev.pid = rfork(RFPROC|RFNOWAIT|RFMEM)){
+				case 0:
+					s = smprint("/tmp/ride.%d.%d", ppid, f->client);
+					c->qtext.fd = create(s, ORDWR|ORCLOSE, DMREAD|DMAPPEND);
+					pipe(&c->ev.in);
+					handlemsgs(c->tdata.fd, &c->ev, &c->qtext);
+					free(s);
+					return;
+				case -1: err = "unable to fork message handler"; break;
+				default: break;
+				}
+			}
+
+			done:
+			wunlock(f->l);
 			respond(r, err);
-			qunlock(&c->wl);
 			exits(nil);
-		case -1:
-			respond(r, "failed to init ride");
-			c->wpid = 0;
-			c->wreq = nil;
-			decref(&r->ref);
-			qunlock(&c->wl);
-			break;
-		default:
-			return;
+		case -1: err = "failed to init ride"; break;
+		default: return;
 		}
 		break;
-	default:
-		respond(r, nil);
+	case Qevent: qlock(c); c->ev.open++; qunlock(c); break;
+	default: break;
 	}
+
+	respond(r, err);
 }
 
 static void
@@ -495,68 +666,43 @@
 fsread(Req *r){
 	Rfid *f;
 	Client *c;
-	char *buf, *err;
-	int n, off;
+	char *err;
+	int fd;
 
-	buf = err = nil;
+	err = nil;
 	f = r->fid->aux;
 	c = clientref(f->client);
 	switch(f->kind){
 	case Qroot: dirread9p(r, genqroot, nil); break;
-	case Qrctl: buf = estrdup(qrctl); break;
-	case Qclone: err = "read prohibited"; break;
+	case Qrctl: readstr(r, qrctl); break;
 	case Qclient: dirread9p(r, genqclient, (void*)f->client); break;
-	case Qctl: buf = estrdup(c->qctl); break;
-	case Qinfo: buf = estrdup(c->qinfo); break;
-	case Qdata:
-		qlock(&c->rl);
-		incref(&r->ref);
-		c->rreq = r;
-		switch(c->rpid = rfork(RFPROC|RFNOWAIT|RFMEM)){
+	case Qctl: readstr(r, c->qctl); break;
+	case Qinfo: readstr(r, c->qinfo); break;
+	case Qtext:
+		switch(f->pid = rfork(RFPROC|RFNOWAIT|RFMEM)){
 		case 0:
-			off = r->ifcall.offset - c->roff;
-			if(c->rn < 0){
-				free(c->r);
-				c->r = nil;
-				c->rn = readmsg(c->fd, &c->r);
-				c->roff = r->ifcall.offset;
-				off = 0;
-			}
+			f->l = &c->qtext;
+			f->unlock = runlock;
+			rlock(f->l);
 
-			n = r->ifcall.count + off < c->rn ?
-				r->ifcall.count : c->rn - off;
-			if(c->rn == off) {
-				r->ofcall.count = 0;
-				c->rn = -1;
-			} else {
-				memmove(r->ofcall.data, c->r + off, n);
-				r->ofcall.count = n;
-			}
+			fd = dup(c->qtext.fd, -1);
+			seek(fd, r->ifcall.offset, 0);
+			r->ofcall.count = read(fd, r->ofcall.data, r->ifcall.count);
+			close(fd);
 
-			c->rpid = 0;
-			c->rreq = nil;
-			decref(&r->ref);
-			respond(r, err);
-			qunlock(&c->rl);
+			runlock(f->l);
+			respond(r, nil);
 			exits(nil);
-		case -1:
-			err = "failed to fork read";
-			c->rpid = 0;
-			c->rreq = nil;
-			decref(&r->ref);
-			qunlock(&c->rl);
-			break;
-		default:
-			return;
+		case -1: err = "failed to fork read"; break;
+		default: return;
 		}
 		break;
+	case Qevent:
+		r->ofcall.count = read(c->ev.out, r->ofcall.data, r->ifcall.count);
+		break;
+	default: err = "read prohibited"; break;
 	}
 
-	if(buf != nil){
-		readstr(r, buf);
-		free(buf);
-	}
-
 	respond(r, err);
 }
 
@@ -564,7 +710,7 @@
 fswrite(Req *r){
 	Rfid *f;
 	Client *c;
-	char *d, *err;
+	char *b, *d, *err;
 	long n;
 
 	d = r->ifcall.data;
@@ -574,39 +720,29 @@
 	err = nil;
 
 	switch(f->kind){
-	case Qrctl:
-		r->ofcall.count = writeqrctl(d, n);
-		break;
-	case Qctl:
-		r->ofcall.count = writeqctl(d, n, f->client);
-		break;
-	case Qdata:
-		qlock(&c->wl);
-		incref(&r->ref);
-		c->wreq = r;
-		switch(c->wpid = rfork(RFPROC|RFNOWAIT|RFMEM)){
+	case Qrctl: r->ofcall.count = writeqrctl(d, n); break;
+	case Qctl: r->ofcall.count = writeqctl(d, n, f->client); break;
+	case Qtext:
+		switch(f->pid = rfork(RFPROC|RFNOWAIT|RFMEM)){
 		case 0:
-			r->ofcall.count = writemsg(c->fd, d, n);
+			f->l = &c->qtext;
+			f->unlock = wunlock;
+			wlock(f->l);
 
-			c->wpid = 0;
-			c->wreq = nil;
-			decref(&r->ref);
+			r->ofcall.count = write(c->qtext.fd, d, r->ifcall.count);
+			b = ecalloc(r->ifcall.count + 1);
+			memmove(b, d, r->ifcall.count);
+			writecmd(c->tdata.fd, TExecute, b, 0);
+			free(b);
+
+			wunlock(f->l);
 			respond(r, nil);
-			qunlock(&c->wl);
 			exits(nil);
-		case -1:
-			err = "failed to fork write";
-			c->wpid = 0;
-			c->wreq = nil;
-			decref(&r->ref);
-			qunlock(&c->wl);
-			break;
-		default:
-			return;
+		case -1: err = "failed to fork write"; break;
+		default: return;
 		}
 		break;
-	default:
-		err = "write prohibited";
+	default: err = "write prohibited"; break;
 	}
 
 	respond(r, err);
@@ -616,24 +752,12 @@
 fsflush(Req *r){
 	Req *o;
 	Rfid *f;
-	Client *c;
 
 	if(o = r->oldreq)
-	if(f = o->fid->aux)
-	if(c = clientref(f->client))
-	if(o == c->rreq){
-		postnote(PNPROC, c->rpid, "interrupt");
-		respond(c->rreq, "interrupted");
-		c->rpid = 0;
-		decref(&c->rreq->ref);
-		qunlock(&c->rl);
-	} else if (o == c->wreq){
-		postnote(PNPROC, c->wpid, "interrupt");
-		respond(c->wreq, "interrupted");
-		c->wpid = 0;
-		c->wreq = nil;
-		decref(&c->wreq->ref);
-		qunlock(&c->wl);
+	if(f = o->fid->aux){
+		if(f->pid) postnote(PNPROC, f->pid, "interrupt");
+		if(f->l) f->unlock(f->l);
+		respond(o, "interrupted");
 	}
 
 	respond(r, nil);
@@ -653,46 +777,51 @@
 fswalk1(Fid *fid, char *name, Qid *qid){
 	Rfid *f;
 	Client *c;
-	int i, n;
-	char *nend;
+	char *s;
+	int n, i;
 
 	if(!(fid->qid.type&QTDIR))
 		return "cannot walk from non-directory";
 
 	f = fid->aux;
-	n = -1;
 	if(strcmp(name, "..") == 0){
 		switch(f->kind){
-		case Qroot:
-			break;
+		case Qroot: break;
 		case Qclient:
-			rmclient(f->client);
+			f->kind = Qroot;
+			if(f->client > -1) rmclient(f->client);
 			f->client = -1;
 			break;
-		default:
-			if(f->kind > Qclient)
-				f->kind = Qclient;
 		}
 	} else {
-		for(i = f->kind+1; i<QCOUNT; i++){
+		for(i = f->kind+1; i<QCOUNT; i++)
 			if(nametab[i] && strcmp(name, nametab[i]) == 0)
 				break;
-			if(i == Qclient){
-				n = strtol(name, &nend, 10);
-				c = clientref(n);
-				if(*nend == 0 && c != nil && c->ref != 0){
-					incref(c);
-					f->client = n;
-					break;
-				}	
-			}
+
+		n = strtol(name, &s, 10);
+		if(f->kind == Qroot && i == QCOUNT && *s == 0){
+			i = Qclient;
+			f->client = n;
 		}
-		if(i >= QCOUNT)
-			return "directory entry not found";
+
+		c = clientref(f->client);
+		switch(i){
+		case Qclient:
+			if(c == nil || c->ref == 0) return "directory entry not found";
+			incref(c);
+			break;
+		case Qtext: if(c->addr == nil) return "directory entry not found"; break;
+		case Qinfo: if(c->qinfo == nil) return "directory entry not found"; break;
+		case Qevent: if(c->ev.in == 0) return "directory entry not found"; break;
+		case QCOUNT: return "directory entry not found"; break;
+		}
+
 		f->kind = i;
 	}
-	mkqid(qid, f->kind, n);
+
+	mkqid(qid, f->kind, f->client);
 	fid->qid = *qid;
+
 	return nil;
 }
 
@@ -738,7 +867,6 @@
 	user = getuser();
 	mtpt = "/mnt/ride";
 	defport = "4502";
-	bufsz = 4096;
 	time0 = time(0);
 	nclients = 256;
 
@@ -751,7 +879,6 @@
 	}ARGEND
 
 	cpool = ecalloc(nclients*sizeof(*cpool));
-	qrctl = ecalloc(bufsz);
 	genqrctl();
 
 	JSONfmtinstall();
--- a/test
+++ b/test
@@ -2,9 +2,8 @@
 
 fn fail{ >[1=2] echo $1 >[1=2]; exit $1 }
 
-~ $1 '' || addr=$1
-~ $addr '' && {
-	usage
+argv0=$0
+~ $rideaddr '' && {
 	fail 'missing server address argument'
 }
 
@@ -40,11 +39,10 @@
 		fail 'Could not read client ctl'
 
 	x=`{grep connect ctl}
-	echo 'connect 127.0.0.1' >ctl ||
+	echo 'connect localhost' >ctl ||
 		fail 'Could not write client ctl'
-
 	y=`{grep connect ctl}
-	~ $y(2) 127.0.0.1 ||
+	~ $y(2) localhost ||
 		fail 'Written and read client ctl values differ'
 	echo umask $x(2) >ctl
 
@@ -56,43 +54,29 @@
 ls -d $n >[2]/dev/null &&
 	fail 'Did not clean up closed client'
 
-
 <clone {
 	cd `{read}
+	echo 'connect '^$rideaddr >ctl
 
-	echo 'connect '^$addr >ctl ||
+	echo >text ||
 		fail 'Could not connect to server'
 
-	<>data >[1=0] {
+	>text {
 		~ `{wc -c info} 0 &&
 			fail 'Did not receive RIDE info'
 
-		cat >/dev/null ||
-			fail 'Did not receive initial message'
-
-		echo -n '["Execute",{"text":"⍳5\n","trace":0}]' ||
+		# expect events: prompt > response > prompt
+		# XXX: >/dev/null and >/fd/1 mysteriously effect different text
+		<event for(n in 0 1 2) x=`{read} & evpid=$apid
+		echo '⍳5' ||
 			fail 'Could not send message'
 
-		res='["AppendSessionOutput",{"result":"⍳5\n","type":14,"group":0}]'
-		~ `{cat} $res ||
-			fail 'Did not receive response'
+		wait $evpid
+		tx='      ⍳5
+0 1 2 3 4
+      '
+		~ `''{cat text} $tx ||
+			fail 'Did not receive expected response'
 	}
 	cd ..
-}
-
-<clone {
-	cd `{read}
-	echo 'connect '^$addr >ctl
-
-	>data {
-		msg='["Execute",{"text":"⍳5\n","trace":0}]'
-		echo -n $msg || fail 'Could not send message'
-	} & tpid=$apid
-
-	<data {
-		cat >/dev/null || fail 'Could not receive message'
-	} & rpid=$apid
-
-	wait $tpid || fail $status
-	wait $rpid || fail $status
 }
--