code: drawterm

Download patch

ref: 934846f35caaf236b554c5253cabc1032af2a27a
parent: 0189e66e88d8e61be9d8e448d70a088793b1b77d
author: Russ Cox <rsc@swtch.com>
date: Mon Aug 8 08:50:13 EDT 2005

a

diff: cannot open b/exportfs//null: file does not exist: 'b/exportfs//null' diff: cannot open b/gui-win32//null: file does not exist: 'b/gui-win32//null' diff: cannot open b/gui-x11//null: file does not exist: 'b/gui-x11//null' diff: cannot open b/include//null: file does not exist: 'b/include//null' diff: cannot open b/kern//null: file does not exist: 'b/kern//null' diff: cannot open b/libauthsrv//null: file does not exist: 'b/libauthsrv//null' diff: cannot open b/libc//null: file does not exist: 'b/libc//null' diff: cannot open b/libdraw//null: file does not exist: 'b/libdraw//null' diff: cannot open b/libmemdraw//null: file does not exist: 'b/libmemdraw//null' diff: cannot open b/libmemlayer//null: file does not exist: 'b/libmemlayer//null' diff: cannot open b/libmp//null: file does not exist: 'b/libmp//null' diff: cannot open b/libsec//null: file does not exist: 'b/libsec//null' diff: cannot open b/posix-386//null: file does not exist: 'b/posix-386//null' diff: cannot open b/posix-power//null: file does not exist: 'b/posix-power//null' diff: cannot open b/win32-386//null: file does not exist: 'b/win32-386//null'
--- /dev/null
+++ b/Makefile
@@ -1,0 +1,82 @@
+TARG=drawterm
+CC=gcc
+CFLAGS=-Iinclude -c -ggdb -D_THREAD_SAFE -pthread # not ready for this yet: -Wall
+O=o
+#CC=cl
+#CFLAGS=-c -nologo -W3 -YX -Zi -MT -Zl -Iinclude -DWINDOWS
+#O=obj
+
+OFILES=\
+	main.$O\
+	cpu.$O\
+	readcons.$O\
+	secstore.$O\
+    latin1.$O\
+
+LIBS=\
+	kern/libkern.a\
+	exportfs/libexportfs.a\
+	libauthsrv/libauthsrv.a\
+	libsec/libsec.a\
+	libmp/libmp.a\
+	libmemdraw/libmemdraw.a\
+	libmemlayer/libmemlayer.a\
+	libdraw/libdraw.a\
+	libc/libc.a\
+	kern/libkern.a\
+	gui-x11/libx11.a\
+	libmemdraw/libmemdraw.a\
+	libdraw/libdraw.a\
+	kern/libkern.a\
+	libc/libc.a\
+	libmemdraw/libmemdraw.a\
+	libmemlayer/libmemlayer.a\
+	libdraw/libdraw.a\
+	libmachdep.a
+
+$(TARG): $(OFILES) $(LIBS)
+	$(CC) -pthread -o $(TARG) $(OFILES) $(LIBS) -L/usr/X11R6/lib -lX11 -ggdb
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+clean:
+	rm -f *.o */*.o */*.a drawterm  *.a
+
+kern/libkern.a:
+	(cd kern; make)
+
+exportfs/libexportfs.a:
+	(cd exportfs; make)
+
+libauthsrv/libauthsrv.a:
+	(cd libauthsrv; make)
+
+libmp/libmp.a:
+	(cd libmp; make)
+
+libsec/libsec.a:
+	(cd libsec; make)
+
+libmemdraw/libmemdraw.a:
+	(cd libmemdraw; make)
+
+libmemlayer/libmemlayer.a:
+	(cd libmemlayer; make)
+
+libdraw/libdraw.a:
+	(cd libdraw; make)
+
+libc/libc.a:
+	(cd libc; make)
+
+gui-x11/libx11.a:
+	(cd gui-x11; make)
+
+#libmachdep.a:
+#	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/'`; \
+#	(cd gcc$$arch &&  make)
+
+libmachdep.a:
+	(cd posix-386; make)
+
--- /dev/null
+++ b/Notes
@@ -1,0 +1,16 @@
+
+Win32 port Notes:
+
+* Issues:
+
+	** ownership questions on files are completely deferred by
+	marking them as unknown.  It works for now, but probably
+	should be handled correctly.
+
+	** No performance measurements have been done.  The interactive
+	response seems faster than old drawterm, but the i/o seems
+	slower.
+
+	** fs functions in devntfs.c need to handle conversions to/from
+	widechar and utf-8.
+
--- /dev/null
+++ b/args.h
@@ -1,0 +1,20 @@
+extern char *argv0;
+#define	ARGBEGIN	for((argv0? 0: (argv0=*argv)),argv++,argc--;\
+			    argv[0] && argv[0][0]=='-' && argv[0][1];\
+			    argc--, argv++) {\
+				char *_args, *_argt;\
+				Rune _argc;\
+				_args = &argv[0][1];\
+				if(_args[0]=='-' && _args[1]==0){\
+					argc--; argv++; break;\
+				}\
+				_argc = 0;\
+				while(*_args && (_args += chartorune(&_argc, _args)))\
+				switch(_argc)
+#define	ARGEND		SET(_argt);USED(_argt); USED(_argc); USED(_args);}USED(argv); USED(argc);
+#define	ARGF()		(_argt=_args, _args="",\
+				(*_argt? _argt: argv[1]? (argc--, *++argv): 0))
+#define	ARGC()		_argc
+
+#define	EARGF(x)		(_argt=_args, _args="",\
+				(*_argt? _argt: argv[1]? (argc--, *++argv): (x, (char*)0)))
--- /dev/null
+++ b/cpu.c
@@ -1,0 +1,606 @@
+/*
+ * cpu.c - Make a connection to a cpu server
+ *
+ *	   Invoked by listen as 'cpu -R | -N service net netdir'
+ *	    	   by users  as 'cpu [-h system] [-c cmd args ...]'
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <authsrv.h>
+#include <libsec.h>
+#include "args.h"
+#include "drawterm.h"
+
+#define Maxfdata 8192
+#define MaxStr 128
+
+static char*	getuser(void);
+static void	fatal(int, char*, ...);
+static void	catcher(void*, char*);
+static void	usage(void);
+static void	writestr(int, char*, char*, int);
+static int	readstr(int, char*, int);
+static char	*rexcall(int*, char*, char*);
+static int	setamalg(char*);
+static char *keyspec = "";
+static AuthInfo *p9any(int);
+
+static int 	notechan;
+#define system csystem
+static char	*system;
+static int	cflag;
+int	dbg;
+
+static char	*srvname = "ncpu";
+static char	*ealgs = "rc4_256 sha1";
+
+/* message size for exportfs; may be larger so we can do big graphics in CPU window */
+static int	msgsize = Maxfdata+IOHDRSZ;
+
+/* authentication mechanisms */
+static int	netkeyauth(int);
+static int	netkeysrvauth(int, char*);
+static int	p9auth(int);
+static int	srvp9auth(int, char*);
+static int	noauth(int);
+static int	srvnoauth(int, char*);
+
+char *authserver;
+
+typedef struct AuthMethod AuthMethod;
+struct AuthMethod {
+	char	*name;			/* name of method */
+	int	(*cf)(int);		/* client side authentication */
+	int	(*sf)(int, char*);	/* server side authentication */
+} authmethod[] =
+{
+	{ "p9",		p9auth,		srvp9auth,},
+	{ "netkey",	netkeyauth,	netkeysrvauth,},
+//	{ "none",	noauth,		srvnoauth,},
+	{ nil,	nil}
+};
+AuthMethod *am = authmethod;	/* default is p9 */
+
+char *p9authproto = "p9any";
+
+int setam(char*);
+
+void
+exits(char *s)
+{
+	print("\ngoodbye\n");
+	for(;;) osyield();
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: drawterm [-a authserver] [-c cpuserver] [-s secstore] [-u user]\n");
+	exits("usage");
+}
+int fdd;
+
+void
+cpumain(int argc, char **argv)
+{
+	char dat[MaxStr], buf[MaxStr], cmd[MaxStr], *p, *err, *secstoreserver, *s;
+	int fd, ms, data;
+
+	/* see if we should use a larger message size */
+	fd = open("/dev/draw", OREAD);
+	if(fd > 0){
+		ms = iounit(fd);
+		if(msgsize < ms+IOHDRSZ)
+			msgsize = ms+IOHDRSZ;
+		close(fd);
+	}
+
+        user = readcons("user", getenv("USER"), 0);
+	secstoreserver = nil;
+	ARGBEGIN{
+	case 'a':
+		authserver = EARGF(usage());
+		break;
+	case 'e':
+		ealgs = EARGF(usage());
+		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
+			ealgs = nil;
+		break;
+	case 'd':
+		dbg++;
+		break;
+	case 'c':
+		system = EARGF(usage());
+		break;
+/*
+	case 'c':
+		cflag++;
+		cmd[0] = '!';
+		cmd[1] = '\0';
+		while(p = ARGF()) {
+			strcat(cmd, " ");
+			strcat(cmd, p);
+		}
+		break;
+*/
+	case 'u':
+		user = EARGF(usage());
+		break;
+	case 's':
+		secstoreserver = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(secstoreserver == nil)
+		secstoreserver = authserver;
+
+        if(secstoreserver && havesecstore(secstoreserver, user)){
+                s = secstorefetch(secstoreserver, user, nil);
+                if(s){
+                        if(strlen(s) >= sizeof secstorebuf)
+                                panic("secstore data too big");
+                        strcpy(secstorebuf, s);
+                }
+        }
+
+
+	if(argc != 0)
+		usage();
+
+	if(system == nil) {
+		p = getenv("cpu");
+		if(p == 0)
+			fatal(0, "set $cpu");
+		system = p;
+	}
+
+	if(err = rexcall(&data, system, srvname))
+		fatal(1, "%s: %s", err, system);
+
+	/* Tell the remote side the command to execute and where our working directory is */
+	if(cflag)
+		writestr(data, cmd, "command", 0);
+	if(getcwd(dat, sizeof(dat)) == 0)
+		writestr(data, "NO", "dir", 0);
+	else
+		writestr(data, dat, "dir", 0);
+
+	/* 
+	 *  Wait for the other end to execute and start our file service
+	 *  of /mnt/term
+	 */
+	if(readstr(data, buf, sizeof(buf)) < 0)
+		fatal(1, "waiting for FS: %r");
+	if(strncmp("FS", buf, 2) != 0) {
+		print("remote cpu: %s", buf);
+		exits(buf);
+	}
+
+	if(readstr(data, buf, sizeof buf) < 0)
+		fatal(1, "waiting for remote export: %r");
+	if(strcmp(buf, "/") != 0){
+		print("remote cpu: %s" , buf);
+		exits(buf);
+	}
+	write(data, "OK", 2);
+
+	/* Begin serving the gnot namespace */
+	exportfs(data, msgsize);
+	fatal(1, "starting exportfs");
+}
+
+void
+fatal(int syserr, char *fmt, ...)
+{
+	Fmt f;
+	char *str;
+	va_list arg;
+
+	fmtstrinit(&f);
+	fmtprint(&f, "cpu: ");
+	va_start(arg, fmt);
+	fmtvprint(&f, fmt, arg);
+	va_end(arg);
+	if(syserr)
+		fmtprint(&f, ": %r");
+	fmtprint(&f, "\n");
+	str = fmtstrflush(&f);
+	write(2, str, strlen(str));
+	exits(str);
+}
+
+char *negstr = "negotiating authentication method";
+
+char bug[256];
+
+char*
+rexcall(int *fd, char *host, char *service)
+{
+	char *na;
+	char dir[MaxStr];
+	char err[ERRMAX];
+	char msg[MaxStr];
+	int n;
+
+	na = netmkaddr(host, "tcp", "17010");
+	if((*fd = dial(na, 0, dir, 0)) < 0)
+		return "can't dial";
+
+	/* negotiate authentication mechanism */
+	if(ealgs != nil)
+		snprint(msg, sizeof(msg), "%s %s", am->name, ealgs);
+	else
+		snprint(msg, sizeof(msg), "%s", am->name);
+	writestr(*fd, msg, negstr, 0);
+	n = readstr(*fd, err, sizeof err);
+	if(n < 0)
+		return negstr;
+	if(*err){
+		werrstr(err);
+		return negstr;
+	}
+
+	/* authenticate */
+	*fd = (*am->cf)(*fd);
+	if(*fd < 0)
+		return "can't authenticate";
+	return 0;
+}
+
+void
+writestr(int fd, char *str, char *thing, int ignore)
+{
+	int l, n;
+
+	l = strlen(str);
+	n = write(fd, str, l+1);
+	if(!ignore && n < 0)
+		fatal(1, "writing network: %s", thing);
+}
+
+int
+readstr(int fd, char *str, int len)
+{
+	int n;
+
+	while(len) {
+		n = read(fd, str, 1);
+		if(n < 0) 
+			return -1;
+		if(*str == '\0')
+			return 0;
+		str++;
+		len--;
+	}
+	return -1;
+}
+
+static int
+readln(char *buf, int n)
+{
+	int i;
+	char *p;
+
+	n--;	/* room for \0 */
+	p = buf;
+	for(i=0; i<n; i++){
+		if(read(0, p, 1) != 1)
+			break;
+		if(*p == '\n' || *p == '\r')
+			break;
+		p++;
+	}
+	*p = '\0';
+	return p-buf;
+}
+
+/*
+ *  user level challenge/response
+ */
+static int
+netkeyauth(int fd)
+{
+	char chall[32];
+	char resp[32];
+
+	strecpy(chall, chall+sizeof chall, getuser());
+	print("user[%s]: ", chall);
+	if(readln(resp, sizeof(resp)) < 0)
+		return -1;
+	if(*resp != 0)
+		strcpy(chall, resp);
+	writestr(fd, chall, "challenge/response", 1);
+
+	for(;;){
+		if(readstr(fd, chall, sizeof chall) < 0)
+			break;
+		if(*chall == 0)
+			return fd;
+		print("challenge: %s\nresponse: ", chall);
+		if(readln(resp, sizeof(resp)) < 0)
+			break;
+		writestr(fd, resp, "challenge/response", 1);
+	}
+	return -1;
+}
+
+static int
+netkeysrvauth(int fd, char *user)
+{
+	return -1;
+}
+
+static void
+mksecret(char *t, uchar *f)
+{
+	sprint(t, "%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
+		f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9]);
+}
+
+/*
+ *  plan9 authentication followed by rc4 encryption
+ */
+static int
+p9auth(int fd)
+{
+	uchar key[16];
+	uchar digest[SHA1dlen];
+	char fromclientsecret[21];
+	char fromserversecret[21];
+	int i;
+	AuthInfo *ai;
+
+	ai = p9any(fd);
+	if(ai == nil)
+		return -1;
+	memmove(key+4, ai->secret, ai->nsecret);
+	if(ealgs == nil)
+		return fd;
+
+	/* exchange random numbers */
+	srand(truerand());
+	for(i = 0; i < 4; i++)
+		key[i] = rand();
+	if(write(fd, key, 4) != 4)
+		return -1;
+	if(readn(fd, key+12, 4) != 4)
+		return -1;
+
+	/* scramble into two secrets */
+	sha1(key, sizeof(key), digest, nil);
+	mksecret(fromclientsecret, digest);
+	mksecret(fromserversecret, digest+10);
+
+	/* set up encryption */
+	i = pushssl(fd, ealgs, fromclientsecret, fromserversecret, nil);
+	if(i < 0)
+		werrstr("can't establish ssl connection: %r");
+	return i;
+}
+
+int
+authdial(char *net, char *dom)
+{
+	int fd;
+	fd = dial(netmkaddr(authserver, "tcp", "567"), 0, 0, 0);
+	//print("authdial %d\n", fd);
+	return fd;
+}
+
+static int
+getastickets(Ticketreq *tr, char *trbuf, char *tbuf)
+{
+	int asfd, rv;
+	char *dom;
+
+	dom = tr->authdom;
+	asfd = authdial(nil, dom);
+	if(asfd < 0)
+		return -1;
+	rv = _asgetticket(asfd, trbuf, tbuf);
+	close(asfd);
+	return rv;
+}
+
+static int
+mkserverticket(Ticketreq *tr, char *authkey, char *tbuf)
+{
+	int i;
+	Ticket t;
+
+	if(strcmp(tr->authid, tr->hostid) != 0)
+		return -1;
+	memset(&t, 0, sizeof(t));
+	memmove(t.chal, tr->chal, CHALLEN);
+	strcpy(t.cuid, tr->uid);
+	strcpy(t.suid, tr->uid);
+	for(i=0; i<DESKEYLEN; i++)
+		t.key[i] = fastrand();
+	t.num = AuthTc;
+	convT2M(&t, tbuf, authkey);
+	t.num = AuthTs;
+	convT2M(&t, tbuf+TICKETLEN, authkey);
+	return 0;
+}
+
+static int
+gettickets(Ticketreq *tr, char *key, char *trbuf, char *tbuf)
+{
+	if(getastickets(tr, trbuf, tbuf) >= 0)
+		return 0;
+	return mkserverticket(tr, key, tbuf);
+}
+
+AuthInfo*
+p9any(int fd)
+{
+	char buf[1024], buf2[1024], cchal[CHALLEN], *bbuf, *p, *dom, *u;
+	char *pass;
+	char tbuf[TICKETLEN+TICKETLEN+AUTHENTLEN], trbuf[TICKREQLEN];
+	char authkey[DESKEYLEN];
+	Authenticator auth;
+	int i, v2, n;
+	Ticketreq tr;
+	Ticket t;
+	AuthInfo *ai;
+
+	if((n = readstr(fd, buf, sizeof buf)) < 0)
+		fatal(1, "cannot read p9any negotiation");
+	bbuf = buf;
+	v2 = 0;
+	if(strncmp(buf, "v.2 ", 4) == 0){
+		v2 = 1;
+		bbuf += 4;
+	}
+	if(p = strchr(bbuf, ' '))
+		*p = 0;
+	p = bbuf;
+	if((dom = strchr(p, '@')) == nil)
+		fatal(1, "bad p9any domain");
+	*dom++ = 0;
+	if(strcmp(p, "p9sk1") != 0)
+		fatal(1, "server did not offer p9sk1");
+
+	sprint(buf2, "%s %s", p, dom);
+	if(write(fd, buf2, strlen(buf2)+1) != strlen(buf2)+1)
+		fatal(1, "cannot write user/domain choice in p9any");
+	if(v2){
+		if((n = readstr(fd, buf, sizeof buf)) != 3)
+			fatal(1, "cannot read OK in p9any");
+		if(memcmp(buf, "OK\0", 3) != 0)
+			fatal(1, "did not get OK in p9any");
+	}
+	for(i=0; i<CHALLEN; i++)
+		cchal[i] = fastrand();
+	if(write(fd, cchal, 8) != 8)
+		fatal(1, "cannot write p9sk1 challenge");
+
+	if(readn(fd, trbuf, TICKREQLEN) != TICKREQLEN)
+		fatal(1, "cannot read ticket request in p9sk1");
+
+
+	convM2TR(trbuf, &tr);
+	u = user;
+	pass = findkey(&u, tr.authdom);
+	if(pass == nil)
+	again:
+		pass = getkey(u, tr.authdom);
+	if(pass == nil)
+		fatal(1, "no password");
+
+	passtokey(authkey, pass);
+	memset(pass, 0, strlen(pass));
+
+	tr.type = AuthTreq;
+	strecpy(tr.hostid, tr.hostid+sizeof tr.hostid, u);
+	strecpy(tr.uid, tr.uid+sizeof tr.uid, u);
+	convTR2M(&tr, trbuf);
+
+	if(gettickets(&tr, authkey, trbuf, tbuf) < 0)
+		fatal(1, "cannot get auth tickets in p9sk1");
+
+	convM2T(tbuf, &t, authkey);
+	if(t.num != AuthTc){
+		print("?password mismatch with auth server\n");
+		goto again;
+	}
+	memmove(tbuf, tbuf+TICKETLEN, TICKETLEN);
+
+	auth.num = AuthAc;
+	memmove(auth.chal, tr.chal, CHALLEN);
+	auth.id = 0;
+	convA2M(&auth, tbuf+TICKETLEN, t.key);
+
+	if(write(fd, tbuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
+		fatal(1, "cannot send ticket and authenticator back in p9sk1");
+
+	if(readn(fd, tbuf, AUTHENTLEN) != AUTHENTLEN)
+		fatal(1, "cannot read authenticator in p9sk1");
+	
+	convM2A(tbuf, &auth, t.key);
+	if(auth.num != AuthAs
+	|| memcmp(auth.chal, cchal, CHALLEN) != 0
+	|| auth.id != 0){
+		print("?you and auth server agree about password.\n");
+		print("?server is confused.\n");
+		fatal(1, "server lies got %llux.%d want %llux.%d", *(vlong*)auth.chal, auth.id, *(vlong*)cchal, 0);
+	}
+	//print("i am %s there.\n", t.suid);
+	ai = mallocz(sizeof(AuthInfo), 1);
+	ai->secret = mallocz(8, 1);
+	des56to64((uchar*)t.key, ai->secret);
+	ai->nsecret = 8;
+	ai->suid = strdup(t.suid);
+	ai->cuid = strdup(t.cuid);
+	memset(authkey, 0, sizeof authkey);
+	return ai;
+}
+
+static int
+noauth(int fd)
+{
+	ealgs = nil;
+	return fd;
+}
+
+static int
+srvnoauth(int fd, char *user)
+{
+	strecpy(user, user+MaxStr, getuser());
+	ealgs = nil;
+	return fd;
+}
+
+void
+loghex(uchar *p, int n)
+{
+	char buf[100];
+	int i;
+
+	for(i = 0; i < n; i++)
+		sprint(buf+2*i, "%2.2ux", p[i]);
+//	syslog(0, "cpu", buf);
+}
+
+static int
+srvp9auth(int fd, char *user)
+{
+	return -1;
+}
+
+/*
+ *  set authentication mechanism
+ */
+int
+setam(char *name)
+{
+	for(am = authmethod; am->name != nil; am++)
+		if(strcmp(am->name, name) == 0)
+			return 0;
+	am = authmethod;
+	return -1;
+}
+
+/*
+ *  set authentication mechanism and encryption/hash algs
+ */
+int
+setamalg(char *s)
+{
+	ealgs = strchr(s, ' ');
+	if(ealgs != nil)
+		*ealgs++ = 0;
+	return setam(s);
+}
+
+char*
+getuser(void)
+{
+	return getenv("USER");
+}
+
--- /dev/null
+++ b/drawterm.h
@@ -1,0 +1,10 @@
+extern int havesecstore(char *addr, char *owner);
+extern char *secstore;
+extern char secstorebuf[65536];
+extern char *secstorefetch(char *addr, char *owner, char *passwd);
+extern char *authaddr;
+extern char *readcons(char *prompt, char *def, int secret);
+extern int exportfs(int, int);
+extern char *user;
+extern char *getkey(char*, char*);
+extern char *findkey(char**, char*);
binary files /dev/null b/drawterm.ico differ
--- /dev/null
+++ b/drawterm.rc
@@ -1,0 +1,72 @@
+//Microsoft Developer Studio generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_ICON1               ICON    DISCARDABLE     "drawterm.ico"
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "#include ""afxres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE DISCARDABLE 
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+#endif    // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
binary files /dev/null b/drawterm.res differ
--- /dev/null
+++ b/exportfs/Makefile
@@ -1,0 +1,16 @@
+LIB=libexportfs.a
+CC=gcc
+CFLAGS=-I../include -I. -I.. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	exportfs.$O\
+	exportsrv.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/exportfs/exportfs.c
@@ -1,0 +1,503 @@
+/*
+ * exportfs - Export a plan 9 name space across a network
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <libsec.h>
+#include "drawterm.h"
+#define Extern
+#include "exportfs.h"
+
+/* #define QIDPATH	((1LL<<48)-1) */
+#define QIDPATH	((((vlong)1)<<48)-1)
+vlong newqid = 0;
+
+void (*fcalls[256])(Fsrpc*);
+
+/* accounting and debugging counters */
+int	filecnt;
+int	freecnt;
+int	qidcnt;
+int	qfreecnt;
+int	ncollision;
+int	netfd;
+
+int
+exportfs(int fd, int msgsz)
+{
+	char buf[ERRMAX], ebuf[ERRMAX];
+	Fsrpc *r;
+	int n;
+	char *dbfile, *srv, *file;
+	ulong initial;
+
+	fcalls[Tversion] = Xversion;
+	fcalls[Tauth] = Xauth;
+	fcalls[Tflush] = Xflush;
+	fcalls[Tattach] = Xattach;
+	fcalls[Twalk] = Xwalk;
+	fcalls[Topen] = slave;
+	fcalls[Tcreate] = Xcreate;
+	fcalls[Tclunk] = Xclunk;
+	fcalls[Tread] = slave;
+	fcalls[Twrite] = slave;
+	fcalls[Tremove] = Xremove;
+	fcalls[Tstat] = Xstat;
+	fcalls[Twstat] = Xwstat;
+
+	srvfd = -1;
+	netfd = fd;
+	//dbg = 1;
+
+	strcpy(buf, "this is buf");
+	strcpy(ebuf, "this is ebuf");
+	DEBUG(DFD, "exportfs: started\n");
+
+//	rfork(RFNOTEG);
+
+	messagesize = msgsz;
+	if(messagesize == 0){
+		messagesize = iounit(netfd);
+		if(messagesize == 0)
+			messagesize = 8192+IOHDRSZ;
+	}
+
+	Workq = emallocz(sizeof(Fsrpc)*Nr_workbufs);
+//	for(i=0; i<Nr_workbufs; i++)
+//		Workq[i].buf = emallocz(messagesize);
+	fhash = emallocz(sizeof(Fid*)*FHASHSIZE);
+
+	fmtinstall('F', fcallfmt);
+
+	initroot();
+
+	DEBUG(DFD, "exportfs: %s\n", buf);
+
+	/*
+	 * Start serving file requests from the network
+	 */
+	for(;;) {
+		r = getsbuf();
+		if(r == 0)
+			fatal("Out of service buffers");
+			
+		DEBUG(DFD, "read9p...");
+		n = read9pmsg(netfd, r->buf, messagesize);
+		if(n <= 0)
+			fatal(nil);
+
+		if(convM2S(r->buf, n, &r->work) == 0)
+			fatal("convM2S format error");
+
+		DEBUG(DFD, "%F\n", &r->work);
+		(fcalls[r->work.type])(r);
+	}
+}
+
+void
+reply(Fcall *r, Fcall *t, char *err)
+{
+	uchar *data;
+	int m, n;
+
+	t->tag = r->tag;
+	t->fid = r->fid;
+	if(err) {
+		t->type = Rerror;
+		t->ename = err;
+	}
+	else 
+		t->type = r->type + 1;
+
+	DEBUG(DFD, "\t%F\n", t);
+
+	data = malloc(messagesize);	/* not mallocz; no need to clear */
+	if(data == nil)
+		fatal(Enomem);
+	n = convS2M(t, data, messagesize);
+	if((m=write(netfd, data, n))!=n){
+		fprint(2, "wrote %d got %d (%r)\n", n, m);
+		fatal("write");
+	}
+	free(data);
+}
+
+Fid *
+getfid(int nr)
+{
+	Fid *f;
+
+	for(f = fidhash(nr); f; f = f->next)
+		if(f->nr == nr)
+			return f;
+
+	return 0;
+}
+
+int
+freefid(int nr)
+{
+	Fid *f, **l;
+	char buf[128];
+
+	l = &fidhash(nr);
+	for(f = *l; f; f = f->next) {
+		if(f->nr == nr) {
+			if(f->mid) {
+				sprint(buf, "/mnt/exportfs/%d", f->mid);
+				unmount(0, buf);
+				psmap[f->mid] = 0;
+			}
+			if(f->f) {
+				freefile(f->f);
+				f->f = nil;
+			}
+			*l = f->next;
+			f->next = fidfree;
+			fidfree = f;
+			return 1;
+		}
+		l = &f->next;
+	}
+
+	return 0;	
+}
+
+Fid *
+newfid(int nr)
+{
+	Fid *new, **l;
+	int i;
+
+	l = &fidhash(nr);
+	for(new = *l; new; new = new->next)
+		if(new->nr == nr)
+			return 0;
+
+	if(fidfree == 0) {
+		fidfree = emallocz(sizeof(Fid) * Fidchunk);
+
+		for(i = 0; i < Fidchunk-1; i++)
+			fidfree[i].next = &fidfree[i+1];
+
+		fidfree[Fidchunk-1].next = 0;
+	}
+
+	new = fidfree;
+	fidfree = new->next;
+
+	memset(new, 0, sizeof(Fid));
+	new->next = *l;
+	*l = new;
+	new->nr = nr;
+	new->fid = -1;
+	new->mid = 0;
+
+	return new;	
+}
+
+Fsrpc *
+getsbuf(void)
+{
+	static int ap;
+	int look, rounds;
+	Fsrpc *wb;
+	int small_instead_of_fast = 1;
+
+	if(small_instead_of_fast)
+		ap = 0;	/* so we always start looking at the beginning and reuse buffers */
+
+	for(rounds = 0; rounds < 10; rounds++) {
+		for(look = 0; look < Nr_workbufs; look++) {
+			if(++ap == Nr_workbufs)
+				ap = 0;
+			if(Workq[ap].busy == 0)
+				break;
+		}
+
+		if(look == Nr_workbufs){
+			sleep(10 * rounds);
+			continue;
+		}
+
+		wb = &Workq[ap];
+		wb->pid = 0;
+		wb->canint = 0;
+		wb->flushtag = NOTAG;
+		wb->busy = 1;
+		if(wb->buf == nil)	/* allocate buffers dynamically to keep size down */
+			wb->buf = emallocz(messagesize);
+		return wb;
+	}
+	fatal("No more work buffers");
+	return nil;
+}
+
+void
+freefile(File *f)
+{
+	File *parent, *child;
+
+Loop:
+	f->ref--;
+	if(f->ref > 0)
+		return;
+	freecnt++;
+	if(f->ref < 0) abort();
+	DEBUG(DFD, "free %s\n", f->name);
+	/* delete from parent */
+	parent = f->parent;
+	if(parent->child == f)
+		parent->child = f->childlist;
+	else{
+		for(child=parent->child; child->childlist!=f; child=child->childlist)
+			if(child->childlist == nil)
+				fatal("bad child list");
+		child->childlist = f->childlist;
+	}
+	freeqid(f->qidt);
+	free(f->name);
+	f->name = nil;
+	free(f);
+	f = parent;
+	if(f != nil)
+		goto Loop;
+}
+
+File *
+file(File *parent, char *name)
+{
+	Dir *dir;
+	char *path;
+	File *f;
+
+	DEBUG(DFD, "\tfile: 0x%p %s name %s\n", parent, parent->name, name);
+
+	path = makepath(parent, name);
+	dir = dirstat(path);
+	free(path);
+	if(dir == nil)
+		return nil;
+
+	for(f = parent->child; f; f = f->childlist)
+		if(strcmp(name, f->name) == 0)
+			break;
+
+	if(f == nil){
+		f = emallocz(sizeof(File));
+		f->name = estrdup(name);
+
+		f->parent = parent;
+		f->childlist = parent->child;
+		parent->child = f;
+		parent->ref++;
+		f->ref = 0;
+		filecnt++;
+	}
+	f->ref++;
+	f->qid.type = dir->qid.type;
+	f->qid.vers = dir->qid.vers;
+	f->qidt = uniqueqid(dir);
+	f->qid.path = f->qidt->uniqpath;
+
+	f->inval = 0;
+
+	free(dir);
+
+	return f;
+}
+
+void
+initroot(void)
+{
+	Dir *dir;
+
+	root = emallocz(sizeof(File));
+	root->name = estrdup(".");
+
+	dir = dirstat(root->name);
+	if(dir == nil)
+		fatal("root stat");
+
+	root->ref = 1;
+	root->qid.vers = dir->qid.vers;
+	root->qidt = uniqueqid(dir);
+	root->qid.path = root->qidt->uniqpath;
+	root->qid.type = QTDIR;
+	free(dir);
+
+	psmpt = emallocz(sizeof(File));
+	psmpt->name = estrdup("/");
+
+	dir = dirstat(psmpt->name);
+	if(dir == nil)
+		return;
+
+	psmpt->ref = 1;
+	psmpt->qid.vers = dir->qid.vers;
+	psmpt->qidt = uniqueqid(dir);
+	psmpt->qid.path = psmpt->qidt->uniqpath;
+	free(dir);
+
+	psmpt = file(psmpt, "mnt");
+	if(psmpt == 0)
+		return;
+	psmpt = file(psmpt, "exportfs");
+}
+
+char*
+makepath(File *p, char *name)
+{
+	int i, n;
+	char *c, *s, *path, *seg[256];
+
+	seg[0] = name;
+	n = strlen(name)+2;
+	for(i = 1; i < 256 && p; i++, p = p->parent){
+		seg[i] = p->name;
+		n += strlen(p->name)+1;
+	}
+	path = malloc(n);
+	if(path == nil)
+		fatal("out of memory");
+	s = path;
+
+	while(i--) {
+		for(c = seg[i]; *c; c++)
+			*s++ = *c;
+		*s++ = '/';
+	}
+	while(s[-1] == '/')
+		s--;
+	*s = '\0';
+
+	return path;
+}
+
+int
+qidhash(vlong path)
+{
+	int h, n;
+
+	h = 0;
+	for(n=0; n<64; n+=Nqidbits){
+		h ^= path;
+		path >>= Nqidbits;
+	}
+	return h & (Nqidtab-1);
+}
+
+void
+freeqid(Qidtab *q)
+{
+	ulong h;
+	Qidtab *l;
+
+	q->ref--;
+	if(q->ref > 0)
+		return;
+	qfreecnt++;
+	h = qidhash(q->path);
+	if(qidtab[h] == q)
+		qidtab[h] = q->next;
+	else{
+		for(l=qidtab[h]; l->next!=q; l=l->next)
+			if(l->next == nil)
+				fatal("bad qid list");
+		l->next = q->next;
+	}
+	free(q);
+}
+
+Qidtab*
+qidlookup(Dir *d)
+{
+	ulong h;
+	Qidtab *q;
+
+	h = qidhash(d->qid.path);
+	for(q=qidtab[h]; q!=nil; q=q->next)
+		if(q->type==d->type && q->dev==d->dev && q->path==d->qid.path)
+			return q;
+	return nil;
+}
+
+int
+qidexists(vlong path)
+{
+	int h;
+	Qidtab *q;
+
+	for(h=0; h<Nqidtab; h++)
+		for(q=qidtab[h]; q!=nil; q=q->next)
+			if(q->uniqpath == path)
+				return 1;
+	return 0;
+}
+
+Qidtab*
+uniqueqid(Dir *d)
+{
+	ulong h;
+	vlong path;
+	Qidtab *q;
+
+	q = qidlookup(d);
+	if(q != nil){
+		q->ref++;
+		return q;
+	}
+	path = d->qid.path;
+	while(qidexists(path)){
+		DEBUG(DFD, "collision on %s\n", d->name);
+		/* collision: find a new one */
+		ncollision++;
+		path &= QIDPATH;
+		++newqid;
+		if(newqid >= (1<<16)){
+			DEBUG(DFD, "collision wraparound\n");
+			newqid = 1;
+		}
+		path |= newqid<<48;
+		DEBUG(DFD, "assign qid %.16llux\n", path);
+	}
+	q = mallocz(sizeof(Qidtab), 1);
+	if(q == nil)
+		fatal("no memory for qid table");
+	qidcnt++;
+	q->ref = 1;
+	q->type = d->type;
+	q->dev = d->dev;
+	q->path = d->qid.path;
+	q->uniqpath = path;
+	h = qidhash(d->qid.path);
+	q->next = qidtab[h];
+	qidtab[h] = q;
+	return q;
+}
+
+void
+fatal(char *s, ...)
+{
+	char buf[ERRMAX];
+	va_list arg;
+	Proc *m;
+
+	if (s) {
+		va_start(arg, s);
+		vsnprint(buf, ERRMAX, s, arg);
+		va_end(arg);
+	}
+
+	/* Clear away the slave children */
+//	for(m = Proclist; m; m = m->next)
+//		postnote(PNPROC, m->pid, "kill");
+
+	DEBUG(DFD, "%s\n", buf);
+	if (s) 
+		sysfatal(buf);
+	else
+		exits(nil);
+}
+
--- /dev/null
+++ b/exportfs/exportfs.h
@@ -1,0 +1,148 @@
+/*
+ * exportfs.h - definitions for exporting file server
+ */
+
+#define DEBUG		if(!dbg){}else fprint
+#define DFD		2
+#define fidhash(s)	fhash[s%FHASHSIZE]
+
+#define Proc	Exproc
+
+
+typedef struct Fsrpc Fsrpc;
+typedef struct Fid Fid;
+typedef struct File File;
+typedef struct Proc Proc;
+typedef struct Qidtab Qidtab;
+
+struct Fsrpc
+{
+	int	busy;		/* Work buffer has pending rpc to service */
+	int	pid;		/* Pid of slave process executing the rpc */
+	int	canint;		/* Interrupt gate */
+	int	flushtag;	/* Tag on which to reply to flush */
+	Fcall work;		/* Plan 9 incoming Fcall */
+	uchar	*buf;	/* Data buffer */
+};
+
+struct Fid
+{
+	int	fid;		/* system fd for i/o */
+	File	*f;		/* File attached to this fid */
+	int	mode;
+	int	nr;		/* fid number */
+	int	mid;		/* Mount id */
+	Fid	*next;		/* hash link */
+};
+
+struct File
+{
+	char	*name;
+	int	ref;
+	Qid	qid;
+	Qidtab	*qidt;
+	int	inval;
+	File	*parent;
+	File	*child;
+	File	*childlist;
+};
+
+struct Proc
+{
+	int	pid;
+	int	busy;
+	Proc	*next;
+};
+
+struct Qidtab
+{
+	int	ref;
+	int	type;
+	int	dev;
+	vlong	path;
+	vlong	uniqpath;
+	Qidtab	*next;
+};
+
+enum
+{
+	MAXPROC		= 50,
+	FHASHSIZE	= 64,
+	Nr_workbufs 	= 50,
+	Fidchunk	= 1000,
+	Npsmpt		= 32,
+	Nqidbits		= 5,
+	Nqidtab		= (1<<Nqidbits),
+};
+
+#define Enomem Exenomem
+#define Ebadfix Exebadfid
+#define Enotdir Exenotdir
+#define Edupfid Exedupfid
+#define Eopen Exeopen
+#define Exmnt Exexmnt
+#define Emip Exemip
+#define Enopsmt Exenopsmt
+
+extern char Ebadfid[];
+extern char Enotdir[];
+extern char Edupfid[];
+extern char Eopen[];
+extern char Exmnt[];
+extern char Enomem[];
+extern char Emip[];
+extern char Enopsmt[];
+
+Extern Fsrpc	*Workq;
+Extern int  	dbg;
+Extern File	*root;
+Extern File	*psmpt;
+Extern Fid	**fhash;
+Extern Fid	*fidfree;
+Extern Proc	*Proclist;
+Extern char	psmap[Npsmpt];
+Extern Qidtab	*qidtab[Nqidtab];
+Extern ulong	messagesize;
+Extern int		srvfd;
+
+/* File system protocol service procedures */
+void Xattach(Fsrpc*);
+void Xauth(Fsrpc*);
+void Xclunk(Fsrpc*); 
+void Xcreate(Fsrpc*);
+void Xflush(Fsrpc*); 
+void Xnop(Fsrpc*);
+void Xremove(Fsrpc*);
+void Xstat(Fsrpc*);
+void Xversion(Fsrpc*);
+void Xwalk(Fsrpc*);
+void Xwstat(Fsrpc*);
+void slave(Fsrpc*);
+
+void	reply(Fcall*, Fcall*, char*);
+Fid 	*getfid(int);
+int	freefid(int);
+Fid	*newfid(int);
+Fsrpc	*getsbuf(void);
+void	initroot(void);
+void	fatal(char*, ...);
+char*	makepath(File*, char*);
+File	*file(File*, char*);
+void	freefile(File*);
+void	slaveopen(Fsrpc*);
+void	slaveread(Fsrpc*);
+void	slavewrite(Fsrpc*);
+void	blockingslave(void*);
+void	reopen(Fid *f);
+void	noteproc(int, char*);
+void	flushaction(void*, char*);
+void	pushfcall(char*);
+Qidtab* uniqueqid(Dir*);
+void	freeqid(Qidtab*);
+char*	estrdup(char*);
+void*	emallocz(uint);
+int		readmessage(int, char*, int);
+
+#define notify
+#define noted
+#define exits
--- /dev/null
+++ b/exportfs/exportsrv.c
@@ -1,0 +1,679 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#define Extern	extern
+#include "exportfs.h"
+
+char Ebadfid[] = "Bad fid";
+char Enotdir[] = "Not a directory";
+char Edupfid[] = "Fid already in use";
+char Eopen[] = "Fid already opened";
+char Exmnt[] = "Cannot .. past mount point";
+char Emip[] = "Mount in progress";
+char Enopsmt[] = "Out of pseudo mount points";
+char Enomem[] = "No memory";
+char Eversion[] = "Bad 9P2000 version";
+
+int iounit(int x)
+{
+	return 8192+IOHDRSZ;
+}
+
+void*
+emallocz(ulong n)
+{
+	void *v;
+
+	v = mallocz(n, 1);
+	if(v == nil)
+		panic("out of memory");
+	return v;
+}
+
+ulong messagesize;
+
+void
+Xversion(Fsrpc *t)
+{
+	Fcall rhdr;
+
+	if(t->work.msize > messagesize)
+		t->work.msize = messagesize;
+	messagesize = t->work.msize;
+	if(strncmp(t->work.version, "9P2000", 6) != 0){
+		reply(&t->work, &rhdr, Eversion);
+		return;
+	}
+	rhdr.version = "9P2000";
+	rhdr.msize = t->work.msize;
+	reply(&t->work, &rhdr, 0);
+	t->busy = 0;
+}
+
+void
+Xauth(Fsrpc *t)
+{
+	Fcall rhdr;
+
+	reply(&t->work, &rhdr, "exportfs: authentication not required");
+	t->busy = 0;
+}
+
+void
+Xflush(Fsrpc *t)
+{
+	Fsrpc *w, *e;
+	Fcall rhdr;
+
+	e = &Workq[Nr_workbufs];
+
+	for(w = Workq; w < e; w++) {
+		if(w->work.tag == t->work.oldtag) {
+			DEBUG(DFD, "\tQ busy %d pid %d can %d\n", w->busy, w->pid, w->canint);
+			if(w->busy && w->pid) {
+				w->flushtag = t->work.tag;
+				DEBUG(DFD, "\tset flushtag %d\n", t->work.tag);
+			//	if(w->canint)
+			//		postnote(PNPROC, w->pid, "flush");
+				t->busy = 0;
+				return;
+			}
+		}
+	}
+
+	reply(&t->work, &rhdr, 0);
+	DEBUG(DFD, "\tflush reply\n");
+	t->busy = 0;
+}
+
+void
+Xattach(Fsrpc *t)
+{
+	int i, nfd;
+	Fcall rhdr;
+	Fid *f;
+	char buf[128];
+
+	f = newfid(t->work.fid);
+	if(f == 0) {
+		reply(&t->work, &rhdr, Ebadfid);
+		t->busy = 0;
+		return;
+	}
+
+	if(srvfd >= 0){
+/*
+		if(psmpt == 0){
+		Nomount:
+			reply(&t->work, &rhdr, Enopsmt);
+			t->busy = 0;
+			freefid(t->work.fid);
+			return;
+		}
+		for(i=0; i<Npsmpt; i++)
+			if(psmap[i] == 0)
+				break;
+		if(i >= Npsmpt)
+			goto Nomount;
+		sprint(buf, "%d", i);
+		f->f = file(psmpt, buf);
+		if(f->f == nil)
+			goto Nomount;
+		sprint(buf, "/mnt/exportfs/%d", i);
+		nfd = dup(srvfd, -1);
+		if(amount(nfd, buf, MREPL|MCREATE, t->work.aname) < 0){
+			errstr(buf, sizeof buf);
+			reply(&t->work, &rhdr, buf);
+			t->busy = 0;
+			freefid(t->work.fid);
+			close(nfd);
+			return;
+		}
+		psmap[i] = 1;
+		f->mid = i;
+*/
+	}else{
+		f->f = root;
+		f->f->ref++;
+	}
+
+	rhdr.qid = f->f->qid;
+	reply(&t->work, &rhdr, 0);
+	t->busy = 0;
+}
+
+Fid*
+clonefid(Fid *f, int new)
+{
+	Fid *n;
+
+	n = newfid(new);
+	if(n == 0) {
+		n = getfid(new);
+		if(n == 0)
+			fatal("inconsistent fids");
+		if(n->fid >= 0)
+			close(n->fid);
+		freefid(new);
+		n = newfid(new);
+		if(n == 0)
+			fatal("inconsistent fids2");
+	}
+	n->f = f->f;
+	n->f->ref++;
+	return n;
+}
+
+void
+Xwalk(Fsrpc *t)
+{
+	char err[ERRMAX], *e;
+	Fcall rhdr;
+	Fid *f, *nf;
+	File *wf;
+	int i;
+
+	f = getfid(t->work.fid);
+	if(f == 0) {
+		reply(&t->work, &rhdr, Ebadfid);
+		t->busy = 0;
+		return;
+	}
+
+	nf = nil;
+	if(t->work.newfid != t->work.fid){
+		nf = clonefid(f, t->work.newfid);
+		f = nf;
+	}
+
+	rhdr.nwqid = 0;
+	e = nil;
+	for(i=0; i<t->work.nwname; i++){
+		if(i == MAXWELEM){
+			e = "Too many path elements";
+			break;
+		}
+
+		if(strcmp(t->work.wname[i], "..") == 0) {
+			if(f->f->parent == nil) {
+				e = Exmnt;
+				break;
+			}
+			wf = f->f->parent;
+			wf->ref++;
+			goto Accept;
+		}
+	
+		wf = file(f->f, t->work.wname[i]);
+		if(wf == 0){
+			errstr(err, sizeof err);
+			e = err;
+			break;
+		}
+    Accept:
+		freefile(f->f);
+		rhdr.wqid[rhdr.nwqid++] = wf->qid;
+		f->f = wf;
+		continue;
+	}
+
+	if(nf!=nil && (e!=nil || rhdr.nwqid!=t->work.nwname))
+		freefid(t->work.newfid);
+	if(rhdr.nwqid > 0)
+		e = nil;
+	reply(&t->work, &rhdr, e);
+	t->busy = 0;
+}
+
+void
+Xclunk(Fsrpc *t)
+{
+	Fcall rhdr;
+	Fid *f;
+
+	f = getfid(t->work.fid);
+	if(f == 0) {
+		reply(&t->work, &rhdr, Ebadfid);
+		t->busy = 0;
+		return;
+	}
+
+	if(f->fid >= 0)
+		close(f->fid);
+
+	freefid(t->work.fid);
+	reply(&t->work, &rhdr, 0);
+	t->busy = 0;
+}
+
+void
+Xstat(Fsrpc *t)
+{
+	char err[ERRMAX], *path;
+	Fcall rhdr;
+	Fid *f;
+	Dir *d;
+	int s;
+	uchar *statbuf;
+
+	f = getfid(t->work.fid);
+	if(f == 0) {
+		reply(&t->work, &rhdr, Ebadfid);
+		t->busy = 0;
+		return;
+	}
+	if(f->fid >= 0)
+		d = dirfstat(f->fid);
+	else {
+		path = makepath(f->f, "");
+		d = dirstat(path);
+		free(path);
+	}
+
+	if(d == nil) {
+		errstr(err, sizeof err);
+		reply(&t->work, &rhdr, err);
+		t->busy = 0;
+		return;
+	}
+
+	d->qid.path = f->f->qidt->uniqpath;
+	s = sizeD2M(d);
+	statbuf = emallocz(s);
+	s = convD2M(d, statbuf, s);
+	free(d);
+	rhdr.nstat = s;
+	rhdr.stat = statbuf;
+	reply(&t->work, &rhdr, 0);
+	free(statbuf);
+	t->busy = 0;
+}
+
+static int
+getiounit(int fd)
+{
+	int n;
+
+	n = iounit(fd);
+	if(n > messagesize-IOHDRSZ)
+		n = messagesize-IOHDRSZ;
+	return n;
+}
+
+void
+Xcreate(Fsrpc *t)
+{
+	char err[ERRMAX], *path;
+	Fcall rhdr;
+	Fid *f;
+	File *nf;
+
+	f = getfid(t->work.fid);
+	if(f == 0) {
+		reply(&t->work, &rhdr, Ebadfid);
+		t->busy = 0;
+		return;
+	}
+	
+
+	path = makepath(f->f, t->work.name);
+	f->fid = create(path, t->work.mode, t->work.perm);
+	free(path);
+	if(f->fid < 0) {
+		errstr(err, sizeof err);
+		reply(&t->work, &rhdr, err);
+		t->busy = 0;
+		return;
+	}
+
+	nf = file(f->f, t->work.name);
+	if(nf == 0) {
+		errstr(err, sizeof err);
+		reply(&t->work, &rhdr, err);
+		t->busy = 0;
+		return;
+	}
+
+	f->mode = t->work.mode;
+	freefile(f->f);
+	f->f = nf;
+	rhdr.qid = f->f->qid;
+	rhdr.iounit = getiounit(f->fid);
+	reply(&t->work, &rhdr, 0);
+	t->busy = 0;
+}
+
+void
+Xremove(Fsrpc *t)
+{
+	char err[ERRMAX], *path;
+	Fcall rhdr;
+	Fid *f;
+
+	f = getfid(t->work.fid);
+	if(f == 0) {
+		reply(&t->work, &rhdr, Ebadfid);
+		t->busy = 0;
+		return;
+	}
+
+	path = makepath(f->f, "");
+	DEBUG(DFD, "\tremove: %s\n", path);
+	if(remove(path) < 0) {
+		free(path);
+		errstr(err, sizeof err);
+		reply(&t->work, &rhdr, err);
+		t->busy = 0;
+		return;
+	}
+	free(path);
+
+	f->f->inval = 1;
+	if(f->fid >= 0)
+		close(f->fid);
+	freefid(t->work.fid);
+
+	reply(&t->work, &rhdr, 0);
+	t->busy = 0;
+}
+
+void
+Xwstat(Fsrpc *t)
+{
+	char err[ERRMAX], *path;
+	Fcall rhdr;
+	Fid *f;
+	int s;
+	char *strings;
+	Dir d;
+
+	f = getfid(t->work.fid);
+	if(f == 0) {
+		reply(&t->work, &rhdr, Ebadfid);
+		t->busy = 0;
+		return;
+	}
+	strings = emallocz(t->work.nstat);	/* ample */
+	if(convM2D(t->work.stat, t->work.nstat, &d, strings) < 0){
+		rerrstr(err, sizeof err);
+		reply(&t->work, &rhdr, err);
+		t->busy = 0;
+		free(strings);
+		return;
+	}
+
+	if(f->fid >= 0)
+		s = dirfwstat(f->fid, &d);
+	else {
+		path = makepath(f->f, "");
+		s = dirwstat(path, &d);
+		free(path);
+	}
+	if(s < 0) {
+		rerrstr(err, sizeof err);
+		reply(&t->work, &rhdr, err);
+	}
+	else {
+		/* wstat may really be rename */
+		if(strcmp(d.name, f->f->name)!=0 && strcmp(d.name, "")!=0){
+			free(f->f->name);
+			f->f->name = estrdup(d.name);
+		}
+		reply(&t->work, &rhdr, 0);
+	}
+	free(strings);
+	t->busy = 0;
+}
+
+void
+slave(Fsrpc *f)
+{
+	Proc *p;
+	int pid;
+	static int nproc;
+
+	for(;;) {
+		for(p = Proclist; p; p = p->next) {
+			if(p->busy == 0) {
+				f->pid = p->pid;
+				p->busy = 1;
+				pid = rendezvous(p->pid, (ulong)f);
+				if(pid != p->pid)
+					fatal("rendezvous sync fail");
+				return;
+			}	
+		}
+
+		if(++nproc > MAXPROC)
+			fatal("too many procs");
+
+		pid = kproc("slave", blockingslave, nil);
+		DEBUG(DFD, "slave pid %d\n", pid);
+		if(pid == -1)
+			fatal("kproc");
+
+		p = malloc(sizeof(Proc));
+		if(p == 0)
+			fatal("out of memory");
+
+		p->busy = 0;
+		p->pid = pid;
+		p->next = Proclist;
+		Proclist = p;
+
+DEBUG(DFD, "parent %d rendez\n", pid);
+		rendezvous(pid, (ulong)p);
+DEBUG(DFD, "parent %d went\n", pid);
+	}
+}
+
+void
+blockingslave(void *x)
+{
+	Fsrpc *p;
+	Fcall rhdr;
+	Proc *m;
+	int pid;
+
+	USED(x);
+
+	notify(flushaction);
+
+	pid = getpid();
+
+DEBUG(DFD, "blockingslave %d rendez\n", pid);
+	m = (Proc*)rendezvous(pid, 0);
+DEBUG(DFD, "blockingslave %d rendez got %p\n", pid, m);
+	
+	for(;;) {
+		p = (Fsrpc*)rendezvous(pid, pid);
+		if((int)p == ~0)			/* Interrupted */
+			continue;
+
+		DEBUG(DFD, "\tslave: %d %F b %d p %d\n", pid, &p->work, p->busy, p->pid);
+		if(p->flushtag != NOTAG)
+			goto flushme;
+
+		switch(p->work.type) {
+		case Tread:
+			slaveread(p);
+			break;
+
+		case Twrite:
+			slavewrite(p);
+			break;
+
+		case Topen:
+			slaveopen(p);
+			break;
+
+		default:
+			reply(&p->work, &rhdr, "exportfs: slave type error");
+		}
+		if(p->flushtag != NOTAG) {
+flushme:
+			p->work.type = Tflush;
+			p->work.tag = p->flushtag;
+			reply(&p->work, &rhdr, 0);
+		}
+		p->busy = 0;
+		m->busy = 0;
+	}
+}
+
+int
+openmount(int sfd)
+{
+	werrstr("openmount not implemented");
+	return -1;
+}
+
+void
+slaveopen(Fsrpc *p)
+{
+	char err[ERRMAX], *path;
+	Fcall *work, rhdr;
+	Fid *f;
+	Dir *d;
+
+	work = &p->work;
+
+	f = getfid(work->fid);
+	if(f == 0) {
+		reply(work, &rhdr, Ebadfid);
+		return;
+	}
+	if(f->fid >= 0) {
+		close(f->fid);
+		f->fid = -1;
+	}
+	
+	path = makepath(f->f, "");
+	DEBUG(DFD, "\topen: %s %d\n", path, work->mode);
+
+	p->canint = 1;
+	if(p->flushtag != NOTAG){
+		free(path);
+		return;
+	}
+	/* There is a race here I ignore because there are no locks */
+	f->fid = open(path, work->mode);
+	free(path);
+	p->canint = 0;
+	if(f->fid < 0 || (d = dirfstat(f->fid)) == nil) {
+	Error:
+		errstr(err, sizeof err);
+		reply(work, &rhdr, err);
+		return;
+	}
+	f->f->qid = d->qid;
+	free(d);
+	if(f->f->qid.type & QTMOUNT){	/* fork new exportfs for this */
+		f->fid = openmount(f->fid);
+		if(f->fid < 0)
+			goto Error;
+	}
+
+	DEBUG(DFD, "\topen: fd %d\n", f->fid);
+	f->mode = work->mode;
+	rhdr.iounit = getiounit(f->fid);
+	rhdr.qid = f->f->qid;
+	reply(work, &rhdr, 0);
+}
+
+void
+slaveread(Fsrpc *p)
+{
+	Fid *f;
+	int n, r;
+	Fcall *work, rhdr;
+	char *data, err[ERRMAX];
+
+	work = &p->work;
+
+	f = getfid(work->fid);
+	if(f == 0) {
+		reply(work, &rhdr, Ebadfid);
+		return;
+	}
+
+	n = (work->count > messagesize-IOHDRSZ) ? messagesize-IOHDRSZ : work->count;
+	p->canint = 1;
+	if(p->flushtag != NOTAG)
+		return;
+	data = malloc(n);
+	if(data == nil)
+		fatal(Enomem);
+
+	/* can't just call pread, since directories must update the offset */
+	r = pread(f->fid, data, n, work->offset);
+	p->canint = 0;
+	if(r < 0) {
+		free(data);
+		errstr(err, sizeof err);
+		reply(work, &rhdr, err);
+		return;
+	}
+
+	DEBUG(DFD, "\tread: fd=%d %d bytes\n", f->fid, r);
+
+	rhdr.data = data;
+	rhdr.count = r;
+	reply(work, &rhdr, 0);
+	free(data);
+}
+
+void
+slavewrite(Fsrpc *p)
+{
+	char err[ERRMAX];
+	Fcall *work, rhdr;
+	Fid *f;
+	int n;
+
+	work = &p->work;
+
+	f = getfid(work->fid);
+	if(f == 0) {
+		reply(work, &rhdr, Ebadfid);
+		return;
+	}
+
+	n = (work->count > messagesize-IOHDRSZ) ? messagesize-IOHDRSZ : work->count;
+	p->canint = 1;
+	if(p->flushtag != NOTAG)
+		return;
+	n = pwrite(f->fid, work->data, n, work->offset);
+	p->canint = 0;
+	if(n < 0) {
+		errstr(err, sizeof err);
+		reply(work, &rhdr, err);
+		return;
+	}
+
+	DEBUG(DFD, "\twrite: %d bytes fd=%d\n", n, f->fid);
+
+	rhdr.count = n;
+	reply(work, &rhdr, 0);
+}
+
+void
+reopen(Fid *f)
+{
+	USED(f);
+	fatal("reopen");
+}
+
+void
+flushaction(void *a, char *cause)
+{
+	USED(a);
+	if(strncmp(cause, "sys:", 4) == 0 && !strstr(cause, "pipe")) {
+		fprint(2, "exportsrv: note: %s\n", cause);
+		exits("noted");
+	}
+	if(strncmp(cause, "kill", 4) == 0)
+		noted(NDFLT);
+
+	noted(NCONT);
+}
--- /dev/null
+++ b/exportfs/mkfile
@@ -1,0 +1,11 @@
+<$DSRC/mkfile-$CONF
+TARG=libexportfs.$L
+
+OFILES=\
+	exportfs.$O\
+	exportsrv.$O
+
+HFILES=\
+	exportfs.h
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/gui-win32/alloc.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memimage*
+allocmemimage(Rectangle r, ulong chan)
+{
+	return _allocmemimage(r, chan);
+}
+
+void
+freememimage(Memimage *i)
+{
+	_freememimage(i);
+}
+
+void
+memfillcolor(Memimage *i, ulong val)
+{
+	_memfillcolor(i, val);
+}
+
--- /dev/null
+++ b/gui-win32/cload.c
@@ -1,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	return _cloadmemimage(i, r, data, ndata);
+}
--- /dev/null
+++ b/gui-win32/draw.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+void
+memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point sp, Memimage *mask, Point mp, int op)
+{
+	_memimagedraw(_memimagedrawsetup(dst, r, src, sp, mask, mp, op));
+}
+
+ulong
+pixelbits(Memimage *m, Point p)
+{
+	return _pixelbits(m, p);
+}
+
+void
+memimageinit(void)
+{
+	_memimageinit();
+}
--- /dev/null
+++ b/gui-win32/load.c
@@ -1,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	return _loadmemimage(i, r, data, ndata);
+}
--- /dev/null
+++ b/gui-win32/mkfile
@@ -1,0 +1,14 @@
+<$DSRC/mkfile-$CONF
+TARG=libgui.$L
+
+OFILES=\
+	alloc.$O\
+	cload.$O\
+	draw.$O\
+	load.$O\
+	screen.$O\
+	wstrtoutf.$O
+
+HFILES=\
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/gui-win32/screen.c
@@ -1,0 +1,642 @@
+#include	<windows.h>
+// #include	"winduhz.h"
+
+#undef Rectangle
+#define Rectangle _Rectangle
+
+#include <u.h>
+#include <libc.h>
+#include <dat.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "error.h"
+#include "screen.h"
+#include "keyboard.h"
+#include "fns.h"
+
+Memimage	*gscreen;
+Screeninfo	screen;
+
+extern int mousequeue;
+static int depth;
+
+static	HINSTANCE	inst;
+static	HWND		window;
+static	HPALETTE	palette;
+static	LOGPALETTE	*logpal;
+static  Lock		gdilock;
+static 	BITMAPINFO	*bmi;
+static	HCURSOR		hcursor;
+
+static void	winproc(void *);
+static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+static void	paletteinit(void);
+static void	bmiinit(void);
+static void	screenload2(Rectangle r, int ldepth, uchar *p, Point pt, int step);
+
+static int readybit;
+static Rendez	rend;
+
+Point	ZP;
+
+static
+isready(void*a)
+{
+	return readybit;
+}
+
+void
+screeninit(void)
+{
+	int fmt;
+	int dx, dy;
+
+	memimageinit();
+	if(depth == 0)
+		depth = GetDeviceCaps(GetDC(NULL), BITSPIXEL);
+	switch(depth){
+	case 32:
+		screen.dibtype = DIB_RGB_COLORS;
+		screen.depth = 32;
+		fmt = XRGB32;
+		break;
+	case 24:
+		screen.dibtype = DIB_RGB_COLORS;
+		screen.depth = 24;
+		fmt = RGB24;
+		break;
+	case 16:
+		screen.dibtype = DIB_RGB_COLORS;
+		screen.depth = 16;
+		fmt = RGB15;	/* [sic] */
+		break;
+	case 8:
+	default:
+		screen.dibtype = DIB_PAL_COLORS;
+		screen.depth = 8;
+		depth = 8;
+		fmt = CMAP8;
+		break;
+	}
+	dx = GetDeviceCaps(GetDC(NULL), HORZRES);
+	dy = GetDeviceCaps(GetDC(NULL), VERTRES);
+
+	gscreen = allocmemimage(Rect(0,0,dx,dy), fmt);
+	kproc("winscreen", winproc, 0);
+	sleep(&rend, isready, 0);
+}
+
+uchar*
+attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen, void **X)
+{
+	*r = gscreen->r;
+	*chan = gscreen->chan;
+	*depth = gscreen->depth;
+	*width = gscreen->width;
+	*softscreen = 1;
+
+	return gscreen->data->bdata;
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+	screenload(r, gscreen->depth, byteaddr(gscreen, ZP), ZP,
+		gscreen->width*sizeof(ulong));
+//	Sleep(100);
+}
+
+void
+screenload(Rectangle r, int depth, uchar *p, Point pt, int step)
+{
+	int dx, dy, delx;
+	HDC hdc;
+	RECT winr;
+
+	if(depth != gscreen->depth)
+		panic("screenload: bad ldepth");
+
+	/*
+	 * Sometimes we do get rectangles that are off the
+	 * screen to the negative axes, for example, when
+	 * dragging around a window border in a Move operation.
+	 */
+	if(rectclip(&r, gscreen->r) == 0)
+		return;
+
+	if(step&3 != 0 || ((pt.x*depth)%32) != 0 || (ulong)p&3 != 0)
+		panic("screenload: bad params %d %d %ux", step, pt.x, p);
+	dx = r.max.x - r.min.x;
+	dy = r.max.y - r.min.y;
+
+	if(dx <= 0 || dy <= 0)
+		return;
+
+	if(depth == 24)
+		delx = r.min.x % 4;
+	else
+		delx = r.min.x & (31/depth);
+
+	p += (r.min.y-pt.y)*step;
+	p += ((r.min.x-delx-pt.x)*depth)>>3;
+
+	if(GetWindowRect(window, &winr)==0)
+		return;
+	if(rectclip(&r, Rect(0, 0, winr.right-winr.left, winr.bottom-winr.top))==0)
+		return;
+	
+	lock(&gdilock);
+
+	hdc = GetDC(window);
+	SelectPalette(hdc, palette, 0);
+	RealizePalette(hdc);
+
+//FillRect(hdc,(void*)&r, GetStockObject(BLACK_BRUSH));
+//GdiFlush();
+//Sleep(100);
+
+	bmi->bmiHeader.biWidth = (step*8)/depth;
+	bmi->bmiHeader.biHeight = -dy;	/* - => origin upper left */
+
+	StretchDIBits(hdc, r.min.x, r.min.y, dx, dy,
+		delx, 0, dx, dy, p, bmi, screen.dibtype, SRCCOPY);
+
+	ReleaseDC(window, hdc);
+
+	GdiFlush();
+ 
+	unlock(&gdilock);
+}
+
+static void
+winproc(void *a)
+{
+	WNDCLASS wc;
+	MSG msg;
+
+	inst = GetModuleHandle(NULL);
+
+	paletteinit();
+	bmiinit();
+	terminit();
+
+	wc.style = 0;
+	wc.lpfnWndProc = WindowProc;
+	wc.cbClsExtra = 0;
+	wc.cbWndExtra = 0;
+	wc.hInstance = inst;
+	wc.hIcon = LoadIcon(inst, NULL);
+	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+	wc.hbrBackground = GetStockObject(WHITE_BRUSH);
+	wc.lpszMenuName = 0;
+	wc.lpszClassName = "9pmgraphics";
+	RegisterClass(&wc);
+
+	window = CreateWindowEx(
+		0,			/* extended style */
+		"9pmgraphics",		/* class */
+		"drawterm screen",		/* caption */
+		WS_OVERLAPPEDWINDOW,    /* style */
+		CW_USEDEFAULT,		/* init. x pos */
+		CW_USEDEFAULT,		/* init. y pos */
+		CW_USEDEFAULT,		/* init. x size */
+		CW_USEDEFAULT,		/* init. y size */
+		NULL,			/* parent window (actually owner window for overlapped)*/
+		NULL,			/* menu handle */
+		inst,			/* program handle */
+		NULL			/* create parms */
+		);
+
+	if(window == nil)
+		panic("can't make window\n");
+
+	ShowWindow(window, SW_SHOWDEFAULT);
+	UpdateWindow(window);
+
+	readybit = 1;
+	wakeup(&rend);
+
+	screen.reshaped = 0;
+
+	while(GetMessage(&msg, NULL, 0, 0)) {
+		TranslateMessage(&msg);
+		DispatchMessage(&msg);
+	}
+//	MessageBox(0, "winproc", "exits", MB_OK);
+	ExitProcess(0);
+}
+
+int
+col(int v, int n)
+{
+	int i, c;
+
+	c = 0;
+	for(i = 0; i < 8; i += n)
+		c |= v << (16-(n+i));
+	return c >> 8;
+}
+
+
+void
+paletteinit(void)
+{
+	PALETTEENTRY *pal;
+	int r, g, b, cr, cg, cb, v;
+	int num, den;
+	int i, j;
+
+	logpal = mallocz(sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY), 1);
+	if(logpal == nil)
+		panic("out of memory");
+	logpal->palVersion = 0x300;
+	logpal->palNumEntries = 256;
+	pal = logpal->palPalEntry;
+
+	for(r=0,i=0; r<4; r++) {
+		for(v=0; v<4; v++,i+=16){
+			for(g=0,j=v-r; g<4; g++) {
+				for(b=0; b<4; b++,j++){
+					den=r;
+					if(g>den)
+						den=g;
+					if(b>den)
+						den=b;
+					/* divide check -- pick grey shades */
+					if(den==0)
+						cr=cg=cb=v*17;
+					else{
+						num=17*(4*den+v);
+						cr=r*num/den;
+						cg=g*num/den;
+						cb=b*num/den;
+					}
+					pal[i+(j&15)].peRed = cr;
+					pal[i+(j&15)].peGreen = cg;
+					pal[i+(j&15)].peBlue = cb;
+					pal[i+(j&15)].peFlags = 0;
+				}
+			}
+		}
+	}
+	palette = CreatePalette(logpal);
+}
+
+
+void
+getcolor(ulong i, ulong *r, ulong *g, ulong *b)
+{
+	PALETTEENTRY *pal;
+
+	pal = logpal->palPalEntry;
+	*r = pal[i].peRed;
+	*g = pal[i].peGreen;
+	*b = pal[i].peBlue;
+}
+
+void
+bmiinit(void)
+{
+	ushort *p;
+	int i;
+
+	bmi = mallocz(sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD), 1);
+	if(bmi == 0)
+		panic("out of memory");
+	bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+	bmi->bmiHeader.biWidth = 0;
+	bmi->bmiHeader.biHeight = 0;	/* - => origin upper left */
+	bmi->bmiHeader.biPlanes = 1;
+	bmi->bmiHeader.biBitCount = depth;
+	bmi->bmiHeader.biCompression = BI_RGB;
+	bmi->bmiHeader.biSizeImage = 0;
+	bmi->bmiHeader.biXPelsPerMeter = 0;
+	bmi->bmiHeader.biYPelsPerMeter = 0;
+	bmi->bmiHeader.biClrUsed = 0;
+	bmi->bmiHeader.biClrImportant = 0;	/* number of important colors: 0 means all */
+
+	p = (ushort*)bmi->bmiColors;
+	for(i = 0; i < 256; i++)
+		p[i] = i;
+}
+
+LRESULT CALLBACK
+WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+	PAINTSTRUCT paint;
+	HDC hdc;
+	LONG x, y, b;
+	int i;
+	Rectangle r;
+
+	switch(msg) {
+	case WM_CREATE:
+		break;
+	case WM_SETCURSOR:
+		/* User set */
+		if(hcursor != NULL) {
+			SetCursor(hcursor);
+			return 1;
+		}
+		return DefWindowProc(hwnd, msg, wparam, lparam);
+	case WM_MOUSEMOVE:
+	case WM_LBUTTONUP:
+	case WM_MBUTTONUP:
+	case WM_RBUTTONUP:
+	case WM_LBUTTONDOWN:
+	case WM_MBUTTONDOWN:
+	case WM_RBUTTONDOWN:
+		x = LOWORD(lparam);
+		y = HIWORD(lparam);
+		b = 0;
+		if(wparam & MK_LBUTTON)
+			b = 1;
+		if(wparam & MK_MBUTTON)
+			b |= 2;
+		if(wparam & MK_RBUTTON) {
+			if(wparam & MK_SHIFT)
+				b |= 2;
+			else
+				b |= 4;
+		}
+		lock(&mouse.lk);
+		i = mouse.wi;
+		if(mousequeue) {
+			if(i == mouse.ri || mouse.lastb != b || mouse.trans) {
+				mouse.wi = (i+1)%Mousequeue;
+				if(mouse.wi == mouse.ri)
+					mouse.ri = (mouse.ri+1)%Mousequeue;
+				mouse.trans = mouse.lastb != b;
+			} else {
+				i = (i-1+Mousequeue)%Mousequeue;
+			}
+		} else {
+			mouse.wi = (i+1)%Mousequeue;
+			mouse.ri = i;
+		}
+		mouse.queue[i].xy.x = x;
+		mouse.queue[i].xy.y = y;
+		mouse.queue[i].buttons = b;
+		mouse.queue[i].msec = ticks();
+		mouse.lastb = b;
+		unlock(&mouse.lk);
+		wakeup(&mouse.r);
+		break;
+
+	case WM_CHAR:
+		/* repeat count is lparam & 0xf */
+		switch(wparam){
+		case '\n':
+			wparam = '\r';
+			break;
+		case '\r':
+			wparam = '\n';
+			break;
+		}
+		kbdputc(kbdq, wparam);
+		break;
+
+	case WM_SYSKEYUP:
+		break;
+	case WM_SYSKEYDOWN:
+	case WM_KEYDOWN:
+		switch(wparam) {
+		case VK_MENU:
+			kbdputc(kbdq, Kalt);
+			break;
+		case VK_INSERT:
+			kbdputc(kbdq, Kins);
+			break;
+		case VK_DELETE:
+//			kbdputc(kbdq, Kdel);
+			kbdputc(kbdq, 0x7f);	// should have Kdel in keyboard.h
+			break;
+		case VK_UP:
+			kbdputc(kbdq, Kup);
+			break;
+		case VK_DOWN:
+			kbdputc(kbdq, Kdown);
+			break;
+		case VK_LEFT:
+			kbdputc(kbdq, Kleft);
+			break;
+		case VK_RIGHT:
+			kbdputc(kbdq, Kright);
+			break;
+		}
+		break;
+
+	case WM_CLOSE:
+		DestroyWindow(hwnd);
+		break;
+
+	case WM_DESTROY:
+		PostQuitMessage(0);
+		break;
+
+	case WM_PALETTECHANGED:
+		if((HWND)wparam == hwnd)
+			break;
+	/* fall through */
+	case WM_QUERYNEWPALETTE:
+		hdc = GetDC(hwnd);
+		SelectPalette(hdc, palette, 0);
+		if(RealizePalette(hdc) != 0)
+			InvalidateRect(hwnd, nil, 0);
+		ReleaseDC(hwnd, hdc);
+		break;
+
+	case WM_PAINT:
+		hdc = BeginPaint(hwnd, &paint);
+		r.min.x = paint.rcPaint.left;
+		r.min.y = paint.rcPaint.top;
+		r.max.x = paint.rcPaint.right;
+		r.max.y = paint.rcPaint.bottom;
+		flushmemscreen(r);
+		EndPaint(hwnd, &paint);
+		break;
+	case WM_COMMAND:
+	case WM_SETFOCUS:
+	case WM_DEVMODECHANGE:
+	case WM_WININICHANGE:
+	case WM_INITMENU:
+	default:
+		return DefWindowProc(hwnd, msg, wparam, lparam);
+	}
+	return 0;
+}
+
+void
+mouseset(Point xy)
+{
+	POINT pt;
+
+	pt.x = xy.x;
+	pt.y = xy.y;
+	MapWindowPoints(window, 0, &pt, 1);
+	SetCursorPos(pt.x, pt.y);
+}
+
+void
+setcursor(void)
+{
+	HCURSOR nh;
+	int x, y, h, w;
+	uchar *sp, *cp;
+	uchar *and, *xor;
+
+	h = GetSystemMetrics(SM_CYCURSOR);
+	w = (GetSystemMetrics(SM_CXCURSOR)+7)/8;
+
+	and = mallocz(h*w, 1);
+	memset(and, 0xff, h*w);
+	xor = mallocz(h*w, 1);
+	
+	lock(&cursor.lk);
+	for(y=0,sp=cursor.set,cp=cursor.clr; y<16; y++) {
+		for(x=0; x<2; x++) {
+			and[y*w+x] = ~(*sp|*cp);
+			xor[y*w+x] = ~*sp & *cp;
+			cp++;
+			sp++;
+		}
+	}
+	nh = CreateCursor(inst, -cursor.offset.x, -cursor.offset.y,
+			GetSystemMetrics(SM_CXCURSOR), h,
+			and, xor);
+	if(nh != NULL) {
+		SetCursor(nh);
+		if(hcursor != NULL)
+			DestroyCursor(hcursor);
+		hcursor = nh;
+	}
+	unlock(&cursor.lk);
+
+	free(and);
+	free(xor);
+
+	PostMessage(window, WM_SETCURSOR, (int)window, 0);
+}
+
+void
+cursorarrow(void)
+{
+	if(hcursor != 0) {
+		DestroyCursor(hcursor);
+		hcursor = 0;
+	}
+	SetCursor(LoadCursor(0, IDC_ARROW));
+	PostMessage(window, WM_SETCURSOR, (int)window, 0);
+}
+
+
+void
+setcolor(ulong index, ulong red, ulong green, ulong blue)
+{
+}
+
+
+uchar*
+clipreadunicode(HANDLE h)
+{
+	Rune *p;
+	int n;
+	uchar *q;
+	
+	p = GlobalLock(h);
+	n = wstrutflen(p)+1;
+	q = malloc(n);
+	wstrtoutf(q, p, n);
+	GlobalUnlock(h);
+
+	return q;
+}
+
+uchar *
+clipreadutf(HANDLE h)
+{
+	uchar *p;
+
+	p = GlobalLock(h);
+	p = strdup(p);
+	GlobalUnlock(h);
+	
+	return p;
+}
+
+
+uchar*
+clipread()
+{
+	HANDLE h;
+	uchar *p;
+
+	if(!OpenClipboard(window)) {
+		oserror();
+		return strdup("");
+	}
+
+	if(h = GetClipboardData(CF_UNICODETEXT))
+		p = clipreadunicode(h);
+	else if(h = GetClipboardData(CF_TEXT))
+		p = clipreadutf(h);
+	else {
+		oserror();
+		p = strdup("");
+	}
+	
+	CloseClipboard();
+	return p;
+}
+
+int
+clipwrite(char *buf)
+{
+	HANDLE h;
+	char *p, *e;
+	Rune *rp;
+	int n = strlen(buf);
+
+	if(!OpenClipboard(window)) {
+		oserror();
+		return -1;
+	}
+
+	if(!EmptyClipboard()) {
+		oserror();
+		CloseClipboard();
+		return -1;
+	}
+
+	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, (n+1)*sizeof(Rune));
+	if(h == NULL)
+		panic("out of memory");
+	rp = GlobalLock(h);
+	p = buf;
+	e = p+n;
+	while(p<e)
+		p += chartorune(rp++, p);
+	*rp = 0;
+	GlobalUnlock(h);
+
+	SetClipboardData(CF_UNICODETEXT, h);
+
+	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, n+1);
+	if(h == NULL)
+		panic("out of memory");
+	p = GlobalLock(h);
+	memcpy(p, buf, n);
+	p[n] = 0;
+	GlobalUnlock(h);
+	
+	SetClipboardData(CF_TEXT, h);
+
+	CloseClipboard();
+	return n;
+}
+
+int
+atlocalconsole(void)
+{
+	return 1;
+}
--- /dev/null
+++ b/gui-win32/wstrtoutf.c
@@ -1,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+
+int
+wstrutflen(Rune *s)
+{
+	int n;
+	
+	for(n=0; *s; n+=runelen(*s),s++)
+		;
+	return n;
+}
+
+int
+wstrtoutf(char *s, Rune *t, int n)
+{
+	int i;
+	char *s0;
+
+	s0 = s;
+	if(n <= 0)
+		return wstrutflen(t)+1;
+	while(*t) {
+		if(n < UTFmax+1 && n < runelen(*t)+1) {
+			*s = 0;
+			return i+wstrutflen(t)+1;
+		}
+		i = runetochar(s, t);
+		s += i;
+		n -= i;
+		t++;
+	}
+	*s = 0;
+	return s-s0;
+}
--- /dev/null
+++ b/gui-x11/Makefile
@@ -1,0 +1,20 @@
+LIB=libx11.a
+CC=gcc
+CFLAGS=-I../include -I. -I/usr/X11R6/include -I../kern -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	alloc.$O\
+	cload.$O\
+	draw.$O\
+	load.$O\
+	screen.$O\
+	keysym2ucs-x11.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/gui-x11/alloc.c
@@ -1,0 +1,209 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "xmem.h"
+
+/* perfect approximation to NTSC = .299r+.587g+.114b when 0 ≤ r,g,b < 256 */
+#define RGB2K(r,g,b)	((156763*(r)+307758*(g)+59769*(b))>>19)
+
+Memimage*
+xallocmemimage(Rectangle r, ulong chan, int pmid)
+{
+	Memimage *m;
+	Xmem *xm;
+	XImage *xi;
+	int offset;
+	int d;
+	
+	m = _allocmemimage(r, chan);
+	if(chan != GREY1 && chan != xscreenchan)
+		return m;
+
+	d = m->depth;
+	xm = mallocz(sizeof(Xmem), 1);
+	if(pmid != PMundef)
+		xm->pmid = pmid;
+	else
+		xm->pmid = XCreatePixmap(xdisplay, xscreenid, Dx(r), Dy(r), (d==32) ? 24 : d);
+		
+	if(m->depth == 24)
+		offset = r.min.x&(4-1);
+	else
+		offset = r.min.x&(31/m->depth);
+	r.min.x -= offset;
+	
+	assert(wordsperline(r, m->depth) <= m->width);
+
+	xi = XCreateImage(xdisplay, xvis, m->depth==32?24:m->depth, ZPixmap, 0,
+		(char*)m->data->bdata, Dx(r), Dy(r), 32, m->width*sizeof(ulong));
+	
+	if(xi == nil){
+		_freememimage(m);
+		return nil;
+	}
+
+	xm->xi = xi;
+	xm->pc = getcallerpc(&r);
+	xm->r = r;
+	
+	/*
+	 * Set the parameters of the XImage so its memory looks exactly like a
+	 * Memimage, so we can call _memimagedraw on the same data.  All frame
+	 * buffers we've seen, and Plan 9's graphics code, require big-endian
+	 * bits within bytes, but little endian byte order within pixels.
+	 */
+	xi->bitmap_unit = m->depth < 8 || m->depth == 24 ? 8 : m->depth;
+	xi->byte_order = LSBFirst;
+	xi->bitmap_bit_order = MSBFirst;
+	xi->bitmap_pad = 32;
+	xm->r = Rect(0,0,0,0);
+	XInitImage(xi);
+	XFlush(xdisplay);
+
+	m->X = xm;
+	return m;
+}
+
+Memimage*
+allocmemimage(Rectangle r, ulong chan)
+{
+	return xallocmemimage(r, chan, PMundef);
+}
+
+void
+freememimage(Memimage *m)
+{
+	Xmem *xm;
+	
+	if(m == nil)
+		return;
+		
+	if(m->data->ref == 1){
+		if((xm = m->X) != nil){
+			if(xm->xi){
+				xm->xi->data = nil;
+				XFree(xm->xi);
+			}
+			XFreePixmap(xdisplay, xm->pmid);
+			free(xm);
+			m->X = nil;
+		}
+	}
+	_freememimage(m);
+}
+
+void
+memfillcolor(Memimage *m, ulong val)
+{
+	_memfillcolor(m, val);
+	if(m->X){
+		if((val & 0xFF) == 0xFF)
+			xfillcolor(m, m->r, _rgbatoimg(m, val));
+		else
+			putXdata(m, m->r);
+	}
+}
+
+static void
+addrect(Rectangle *rp, Rectangle r)
+{
+	if(rp->min.x >= rp->max.x)
+		*rp = r;
+	else
+		combinerect(rp, r);
+}
+
+XImage*
+getXdata(Memimage *m, Rectangle r)
+{
+	uchar *p;
+	int x, y;
+	Xmem *xm;
+	Point xdelta, delta;
+	Point tp;
+
+ 	xm = m->X;
+ 	if(xm == nil)
+ 		return;
+ 
+	assert(xm != nil && xm->xi != nil);
+	
+ 	if(xm->dirty == 0)
+ 		return xm->xi;
+ 		
+ 	r = xm->dirtyr;
+	if(Dx(r)==0 || Dy(r)==0)
+		return xm->xi;
+
+	delta = subpt(r.min, m->r.min);
+	tp = xm->r.min;	/* avoid unaligned access on digital unix */
+	xdelta = subpt(r.min, tp);
+	
+	XGetSubImage(xdisplay, xm->pmid, delta.x, delta.y, Dx(r), Dy(r),
+		AllPlanes, ZPixmap, xm->xi, xdelta.x, xdelta.y);
+		
+	if(xtblbit && m->chan == CMAP8)
+		for(y=r.min.y; y<r.max.y; y++)
+			for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
+				*p = x11toplan9[*p];
+				
+	xm->dirty = 0;
+	xm->dirtyr = Rect(0,0,0,0);
+	return xm->xi;
+}
+
+void
+putXdata(Memimage *m, Rectangle r)
+{
+	Xmem *xm;
+	XImage *xi;
+	GC g;
+	int offset;
+	Point xdelta, delta;
+	Point tp;
+	int x, y;
+	uchar *p;
+
+	xm = m->X;
+	if(xm == nil)
+		return;
+		
+	assert(xm != nil);
+	assert(xm->xi != nil);
+
+	xi = xm->xi;
+
+	g = (m->chan == GREY1) ? xgccopy0 : xgccopy;
+	if(m->depth == 24)
+		offset = r.min.x % 4;
+	else
+		offset = m->r.min.x & (31/m->depth);
+
+	delta = subpt(r.min, m->r.min);
+	tp = xm->r.min;	/* avoid unaligned access on digital unix */
+	xdelta = subpt(r.min, tp);
+	
+	if(xtblbit && m->chan == CMAP8)
+		for(y=r.min.y; y<r.max.y; y++)
+			for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
+				*p = plan9tox11[*p];
+	
+	XPutImage(xdisplay, xm->pmid, g, xi, xdelta.x, xdelta.y, delta.x, delta.y, Dx(r), Dy(r));
+
+	if(xtblbit && m->chan == CMAP8)
+		for(y=r.min.y; y<r.max.y; y++)
+			for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
+				*p = x11toplan9[*p];
+}
+
+void
+dirtyXdata(Memimage *m, Rectangle r)
+{
+	Xmem *xm;
+	
+	if((xm = m->X) != nil){
+		xm->dirty = 1;
+		addrect(&xm->dirtyr, r);
+	}
+}
--- /dev/null
+++ b/gui-x11/cload.c
@@ -1,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "xmem.h"
+
+int
+cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int n;
+
+	n = _cloadmemimage(i, r, data, ndata);
+	if(n > 0 && i->X)
+		putXdata(i, r);
+	return n;
+}
--- /dev/null
+++ b/gui-x11/draw.c
@@ -1,0 +1,189 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "xmem.h"
+
+void xfillcolor(Memimage*, Rectangle, ulong);
+static int xdraw(Memdrawparam*);
+
+int	xgcfillcolor = 0;
+int	xgcfillcolor0 = 0;
+int	xgczeropm = 0;
+int	xgczeropm0 = 0;
+int	xgcsimplecolor = 0;
+int	xgcsimplecolor0 = 0;
+int	xgcsimplepm = 0; 
+int	xgcsimplepm0 = 0;
+int	xgcreplsrctile = 0;
+int	xgcreplsrctile0 = 0;
+
+void
+memimageinit(void)
+{
+	static int didinit = 0;
+	
+	if(didinit)
+		return;
+
+	didinit = 1;
+	_memimageinit();
+	
+	xfillcolor(memblack, memblack->r, 0);
+	xfillcolor(memwhite, memwhite->r, 1);
+}
+
+void
+memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point sp, Memimage *mask, Point mp, int op)
+{
+	int didx;
+	Rectangle dr, mr, sr;
+	Memdrawparam *par;
+	
+	if((par = _memimagedrawsetup(dst, r, src, sp, mask, mp, op)) == nil)
+		return;
+	_memimagedraw(par);
+	if(!xdraw(par))
+		putXdata(dst, par->r);
+}
+
+void
+xfillcolor(Memimage *m, Rectangle r, ulong v)
+{
+	GC gc;
+	Xmem *dxm;
+
+	dxm = m->X;
+	assert(dxm != nil);
+	r = rectsubpt(r, m->r.min);
+		
+	if(m->chan == GREY1){
+		gc = xgcfill0;
+		if(xgcfillcolor0 != v){
+			XSetForeground(xdisplay, gc, v);
+			xgcfillcolor0 = v;
+		}
+	}else{
+		if(m->chan == CMAP8 && xtblbit)
+			v = plan9tox11[v];
+				
+		gc = xgcfill;
+		if(xgcfillcolor != v){
+			XSetForeground(xdisplay, gc, v);
+			xgcfillcolor = v;
+		}
+	}
+	XFillRectangle(xdisplay, dxm->pmid, gc, r.min.x, r.min.y, Dx(r), Dy(r));
+}
+
+static int
+xdraw(Memdrawparam *par)
+{
+	int dy, dx;
+	unsigned m;
+	Memimage *src, *dst, *mask;
+	Xmem *dxm, *sxm, *mxm;
+	GC gc;
+	Rectangle r, sr, mr;
+	ulong sdval;
+
+	dx = Dx(par->r);
+	dy = Dy(par->r);
+	src = par->src;
+	dst = par->dst;
+	mask = par->mask;
+	r = par->r;
+	sr = par->sr;
+	mr = par->mr;
+	sdval = par->sdval;
+
+return 0;
+	if((dxm = dst->X) == nil)
+		return 0;
+
+	/*
+	 * If we have an opaque mask and source is one opaque pixel we can convert to the
+	 * destination format and just XFillRectangle.
+	 */
+	m = Simplesrc|Simplemask|Fullmask;
+	if((par->state&m)==m){
+		xfillcolor(dst, r, sdval);
+		dirtyXdata(dst, par->r);
+		return 1;
+	}
+
+	/*
+	 * If no source alpha, an opaque mask, we can just copy the
+	 * source onto the destination.  If the channels are the same and
+	 * the source is not replicated, XCopyArea suffices.
+	 */
+	m = Simplemask|Fullmask;
+	if((par->state&(m|Replsrc))==m && src->chan == dst->chan && src->X){
+		sxm = src->X;
+		r = rectsubpt(r, dst->r.min);		
+		sr = rectsubpt(sr, src->r.min);
+		if(dst->chan == GREY1)
+			gc = xgccopy0;
+		else
+			gc = xgccopy;
+		XCopyArea(xdisplay, sxm->pmid, dxm->pmid, gc, 
+			sr.min.x, sr.min.y, dx, dy, r.min.x, r.min.y);
+		dirtyXdata(dst, par->r);
+		return 1;
+	}
+	
+	/*
+	 * If no source alpha, a 1-bit mask, and a simple source
+	 * we can just copy through the mask onto the destination.
+	 */
+	if(dst->X && mask->X && !(mask->flags&Frepl)
+	&& mask->chan == GREY1 && (par->state&Simplesrc)){
+		Point p;
+
+		mxm = mask->X;
+		r = rectsubpt(r, dst->r.min);		
+		mr = rectsubpt(mr, mask->r.min);
+		p = subpt(r.min, mr.min);
+		if(dst->chan == GREY1){
+			gc = xgcsimplesrc0;
+			if(xgcsimplecolor0 != sdval){
+				XSetForeground(xdisplay, gc, sdval);
+				xgcsimplecolor0 = sdval;
+			}
+			if(xgcsimplepm0 != mxm->pmid){
+				XSetStipple(xdisplay, gc, mxm->pmid);
+				xgcsimplepm0 = mxm->pmid;
+			}
+		}else{
+		/* somehow this doesn't work on rob's mac 
+			gc = xgcsimplesrc;
+			if(dst->chan == CMAP8 && xtblbit)
+				sdval = plan9tox11[sdval];
+				
+			if(xgcsimplecolor != sdval){
+				XSetForeground(xdisplay, gc, sdval);
+				xgcsimplecolor = sdval;
+			}
+			if(xgcsimplepm != mxm->pmid){
+				XSetStipple(xdisplay, gc, mxm->pmid);
+				xgcsimplepm = mxm->pmid;
+			}
+		*/
+			return 0;
+		}
+		XSetTSOrigin(xdisplay, gc, p.x, p.y);
+		XFillRectangle(xdisplay, dxm->pmid, gc, r.min.x, r.min.y, dx, dy);
+		dirtyXdata(dst, par->r);
+		return 1;
+	}
+	return 0;
+}
+
+ulong
+pixelbits(Memimage *m, Point p)
+{
+	Xmem *xm;
+	if(m->X)
+		getXdata(m, Rect(p.x, p.y, p.x+1, p.y+1));
+	return _pixelbits(m, p);
+}
--- /dev/null
+++ b/gui-x11/keysym2ucs-x11.c
@@ -1,0 +1,857 @@
+/* $XFree86: xc/programs/xterm/keysym2ucs.c,v 1.5 2001/06/18 19:09:26 dickey Exp $
+ * This module converts keysym values into the corresponding ISO 10646
+ * (UCS, Unicode) values.
+ *
+ * The array keysymtab[] contains pairs of X11 keysym values for graphical
+ * characters and the corresponding Unicode value. The function
+ * keysym2ucs() maps a keysym onto a Unicode value using a binary search,
+ * therefore keysymtab[] must remain SORTED by keysym value.
+ *
+ * The keysym -> UTF-8 conversion will hopefully one day be provided
+ * by Xlib via XmbLookupString() and should ideally not have to be
+ * done in X applications. But we are not there yet.
+ *
+ * We allow to represent any UCS character in the range U-00000000 to
+ * U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff.
+ * This admittedly does not cover the entire 31-bit space of UCS, but
+ * it does cover all of the characters up to U-10FFFF, which can be
+ * represented by UTF-16, and more, and it is very unlikely that higher
+ * UCS codes will ever be assigned by ISO. So to get Unicode character
+ * U+ABCD you can directly use keysym 0x0100abcd.
+ *
+ * NOTE: The comments in the table below contain the actual character
+ * encoded in UTF-8, so for viewing and editing best use an editor in
+ * UTF-8 mode.
+ *
+ * Author: Markus G. Kuhn <mkuhn@acm.org>, University of Cambridge, April 2001
+ *
+ * Special thanks to Richard Verhoeven <river@win.tue.nl> for preparing
+ * an initial draft of the mapping table.
+ *
+ * This software is in the public domain. Share and enjoy!
+ *
+ * AUTOMATICALLY GENERATED FILE, DO NOT EDIT !!! (unicode/convmap.pl)
+ */
+
+#ifndef KEYSYM2UCS_INCLUDED
+  
+#include "keysym2ucs.h"
+#define VISIBLE /* */
+
+#else
+
+#define VISIBLE static
+
+#endif
+
+static struct codepair {
+  unsigned short keysym;
+  unsigned short ucs;
+} keysymtab[] = {
+  { 0x01a1, 0x0104 }, /*                     Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */
+  { 0x01a2, 0x02d8 }, /*                       breve ˘ BREVE */
+  { 0x01a3, 0x0141 }, /*                     Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */
+  { 0x01a5, 0x013d }, /*                      Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */
+  { 0x01a6, 0x015a }, /*                      Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */
+  { 0x01a9, 0x0160 }, /*                      Scaron Š LATIN CAPITAL LETTER S WITH CARON */
+  { 0x01aa, 0x015e }, /*                    Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */
+  { 0x01ab, 0x0164 }, /*                      Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */
+  { 0x01ac, 0x0179 }, /*                      Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */
+  { 0x01ae, 0x017d }, /*                      Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */
+  { 0x01af, 0x017b }, /*                   Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+  { 0x01b1, 0x0105 }, /*                     aogonek ą LATIN SMALL LETTER A WITH OGONEK */
+  { 0x01b2, 0x02db }, /*                      ogonek ˛ OGONEK */
+  { 0x01b3, 0x0142 }, /*                     lstroke ł LATIN SMALL LETTER L WITH STROKE */
+  { 0x01b5, 0x013e }, /*                      lcaron ľ LATIN SMALL LETTER L WITH CARON */
+  { 0x01b6, 0x015b }, /*                      sacute ś LATIN SMALL LETTER S WITH ACUTE */
+  { 0x01b7, 0x02c7 }, /*                       caron ˇ CARON */
+  { 0x01b9, 0x0161 }, /*                      scaron š LATIN SMALL LETTER S WITH CARON */
+  { 0x01ba, 0x015f }, /*                    scedilla ş LATIN SMALL LETTER S WITH CEDILLA */
+  { 0x01bb, 0x0165 }, /*                      tcaron ť LATIN SMALL LETTER T WITH CARON */
+  { 0x01bc, 0x017a }, /*                      zacute ź LATIN SMALL LETTER Z WITH ACUTE */
+  { 0x01bd, 0x02dd }, /*                 doubleacute ˝ DOUBLE ACUTE ACCENT */
+  { 0x01be, 0x017e }, /*                      zcaron ž LATIN SMALL LETTER Z WITH CARON */
+  { 0x01bf, 0x017c }, /*                   zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */
+  { 0x01c0, 0x0154 }, /*                      Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */
+  { 0x01c3, 0x0102 }, /*                      Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */
+  { 0x01c5, 0x0139 }, /*                      Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */
+  { 0x01c6, 0x0106 }, /*                      Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */
+  { 0x01c8, 0x010c }, /*                      Ccaron Č LATIN CAPITAL LETTER C WITH CARON */
+  { 0x01ca, 0x0118 }, /*                     Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */
+  { 0x01cc, 0x011a }, /*                      Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */
+  { 0x01cf, 0x010e }, /*                      Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */
+  { 0x01d0, 0x0110 }, /*                     Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */
+  { 0x01d1, 0x0143 }, /*                      Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */
+  { 0x01d2, 0x0147 }, /*                      Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */
+  { 0x01d5, 0x0150 }, /*                Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+  { 0x01d8, 0x0158 }, /*                      Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */
+  { 0x01d9, 0x016e }, /*                       Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */
+  { 0x01db, 0x0170 }, /*                Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+  { 0x01de, 0x0162 }, /*                    Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */
+  { 0x01e0, 0x0155 }, /*                      racute ŕ LATIN SMALL LETTER R WITH ACUTE */
+  { 0x01e3, 0x0103 }, /*                      abreve ă LATIN SMALL LETTER A WITH BREVE */
+  { 0x01e5, 0x013a }, /*                      lacute ĺ LATIN SMALL LETTER L WITH ACUTE */
+  { 0x01e6, 0x0107 }, /*                      cacute ć LATIN SMALL LETTER C WITH ACUTE */
+  { 0x01e8, 0x010d }, /*                      ccaron č LATIN SMALL LETTER C WITH CARON */
+  { 0x01ea, 0x0119 }, /*                     eogonek ę LATIN SMALL LETTER E WITH OGONEK */
+  { 0x01ec, 0x011b }, /*                      ecaron ě LATIN SMALL LETTER E WITH CARON */
+  { 0x01ef, 0x010f }, /*                      dcaron ď LATIN SMALL LETTER D WITH CARON */
+  { 0x01f0, 0x0111 }, /*                     dstroke đ LATIN SMALL LETTER D WITH STROKE */
+  { 0x01f1, 0x0144 }, /*                      nacute ń LATIN SMALL LETTER N WITH ACUTE */
+  { 0x01f2, 0x0148 }, /*                      ncaron ň LATIN SMALL LETTER N WITH CARON */
+  { 0x01f5, 0x0151 }, /*                odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */
+  { 0x01f8, 0x0159 }, /*                      rcaron ř LATIN SMALL LETTER R WITH CARON */
+  { 0x01f9, 0x016f }, /*                       uring ů LATIN SMALL LETTER U WITH RING ABOVE */
+  { 0x01fb, 0x0171 }, /*                udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */
+  { 0x01fe, 0x0163 }, /*                    tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */
+  { 0x01ff, 0x02d9 }, /*                    abovedot ˙ DOT ABOVE */
+  { 0x02a1, 0x0126 }, /*                     Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */
+  { 0x02a6, 0x0124 }, /*                 Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+  { 0x02a9, 0x0130 }, /*                   Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */
+  { 0x02ab, 0x011e }, /*                      Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */
+  { 0x02ac, 0x0134 }, /*                 Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+  { 0x02b1, 0x0127 }, /*                     hstroke ħ LATIN SMALL LETTER H WITH STROKE */
+  { 0x02b6, 0x0125 }, /*                 hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */
+  { 0x02b9, 0x0131 }, /*                    idotless ı LATIN SMALL LETTER DOTLESS I */
+  { 0x02bb, 0x011f }, /*                      gbreve ğ LATIN SMALL LETTER G WITH BREVE */
+  { 0x02bc, 0x0135 }, /*                 jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */
+  { 0x02c5, 0x010a }, /*                   Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */
+  { 0x02c6, 0x0108 }, /*                 Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+  { 0x02d5, 0x0120 }, /*                   Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */
+  { 0x02d8, 0x011c }, /*                 Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+  { 0x02dd, 0x016c }, /*                      Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */
+  { 0x02de, 0x015c }, /*                 Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+  { 0x02e5, 0x010b }, /*                   cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */
+  { 0x02e6, 0x0109 }, /*                 ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */
+  { 0x02f5, 0x0121 }, /*                   gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */
+  { 0x02f8, 0x011d }, /*                 gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */
+  { 0x02fd, 0x016d }, /*                      ubreve ŭ LATIN SMALL LETTER U WITH BREVE */
+  { 0x02fe, 0x015d }, /*                 scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */
+  { 0x03a2, 0x0138 }, /*                         kra ĸ LATIN SMALL LETTER KRA */
+  { 0x03a3, 0x0156 }, /*                    Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */
+  { 0x03a5, 0x0128 }, /*                      Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */
+  { 0x03a6, 0x013b }, /*                    Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */
+  { 0x03aa, 0x0112 }, /*                     Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */
+  { 0x03ab, 0x0122 }, /*                    Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */
+  { 0x03ac, 0x0166 }, /*                      Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */
+  { 0x03b3, 0x0157 }, /*                    rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */
+  { 0x03b5, 0x0129 }, /*                      itilde ĩ LATIN SMALL LETTER I WITH TILDE */
+  { 0x03b6, 0x013c }, /*                    lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */
+  { 0x03ba, 0x0113 }, /*                     emacron ē LATIN SMALL LETTER E WITH MACRON */
+  { 0x03bb, 0x0123 }, /*                    gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */
+  { 0x03bc, 0x0167 }, /*                      tslash ŧ LATIN SMALL LETTER T WITH STROKE */
+  { 0x03bd, 0x014a }, /*                         ENG Ŋ LATIN CAPITAL LETTER ENG */
+  { 0x03bf, 0x014b }, /*                         eng ŋ LATIN SMALL LETTER ENG */
+  { 0x03c0, 0x0100 }, /*                     Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */
+  { 0x03c7, 0x012e }, /*                     Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */
+  { 0x03cc, 0x0116 }, /*                   Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */
+  { 0x03cf, 0x012a }, /*                     Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */
+  { 0x03d1, 0x0145 }, /*                    Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */
+  { 0x03d2, 0x014c }, /*                     Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */
+  { 0x03d3, 0x0136 }, /*                    Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */
+  { 0x03d9, 0x0172 }, /*                     Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */
+  { 0x03dd, 0x0168 }, /*                      Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */
+  { 0x03de, 0x016a }, /*                     Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */
+  { 0x03e0, 0x0101 }, /*                     amacron ā LATIN SMALL LETTER A WITH MACRON */
+  { 0x03e7, 0x012f }, /*                     iogonek į LATIN SMALL LETTER I WITH OGONEK */
+  { 0x03ec, 0x0117 }, /*                   eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */
+  { 0x03ef, 0x012b }, /*                     imacron ī LATIN SMALL LETTER I WITH MACRON */
+  { 0x03f1, 0x0146 }, /*                    ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */
+  { 0x03f2, 0x014d }, /*                     omacron ō LATIN SMALL LETTER O WITH MACRON */
+  { 0x03f3, 0x0137 }, /*                    kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */
+  { 0x03f9, 0x0173 }, /*                     uogonek ų LATIN SMALL LETTER U WITH OGONEK */
+  { 0x03fd, 0x0169 }, /*                      utilde ũ LATIN SMALL LETTER U WITH TILDE */
+  { 0x03fe, 0x016b }, /*                     umacron ū LATIN SMALL LETTER U WITH MACRON */
+  { 0x047e, 0x203e }, /*                    overline ‾ OVERLINE */
+  { 0x04a1, 0x3002 }, /*               kana_fullstop 。 IDEOGRAPHIC FULL STOP */
+  { 0x04a2, 0x300c }, /*         kana_openingbracket 「 LEFT CORNER BRACKET */
+  { 0x04a3, 0x300d }, /*         kana_closingbracket 」 RIGHT CORNER BRACKET */
+  { 0x04a4, 0x3001 }, /*                  kana_comma 、 IDEOGRAPHIC COMMA */
+  { 0x04a5, 0x30fb }, /*            kana_conjunctive ・ KATAKANA MIDDLE DOT */
+  { 0x04a6, 0x30f2 }, /*                     kana_WO ヲ KATAKANA LETTER WO */
+  { 0x04a7, 0x30a1 }, /*                      kana_a ァ KATAKANA LETTER SMALL A */
+  { 0x04a8, 0x30a3 }, /*                      kana_i ィ KATAKANA LETTER SMALL I */
+  { 0x04a9, 0x30a5 }, /*                      kana_u ゥ KATAKANA LETTER SMALL U */
+  { 0x04aa, 0x30a7 }, /*                      kana_e ェ KATAKANA LETTER SMALL E */
+  { 0x04ab, 0x30a9 }, /*                      kana_o ォ KATAKANA LETTER SMALL O */
+  { 0x04ac, 0x30e3 }, /*                     kana_ya ャ KATAKANA LETTER SMALL YA */
+  { 0x04ad, 0x30e5 }, /*                     kana_yu ュ KATAKANA LETTER SMALL YU */
+  { 0x04ae, 0x30e7 }, /*                     kana_yo ョ KATAKANA LETTER SMALL YO */
+  { 0x04af, 0x30c3 }, /*                    kana_tsu ッ KATAKANA LETTER SMALL TU */
+  { 0x04b0, 0x30fc }, /*              prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */
+  { 0x04b1, 0x30a2 }, /*                      kana_A ア KATAKANA LETTER A */
+  { 0x04b2, 0x30a4 }, /*                      kana_I イ KATAKANA LETTER I */
+  { 0x04b3, 0x30a6 }, /*                      kana_U ウ KATAKANA LETTER U */
+  { 0x04b4, 0x30a8 }, /*                      kana_E エ KATAKANA LETTER E */
+  { 0x04b5, 0x30aa }, /*                      kana_O オ KATAKANA LETTER O */
+  { 0x04b6, 0x30ab }, /*                     kana_KA カ KATAKANA LETTER KA */
+  { 0x04b7, 0x30ad }, /*                     kana_KI キ KATAKANA LETTER KI */
+  { 0x04b8, 0x30af }, /*                     kana_KU ク KATAKANA LETTER KU */
+  { 0x04b9, 0x30b1 }, /*                     kana_KE ケ KATAKANA LETTER KE */
+  { 0x04ba, 0x30b3 }, /*                     kana_KO コ KATAKANA LETTER KO */
+  { 0x04bb, 0x30b5 }, /*                     kana_SA サ KATAKANA LETTER SA */
+  { 0x04bc, 0x30b7 }, /*                    kana_SHI シ KATAKANA LETTER SI */
+  { 0x04bd, 0x30b9 }, /*                     kana_SU ス KATAKANA LETTER SU */
+  { 0x04be, 0x30bb }, /*                     kana_SE セ KATAKANA LETTER SE */
+  { 0x04bf, 0x30bd }, /*                     kana_SO ソ KATAKANA LETTER SO */
+  { 0x04c0, 0x30bf }, /*                     kana_TA タ KATAKANA LETTER TA */
+  { 0x04c1, 0x30c1 }, /*                    kana_CHI チ KATAKANA LETTER TI */
+  { 0x04c2, 0x30c4 }, /*                    kana_TSU ツ KATAKANA LETTER TU */
+  { 0x04c3, 0x30c6 }, /*                     kana_TE テ KATAKANA LETTER TE */
+  { 0x04c4, 0x30c8 }, /*                     kana_TO ト KATAKANA LETTER TO */
+  { 0x04c5, 0x30ca }, /*                     kana_NA ナ KATAKANA LETTER NA */
+  { 0x04c6, 0x30cb }, /*                     kana_NI ニ KATAKANA LETTER NI */
+  { 0x04c7, 0x30cc }, /*                     kana_NU ヌ KATAKANA LETTER NU */
+  { 0x04c8, 0x30cd }, /*                     kana_NE ネ KATAKANA LETTER NE */
+  { 0x04c9, 0x30ce }, /*                     kana_NO ノ KATAKANA LETTER NO */
+  { 0x04ca, 0x30cf }, /*                     kana_HA ハ KATAKANA LETTER HA */
+  { 0x04cb, 0x30d2 }, /*                     kana_HI ヒ KATAKANA LETTER HI */
+  { 0x04cc, 0x30d5 }, /*                     kana_FU フ KATAKANA LETTER HU */
+  { 0x04cd, 0x30d8 }, /*                     kana_HE ヘ KATAKANA LETTER HE */
+  { 0x04ce, 0x30db }, /*                     kana_HO ホ KATAKANA LETTER HO */
+  { 0x04cf, 0x30de }, /*                     kana_MA マ KATAKANA LETTER MA */
+  { 0x04d0, 0x30df }, /*                     kana_MI ミ KATAKANA LETTER MI */
+  { 0x04d1, 0x30e0 }, /*                     kana_MU ム KATAKANA LETTER MU */
+  { 0x04d2, 0x30e1 }, /*                     kana_ME メ KATAKANA LETTER ME */
+  { 0x04d3, 0x30e2 }, /*                     kana_MO モ KATAKANA LETTER MO */
+  { 0x04d4, 0x30e4 }, /*                     kana_YA ヤ KATAKANA LETTER YA */
+  { 0x04d5, 0x30e6 }, /*                     kana_YU ユ KATAKANA LETTER YU */
+  { 0x04d6, 0x30e8 }, /*                     kana_YO ヨ KATAKANA LETTER YO */
+  { 0x04d7, 0x30e9 }, /*                     kana_RA ラ KATAKANA LETTER RA */
+  { 0x04d8, 0x30ea }, /*                     kana_RI リ KATAKANA LETTER RI */
+  { 0x04d9, 0x30eb }, /*                     kana_RU ル KATAKANA LETTER RU */
+  { 0x04da, 0x30ec }, /*                     kana_RE レ KATAKANA LETTER RE */
+  { 0x04db, 0x30ed }, /*                     kana_RO ロ KATAKANA LETTER RO */
+  { 0x04dc, 0x30ef }, /*                     kana_WA ワ KATAKANA LETTER WA */
+  { 0x04dd, 0x30f3 }, /*                      kana_N ン KATAKANA LETTER N */
+  { 0x04de, 0x309b }, /*                 voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */
+  { 0x04df, 0x309c }, /*             semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+  { 0x05ac, 0x060c }, /*                Arabic_comma ، ARABIC COMMA */
+  { 0x05bb, 0x061b }, /*            Arabic_semicolon ؛ ARABIC SEMICOLON */
+  { 0x05bf, 0x061f }, /*        Arabic_question_mark ؟ ARABIC QUESTION MARK */
+  { 0x05c1, 0x0621 }, /*                Arabic_hamza ء ARABIC LETTER HAMZA */
+  { 0x05c2, 0x0622 }, /*          Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */
+  { 0x05c3, 0x0623 }, /*          Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */
+  { 0x05c4, 0x0624 }, /*           Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */
+  { 0x05c5, 0x0625 }, /*       Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */
+  { 0x05c6, 0x0626 }, /*           Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */
+  { 0x05c7, 0x0627 }, /*                 Arabic_alef ا ARABIC LETTER ALEF */
+  { 0x05c8, 0x0628 }, /*                  Arabic_beh ب ARABIC LETTER BEH */
+  { 0x05c9, 0x0629 }, /*           Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */
+  { 0x05ca, 0x062a }, /*                  Arabic_teh ت ARABIC LETTER TEH */
+  { 0x05cb, 0x062b }, /*                 Arabic_theh ث ARABIC LETTER THEH */
+  { 0x05cc, 0x062c }, /*                 Arabic_jeem ج ARABIC LETTER JEEM */
+  { 0x05cd, 0x062d }, /*                  Arabic_hah ح ARABIC LETTER HAH */
+  { 0x05ce, 0x062e }, /*                 Arabic_khah خ ARABIC LETTER KHAH */
+  { 0x05cf, 0x062f }, /*                  Arabic_dal د ARABIC LETTER DAL */
+  { 0x05d0, 0x0630 }, /*                 Arabic_thal ذ ARABIC LETTER THAL */
+  { 0x05d1, 0x0631 }, /*                   Arabic_ra ر ARABIC LETTER REH */
+  { 0x05d2, 0x0632 }, /*                 Arabic_zain ز ARABIC LETTER ZAIN */
+  { 0x05d3, 0x0633 }, /*                 Arabic_seen س ARABIC LETTER SEEN */
+  { 0x05d4, 0x0634 }, /*                Arabic_sheen ش ARABIC LETTER SHEEN */
+  { 0x05d5, 0x0635 }, /*                  Arabic_sad ص ARABIC LETTER SAD */
+  { 0x05d6, 0x0636 }, /*                  Arabic_dad ض ARABIC LETTER DAD */
+  { 0x05d7, 0x0637 }, /*                  Arabic_tah ط ARABIC LETTER TAH */
+  { 0x05d8, 0x0638 }, /*                  Arabic_zah ظ ARABIC LETTER ZAH */
+  { 0x05d9, 0x0639 }, /*                  Arabic_ain ع ARABIC LETTER AIN */
+  { 0x05da, 0x063a }, /*                Arabic_ghain غ ARABIC LETTER GHAIN */
+  { 0x05e0, 0x0640 }, /*              Arabic_tatweel ـ ARABIC TATWEEL */
+  { 0x05e1, 0x0641 }, /*                  Arabic_feh ف ARABIC LETTER FEH */
+  { 0x05e2, 0x0642 }, /*                  Arabic_qaf ق ARABIC LETTER QAF */
+  { 0x05e3, 0x0643 }, /*                  Arabic_kaf ك ARABIC LETTER KAF */
+  { 0x05e4, 0x0644 }, /*                  Arabic_lam ل ARABIC LETTER LAM */
+  { 0x05e5, 0x0645 }, /*                 Arabic_meem م ARABIC LETTER MEEM */
+  { 0x05e6, 0x0646 }, /*                 Arabic_noon ن ARABIC LETTER NOON */
+  { 0x05e7, 0x0647 }, /*                   Arabic_ha ه ARABIC LETTER HEH */
+  { 0x05e8, 0x0648 }, /*                  Arabic_waw و ARABIC LETTER WAW */
+  { 0x05e9, 0x0649 }, /*          Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */
+  { 0x05ea, 0x064a }, /*                  Arabic_yeh ي ARABIC LETTER YEH */
+  { 0x05eb, 0x064b }, /*             Arabic_fathatan ً ARABIC FATHATAN */
+  { 0x05ec, 0x064c }, /*             Arabic_dammatan ٌ ARABIC DAMMATAN */
+  { 0x05ed, 0x064d }, /*             Arabic_kasratan ٍ ARABIC KASRATAN */
+  { 0x05ee, 0x064e }, /*                Arabic_fatha َ ARABIC FATHA */
+  { 0x05ef, 0x064f }, /*                Arabic_damma ُ ARABIC DAMMA */
+  { 0x05f0, 0x0650 }, /*                Arabic_kasra ِ ARABIC KASRA */
+  { 0x05f1, 0x0651 }, /*               Arabic_shadda ّ ARABIC SHADDA */
+  { 0x05f2, 0x0652 }, /*                Arabic_sukun ْ ARABIC SUKUN */
+  { 0x06a1, 0x0452 }, /*                 Serbian_dje ђ CYRILLIC SMALL LETTER DJE */
+  { 0x06a2, 0x0453 }, /*               Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */
+  { 0x06a3, 0x0451 }, /*                 Cyrillic_io ё CYRILLIC SMALL LETTER IO */
+  { 0x06a4, 0x0454 }, /*                Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */
+  { 0x06a5, 0x0455 }, /*               Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */
+  { 0x06a6, 0x0456 }, /*                 Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
+  { 0x06a7, 0x0457 }, /*                Ukrainian_yi ї CYRILLIC SMALL LETTER YI */
+  { 0x06a8, 0x0458 }, /*                 Cyrillic_je ј CYRILLIC SMALL LETTER JE */
+  { 0x06a9, 0x0459 }, /*                Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */
+  { 0x06aa, 0x045a }, /*                Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */
+  { 0x06ab, 0x045b }, /*                Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */
+  { 0x06ac, 0x045c }, /*               Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */
+  { 0x06ae, 0x045e }, /*         Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */
+  { 0x06af, 0x045f }, /*               Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */
+  { 0x06b0, 0x2116 }, /*                  numerosign № NUMERO SIGN */
+  { 0x06b1, 0x0402 }, /*                 Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */
+  { 0x06b2, 0x0403 }, /*               Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */
+  { 0x06b3, 0x0401 }, /*                 Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */
+  { 0x06b4, 0x0404 }, /*                Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+  { 0x06b5, 0x0405 }, /*               Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */
+  { 0x06b6, 0x0406 }, /*                 Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
+  { 0x06b7, 0x0407 }, /*                Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */
+  { 0x06b8, 0x0408 }, /*                 Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */
+  { 0x06b9, 0x0409 }, /*                Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */
+  { 0x06ba, 0x040a }, /*                Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */
+  { 0x06bb, 0x040b }, /*                Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */
+  { 0x06bc, 0x040c }, /*               Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */
+  { 0x06be, 0x040e }, /*         Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */
+  { 0x06bf, 0x040f }, /*               Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */
+  { 0x06c0, 0x044e }, /*                 Cyrillic_yu ю CYRILLIC SMALL LETTER YU */
+  { 0x06c1, 0x0430 }, /*                  Cyrillic_a а CYRILLIC SMALL LETTER A */
+  { 0x06c2, 0x0431 }, /*                 Cyrillic_be б CYRILLIC SMALL LETTER BE */
+  { 0x06c3, 0x0446 }, /*                Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */
+  { 0x06c4, 0x0434 }, /*                 Cyrillic_de д CYRILLIC SMALL LETTER DE */
+  { 0x06c5, 0x0435 }, /*                 Cyrillic_ie е CYRILLIC SMALL LETTER IE */
+  { 0x06c6, 0x0444 }, /*                 Cyrillic_ef ф CYRILLIC SMALL LETTER EF */
+  { 0x06c7, 0x0433 }, /*                Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */
+  { 0x06c8, 0x0445 }, /*                 Cyrillic_ha х CYRILLIC SMALL LETTER HA */
+  { 0x06c9, 0x0438 }, /*                  Cyrillic_i и CYRILLIC SMALL LETTER I */
+  { 0x06ca, 0x0439 }, /*             Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */
+  { 0x06cb, 0x043a }, /*                 Cyrillic_ka к CYRILLIC SMALL LETTER KA */
+  { 0x06cc, 0x043b }, /*                 Cyrillic_el л CYRILLIC SMALL LETTER EL */
+  { 0x06cd, 0x043c }, /*                 Cyrillic_em м CYRILLIC SMALL LETTER EM */
+  { 0x06ce, 0x043d }, /*                 Cyrillic_en н CYRILLIC SMALL LETTER EN */
+  { 0x06cf, 0x043e }, /*                  Cyrillic_o о CYRILLIC SMALL LETTER O */
+  { 0x06d0, 0x043f }, /*                 Cyrillic_pe п CYRILLIC SMALL LETTER PE */
+  { 0x06d1, 0x044f }, /*                 Cyrillic_ya я CYRILLIC SMALL LETTER YA */
+  { 0x06d2, 0x0440 }, /*                 Cyrillic_er р CYRILLIC SMALL LETTER ER */
+  { 0x06d3, 0x0441 }, /*                 Cyrillic_es с CYRILLIC SMALL LETTER ES */
+  { 0x06d4, 0x0442 }, /*                 Cyrillic_te т CYRILLIC SMALL LETTER TE */
+  { 0x06d5, 0x0443 }, /*                  Cyrillic_u у CYRILLIC SMALL LETTER U */
+  { 0x06d6, 0x0436 }, /*                Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */
+  { 0x06d7, 0x0432 }, /*                 Cyrillic_ve в CYRILLIC SMALL LETTER VE */
+  { 0x06d8, 0x044c }, /*           Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */
+  { 0x06d9, 0x044b }, /*               Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */
+  { 0x06da, 0x0437 }, /*                 Cyrillic_ze з CYRILLIC SMALL LETTER ZE */
+  { 0x06db, 0x0448 }, /*                Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */
+  { 0x06dc, 0x044d }, /*                  Cyrillic_e э CYRILLIC SMALL LETTER E */
+  { 0x06dd, 0x0449 }, /*              Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */
+  { 0x06de, 0x0447 }, /*                Cyrillic_che ч CYRILLIC SMALL LETTER CHE */
+  { 0x06df, 0x044a }, /*           Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */
+  { 0x06e0, 0x042e }, /*                 Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */
+  { 0x06e1, 0x0410 }, /*                  Cyrillic_A А CYRILLIC CAPITAL LETTER A */
+  { 0x06e2, 0x0411 }, /*                 Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */
+  { 0x06e3, 0x0426 }, /*                Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */
+  { 0x06e4, 0x0414 }, /*                 Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */
+  { 0x06e5, 0x0415 }, /*                 Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */
+  { 0x06e6, 0x0424 }, /*                 Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */
+  { 0x06e7, 0x0413 }, /*                Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */
+  { 0x06e8, 0x0425 }, /*                 Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */
+  { 0x06e9, 0x0418 }, /*                  Cyrillic_I И CYRILLIC CAPITAL LETTER I */
+  { 0x06ea, 0x0419 }, /*             Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */
+  { 0x06eb, 0x041a }, /*                 Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */
+  { 0x06ec, 0x041b }, /*                 Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */
+  { 0x06ed, 0x041c }, /*                 Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */
+  { 0x06ee, 0x041d }, /*                 Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */
+  { 0x06ef, 0x041e }, /*                  Cyrillic_O О CYRILLIC CAPITAL LETTER O */
+  { 0x06f0, 0x041f }, /*                 Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */
+  { 0x06f1, 0x042f }, /*                 Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */
+  { 0x06f2, 0x0420 }, /*                 Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */
+  { 0x06f3, 0x0421 }, /*                 Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */
+  { 0x06f4, 0x0422 }, /*                 Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */
+  { 0x06f5, 0x0423 }, /*                  Cyrillic_U У CYRILLIC CAPITAL LETTER U */
+  { 0x06f6, 0x0416 }, /*                Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */
+  { 0x06f7, 0x0412 }, /*                 Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */
+  { 0x06f8, 0x042c }, /*           Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */
+  { 0x06f9, 0x042b }, /*               Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */
+  { 0x06fa, 0x0417 }, /*                 Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */
+  { 0x06fb, 0x0428 }, /*                Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */
+  { 0x06fc, 0x042d }, /*                  Cyrillic_E Э CYRILLIC CAPITAL LETTER E */
+  { 0x06fd, 0x0429 }, /*              Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */
+  { 0x06fe, 0x0427 }, /*                Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */
+  { 0x06ff, 0x042a }, /*           Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */
+  { 0x07a1, 0x0386 }, /*           Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */
+  { 0x07a2, 0x0388 }, /*         Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */
+  { 0x07a3, 0x0389 }, /*             Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */
+  { 0x07a4, 0x038a }, /*            Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */
+  { 0x07a5, 0x03aa }, /*         Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+  { 0x07a7, 0x038c }, /*         Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */
+  { 0x07a8, 0x038e }, /*         Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */
+  { 0x07a9, 0x03ab }, /*       Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+  { 0x07ab, 0x038f }, /*           Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */
+  { 0x07ae, 0x0385 }, /*        Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */
+  { 0x07af, 0x2015 }, /*              Greek_horizbar ― HORIZONTAL BAR */
+  { 0x07b1, 0x03ac }, /*           Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */
+  { 0x07b2, 0x03ad }, /*         Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */
+  { 0x07b3, 0x03ae }, /*             Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */
+  { 0x07b4, 0x03af }, /*            Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */
+  { 0x07b5, 0x03ca }, /*          Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */
+  { 0x07b6, 0x0390 }, /*    Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
+  { 0x07b7, 0x03cc }, /*         Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */
+  { 0x07b8, 0x03cd }, /*         Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */
+  { 0x07b9, 0x03cb }, /*       Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
+  { 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
+  { 0x07bb, 0x03ce }, /*           Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */
+  { 0x07c1, 0x0391 }, /*                 Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */
+  { 0x07c2, 0x0392 }, /*                  Greek_BETA Β GREEK CAPITAL LETTER BETA */
+  { 0x07c3, 0x0393 }, /*                 Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */
+  { 0x07c4, 0x0394 }, /*                 Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */
+  { 0x07c5, 0x0395 }, /*               Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */
+  { 0x07c6, 0x0396 }, /*                  Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */
+  { 0x07c7, 0x0397 }, /*                   Greek_ETA Η GREEK CAPITAL LETTER ETA */
+  { 0x07c8, 0x0398 }, /*                 Greek_THETA Θ GREEK CAPITAL LETTER THETA */
+  { 0x07c9, 0x0399 }, /*                  Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */
+  { 0x07ca, 0x039a }, /*                 Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */
+  { 0x07cb, 0x039b }, /*                Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */
+  { 0x07cc, 0x039c }, /*                    Greek_MU Μ GREEK CAPITAL LETTER MU */
+  { 0x07cd, 0x039d }, /*                    Greek_NU Ν GREEK CAPITAL LETTER NU */
+  { 0x07ce, 0x039e }, /*                    Greek_XI Ξ GREEK CAPITAL LETTER XI */
+  { 0x07cf, 0x039f }, /*               Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */
+  { 0x07d0, 0x03a0 }, /*                    Greek_PI Π GREEK CAPITAL LETTER PI */
+  { 0x07d1, 0x03a1 }, /*                   Greek_RHO Ρ GREEK CAPITAL LETTER RHO */
+  { 0x07d2, 0x03a3 }, /*                 Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */
+  { 0x07d4, 0x03a4 }, /*                   Greek_TAU Τ GREEK CAPITAL LETTER TAU */
+  { 0x07d5, 0x03a5 }, /*               Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */
+  { 0x07d6, 0x03a6 }, /*                   Greek_PHI Φ GREEK CAPITAL LETTER PHI */
+  { 0x07d7, 0x03a7 }, /*                   Greek_CHI Χ GREEK CAPITAL LETTER CHI */
+  { 0x07d8, 0x03a8 }, /*                   Greek_PSI Ψ GREEK CAPITAL LETTER PSI */
+  { 0x07d9, 0x03a9 }, /*                 Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */
+  { 0x07e1, 0x03b1 }, /*                 Greek_alpha α GREEK SMALL LETTER ALPHA */
+  { 0x07e2, 0x03b2 }, /*                  Greek_beta β GREEK SMALL LETTER BETA */
+  { 0x07e3, 0x03b3 }, /*                 Greek_gamma γ GREEK SMALL LETTER GAMMA */
+  { 0x07e4, 0x03b4 }, /*                 Greek_delta δ GREEK SMALL LETTER DELTA */
+  { 0x07e5, 0x03b5 }, /*               Greek_epsilon ε GREEK SMALL LETTER EPSILON */
+  { 0x07e6, 0x03b6 }, /*                  Greek_zeta ζ GREEK SMALL LETTER ZETA */
+  { 0x07e7, 0x03b7 }, /*                   Greek_eta η GREEK SMALL LETTER ETA */
+  { 0x07e8, 0x03b8 }, /*                 Greek_theta θ GREEK SMALL LETTER THETA */
+  { 0x07e9, 0x03b9 }, /*                  Greek_iota ι GREEK SMALL LETTER IOTA */
+  { 0x07ea, 0x03ba }, /*                 Greek_kappa κ GREEK SMALL LETTER KAPPA */
+  { 0x07eb, 0x03bb }, /*                Greek_lambda λ GREEK SMALL LETTER LAMDA */
+  { 0x07ec, 0x03bc }, /*                    Greek_mu μ GREEK SMALL LETTER MU */
+  { 0x07ed, 0x03bd }, /*                    Greek_nu ν GREEK SMALL LETTER NU */
+  { 0x07ee, 0x03be }, /*                    Greek_xi ξ GREEK SMALL LETTER XI */
+  { 0x07ef, 0x03bf }, /*               Greek_omicron ο GREEK SMALL LETTER OMICRON */
+  { 0x07f0, 0x03c0 }, /*                    Greek_pi π GREEK SMALL LETTER PI */
+  { 0x07f1, 0x03c1 }, /*                   Greek_rho ρ GREEK SMALL LETTER RHO */
+  { 0x07f2, 0x03c3 }, /*                 Greek_sigma σ GREEK SMALL LETTER SIGMA */
+  { 0x07f3, 0x03c2 }, /*       Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */
+  { 0x07f4, 0x03c4 }, /*                   Greek_tau τ GREEK SMALL LETTER TAU */
+  { 0x07f5, 0x03c5 }, /*               Greek_upsilon υ GREEK SMALL LETTER UPSILON */
+  { 0x07f6, 0x03c6 }, /*                   Greek_phi φ GREEK SMALL LETTER PHI */
+  { 0x07f7, 0x03c7 }, /*                   Greek_chi χ GREEK SMALL LETTER CHI */
+  { 0x07f8, 0x03c8 }, /*                   Greek_psi ψ GREEK SMALL LETTER PSI */
+  { 0x07f9, 0x03c9 }, /*                 Greek_omega ω GREEK SMALL LETTER OMEGA */
+  { 0x08a1, 0x23b7 }, /*                 leftradical ⎷ ??? */
+  { 0x08a2, 0x250c }, /*              topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
+  { 0x08a3, 0x2500 }, /*              horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */
+  { 0x08a4, 0x2320 }, /*                 topintegral ⌠ TOP HALF INTEGRAL */
+  { 0x08a5, 0x2321 }, /*                 botintegral ⌡ BOTTOM HALF INTEGRAL */
+  { 0x08a6, 0x2502 }, /*               vertconnector │ BOX DRAWINGS LIGHT VERTICAL */
+  { 0x08a7, 0x23a1 }, /*            topleftsqbracket ⎡ ??? */
+  { 0x08a8, 0x23a3 }, /*            botleftsqbracket ⎣ ??? */
+  { 0x08a9, 0x23a4 }, /*           toprightsqbracket ⎤ ??? */
+  { 0x08aa, 0x23a6 }, /*           botrightsqbracket ⎦ ??? */
+  { 0x08ab, 0x239b }, /*               topleftparens ⎛ ??? */
+  { 0x08ac, 0x239d }, /*               botleftparens ⎝ ??? */
+  { 0x08ad, 0x239e }, /*              toprightparens ⎞ ??? */
+  { 0x08ae, 0x23a0 }, /*              botrightparens ⎠ ??? */
+  { 0x08af, 0x23a8 }, /*        leftmiddlecurlybrace ⎨ ??? */
+  { 0x08b0, 0x23ac }, /*       rightmiddlecurlybrace ⎬ ??? */
+/*  0x08b1                          topleftsummation ? ??? */
+/*  0x08b2                          botleftsummation ? ??? */
+/*  0x08b3                 topvertsummationconnector ? ??? */
+/*  0x08b4                 botvertsummationconnector ? ??? */
+/*  0x08b5                         toprightsummation ? ??? */
+/*  0x08b6                         botrightsummation ? ??? */
+/*  0x08b7                      rightmiddlesummation ? ??? */
+  { 0x08bc, 0x2264 }, /*               lessthanequal ≤ LESS-THAN OR EQUAL TO */
+  { 0x08bd, 0x2260 }, /*                    notequal ≠ NOT EQUAL TO */
+  { 0x08be, 0x2265 }, /*            greaterthanequal ≥ GREATER-THAN OR EQUAL TO */
+  { 0x08bf, 0x222b }, /*                    integral ∫ INTEGRAL */
+  { 0x08c0, 0x2234 }, /*                   therefore ∴ THEREFORE */
+  { 0x08c1, 0x221d }, /*                   variation ∝ PROPORTIONAL TO */
+  { 0x08c2, 0x221e }, /*                    infinity ∞ INFINITY */
+  { 0x08c5, 0x2207 }, /*                       nabla ∇ NABLA */
+  { 0x08c8, 0x223c }, /*                 approximate ∼ TILDE OPERATOR */
+  { 0x08c9, 0x2243 }, /*                similarequal ≃ ASYMPTOTICALLY EQUAL TO */
+  { 0x08cd, 0x21d4 }, /*                    ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */
+  { 0x08ce, 0x21d2 }, /*                     implies ⇒ RIGHTWARDS DOUBLE ARROW */
+  { 0x08cf, 0x2261 }, /*                   identical ≡ IDENTICAL TO */
+  { 0x08d6, 0x221a }, /*                     radical √ SQUARE ROOT */
+  { 0x08da, 0x2282 }, /*                  includedin ⊂ SUBSET OF */
+  { 0x08db, 0x2283 }, /*                    includes ⊃ SUPERSET OF */
+  { 0x08dc, 0x2229 }, /*                intersection ∩ INTERSECTION */
+  { 0x08dd, 0x222a }, /*                       union ∪ UNION */
+  { 0x08de, 0x2227 }, /*                  logicaland ∧ LOGICAL AND */
+  { 0x08df, 0x2228 }, /*                   logicalor ∨ LOGICAL OR */
+  { 0x08ef, 0x2202 }, /*           partialderivative ∂ PARTIAL DIFFERENTIAL */
+  { 0x08f6, 0x0192 }, /*                    function ƒ LATIN SMALL LETTER F WITH HOOK */
+  { 0x08fb, 0x2190 }, /*                   leftarrow ← LEFTWARDS ARROW */
+  { 0x08fc, 0x2191 }, /*                     uparrow ↑ UPWARDS ARROW */
+  { 0x08fd, 0x2192 }, /*                  rightarrow → RIGHTWARDS ARROW */
+  { 0x08fe, 0x2193 }, /*                   downarrow ↓ DOWNWARDS ARROW */
+/*  0x09df                                     blank ? ??? */
+  { 0x09e0, 0x25c6 }, /*                soliddiamond ◆ BLACK DIAMOND */
+  { 0x09e1, 0x2592 }, /*                checkerboard ▒ MEDIUM SHADE */
+  { 0x09e2, 0x2409 }, /*                          ht ␉ SYMBOL FOR HORIZONTAL TABULATION */
+  { 0x09e3, 0x240c }, /*                          ff ␌ SYMBOL FOR FORM FEED */
+  { 0x09e4, 0x240d }, /*                          cr ␍ SYMBOL FOR CARRIAGE RETURN */
+  { 0x09e5, 0x240a }, /*                          lf ␊ SYMBOL FOR LINE FEED */
+  { 0x09e8, 0x2424 }, /*                          nl ␤ SYMBOL FOR NEWLINE */
+  { 0x09e9, 0x240b }, /*                          vt ␋ SYMBOL FOR VERTICAL TABULATION */
+  { 0x09ea, 0x2518 }, /*              lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */
+  { 0x09eb, 0x2510 }, /*               uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */
+  { 0x09ec, 0x250c }, /*                upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
+  { 0x09ed, 0x2514 }, /*               lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */
+  { 0x09ee, 0x253c }, /*               crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+  { 0x09ef, 0x23ba }, /*              horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */
+  { 0x09f0, 0x23bb }, /*              horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */
+  { 0x09f1, 0x2500 }, /*              horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */
+  { 0x09f2, 0x23bc }, /*              horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */
+  { 0x09f3, 0x23bd }, /*              horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */
+  { 0x09f4, 0x251c }, /*                       leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+  { 0x09f5, 0x2524 }, /*                      rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+  { 0x09f6, 0x2534 }, /*                        bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+  { 0x09f7, 0x252c }, /*                        topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+  { 0x09f8, 0x2502 }, /*                     vertbar │ BOX DRAWINGS LIGHT VERTICAL */
+  { 0x0aa1, 0x2003 }, /*                     emspace   EM SPACE */
+  { 0x0aa2, 0x2002 }, /*                     enspace   EN SPACE */
+  { 0x0aa3, 0x2004 }, /*                    em3space   THREE-PER-EM SPACE */
+  { 0x0aa4, 0x2005 }, /*                    em4space   FOUR-PER-EM SPACE */
+  { 0x0aa5, 0x2007 }, /*                  digitspace   FIGURE SPACE */
+  { 0x0aa6, 0x2008 }, /*                  punctspace   PUNCTUATION SPACE */
+  { 0x0aa7, 0x2009 }, /*                   thinspace   THIN SPACE */
+  { 0x0aa8, 0x200a }, /*                   hairspace   HAIR SPACE */
+  { 0x0aa9, 0x2014 }, /*                      emdash — EM DASH */
+  { 0x0aaa, 0x2013 }, /*                      endash – EN DASH */
+/*  0x0aac                               signifblank ? ??? */
+  { 0x0aae, 0x2026 }, /*                    ellipsis … HORIZONTAL ELLIPSIS */
+  { 0x0aaf, 0x2025 }, /*             doubbaselinedot ‥ TWO DOT LEADER */
+  { 0x0ab0, 0x2153 }, /*                    onethird ⅓ VULGAR FRACTION ONE THIRD */
+  { 0x0ab1, 0x2154 }, /*                   twothirds ⅔ VULGAR FRACTION TWO THIRDS */
+  { 0x0ab2, 0x2155 }, /*                    onefifth ⅕ VULGAR FRACTION ONE FIFTH */
+  { 0x0ab3, 0x2156 }, /*                   twofifths ⅖ VULGAR FRACTION TWO FIFTHS */
+  { 0x0ab4, 0x2157 }, /*                 threefifths ⅗ VULGAR FRACTION THREE FIFTHS */
+  { 0x0ab5, 0x2158 }, /*                  fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */
+  { 0x0ab6, 0x2159 }, /*                    onesixth ⅙ VULGAR FRACTION ONE SIXTH */
+  { 0x0ab7, 0x215a }, /*                  fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */
+  { 0x0ab8, 0x2105 }, /*                      careof ℅ CARE OF */
+  { 0x0abb, 0x2012 }, /*                     figdash ‒ FIGURE DASH */
+  { 0x0abc, 0x2329 }, /*            leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */
+/*  0x0abd                              decimalpoint ? ??? */
+  { 0x0abe, 0x232a }, /*           rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */
+/*  0x0abf                                    marker ? ??? */
+  { 0x0ac3, 0x215b }, /*                   oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */
+  { 0x0ac4, 0x215c }, /*                threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */
+  { 0x0ac5, 0x215d }, /*                 fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */
+  { 0x0ac6, 0x215e }, /*                seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */
+  { 0x0ac9, 0x2122 }, /*                   trademark ™ TRADE MARK SIGN */
+  { 0x0aca, 0x2613 }, /*               signaturemark ☓ SALTIRE */
+/*  0x0acb                         trademarkincircle ? ??? */
+  { 0x0acc, 0x25c1 }, /*            leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */
+  { 0x0acd, 0x25b7 }, /*           rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */
+  { 0x0ace, 0x25cb }, /*                emopencircle ○ WHITE CIRCLE */
+  { 0x0acf, 0x25af }, /*             emopenrectangle ▯ WHITE VERTICAL RECTANGLE */
+  { 0x0ad0, 0x2018 }, /*         leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */
+  { 0x0ad1, 0x2019 }, /*        rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */
+  { 0x0ad2, 0x201c }, /*         leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */
+  { 0x0ad3, 0x201d }, /*        rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */
+  { 0x0ad4, 0x211e }, /*                prescription ℞ PRESCRIPTION TAKE */
+  { 0x0ad6, 0x2032 }, /*                     minutes ′ PRIME */
+  { 0x0ad7, 0x2033 }, /*                     seconds ″ DOUBLE PRIME */
+  { 0x0ad9, 0x271d }, /*                  latincross ✝ LATIN CROSS */
+/*  0x0ada                                  hexagram ? ??? */
+  { 0x0adb, 0x25ac }, /*            filledrectbullet ▬ BLACK RECTANGLE */
+  { 0x0adc, 0x25c0 }, /*         filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */
+  { 0x0add, 0x25b6 }, /*        filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */
+  { 0x0ade, 0x25cf }, /*              emfilledcircle ● BLACK CIRCLE */
+  { 0x0adf, 0x25ae }, /*                emfilledrect ▮ BLACK VERTICAL RECTANGLE */
+  { 0x0ae0, 0x25e6 }, /*            enopencircbullet ◦ WHITE BULLET */
+  { 0x0ae1, 0x25ab }, /*          enopensquarebullet ▫ WHITE SMALL SQUARE */
+  { 0x0ae2, 0x25ad }, /*              openrectbullet ▭ WHITE RECTANGLE */
+  { 0x0ae3, 0x25b3 }, /*             opentribulletup △ WHITE UP-POINTING TRIANGLE */
+  { 0x0ae4, 0x25bd }, /*           opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */
+  { 0x0ae5, 0x2606 }, /*                    openstar ☆ WHITE STAR */
+  { 0x0ae6, 0x2022 }, /*          enfilledcircbullet • BULLET */
+  { 0x0ae7, 0x25aa }, /*            enfilledsqbullet ▪ BLACK SMALL SQUARE */
+  { 0x0ae8, 0x25b2 }, /*           filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */
+  { 0x0ae9, 0x25bc }, /*         filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */
+  { 0x0aea, 0x261c }, /*                 leftpointer ☜ WHITE LEFT POINTING INDEX */
+  { 0x0aeb, 0x261e }, /*                rightpointer ☞ WHITE RIGHT POINTING INDEX */
+  { 0x0aec, 0x2663 }, /*                        club ♣ BLACK CLUB SUIT */
+  { 0x0aed, 0x2666 }, /*                     diamond ♦ BLACK DIAMOND SUIT */
+  { 0x0aee, 0x2665 }, /*                       heart ♥ BLACK HEART SUIT */
+  { 0x0af0, 0x2720 }, /*                maltesecross ✠ MALTESE CROSS */
+  { 0x0af1, 0x2020 }, /*                      dagger † DAGGER */
+  { 0x0af2, 0x2021 }, /*                doubledagger ‡ DOUBLE DAGGER */
+  { 0x0af3, 0x2713 }, /*                   checkmark ✓ CHECK MARK */
+  { 0x0af4, 0x2717 }, /*                 ballotcross ✗ BALLOT X */
+  { 0x0af5, 0x266f }, /*                musicalsharp ♯ MUSIC SHARP SIGN */
+  { 0x0af6, 0x266d }, /*                 musicalflat ♭ MUSIC FLAT SIGN */
+  { 0x0af7, 0x2642 }, /*                  malesymbol ♂ MALE SIGN */
+  { 0x0af8, 0x2640 }, /*                femalesymbol ♀ FEMALE SIGN */
+  { 0x0af9, 0x260e }, /*                   telephone ☎ BLACK TELEPHONE */
+  { 0x0afa, 0x2315 }, /*           telephonerecorder ⌕ TELEPHONE RECORDER */
+  { 0x0afb, 0x2117 }, /*         phonographcopyright ℗ SOUND RECORDING COPYRIGHT */
+  { 0x0afc, 0x2038 }, /*                       caret ‸ CARET */
+  { 0x0afd, 0x201a }, /*          singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */
+  { 0x0afe, 0x201e }, /*          doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */
+/*  0x0aff                                    cursor ? ??? */
+  { 0x0ba3, 0x003c }, /*                   leftcaret < LESS-THAN SIGN */
+  { 0x0ba6, 0x003e }, /*                  rightcaret > GREATER-THAN SIGN */
+  { 0x0ba8, 0x2228 }, /*                   downcaret ∨ LOGICAL OR */
+  { 0x0ba9, 0x2227 }, /*                     upcaret ∧ LOGICAL AND */
+  { 0x0bc0, 0x00af }, /*                     overbar ¯ MACRON */
+  { 0x0bc2, 0x22a5 }, /*                    downtack ⊥ UP TACK */
+  { 0x0bc3, 0x2229 }, /*                      upshoe ∩ INTERSECTION */
+  { 0x0bc4, 0x230a }, /*                   downstile ⌊ LEFT FLOOR */
+  { 0x0bc6, 0x005f }, /*                    underbar _ LOW LINE */
+  { 0x0bca, 0x2218 }, /*                         jot ∘ RING OPERATOR */
+  { 0x0bcc, 0x2395 }, /*                        quad ⎕ APL FUNCTIONAL SYMBOL QUAD */
+  { 0x0bce, 0x22a4 }, /*                      uptack ⊤ DOWN TACK */
+  { 0x0bcf, 0x25cb }, /*                      circle ○ WHITE CIRCLE */
+  { 0x0bd3, 0x2308 }, /*                     upstile ⌈ LEFT CEILING */
+  { 0x0bd6, 0x222a }, /*                    downshoe ∪ UNION */
+  { 0x0bd8, 0x2283 }, /*                   rightshoe ⊃ SUPERSET OF */
+  { 0x0bda, 0x2282 }, /*                    leftshoe ⊂ SUBSET OF */
+  { 0x0bdc, 0x22a2 }, /*                    lefttack ⊢ RIGHT TACK */
+  { 0x0bfc, 0x22a3 }, /*                   righttack ⊣ LEFT TACK */
+  { 0x0cdf, 0x2017 }, /*        hebrew_doublelowline ‗ DOUBLE LOW LINE */
+  { 0x0ce0, 0x05d0 }, /*                hebrew_aleph א HEBREW LETTER ALEF */
+  { 0x0ce1, 0x05d1 }, /*                  hebrew_bet ב HEBREW LETTER BET */
+  { 0x0ce2, 0x05d2 }, /*                hebrew_gimel ג HEBREW LETTER GIMEL */
+  { 0x0ce3, 0x05d3 }, /*                hebrew_dalet ד HEBREW LETTER DALET */
+  { 0x0ce4, 0x05d4 }, /*                   hebrew_he ה HEBREW LETTER HE */
+  { 0x0ce5, 0x05d5 }, /*                  hebrew_waw ו HEBREW LETTER VAV */
+  { 0x0ce6, 0x05d6 }, /*                 hebrew_zain ז HEBREW LETTER ZAYIN */
+  { 0x0ce7, 0x05d7 }, /*                 hebrew_chet ח HEBREW LETTER HET */
+  { 0x0ce8, 0x05d8 }, /*                  hebrew_tet ט HEBREW LETTER TET */
+  { 0x0ce9, 0x05d9 }, /*                  hebrew_yod י HEBREW LETTER YOD */
+  { 0x0cea, 0x05da }, /*            hebrew_finalkaph ך HEBREW LETTER FINAL KAF */
+  { 0x0ceb, 0x05db }, /*                 hebrew_kaph כ HEBREW LETTER KAF */
+  { 0x0cec, 0x05dc }, /*                hebrew_lamed ל HEBREW LETTER LAMED */
+  { 0x0ced, 0x05dd }, /*             hebrew_finalmem ם HEBREW LETTER FINAL MEM */
+  { 0x0cee, 0x05de }, /*                  hebrew_mem מ HEBREW LETTER MEM */
+  { 0x0cef, 0x05df }, /*             hebrew_finalnun ן HEBREW LETTER FINAL NUN */
+  { 0x0cf0, 0x05e0 }, /*                  hebrew_nun נ HEBREW LETTER NUN */
+  { 0x0cf1, 0x05e1 }, /*               hebrew_samech ס HEBREW LETTER SAMEKH */
+  { 0x0cf2, 0x05e2 }, /*                 hebrew_ayin ע HEBREW LETTER AYIN */
+  { 0x0cf3, 0x05e3 }, /*              hebrew_finalpe ף HEBREW LETTER FINAL PE */
+  { 0x0cf4, 0x05e4 }, /*                   hebrew_pe פ HEBREW LETTER PE */
+  { 0x0cf5, 0x05e5 }, /*            hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */
+  { 0x0cf6, 0x05e6 }, /*                 hebrew_zade צ HEBREW LETTER TSADI */
+  { 0x0cf7, 0x05e7 }, /*                 hebrew_qoph ק HEBREW LETTER QOF */
+  { 0x0cf8, 0x05e8 }, /*                 hebrew_resh ר HEBREW LETTER RESH */
+  { 0x0cf9, 0x05e9 }, /*                 hebrew_shin ש HEBREW LETTER SHIN */
+  { 0x0cfa, 0x05ea }, /*                  hebrew_taw ת HEBREW LETTER TAV */
+  { 0x0da1, 0x0e01 }, /*                  Thai_kokai ก THAI CHARACTER KO KAI */
+  { 0x0da2, 0x0e02 }, /*                Thai_khokhai ข THAI CHARACTER KHO KHAI */
+  { 0x0da3, 0x0e03 }, /*               Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */
+  { 0x0da4, 0x0e04 }, /*               Thai_khokhwai ค THAI CHARACTER KHO KHWAI */
+  { 0x0da5, 0x0e05 }, /*                Thai_khokhon ฅ THAI CHARACTER KHO KHON */
+  { 0x0da6, 0x0e06 }, /*             Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */
+  { 0x0da7, 0x0e07 }, /*                 Thai_ngongu ง THAI CHARACTER NGO NGU */
+  { 0x0da8, 0x0e08 }, /*                Thai_chochan จ THAI CHARACTER CHO CHAN */
+  { 0x0da9, 0x0e09 }, /*               Thai_choching ฉ THAI CHARACTER CHO CHING */
+  { 0x0daa, 0x0e0a }, /*               Thai_chochang ช THAI CHARACTER CHO CHANG */
+  { 0x0dab, 0x0e0b }, /*                   Thai_soso ซ THAI CHARACTER SO SO */
+  { 0x0dac, 0x0e0c }, /*                Thai_chochoe ฌ THAI CHARACTER CHO CHOE */
+  { 0x0dad, 0x0e0d }, /*                 Thai_yoying ญ THAI CHARACTER YO YING */
+  { 0x0dae, 0x0e0e }, /*                Thai_dochada ฎ THAI CHARACTER DO CHADA */
+  { 0x0daf, 0x0e0f }, /*                Thai_topatak ฏ THAI CHARACTER TO PATAK */
+  { 0x0db0, 0x0e10 }, /*                Thai_thothan ฐ THAI CHARACTER THO THAN */
+  { 0x0db1, 0x0e11 }, /*          Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */
+  { 0x0db2, 0x0e12 }, /*             Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */
+  { 0x0db3, 0x0e13 }, /*                  Thai_nonen ณ THAI CHARACTER NO NEN */
+  { 0x0db4, 0x0e14 }, /*                  Thai_dodek ด THAI CHARACTER DO DEK */
+  { 0x0db5, 0x0e15 }, /*                  Thai_totao ต THAI CHARACTER TO TAO */
+  { 0x0db6, 0x0e16 }, /*               Thai_thothung ถ THAI CHARACTER THO THUNG */
+  { 0x0db7, 0x0e17 }, /*              Thai_thothahan ท THAI CHARACTER THO THAHAN */
+  { 0x0db8, 0x0e18 }, /*               Thai_thothong ธ THAI CHARACTER THO THONG */
+  { 0x0db9, 0x0e19 }, /*                   Thai_nonu น THAI CHARACTER NO NU */
+  { 0x0dba, 0x0e1a }, /*               Thai_bobaimai บ THAI CHARACTER BO BAIMAI */
+  { 0x0dbb, 0x0e1b }, /*                  Thai_popla ป THAI CHARACTER PO PLA */
+  { 0x0dbc, 0x0e1c }, /*               Thai_phophung ผ THAI CHARACTER PHO PHUNG */
+  { 0x0dbd, 0x0e1d }, /*                   Thai_fofa ฝ THAI CHARACTER FO FA */
+  { 0x0dbe, 0x0e1e }, /*                Thai_phophan พ THAI CHARACTER PHO PHAN */
+  { 0x0dbf, 0x0e1f }, /*                  Thai_fofan ฟ THAI CHARACTER FO FAN */
+  { 0x0dc0, 0x0e20 }, /*             Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */
+  { 0x0dc1, 0x0e21 }, /*                   Thai_moma ม THAI CHARACTER MO MA */
+  { 0x0dc2, 0x0e22 }, /*                  Thai_yoyak ย THAI CHARACTER YO YAK */
+  { 0x0dc3, 0x0e23 }, /*                  Thai_rorua ร THAI CHARACTER RO RUA */
+  { 0x0dc4, 0x0e24 }, /*                     Thai_ru ฤ THAI CHARACTER RU */
+  { 0x0dc5, 0x0e25 }, /*                 Thai_loling ล THAI CHARACTER LO LING */
+  { 0x0dc6, 0x0e26 }, /*                     Thai_lu ฦ THAI CHARACTER LU */
+  { 0x0dc7, 0x0e27 }, /*                 Thai_wowaen ว THAI CHARACTER WO WAEN */
+  { 0x0dc8, 0x0e28 }, /*                 Thai_sosala ศ THAI CHARACTER SO SALA */
+  { 0x0dc9, 0x0e29 }, /*                 Thai_sorusi ษ THAI CHARACTER SO RUSI */
+  { 0x0dca, 0x0e2a }, /*                  Thai_sosua ส THAI CHARACTER SO SUA */
+  { 0x0dcb, 0x0e2b }, /*                  Thai_hohip ห THAI CHARACTER HO HIP */
+  { 0x0dcc, 0x0e2c }, /*                Thai_lochula ฬ THAI CHARACTER LO CHULA */
+  { 0x0dcd, 0x0e2d }, /*                   Thai_oang อ THAI CHARACTER O ANG */
+  { 0x0dce, 0x0e2e }, /*               Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */
+  { 0x0dcf, 0x0e2f }, /*              Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */
+  { 0x0dd0, 0x0e30 }, /*                  Thai_saraa ะ THAI CHARACTER SARA A */
+  { 0x0dd1, 0x0e31 }, /*             Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */
+  { 0x0dd2, 0x0e32 }, /*                 Thai_saraaa า THAI CHARACTER SARA AA */
+  { 0x0dd3, 0x0e33 }, /*                 Thai_saraam ำ THAI CHARACTER SARA AM */
+  { 0x0dd4, 0x0e34 }, /*                  Thai_sarai ิ THAI CHARACTER SARA I */
+  { 0x0dd5, 0x0e35 }, /*                 Thai_saraii ี THAI CHARACTER SARA II */
+  { 0x0dd6, 0x0e36 }, /*                 Thai_saraue ึ THAI CHARACTER SARA UE */
+  { 0x0dd7, 0x0e37 }, /*                Thai_sarauee ื THAI CHARACTER SARA UEE */
+  { 0x0dd8, 0x0e38 }, /*                  Thai_sarau ุ THAI CHARACTER SARA U */
+  { 0x0dd9, 0x0e39 }, /*                 Thai_sarauu ู THAI CHARACTER SARA UU */
+  { 0x0dda, 0x0e3a }, /*                Thai_phinthu ฺ THAI CHARACTER PHINTHU */
+/*  0x0dde                    Thai_maihanakat_maitho ? ??? */
+  { 0x0ddf, 0x0e3f }, /*                   Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */
+  { 0x0de0, 0x0e40 }, /*                  Thai_sarae เ THAI CHARACTER SARA E */
+  { 0x0de1, 0x0e41 }, /*                 Thai_saraae แ THAI CHARACTER SARA AE */
+  { 0x0de2, 0x0e42 }, /*                  Thai_sarao โ THAI CHARACTER SARA O */
+  { 0x0de3, 0x0e43 }, /*          Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */
+  { 0x0de4, 0x0e44 }, /*         Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */
+  { 0x0de5, 0x0e45 }, /*            Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */
+  { 0x0de6, 0x0e46 }, /*               Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */
+  { 0x0de7, 0x0e47 }, /*              Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */
+  { 0x0de8, 0x0e48 }, /*                  Thai_maiek ่ THAI CHARACTER MAI EK */
+  { 0x0de9, 0x0e49 }, /*                 Thai_maitho ้ THAI CHARACTER MAI THO */
+  { 0x0dea, 0x0e4a }, /*                 Thai_maitri ๊ THAI CHARACTER MAI TRI */
+  { 0x0deb, 0x0e4b }, /*            Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */
+  { 0x0dec, 0x0e4c }, /*            Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */
+  { 0x0ded, 0x0e4d }, /*               Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */
+  { 0x0df0, 0x0e50 }, /*                 Thai_leksun ๐ THAI DIGIT ZERO */
+  { 0x0df1, 0x0e51 }, /*                Thai_leknung ๑ THAI DIGIT ONE */
+  { 0x0df2, 0x0e52 }, /*                Thai_leksong ๒ THAI DIGIT TWO */
+  { 0x0df3, 0x0e53 }, /*                 Thai_leksam ๓ THAI DIGIT THREE */
+  { 0x0df4, 0x0e54 }, /*                  Thai_leksi ๔ THAI DIGIT FOUR */
+  { 0x0df5, 0x0e55 }, /*                  Thai_lekha ๕ THAI DIGIT FIVE */
+  { 0x0df6, 0x0e56 }, /*                 Thai_lekhok ๖ THAI DIGIT SIX */
+  { 0x0df7, 0x0e57 }, /*                Thai_lekchet ๗ THAI DIGIT SEVEN */
+  { 0x0df8, 0x0e58 }, /*                Thai_lekpaet ๘ THAI DIGIT EIGHT */
+  { 0x0df9, 0x0e59 }, /*                 Thai_lekkao ๙ THAI DIGIT NINE */
+  { 0x0ea1, 0x3131 }, /*               Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */
+  { 0x0ea2, 0x3132 }, /*          Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */
+  { 0x0ea3, 0x3133 }, /*           Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */
+  { 0x0ea4, 0x3134 }, /*                Hangul_Nieun ㄴ HANGUL LETTER NIEUN */
+  { 0x0ea5, 0x3135 }, /*           Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */
+  { 0x0ea6, 0x3136 }, /*           Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */
+  { 0x0ea7, 0x3137 }, /*               Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */
+  { 0x0ea8, 0x3138 }, /*          Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */
+  { 0x0ea9, 0x3139 }, /*                Hangul_Rieul ㄹ HANGUL LETTER RIEUL */
+  { 0x0eaa, 0x313a }, /*          Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */
+  { 0x0eab, 0x313b }, /*           Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */
+  { 0x0eac, 0x313c }, /*           Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */
+  { 0x0ead, 0x313d }, /*            Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */
+  { 0x0eae, 0x313e }, /*           Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */
+  { 0x0eaf, 0x313f }, /*          Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */
+  { 0x0eb0, 0x3140 }, /*           Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */
+  { 0x0eb1, 0x3141 }, /*                Hangul_Mieum ㅁ HANGUL LETTER MIEUM */
+  { 0x0eb2, 0x3142 }, /*                Hangul_Pieub ㅂ HANGUL LETTER PIEUP */
+  { 0x0eb3, 0x3143 }, /*           Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */
+  { 0x0eb4, 0x3144 }, /*            Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */
+  { 0x0eb5, 0x3145 }, /*                 Hangul_Sios ㅅ HANGUL LETTER SIOS */
+  { 0x0eb6, 0x3146 }, /*            Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */
+  { 0x0eb7, 0x3147 }, /*                Hangul_Ieung ㅇ HANGUL LETTER IEUNG */
+  { 0x0eb8, 0x3148 }, /*                Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */
+  { 0x0eb9, 0x3149 }, /*           Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */
+  { 0x0eba, 0x314a }, /*                Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */
+  { 0x0ebb, 0x314b }, /*               Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */
+  { 0x0ebc, 0x314c }, /*                Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */
+  { 0x0ebd, 0x314d }, /*               Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */
+  { 0x0ebe, 0x314e }, /*                Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */
+  { 0x0ebf, 0x314f }, /*                    Hangul_A ㅏ HANGUL LETTER A */
+  { 0x0ec0, 0x3150 }, /*                   Hangul_AE ㅐ HANGUL LETTER AE */
+  { 0x0ec1, 0x3151 }, /*                   Hangul_YA ㅑ HANGUL LETTER YA */
+  { 0x0ec2, 0x3152 }, /*                  Hangul_YAE ㅒ HANGUL LETTER YAE */
+  { 0x0ec3, 0x3153 }, /*                   Hangul_EO ㅓ HANGUL LETTER EO */
+  { 0x0ec4, 0x3154 }, /*                    Hangul_E ㅔ HANGUL LETTER E */
+  { 0x0ec5, 0x3155 }, /*                  Hangul_YEO ㅕ HANGUL LETTER YEO */
+  { 0x0ec6, 0x3156 }, /*                   Hangul_YE ㅖ HANGUL LETTER YE */
+  { 0x0ec7, 0x3157 }, /*                    Hangul_O ㅗ HANGUL LETTER O */
+  { 0x0ec8, 0x3158 }, /*                   Hangul_WA ㅘ HANGUL LETTER WA */
+  { 0x0ec9, 0x3159 }, /*                  Hangul_WAE ㅙ HANGUL LETTER WAE */
+  { 0x0eca, 0x315a }, /*                   Hangul_OE ㅚ HANGUL LETTER OE */
+  { 0x0ecb, 0x315b }, /*                   Hangul_YO ㅛ HANGUL LETTER YO */
+  { 0x0ecc, 0x315c }, /*                    Hangul_U ㅜ HANGUL LETTER U */
+  { 0x0ecd, 0x315d }, /*                  Hangul_WEO ㅝ HANGUL LETTER WEO */
+  { 0x0ece, 0x315e }, /*                   Hangul_WE ㅞ HANGUL LETTER WE */
+  { 0x0ecf, 0x315f }, /*                   Hangul_WI ㅟ HANGUL LETTER WI */
+  { 0x0ed0, 0x3160 }, /*                   Hangul_YU ㅠ HANGUL LETTER YU */
+  { 0x0ed1, 0x3161 }, /*                   Hangul_EU ㅡ HANGUL LETTER EU */
+  { 0x0ed2, 0x3162 }, /*                   Hangul_YI ㅢ HANGUL LETTER YI */
+  { 0x0ed3, 0x3163 }, /*                    Hangul_I ㅣ HANGUL LETTER I */
+  { 0x0ed4, 0x11a8 }, /*             Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */
+  { 0x0ed5, 0x11a9 }, /*        Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */
+  { 0x0ed6, 0x11aa }, /*         Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */
+  { 0x0ed7, 0x11ab }, /*              Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */
+  { 0x0ed8, 0x11ac }, /*         Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */
+  { 0x0ed9, 0x11ad }, /*         Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */
+  { 0x0eda, 0x11ae }, /*             Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */
+  { 0x0edb, 0x11af }, /*              Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */
+  { 0x0edc, 0x11b0 }, /*        Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */
+  { 0x0edd, 0x11b1 }, /*         Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */
+  { 0x0ede, 0x11b2 }, /*         Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */
+  { 0x0edf, 0x11b3 }, /*          Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */
+  { 0x0ee0, 0x11b4 }, /*         Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */
+  { 0x0ee1, 0x11b5 }, /*        Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */
+  { 0x0ee2, 0x11b6 }, /*         Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */
+  { 0x0ee3, 0x11b7 }, /*              Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */
+  { 0x0ee4, 0x11b8 }, /*              Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */
+  { 0x0ee5, 0x11b9 }, /*          Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */
+  { 0x0ee6, 0x11ba }, /*               Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */
+  { 0x0ee7, 0x11bb }, /*          Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */
+  { 0x0ee8, 0x11bc }, /*              Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */
+  { 0x0ee9, 0x11bd }, /*              Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */
+  { 0x0eea, 0x11be }, /*              Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */
+  { 0x0eeb, 0x11bf }, /*             Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */
+  { 0x0eec, 0x11c0 }, /*              Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */
+  { 0x0eed, 0x11c1 }, /*             Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */
+  { 0x0eee, 0x11c2 }, /*              Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */
+  { 0x0eef, 0x316d }, /*     Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */
+  { 0x0ef0, 0x3171 }, /*    Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */
+  { 0x0ef1, 0x3178 }, /*    Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */
+  { 0x0ef2, 0x317f }, /*              Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */
+  { 0x0ef3, 0x3181 }, /*    Hangul_KkogjiDalrinIeung ㆁ HANGUL LETTER YESIEUNG */
+  { 0x0ef4, 0x3184 }, /*   Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */
+  { 0x0ef5, 0x3186 }, /*          Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */
+  { 0x0ef6, 0x318d }, /*                Hangul_AraeA ㆍ HANGUL LETTER ARAEA */
+  { 0x0ef7, 0x318e }, /*               Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */
+  { 0x0ef8, 0x11eb }, /*            Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */
+  { 0x0ef9, 0x11f0 }, /*  Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */
+  { 0x0efa, 0x11f9 }, /*        Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */
+  { 0x0eff, 0x20a9 }, /*                  Korean_Won ₩ WON SIGN */
+  { 0x13a4, 0x20ac }, /*                        Euro € EURO SIGN */
+  { 0x13bc, 0x0152 }, /*                          OE ΠLATIN CAPITAL LIGATURE OE */
+  { 0x13bd, 0x0153 }, /*                          oe œ LATIN SMALL LIGATURE OE */
+  { 0x13be, 0x0178 }, /*                  Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */
+  { 0x20ac, 0x20ac }, /*                    EuroSign € EURO SIGN */
+};
+
+VISIBLE
+long keysym2ucs(KeySym keysym)
+{
+    int min = 0;
+    int max = sizeof(keysymtab) / sizeof(struct codepair) - 1;
+    int mid;
+
+    /* first check for Latin-1 characters (1:1 mapping) */
+    if ((keysym >= 0x0020 && keysym <= 0x007e) ||
+        (keysym >= 0x00a0 && keysym <= 0x00ff))
+        return keysym;
+
+    /* also check for directly encoded 24-bit UCS characters */
+    if ((keysym & 0xff000000) == 0x01000000)
+	return keysym & 0x00ffffff;
+
+    /* binary search in table */
+    while (max >= min) {
+	mid = (min + max) / 2;
+	if (keysymtab[mid].keysym < keysym)
+	    min = mid + 1;
+	else if (keysymtab[mid].keysym > keysym)
+	    max = mid - 1;
+	else {
+	    /* found it */
+	    return keysymtab[mid].ucs;
+	}
+    }
+
+    /* no matching Unicode value found */
+    return -1;
+}
--- /dev/null
+++ b/gui-x11/keysym2ucs.h
@@ -1,0 +1,9 @@
+/* $XFree86: xc/programs/xterm/keysym2ucs.h,v 1.1 1999/06/12 15:37:18 dawes Exp $ */
+/*
+ * This module converts keysym values into the corresponding ISO 10646-1
+ * (UCS, Unicode) values.
+ */
+
+#include <X11/X.h>
+
+long keysym2ucs(KeySym keysym);
--- /dev/null
+++ b/gui-x11/ksym2utf.h
@@ -1,0 +1,754 @@
+static ulong
+ksym2utf[] = {
+	[0x01a1]	0x0104,
+	[0x01a2]	0x02d8,
+	[0x01a3]	0x0141,
+	[0x01a5]	0x013d,
+	[0x01a6]	0x015a,
+	[0x01a9]	0x0160,
+	[0x01aa]	0x015e,
+	[0x01ab]	0x0164,
+	[0x01ac]	0x0179,
+	[0x01ae]	0x017d,
+	[0x01af]	0x017b,
+	[0x01b1]	0x0105,
+	[0x01b2]	0x02db,
+	[0x01b3]	0x0142,
+	[0x01b5]	0x013e,
+	[0x01b6]	0x015b,
+	[0x01b7]	0x02c7,
+	[0x01b9]	0x0161,
+	[0x01ba]	0x015f,
+	[0x01bb]	0x0165,
+	[0x01bc]	0x017a,
+	[0x01bd]	0x02dd,
+	[0x01be]	0x017e,
+	[0x01bf]	0x017c,
+	[0x01c0]	0x0154,
+	[0x01c3]	0x0102,
+	[0x01c5]	0x0139,
+	[0x01c6]	0x0106,
+	[0x01c8]	0x010c,
+	[0x01ca]	0x0118,
+	[0x01cc]	0x011a,
+	[0x01cf]	0x010e,
+	[0x01d0]	0x0110,
+	[0x01d1]	0x0143,
+	[0x01d2]	0x0147,
+	[0x01d5]	0x0150,
+	[0x01d8]	0x0158,
+	[0x01d9]	0x016e,
+	[0x01db]	0x0170,
+	[0x01de]	0x0162,
+	[0x01e0]	0x0155,
+	[0x01e3]	0x0103,
+	[0x01e5]	0x013a,
+	[0x01e6]	0x0107,
+	[0x01e8]	0x010d,
+	[0x01ea]	0x0119,
+	[0x01ec]	0x011b,
+	[0x01ef]	0x010f,
+	[0x01f0]	0x0111,
+	[0x01f1]	0x0144,
+	[0x01f2]	0x0148,
+	[0x01f5]	0x0151,
+	[0x01f8]	0x0159,
+	[0x01f9]	0x016f,
+	[0x01fb]	0x0171,
+	[0x01fe]	0x0163,
+	[0x01ff]	0x02d9,
+	[0x02a1]	0x0126,
+	[0x02a6]	0x0124,
+	[0x02a9]	0x0130,
+	[0x02ab]	0x011e,
+	[0x02ac]	0x0134,
+	[0x02b1]	0x0127,
+	[0x02b6]	0x0125,
+	[0x02b9]	0x0131,
+	[0x02bb]	0x011f,
+	[0x02bc]	0x0135,
+	[0x02c5]	0x010a,
+	[0x02c6]	0x0108,
+	[0x02d5]	0x0120,
+	[0x02d8]	0x011c,
+	[0x02dd]	0x016c,
+	[0x02de]	0x015c,
+	[0x02e5]	0x010b,
+	[0x02e6]	0x0109,
+	[0x02f5]	0x0121,
+	[0x02f8]	0x011d,
+	[0x02fd]	0x016d,
+	[0x02fe]	0x015d,
+	[0x03a2]	0x0138,
+	[0x03a3]	0x0156,
+	[0x03a5]	0x0128,
+	[0x03a6]	0x013b,
+	[0x03aa]	0x0112,
+	[0x03ab]	0x0122,
+	[0x03ac]	0x0166,
+	[0x03b3]	0x0157,
+	[0x03b5]	0x0129,
+	[0x03b6]	0x013c,
+	[0x03ba]	0x0113,
+	[0x03bb]	0x0123,
+	[0x03bc]	0x0167,
+	[0x03bd]	0x014a,
+	[0x03bf]	0x014b,
+	[0x03c0]	0x0100,
+	[0x03c7]	0x012e,
+	[0x03cc]	0x0116,
+	[0x03cf]	0x012a,
+	[0x03d1]	0x0145,
+	[0x03d2]	0x014c,
+	[0x03d3]	0x0136,
+	[0x03d9]	0x0172,
+	[0x03dd]	0x0168,
+	[0x03de]	0x016a,
+	[0x03e0]	0x0101,
+	[0x03e7]	0x012f,
+	[0x03ec]	0x0117,
+	[0x03ef]	0x012b,
+	[0x03f1]	0x0146,
+	[0x03f2]	0x014d,
+	[0x03f3]	0x0137,
+	[0x03f9]	0x0173,
+	[0x03fd]	0x0169,
+	[0x03fe]	0x016b,
+	[0x047e]	0x203e,
+	[0x04a1]	0x3002,
+	[0x04a2]	0x300c,
+	[0x04a3]	0x300d,
+	[0x04a4]	0x3001,
+	[0x04a5]	0x30fb,
+	[0x04a6]	0x30f2,
+	[0x04a7]	0x30a1,
+	[0x04a8]	0x30a3,
+	[0x04a9]	0x30a5,
+	[0x04aa]	0x30a7,
+	[0x04ab]	0x30a9,
+	[0x04ac]	0x30e3,
+	[0x04ad]	0x30e5,
+	[0x04ae]	0x30e7,
+	[0x04af]	0x30c3,
+	[0x04b0]	0x30fc,
+	[0x04b1]	0x30a2,
+	[0x04b2]	0x30a4,
+	[0x04b3]	0x30a6,
+	[0x04b4]	0x30a8,
+	[0x04b5]	0x30aa,
+	[0x04b6]	0x30ab,
+	[0x04b7]	0x30ad,
+	[0x04b8]	0x30af,
+	[0x04b9]	0x30b1,
+	[0x04ba]	0x30b3,
+	[0x04bb]	0x30b5,
+	[0x04bc]	0x30b7,
+	[0x04bd]	0x30b9,
+	[0x04be]	0x30bb,
+	[0x04bf]	0x30bd,
+	[0x04c0]	0x30bf,
+	[0x04c1]	0x30c1,
+	[0x04c2]	0x30c4,
+	[0x04c3]	0x30c6,
+	[0x04c4]	0x30c8,
+	[0x04c5]	0x30ca,
+	[0x04c6]	0x30cb,
+	[0x04c7]	0x30cc,
+	[0x04c8]	0x30cd,
+	[0x04c9]	0x30ce,
+	[0x04ca]	0x30cf,
+	[0x04cb]	0x30d2,
+	[0x04cc]	0x30d5,
+	[0x04cd]	0x30d8,
+	[0x04ce]	0x30db,
+	[0x04cf]	0x30de,
+	[0x04d0]	0x30df,
+	[0x04d1]	0x30e0,
+	[0x04d2]	0x30e1,
+	[0x04d3]	0x30e2,
+	[0x04d4]	0x30e4,
+	[0x04d5]	0x30e6,
+	[0x04d6]	0x30e8,
+	[0x04d7]	0x30e9,
+	[0x04d8]	0x30ea,
+	[0x04d9]	0x30eb,
+	[0x04da]	0x30ec,
+	[0x04db]	0x30ed,
+	[0x04dc]	0x30ef,
+	[0x04dd]	0x30f3,
+	[0x04de]	0x309b,
+	[0x04df]	0x309c,
+	[0x05ac]	0x060c,
+	[0x05bb]	0x061b,
+	[0x05bf]	0x061f,
+	[0x05c1]	0x0621,
+	[0x05c2]	0x0622,
+	[0x05c3]	0x0623,
+	[0x05c4]	0x0624,
+	[0x05c5]	0x0625,
+	[0x05c6]	0x0626,
+	[0x05c7]	0x0627,
+	[0x05c8]	0x0628,
+	[0x05c9]	0x0629,
+	[0x05ca]	0x062a,
+	[0x05cb]	0x062b,
+	[0x05cc]	0x062c,
+	[0x05cd]	0x062d,
+	[0x05ce]	0x062e,
+	[0x05cf]	0x062f,
+	[0x05d0]	0x0630,
+	[0x05d1]	0x0631,
+	[0x05d2]	0x0632,
+	[0x05d3]	0x0633,
+	[0x05d4]	0x0634,
+	[0x05d5]	0x0635,
+	[0x05d6]	0x0636,
+	[0x05d7]	0x0637,
+	[0x05d8]	0x0638,
+	[0x05d9]	0x0639,
+	[0x05da]	0x063a,
+	[0x05e0]	0x0640,
+	[0x05e1]	0x0641,
+	[0x05e2]	0x0642,
+	[0x05e3]	0x0643,
+	[0x05e4]	0x0644,
+	[0x05e5]	0x0645,
+	[0x05e6]	0x0646,
+	[0x05e7]	0x0647,
+	[0x05e8]	0x0648,
+	[0x05e9]	0x0649,
+	[0x05ea]	0x064a,
+	[0x05eb]	0x064b,
+	[0x05ec]	0x064c,
+	[0x05ed]	0x064d,
+	[0x05ee]	0x064e,
+	[0x05ef]	0x064f,
+	[0x05f0]	0x0650,
+	[0x05f1]	0x0651,
+	[0x05f2]	0x0652,
+	[0x06a1]	0x0452,
+	[0x06a2]	0x0453,
+	[0x06a3]	0x0451,
+	[0x06a4]	0x0454,
+	[0x06a5]	0x0455,
+	[0x06a6]	0x0456,
+	[0x06a7]	0x0457,
+	[0x06a8]	0x0458,
+	[0x06a9]	0x0459,
+	[0x06aa]	0x045a,
+	[0x06ab]	0x045b,
+	[0x06ac]	0x045c,
+	[0x06ae]	0x045e,
+	[0x06af]	0x045f,
+	[0x06b0]	0x2116,
+	[0x06b1]	0x0402,
+	[0x06b2]	0x0403,
+	[0x06b3]	0x0401,
+	[0x06b4]	0x0404,
+	[0x06b5]	0x0405,
+	[0x06b6]	0x0406,
+	[0x06b7]	0x0407,
+	[0x06b8]	0x0408,
+	[0x06b9]	0x0409,
+	[0x06ba]	0x040a,
+	[0x06bb]	0x040b,
+	[0x06bc]	0x040c,
+	[0x06be]	0x040e,
+	[0x06bf]	0x040f,
+	[0x06c0]	0x044e,
+	[0x06c1]	0x0430,
+	[0x06c2]	0x0431,
+	[0x06c3]	0x0446,
+	[0x06c4]	0x0434,
+	[0x06c5]	0x0435,
+	[0x06c6]	0x0444,
+	[0x06c7]	0x0433,
+	[0x06c8]	0x0445,
+	[0x06c9]	0x0438,
+	[0x06ca]	0x0439,
+	[0x06cb]	0x043a,
+	[0x06cc]	0x043b,
+	[0x06cd]	0x043c,
+	[0x06ce]	0x043d,
+	[0x06cf]	0x043e,
+	[0x06d0]	0x043f,
+	[0x06d1]	0x044f,
+	[0x06d2]	0x0440,
+	[0x06d3]	0x0441,
+	[0x06d4]	0x0442,
+	[0x06d5]	0x0443,
+	[0x06d6]	0x0436,
+	[0x06d7]	0x0432,
+	[0x06d8]	0x044c,
+	[0x06d9]	0x044b,
+	[0x06da]	0x0437,
+	[0x06db]	0x0448,
+	[0x06dc]	0x044d,
+	[0x06dd]	0x0449,
+	[0x06de]	0x0447,
+	[0x06df]	0x044a,
+	[0x06e0]	0x042e,
+	[0x06e1]	0x0410,
+	[0x06e2]	0x0411,
+	[0x06e3]	0x0426,
+	[0x06e4]	0x0414,
+	[0x06e5]	0x0415,
+	[0x06e6]	0x0424,
+	[0x06e7]	0x0413,
+	[0x06e8]	0x0425,
+	[0x06e9]	0x0418,
+	[0x06ea]	0x0419,
+	[0x06eb]	0x041a,
+	[0x06ec]	0x041b,
+	[0x06ed]	0x041c,
+	[0x06ee]	0x041d,
+	[0x06ef]	0x041e,
+	[0x06f0]	0x041f,
+	[0x06f1]	0x042f,
+	[0x06f2]	0x0420,
+	[0x06f3]	0x0421,
+	[0x06f4]	0x0422,
+	[0x06f5]	0x0423,
+	[0x06f6]	0x0416,
+	[0x06f7]	0x0412,
+	[0x06f8]	0x042c,
+	[0x06f9]	0x042b,
+	[0x06fa]	0x0417,
+	[0x06fb]	0x0428,
+	[0x06fc]	0x042d,
+	[0x06fd]	0x0429,
+	[0x06fe]	0x0427,
+	[0x06ff]	0x042a,
+	[0x07a1]	0x0386,
+	[0x07a2]	0x0388,
+	[0x07a3]	0x0389,
+	[0x07a4]	0x038a,
+	[0x07a5]	0x03aa,
+	[0x07a7]	0x038c,
+	[0x07a8]	0x038e,
+	[0x07a9]	0x03ab,
+	[0x07ab]	0x038f,
+	[0x07ae]	0x0385,
+	[0x07af]	0x2015,
+	[0x07b1]	0x03ac,
+	[0x07b2]	0x03ad,
+	[0x07b3]	0x03ae,
+	[0x07b4]	0x03af,
+	[0x07b5]	0x03ca,
+	[0x07b6]	0x0390,
+	[0x07b7]	0x03cc,
+	[0x07b8]	0x03cd,
+	[0x07b9]	0x03cb,
+	[0x07ba]	0x03b0,
+	[0x07bb]	0x03ce,
+	[0x07c1]	0x0391,
+	[0x07c2]	0x0392,
+	[0x07c3]	0x0393,
+	[0x07c4]	0x0394,
+	[0x07c5]	0x0395,
+	[0x07c6]	0x0396,
+	[0x07c7]	0x0397,
+	[0x07c8]	0x0398,
+	[0x07c9]	0x0399,
+	[0x07ca]	0x039a,
+	[0x07cb]	0x039b,
+	[0x07cc]	0x039c,
+	[0x07cd]	0x039d,
+	[0x07ce]	0x039e,
+	[0x07cf]	0x039f,
+	[0x07d0]	0x03a0,
+	[0x07d1]	0x03a1,
+	[0x07d2]	0x03a3,
+	[0x07d4]	0x03a4,
+	[0x07d5]	0x03a5,
+	[0x07d6]	0x03a6,
+	[0x07d7]	0x03a7,
+	[0x07d8]	0x03a8,
+	[0x07d9]	0x03a9,
+	[0x07e1]	0x03b1,
+	[0x07e2]	0x03b2,
+	[0x07e3]	0x03b3,
+	[0x07e4]	0x03b4,
+	[0x07e5]	0x03b5,
+	[0x07e6]	0x03b6,
+	[0x07e7]	0x03b7,
+	[0x07e8]	0x03b8,
+	[0x07e9]	0x03b9,
+	[0x07ea]	0x03ba,
+	[0x07eb]	0x03bb,
+	[0x07ec]	0x03bc,
+	[0x07ed]	0x03bd,
+	[0x07ee]	0x03be,
+	[0x07ef]	0x03bf,
+	[0x07f0]	0x03c0,
+	[0x07f1]	0x03c1,
+	[0x07f2]	0x03c3,
+	[0x07f3]	0x03c2,
+	[0x07f4]	0x03c4,
+	[0x07f5]	0x03c5,
+	[0x07f6]	0x03c6,
+	[0x07f7]	0x03c7,
+	[0x07f8]	0x03c8,
+	[0x07f9]	0x03c9,
+	[0x08a4]	0x2320,
+	[0x08a5]	0x2321,
+	[0x08a6]	0x2502,
+	[0x08bc]	0x2264,
+	[0x08bd]	0x2260,
+	[0x08be]	0x2265,
+	[0x08bf]	0x222b,
+	[0x08c0]	0x2234,
+	[0x08c1]	0x221d,
+	[0x08c2]	0x221e,
+	[0x08c5]	0x2207,
+	[0x08c8]	0x2245,
+	[0x08cd]	0x21d4,
+	[0x08ce]	0x21d2,
+	[0x08cf]	0x2261,
+	[0x08d6]	0x221a,
+	[0x08da]	0x2282,
+	[0x08db]	0x2283,
+	[0x08dc]	0x2229,
+	[0x08dd]	0x222a,
+	[0x08de]	0x2227,
+	[0x08df]	0x2228,
+	[0x08ef]	0x2202,
+	[0x08f6]	0x0192,
+	[0x08fb]	0x2190,
+	[0x08fc]	0x2191,
+	[0x08fd]	0x2192,
+	[0x08fe]	0x2193,
+	[0x09df]	0x2422,
+	[0x09e0]	0x25c6,
+	[0x09e1]	0x2592,
+	[0x09e2]	0x2409,
+	[0x09e3]	0x240c,
+	[0x09e4]	0x240d,
+	[0x09e5]	0x240a,
+	[0x09e8]	0x2424,
+	[0x09e9]	0x240b,
+	[0x09ea]	0x2518,
+	[0x09eb]	0x2510,
+	[0x09ec]	0x250c,
+	[0x09ed]	0x2514,
+	[0x09ee]	0x253c,
+	[0x09f1]	0x2500,
+	[0x09f4]	0x251c,
+	[0x09f5]	0x2524,
+	[0x09f6]	0x2534,
+	[0x09f7]	0x252c,
+	[0x09f8]	0x2502,
+	[0x0aa1]	0x2003,
+	[0x0aa2]	0x2002,
+	[0x0aa3]	0x2004,
+	[0x0aa4]	0x2005,
+	[0x0aa5]	0x2007,
+	[0x0aa6]	0x2008,
+	[0x0aa7]	0x2009,
+	[0x0aa8]	0x200a,
+	[0x0aa9]	0x2014,
+	[0x0aaa]	0x2013,
+	[0x0aae]	0x2026,
+	[0x0ab0]	0x2153,
+	[0x0ab1]	0x2154,
+	[0x0ab2]	0x2155,
+	[0x0ab3]	0x2156,
+	[0x0ab4]	0x2157,
+	[0x0ab5]	0x2158,
+	[0x0ab6]	0x2159,
+	[0x0ab7]	0x215a,
+	[0x0ab8]	0x2105,
+	[0x0abb]	0x2012,
+	[0x0abc]	0x2329,
+	[0x0abd]	0x002e,
+	[0x0abe]	0x232a,
+	[0x0ac3]	0x215b,
+	[0x0ac4]	0x215c,
+	[0x0ac5]	0x215d,
+	[0x0ac6]	0x215e,
+	[0x0ac9]	0x2122,
+	[0x0aca]	0x2613,
+	[0x0acc]	0x25c1,
+	[0x0acd]	0x25b7,
+	[0x0ace]	0x25cb,
+	[0x0acf]	0x25a1,
+	[0x0ad0]	0x2018,
+	[0x0ad1]	0x2019,
+	[0x0ad2]	0x201c,
+	[0x0ad3]	0x201d,
+	[0x0ad4]	0x211e,
+	[0x0ad6]	0x2032,
+	[0x0ad7]	0x2033,
+	[0x0ad9]	0x271d,
+	[0x0adb]	0x25ac,
+	[0x0adc]	0x25c0,
+	[0x0add]	0x25b6,
+	[0x0ade]	0x25cf,
+	[0x0adf]	0x25a0,
+	[0x0ae0]	0x25e6,
+	[0x0ae1]	0x25ab,
+	[0x0ae2]	0x25ad,
+	[0x0ae3]	0x25b3,
+	[0x0ae4]	0x25bd,
+	[0x0ae5]	0x2606,
+	[0x0ae6]	0x2022,
+	[0x0ae7]	0x25aa,
+	[0x0ae8]	0x25b2,
+	[0x0ae9]	0x25bc,
+	[0x0aea]	0x261c,
+	[0x0aeb]	0x261e,
+	[0x0aec]	0x2663,
+	[0x0aed]	0x2666,
+	[0x0aee]	0x2665,
+	[0x0af0]	0x2720,
+	[0x0af1]	0x2020,
+	[0x0af2]	0x2021,
+	[0x0af3]	0x2713,
+	[0x0af4]	0x2717,
+	[0x0af5]	0x266f,
+	[0x0af6]	0x266d,
+	[0x0af7]	0x2642,
+	[0x0af8]	0x2640,
+	[0x0af9]	0x260e,
+	[0x0afa]	0x2315,
+	[0x0afb]	0x2117,
+	[0x0afc]	0x2038,
+	[0x0afd]	0x201a,
+	[0x0afe]	0x201e,
+	[0x0ba3]	0x003c,
+	[0x0ba6]	0x003e,
+	[0x0ba8]	0x2228,
+	[0x0ba9]	0x2227,
+	[0x0bc0]	0x00af,
+	[0x0bc2]	0x22a4,
+	[0x0bc3]	0x2229,
+	[0x0bc4]	0x230a,
+	[0x0bc6]	0x005f,
+	[0x0bca]	0x2218,
+	[0x0bcc]	0x2395,
+	[0x0bce]	0x22a5,
+	[0x0bcf]	0x25cb,
+	[0x0bd3]	0x2308,
+	[0x0bd6]	0x222a,
+	[0x0bd8]	0x2283,
+	[0x0bda]	0x2282,
+	[0x0bdc]	0x22a3,
+	[0x0bfc]	0x22a2,
+	[0x0cdf]	0x2017,
+	[0x0ce0]	0x05d0,
+	[0x0ce1]	0x05d1,
+	[0x0ce2]	0x05d2,
+	[0x0ce3]	0x05d3,
+	[0x0ce4]	0x05d4,
+	[0x0ce5]	0x05d5,
+	[0x0ce6]	0x05d6,
+	[0x0ce7]	0x05d7,
+	[0x0ce8]	0x05d8,
+	[0x0ce9]	0x05d9,
+	[0x0cea]	0x05da,
+	[0x0ceb]	0x05db,
+	[0x0cec]	0x05dc,
+	[0x0ced]	0x05dd,
+	[0x0cee]	0x05de,
+	[0x0cef]	0x05df,
+	[0x0cf0]	0x05e0,
+	[0x0cf1]	0x05e1,
+	[0x0cf2]	0x05e2,
+	[0x0cf3]	0x05e3,
+	[0x0cf4]	0x05e4,
+	[0x0cf5]	0x05e5,
+	[0x0cf6]	0x05e6,
+	[0x0cf7]	0x05e7,
+	[0x0cf8]	0x05e8,
+	[0x0cf9]	0x05e9,
+	[0x0cfa]	0x05ea,
+	[0x0da1]	0x0e01,
+	[0x0da2]	0x0e02,
+	[0x0da3]	0x0e03,
+	[0x0da4]	0x0e04,
+	[0x0da5]	0x0e05,
+	[0x0da6]	0x0e06,
+	[0x0da7]	0x0e07,
+	[0x0da8]	0x0e08,
+	[0x0da9]	0x0e09,
+	[0x0daa]	0x0e0a,
+	[0x0dab]	0x0e0b,
+	[0x0dac]	0x0e0c,
+	[0x0dad]	0x0e0d,
+	[0x0dae]	0x0e0e,
+	[0x0daf]	0x0e0f,
+	[0x0db0]	0x0e10,
+	[0x0db1]	0x0e11,
+	[0x0db2]	0x0e12,
+	[0x0db3]	0x0e13,
+	[0x0db4]	0x0e14,
+	[0x0db5]	0x0e15,
+	[0x0db6]	0x0e16,
+	[0x0db7]	0x0e17,
+	[0x0db8]	0x0e18,
+	[0x0db9]	0x0e19,
+	[0x0dba]	0x0e1a,
+	[0x0dbb]	0x0e1b,
+	[0x0dbc]	0x0e1c,
+	[0x0dbd]	0x0e1d,
+	[0x0dbe]	0x0e1e,
+	[0x0dbf]	0x0e1f,
+	[0x0dc0]	0x0e20,
+	[0x0dc1]	0x0e21,
+	[0x0dc2]	0x0e22,
+	[0x0dc3]	0x0e23,
+	[0x0dc4]	0x0e24,
+	[0x0dc5]	0x0e25,
+	[0x0dc6]	0x0e26,
+	[0x0dc7]	0x0e27,
+	[0x0dc8]	0x0e28,
+	[0x0dc9]	0x0e29,
+	[0x0dca]	0x0e2a,
+	[0x0dcb]	0x0e2b,
+	[0x0dcc]	0x0e2c,
+	[0x0dcd]	0x0e2d,
+	[0x0dce]	0x0e2e,
+	[0x0dcf]	0x0e2f,
+	[0x0dd0]	0x0e30,
+	[0x0dd1]	0x0e31,
+	[0x0dd2]	0x0e32,
+	[0x0dd3]	0x0e33,
+	[0x0dd4]	0x0e34,
+	[0x0dd5]	0x0e35,
+	[0x0dd6]	0x0e36,
+	[0x0dd7]	0x0e37,
+	[0x0dd8]	0x0e38,
+	[0x0dd9]	0x0e39,
+	[0x0dda]	0x0e3a,
+	[0x0dde]	0x0e3e,
+	[0x0ddf]	0x0e3f,
+	[0x0de0]	0x0e40,
+	[0x0de1]	0x0e41,
+	[0x0de2]	0x0e42,
+	[0x0de3]	0x0e43,
+	[0x0de4]	0x0e44,
+	[0x0de5]	0x0e45,
+	[0x0de6]	0x0e46,
+	[0x0de7]	0x0e47,
+	[0x0de8]	0x0e48,
+	[0x0de9]	0x0e49,
+	[0x0dea]	0x0e4a,
+	[0x0deb]	0x0e4b,
+	[0x0dec]	0x0e4c,
+	[0x0ded]	0x0e4d,
+	[0x0df0]	0x0e50,
+	[0x0df1]	0x0e51,
+	[0x0df2]	0x0e52,
+	[0x0df3]	0x0e53,
+	[0x0df4]	0x0e54,
+	[0x0df5]	0x0e55,
+	[0x0df6]	0x0e56,
+	[0x0df7]	0x0e57,
+	[0x0df8]	0x0e58,
+	[0x0df9]	0x0e59,
+	[0x0ea1]	0x3131,
+	[0x0ea2]	0x3132,
+	[0x0ea3]	0x3133,
+	[0x0ea4]	0x3134,
+	[0x0ea5]	0x3135,
+	[0x0ea6]	0x3136,
+	[0x0ea7]	0x3137,
+	[0x0ea8]	0x3138,
+	[0x0ea9]	0x3139,
+	[0x0eaa]	0x313a,
+	[0x0eab]	0x313b,
+	[0x0eac]	0x313c,
+	[0x0ead]	0x313d,
+	[0x0eae]	0x313e,
+	[0x0eaf]	0x313f,
+	[0x0eb0]	0x3140,
+	[0x0eb1]	0x3141,
+	[0x0eb2]	0x3142,
+	[0x0eb3]	0x3143,
+	[0x0eb4]	0x3144,
+	[0x0eb5]	0x3145,
+	[0x0eb6]	0x3146,
+	[0x0eb7]	0x3147,
+	[0x0eb8]	0x3148,
+	[0x0eb9]	0x3149,
+	[0x0eba]	0x314a,
+	[0x0ebb]	0x314b,
+	[0x0ebc]	0x314c,
+	[0x0ebd]	0x314d,
+	[0x0ebe]	0x314e,
+	[0x0ebf]	0x314f,
+	[0x0ec0]	0x3150,
+	[0x0ec1]	0x3151,
+	[0x0ec2]	0x3152,
+	[0x0ec3]	0x3153,
+	[0x0ec4]	0x3154,
+	[0x0ec5]	0x3155,
+	[0x0ec6]	0x3156,
+	[0x0ec7]	0x3157,
+	[0x0ec8]	0x3158,
+	[0x0ec9]	0x3159,
+	[0x0eca]	0x315a,
+	[0x0ecb]	0x315b,
+	[0x0ecc]	0x315c,
+	[0x0ecd]	0x315d,
+	[0x0ece]	0x315e,
+	[0x0ecf]	0x315f,
+	[0x0ed0]	0x3160,
+	[0x0ed1]	0x3161,
+	[0x0ed2]	0x3162,
+	[0x0ed3]	0x3163,
+	[0x0ed4]	0x11a8,
+	[0x0ed5]	0x11a9,
+	[0x0ed6]	0x11aa,
+	[0x0ed7]	0x11ab,
+	[0x0ed8]	0x11ac,
+	[0x0ed9]	0x11ad,
+	[0x0eda]	0x11ae,
+	[0x0edb]	0x11af,
+	[0x0edc]	0x11b0,
+	[0x0edd]	0x11b1,
+	[0x0ede]	0x11b2,
+	[0x0edf]	0x11b3,
+	[0x0ee0]	0x11b4,
+	[0x0ee1]	0x11b5,
+	[0x0ee2]	0x11b6,
+	[0x0ee3]	0x11b7,
+	[0x0ee4]	0x11b8,
+	[0x0ee5]	0x11b9,
+	[0x0ee6]	0x11ba,
+	[0x0ee7]	0x11bb,
+	[0x0ee8]	0x11bc,
+	[0x0ee9]	0x11bd,
+	[0x0eea]	0x11be,
+	[0x0eeb]	0x11bf,
+	[0x0eec]	0x11c0,
+	[0x0eed]	0x11c1,
+	[0x0eee]	0x11c2,
+	[0x0eef]	0x316d,
+	[0x0ef0]	0x3171,
+	[0x0ef1]	0x3178,
+	[0x0ef2]	0x317f,
+	[0x0ef4]	0x3184,
+	[0x0ef5]	0x3186,
+	[0x0ef6]	0x318d,
+	[0x0ef7]	0x318e,
+	[0x0ef8]	0x11eb,
+	[0x0efa]	0x11f9,
+	[0x0eff]	0x20a9,
+	[0x13bc]	0x0152,
+	[0x13bd]	0x0153,
+	[0x13be]	0x0178,
+	[0x20a0]	0x20a0,
+	[0x20a1]	0x20a1,
+	[0x20a2]	0x20a2,
+	[0x20a3]	0x20a3,
+	[0x20a4]	0x20a4,
+	[0x20a5]	0x20a5,
+	[0x20a6]	0x20a6,
+	[0x20a7]	0x20a7,
+	[0x20a8]	0x20a8,
+	[0x20a9]	0x20a9,
+	[0x20aa]	0x20aa,
+	[0x20ab]	0x20ab,
+	[0x20ac]	0x20ac,
+};
--- /dev/null
+++ b/gui-x11/load.c
@@ -1,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "xmem.h"
+
+int
+loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int n;
+
+	n = _loadmemimage(i, r, data, ndata);
+	if(n > 0 && i->X)
+		putXdata(i, r);
+	return n;
+}
--- /dev/null
+++ b/gui-x11/screen.c
@@ -1,0 +1,1094 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <stdio.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#include "keysym2ucs.h"
+
+/*
+ * alias defs for image types to overcome name conflicts
+ */
+#define	Point	IPoint
+#define	Rectangle	IRectangle
+#define Display	IDisplay
+#define Font	IFont
+#define Screen	IScreen
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include	"dat.h"
+#include	"fns.h"
+#include	"screen.h"
+
+#undef time
+#undef Point
+#undef Rectangle
+#undef Display
+#undef Font
+#undef Screen
+
+typedef struct ICursor ICursor;
+struct ICursor
+{
+	int	w;
+	int	h;
+	int	hotx;
+	int	hoty;
+	char	*src;
+	char	*mask;
+};
+
+
+#define ABS(x) ((x) < 0 ? -(x) : (x))
+
+enum
+{
+	DblTime	= 300		/* double click time in msec */
+};
+
+XColor			map[256];	/* Plan 9 colormap array */
+XColor			map7[128];	/* Plan 9 colormap array */
+uchar			map7to8[128][2];
+Colormap		xcmap;		/* Default shared colormap  */
+int 			plan9tox11[256]; /* Values for mapping between */
+int 			x11toplan9[256]; /* X11 and Plan 9 */
+int				x24bitswap = 0;	/* swap endian for 24bit RGB */
+int				xtblbit;
+extern int mousequeue;
+
+/* for copy/paste, lifted from plan9ports */
+Atom clipboard; 
+Atom utf8string;
+Atom targets;
+Atom text;
+Atom compoundtext;
+
+static	XModifierKeymap *modmap;
+static	int		keypermod;
+static	Drawable	xdrawable;
+/* static	Atom		wm_take_focus; */
+static	void		xexpose(XEvent*);
+static	void		xmouse(XEvent*);
+static	void		xkeyboard(XEvent*);
+static	void		xmapping(XEvent*);
+static	void		xdestroy(XEvent*);
+static	void		xselect(XEvent*);
+static	void		xproc(void*);
+static	Memimage*		xinitscreen(void);
+static	void		initmap(Window);
+static	GC		creategc(Drawable);
+static	void		graphicscmap(XColor*);
+	int		xscreendepth;
+	Drawable	xscreenid;
+	Display*	xdisplay;
+	Display*	xkmcon;
+	Display*	xsnarfcon;
+	Visual		*xvis;
+	GC		xgcfill, xgccopy, xgcsimplesrc, xgczero, xgcreplsrc;
+	GC		xgcfill0, xgccopy0, xgcsimplesrc0, xgczero0, xgcreplsrc0;
+	ulong		xblack;
+	ulong		xwhite;
+	ulong	xscreenchan;
+	
+extern Memimage* xallocmemimage(IRectangle, ulong, int);
+Memimage *gscreen;
+Screeninfo screen;
+XImage *ximage;
+
+void
+screeninit(void)
+{
+	_memmkcmap();
+
+	gscreen = xinitscreen();
+	kproc("xscreen", xproc, nil);
+
+	memimageinit();
+	terminit();
+	flushmemscreen(gscreen->r);
+}
+
+uchar*
+attachscreen(IRectangle *r, ulong *chan, int *depth,
+	int *width, int *softscreen, void **X)
+{
+	*r = gscreen->r;
+	*chan = gscreen->chan;
+	*depth = gscreen->depth;
+	*width = gscreen->width;
+	*X = gscreen->X;
+	*softscreen = 1;
+
+	return gscreen->data->bdata;
+}
+
+void
+flushmemscreen(IRectangle r)
+{
+	if(r.min.x >= r.max.x || r.min.y >= r.max.y)
+		return;
+	XCopyArea(xdisplay, xscreenid, xdrawable, xgccopy, r.min.x, r.min.y, Dx(r), Dy(r), r.min.x, r.min.y);
+	XFlush(xdisplay);
+}
+
+static int
+revbyte(int b)
+{
+	int r;
+
+	r = 0;
+	r |= (b&0x01) << 7;
+	r |= (b&0x02) << 5;
+	r |= (b&0x04) << 3;
+	r |= (b&0x08) << 1;
+	r |= (b&0x10) >> 1;
+	r |= (b&0x20) >> 3;
+	r |= (b&0x40) >> 5;
+	r |= (b&0x80) >> 7;
+	return r;
+}
+
+void
+mouseset(IPoint xy)
+{
+	XWarpPointer(xdisplay, None, xdrawable, 0, 0, 0, 0, xy.x, xy.y);
+	XFlush(xdisplay);
+}
+
+static Cursor xcursor;
+
+void
+setcursor(void)
+{
+	Cursor xc;
+	XColor fg, bg;
+	Pixmap xsrc, xmask;
+	int i;
+	uchar src[2*16], mask[2*16];
+
+	for(i=0; i<2*16; i++){
+		src[i] = revbyte(cursor.set[i]);
+		mask[i] = revbyte(cursor.set[i] | cursor.clr[i]);
+	}
+
+	fg = map[0];
+	bg = map[255];
+	xsrc = XCreateBitmapFromData(xdisplay, xdrawable, src, 16, 16);
+	xmask = XCreateBitmapFromData(xdisplay, xdrawable, mask, 16, 16);
+	xc = XCreatePixmapCursor(xdisplay, xsrc, xmask, &fg, &bg, -cursor.offset.x, -cursor.offset.y);
+	if(xc != 0) {
+		XDefineCursor(xdisplay, xdrawable, xc);
+		if(xcursor != 0)
+			XFreeCursor(xdisplay, xcursor);
+		xcursor = xc;
+	}
+	XFreePixmap(xdisplay, xsrc);
+	XFreePixmap(xdisplay, xmask);
+	XFlush(xdisplay);
+}
+
+void
+cursorarrow(void)
+{
+	if(xcursor != 0){
+		XFreeCursor(xdisplay, xcursor);
+		xcursor = 0;
+	}
+	XUndefineCursor(xdisplay, xdrawable);
+	XFlush(xdisplay);
+}
+
+static void
+xproc(void *arg)
+{
+	ulong mask;
+	XEvent event;
+
+	mask = 	KeyPressMask|
+		ButtonPressMask|
+		ButtonReleaseMask|
+		PointerMotionMask|
+		Button1MotionMask|
+		Button2MotionMask|
+		Button3MotionMask|
+		Button4MotionMask|
+		Button5MotionMask|
+		ExposureMask|
+		StructureNotifyMask;
+
+	XSelectInput(xkmcon, xdrawable, mask);
+	for(;;) {
+		//XWindowEvent(xkmcon, xdrawable, mask, &event);
+		XNextEvent(xkmcon, &event);
+		xselect(&event);
+		xkeyboard(&event);
+		xmouse(&event);
+		xexpose(&event);
+		xmapping(&event);
+		xdestroy(&event);
+	}
+}
+
+static int
+shutup(Display *d, XErrorEvent *e)
+{
+	char buf[200];
+	iprint("X error: error code=%d, request_code=%d, minor=%d\n", e->error_code, e->request_code, e->minor_code);
+	XGetErrorText(d, e->error_code, buf, sizeof(buf));
+	iprint("%s\n", buf);
+	USED(d);
+	USED(e);
+	return 0;
+}
+
+static int
+panicshutup(Display *d)
+{
+	panic("x error");
+	return -1;
+}
+
+static Memimage*
+xinitscreen(void)
+{
+	Memimage *gscreen;
+	int i, xsize, ysize, pmid;
+	char *argv[2];
+	char *disp_val;
+	Window rootwin;
+	IRectangle r;
+	XWMHints hints;
+	Screen *screen;
+	XVisualInfo xvi;
+	int rootscreennum;
+	XTextProperty name;
+	XClassHint classhints;
+	XSizeHints normalhints;
+	XSetWindowAttributes attrs;
+	XPixmapFormatValues *pfmt;
+	int n;
+	Memdata *md;
+ 
+	xscreenid = 0;
+	xdrawable = 0;
+
+	xdisplay = XOpenDisplay(NULL);
+	if(xdisplay == 0){
+		disp_val = getenv("DISPLAY");
+		if(disp_val == 0)
+			disp_val = "not set";
+		iprint("drawterm: open %r, DISPLAY is %s\n", disp_val);
+		exit(0);
+	}
+
+	XSetErrorHandler(shutup);
+	XSetIOErrorHandler(panicshutup);
+	rootscreennum = DefaultScreen(xdisplay);
+	rootwin = DefaultRootWindow(xdisplay);
+	
+	xscreendepth = DefaultDepth(xdisplay, rootscreennum);
+	if(XMatchVisualInfo(xdisplay, rootscreennum, 16, TrueColor, &xvi)
+	|| XMatchVisualInfo(xdisplay, rootscreennum, 16, DirectColor, &xvi)){
+		xvis = xvi.visual;
+		xscreendepth = 16;
+		xtblbit = 1;
+	}
+	else if(XMatchVisualInfo(xdisplay, rootscreennum, 24, TrueColor, &xvi)
+	|| XMatchVisualInfo(xdisplay, rootscreennum, 24, DirectColor, &xvi)){
+		xvis = xvi.visual;
+		xscreendepth = 24;
+		xtblbit = 1;
+	}
+	else if(XMatchVisualInfo(xdisplay, rootscreennum, 8, PseudoColor, &xvi)
+	|| XMatchVisualInfo(xdisplay, rootscreennum, 8, StaticColor, &xvi)){
+		if(xscreendepth > 8)
+			panic("drawterm: can't deal with colormapped depth %d screens\n", xscreendepth);
+		xvis = xvi.visual;
+		xscreendepth = 8;
+	}
+	else{
+		if(xscreendepth != 8)
+			panic("drawterm: can't deal with depth %d screens\n", xscreendepth);
+		xvis = DefaultVisual(xdisplay, rootscreennum);
+	}
+
+	/*
+	 * xscreendepth is only the number of significant pixel bits,
+	 * not the total.  We need to walk the display list to find
+	 * how many actual bits are being used per pixel.
+	 */
+	xscreenchan = 0; /* not a valid channel */
+	pfmt = XListPixmapFormats(xdisplay, &n);
+	for(i=0; i<n; i++){
+		if(pfmt[i].depth == xscreendepth){
+			switch(pfmt[i].bits_per_pixel){
+			case 1:	/* untested */
+				xscreenchan = GREY1;
+				break;
+			case 2:	/* untested */
+				xscreenchan = GREY2;
+				break;
+			case 4:	/* untested */
+				xscreenchan = GREY4;
+				break;
+			case 8:
+				xscreenchan = CMAP8;
+				break;
+			case 16: /* uses 16 rather than 15, empirically. */
+				xscreenchan = RGB16;
+				break;
+			case 24: /* untested (impossible?) */
+				xscreenchan = RGB24;
+				break;
+			case 32:
+				xscreenchan = CHAN4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8);
+				break;
+			}
+		}
+	}
+	if(xscreenchan == 0)
+		panic("drawterm: unknown screen pixel format\n");
+		
+	screen = DefaultScreenOfDisplay(xdisplay);
+	xcmap = DefaultColormapOfScreen(screen);
+
+	if(xvis->class != StaticColor){
+		graphicscmap(map);
+		initmap(rootwin);
+	}
+
+	if((modmap = XGetModifierMapping(xdisplay)))
+		keypermod = modmap->max_keypermod;
+
+	r.min = ZP;
+	r.max.x = WidthOfScreen(screen);
+	r.max.y = HeightOfScreen(screen);
+
+	md = mallocz(sizeof(Memdata), 1);
+	
+	xsize = Dx(r)*3/4;
+	ysize = Dy(r)*3/4;
+	
+	attrs.colormap = xcmap;
+	attrs.background_pixel = 0;
+	attrs.border_pixel = 0;
+	/* attrs.override_redirect = 1;*/ /* WM leave me alone! |CWOverrideRedirect */
+	xdrawable = XCreateWindow(xdisplay, rootwin, 0, 0, xsize, ysize, 0, 
+		xscreendepth, InputOutput, xvis, CWBackPixel|CWBorderPixel|CWColormap, &attrs);
+
+	/*
+	 * set up property as required by ICCCM
+	 */
+	name.value = (uchar*)"drawterm";
+	name.encoding = XA_STRING;
+	name.format = 8;
+	name.nitems = strlen(name.value);
+	normalhints.flags = USSize|PMaxSize;
+	normalhints.max_width = Dx(r);
+	normalhints.max_height = Dy(r);
+	normalhints.width = xsize;
+	normalhints.height = ysize;
+	hints.flags = InputHint|StateHint;
+	hints.input = 1;
+	hints.initial_state = NormalState;
+	classhints.res_name = "drawterm";
+	classhints.res_class = "Drawterm";
+	argv[0] = "drawterm";
+	argv[1] = nil;
+	XSetWMProperties(xdisplay, xdrawable,
+		&name,			/* XA_WM_NAME property for ICCCM */
+		&name,			/* XA_WM_ICON_NAME */
+		argv,			/* XA_WM_COMMAND */
+		1,			/* argc */
+		&normalhints,		/* XA_WM_NORMAL_HINTS */
+		&hints,			/* XA_WM_HINTS */
+		&classhints);		/* XA_WM_CLASS */
+	XFlush(xdisplay);
+	
+	/*
+	 * put the window on the screen
+	 */
+	XMapWindow(xdisplay, xdrawable);
+	XFlush(xdisplay);
+
+	xscreenid = XCreatePixmap(xdisplay, xdrawable, Dx(r), Dy(r), xscreendepth);
+	gscreen = xallocmemimage(r, xscreenchan, xscreenid);
+	
+	xgcfill = creategc(xscreenid);
+	XSetFillStyle(xdisplay, xgcfill, FillSolid);
+	xgccopy = creategc(xscreenid);
+	xgcsimplesrc = creategc(xscreenid);
+	XSetFillStyle(xdisplay, xgcsimplesrc, FillStippled);
+	xgczero = creategc(xscreenid);
+	xgcreplsrc = creategc(xscreenid);
+	XSetFillStyle(xdisplay, xgcreplsrc, FillTiled);
+
+	pmid = XCreatePixmap(xdisplay, xdrawable, 1, 1, 1);
+	xgcfill0 = creategc(pmid);
+	XSetForeground(xdisplay, xgcfill0, 0);
+	XSetFillStyle(xdisplay, xgcfill0, FillSolid);
+	xgccopy0 = creategc(pmid);
+	xgcsimplesrc0 = creategc(pmid);
+	XSetFillStyle(xdisplay, xgcsimplesrc0, FillStippled);
+	xgczero0 = creategc(pmid);
+	xgcreplsrc0 = creategc(pmid);
+	XSetFillStyle(xdisplay, xgcreplsrc0, FillTiled);
+	XFreePixmap(xdisplay, pmid);
+
+	XSetForeground(xdisplay, xgccopy, plan9tox11[0]);
+	XFillRectangle(xdisplay, xscreenid, xgccopy, 0, 0, xsize, ysize);
+
+	xkmcon = XOpenDisplay(NULL);
+	if(xkmcon == 0){
+		disp_val = getenv("DISPLAY");
+		if(disp_val == 0)
+			disp_val = "not set";
+		iprint("drawterm: open %r, DISPLAY is %s\n", disp_val);
+		exit(0);
+	}
+	xsnarfcon = XOpenDisplay(NULL);
+	if(xsnarfcon == 0){
+		disp_val = getenv("DISPLAY");
+		if(disp_val == 0)
+			disp_val = "not set";
+		iprint("drawterm: open %r, DISPLAY is %s\n", disp_val);
+		exit(0);
+	}
+
+    clipboard = XInternAtom(xkmcon, "CLIPBOARD", False);
+    utf8string = XInternAtom(xkmcon, "UTF8_STRING", False);
+    targets = XInternAtom(xkmcon, "TARGETS", False);
+    text = XInternAtom(xkmcon, "TEXT", False);
+    compoundtext = XInternAtom(xkmcon, "COMPOUND_TEXT", False);
+
+	xblack = screen->black_pixel;
+	xwhite = screen->white_pixel;
+	return gscreen;
+}
+
+static void
+graphicscmap(XColor *map)
+{
+	int r, g, b, cr, cg, cb, v, num, den, idx, v7, idx7;
+
+	for(r=0; r!=4; r++) {
+		for(g = 0; g != 4; g++) {
+			for(b = 0; b!=4; b++) {
+				for(v = 0; v!=4; v++) {
+					den=r;
+					if(g > den)
+						den=g;
+					if(b > den)
+						den=b;
+					/* divide check -- pick grey shades */
+					if(den==0)
+						cr=cg=cb=v*17;
+					else {
+						num=17*(4*den+v);
+						cr=r*num/den;
+						cg=g*num/den;
+						cb=b*num/den;
+					}
+					idx = r*64 + v*16 + ((g*4 + b + v - r) & 15);
+					map[idx].red = cr*0x0101;
+					map[idx].green = cg*0x0101;
+					map[idx].blue = cb*0x0101;
+					map[idx].pixel = idx;
+					map[idx].flags = DoRed|DoGreen|DoBlue;
+
+					v7 = v >> 1;
+					idx7 = r*32 + v7*16 + g*4 + b;
+					if((v & 1) == v7){
+						map7to8[idx7][0] = idx;
+						if(den == 0) { 		/* divide check -- pick grey shades */
+							cr = ((255.0/7.0)*v7)+0.5;
+							cg = cr;
+							cb = cr;
+						}
+						else {
+							num=17*15*(4*den+v7*2)/14;
+							cr=r*num/den;
+							cg=g*num/den;
+							cb=b*num/den;
+						}
+						map7[idx7].red = cr*0x0101;
+						map7[idx7].green = cg*0x0101;
+						map7[idx7].blue = cb*0x0101;
+						map7[idx7].pixel = idx7;
+						map7[idx7].flags = DoRed|DoGreen|DoBlue;
+					}
+					else
+						map7to8[idx7][1] = idx;
+				}
+			}
+		}
+	}
+}
+
+/*
+ * Initialize and install the drawterm colormap as a private colormap for this
+ * application.  Drawterm gets the best colors here when it has the cursor focus.
+ */  
+static void 
+initmap(Window w)
+{
+	XColor c;
+	int i;
+	ulong p, pp;
+	char buf[30];
+
+	if(xscreendepth <= 1)
+		return;
+
+	if(xscreendepth >= 24) {
+		/* The pixel value returned from XGetPixel needs to
+		 * be converted to RGB so we can call rgb2cmap()
+		 * to translate between 24 bit X and our color. Unfortunately,
+		 * the return value appears to be display server endian 
+		 * dependant. Therefore, we run some heuristics to later
+		 * determine how to mask the int value correctly.
+		 * Yeah, I know we can look at xvis->byte_order but 
+		 * some displays say MSB even though they run on LSB.
+		 * Besides, this is more anal.
+		 */
+
+		c = map[19];
+		/* find out index into colormap for our RGB */
+		if(!XAllocColor(xdisplay, xcmap, &c))
+			panic("drawterm: screen-x11 can't alloc color");
+
+		p  = c.pixel;
+		pp = rgb2cmap((p>>16)&0xff,(p>>8)&0xff,p&0xff);
+		if(pp!=map[19].pixel) {
+			/* check if endian is other way */
+			pp = rgb2cmap(p&0xff,(p>>8)&0xff,(p>>16)&0xff);
+			if(pp!=map[19].pixel)
+				panic("cannot detect x server byte order");
+			switch(xscreenchan){
+			case RGB24:
+				xscreenchan = BGR24;
+				break;
+			case XRGB32:
+				xscreenchan = XBGR32;
+				break;
+			default:
+				panic("don't know how to byteswap channel %s", 
+					chantostr(buf, xscreenchan));
+				break;
+			}
+		}
+	} else if(xvis->class == TrueColor || xvis->class == DirectColor) {
+	} else if(xvis->class == PseudoColor) {
+		if(xtblbit == 0){
+			xcmap = XCreateColormap(xdisplay, w, xvis, AllocAll); 
+			XStoreColors(xdisplay, xcmap, map, 256);
+			for(i = 0; i < 256; i++) {
+				plan9tox11[i] = i;
+				x11toplan9[i] = i;
+			}
+		}
+		else {
+			for(i = 0; i < 128; i++) {
+				c = map7[i];
+				if(!XAllocColor(xdisplay, xcmap, &c)) {
+					iprint("drawterm: can't alloc colors in default map, don't use -7\n");
+					exit(0);
+				}
+				plan9tox11[map7to8[i][0]] = c.pixel;
+				plan9tox11[map7to8[i][1]] = c.pixel;
+				x11toplan9[c.pixel] = map7to8[i][0];
+			}
+		}
+	}
+	else
+		panic("drawterm: unsupported visual class %d\n", xvis->class);
+}
+
+static void
+xdestroy(XEvent *e)
+{
+	XDestroyWindowEvent *xe;
+	if(e->type != DestroyNotify)
+		return;
+	xe = (XDestroyWindowEvent*)e;
+	if(xe->window == xdrawable)
+		exit(0);
+}
+
+static void
+xselection(XEvent *e)
+{
+	XSelectionRequestEvent *xre;
+	XSelectionEvent *xe;
+}
+
+static void
+xmapping(XEvent *e)
+{
+	XMappingEvent *xe;
+
+	if(e->type != MappingNotify)
+		return;
+	xe = (XMappingEvent*)e;
+	if(modmap)
+		XFreeModifiermap(modmap);
+	modmap = XGetModifierMapping(xe->display);
+	if(modmap)
+		keypermod = modmap->max_keypermod;
+}
+
+
+/*
+ * Disable generation of GraphicsExpose/NoExpose events in the GC.
+ */
+static GC
+creategc(Drawable d)
+{
+	XGCValues gcv;
+
+	gcv.function = GXcopy;
+	gcv.graphics_exposures = False;
+	return XCreateGC(xdisplay, d, GCFunction|GCGraphicsExposures, &gcv);
+}
+
+static void
+xexpose(XEvent *e)
+{
+	IRectangle r;
+	XExposeEvent *xe;
+
+	if(e->type != Expose)
+		return;
+	xe = (XExposeEvent*)e;
+	r.min.x = xe->x;
+	r.min.y = xe->y;
+	r.max.x = xe->x + xe->width;
+	r.max.y = xe->y + xe->height;
+	drawflushr(r);
+}
+
+static void
+xkeyboard(XEvent *e)
+{
+	KeySym k;
+	unsigned int md;
+
+	/*
+	 * I tried using XtGetActionKeysym, but it didn't seem to
+	 * do case conversion properly
+	 * (at least, with Xterminal servers and R4 intrinsics)
+	 */
+	if(e->xany.type != KeyPress)
+		return;
+
+
+	XLookupString(e,NULL,0,&k,NULL);
+
+	if(k == XK_Multi_key || k == NoSymbol)
+		return;
+	if(k&0xFF00){
+		switch(k){
+		case XK_BackSpace:
+		case XK_Tab:
+		case XK_Escape:
+		case XK_Delete:
+		case XK_KP_0:
+		case XK_KP_1:
+		case XK_KP_2:
+		case XK_KP_3:
+		case XK_KP_4:
+		case XK_KP_5:
+		case XK_KP_6:
+		case XK_KP_7:
+		case XK_KP_8:
+		case XK_KP_9:
+		case XK_KP_Divide:
+		case XK_KP_Multiply:
+		case XK_KP_Subtract:
+		case XK_KP_Add:
+		case XK_KP_Decimal:
+			k &= 0x7F;
+			break;
+		case XK_Linefeed:
+			k = '\r';
+			break;
+		case XK_KP_Space:
+			k = ' ';
+			break;
+		case XK_Home:
+		case XK_KP_Home:
+			k = Khome;
+			break;
+		case XK_Left:
+		case XK_KP_Left:
+			k = Kleft;
+			break;
+		case XK_Up:
+		case XK_KP_Up:
+			k = Kup;
+			break;
+		case XK_Down:
+		case XK_KP_Down:
+			k = Kdown;
+			break;
+		case XK_Right:
+		case XK_KP_Right:
+			k = Kright;
+			break;
+		case XK_Page_Down:
+		case XK_KP_Page_Down:
+			k = Kpgdown;
+			break;
+		case XK_End:
+		case XK_KP_End:
+			k = Kend;
+			break;
+		case XK_Page_Up:	
+		case XK_KP_Page_Up:
+			k = Kpgup;
+			break;
+		case XK_Insert:
+		case XK_KP_Insert:
+			k = Kins;
+			break;
+		case XK_KP_Enter:
+		case XK_Return:
+			k = '\n';
+			break;
+		case XK_Alt_L:
+		case XK_Alt_R:
+			k = Kalt;
+			break;
+		case XK_Shift_L:
+		case XK_Shift_R:
+		case XK_Control_L:
+		case XK_Control_R:
+		case XK_Caps_Lock:
+		case XK_Shift_Lock:
+
+		case XK_Meta_L:
+		case XK_Meta_R:
+		case XK_Super_L:
+		case XK_Super_R:
+		case XK_Hyper_L:
+		case XK_Hyper_R:
+            return;
+		default:		/* not ISO-1 or tty control */
+  			if(k>0xff) {
+                k = keysym2ucs(k); /* supplied by X */
+                if(k == -1) return;
+           	}
+		}
+	}
+
+	/* Compensate for servers that call a minus a hyphen */
+	if(k == XK_hyphen)
+		k = XK_minus;
+	/* Do control mapping ourselves if translator doesn't */
+	if(e->xkey.state&ControlMask)
+		k &= 0x9f;
+	if(k == NoSymbol) {
+		return;
+	}
+
+	kbdputc(kbdq, k);
+}
+
+static void
+xmouse(XEvent *e)
+{
+	Mousestate ms;
+	int i, s;
+	XButtonEvent *be;
+	XMotionEvent *me;
+
+	switch(e->type){
+	case ButtonPress:
+		be = (XButtonEvent *)e;
+		ms.xy.x = be->x;
+		ms.xy.y = be->y;
+		s = be->state;
+		ms.msec = be->time;
+		switch(be->button){
+		case 1:
+			s |= Button1Mask;
+			break;
+		case 2:
+			s |= Button2Mask;
+			break;
+		case 3:
+			s |= Button3Mask;
+			break;
+		case 4:
+			s |= Button4Mask;
+			break;
+		case 5:
+			s |= Button5Mask;
+			break;
+		}
+		break;
+	case ButtonRelease:
+		be = (XButtonEvent *)e;
+		ms.xy.x = be->x;
+		ms.xy.y = be->y;
+		ms.msec = be->time;
+		s = be->state;
+		switch(be->button){
+		case 1:
+			s &= ~Button1Mask;
+			break;
+		case 2:
+			s &= ~Button2Mask;
+			break;
+		case 3:
+			s &= ~Button3Mask;
+			break;
+		case 4:
+			s &= ~Button4Mask;
+			break;
+		case 5:
+			s &= ~Button5Mask;
+			break;
+		}
+		break;
+	case MotionNotify:
+		me = (XMotionEvent *)e;
+		s = me->state;
+		ms.xy.x = me->x;
+		ms.xy.y = me->y;
+		ms.msec = me->time;
+		break;
+	default:
+		return;
+	}
+
+	ms.buttons = 0;
+	if(s & Button1Mask)
+		ms.buttons |= 1;
+	if(s & Button2Mask)
+		ms.buttons |= 2;
+	if(s & Button3Mask)
+		ms.buttons |= 4;
+	if(s & Button4Mask)
+		ms.buttons |= 8;
+	if(s & Button5Mask)
+		ms.buttons |= 16;
+
+	lock(&mouse.lk);
+	i = mouse.wi;
+	if(mousequeue) {
+		if(i == mouse.ri || mouse.lastb != ms.buttons || mouse.trans) {
+			mouse.wi = (i+1)%Mousequeue;
+			if(mouse.wi == mouse.ri)
+				mouse.ri = (mouse.ri+1)%Mousequeue;
+			mouse.trans = mouse.lastb != ms.buttons;
+		} else {
+			i = (i-1+Mousequeue)%Mousequeue;
+		}
+	} else {
+		mouse.wi = (i+1)%Mousequeue;
+		mouse.ri = i;
+	}
+	mouse.queue[i] = ms;
+	mouse.lastb = ms.buttons;
+	unlock(&mouse.lk);
+	wakeup(&mouse.r);
+}
+
+void
+getcolor(ulong i, ulong *r, ulong *g, ulong *b)
+{
+	ulong v;
+	
+	v = cmap2rgb(i);
+	*r = (v>>16)&0xFF;
+	*g = (v>>8)&0xFF;
+	*b = v&0xFF;
+}
+
+void
+setcolor(ulong i, ulong r, ulong g, ulong b)
+{
+	/* no-op */
+}
+
+typedef struct Clip	Clip;
+struct Clip
+{
+	char buf[SnarfSize];
+	ulong n;
+	int want, have;
+	QLock lk;
+	Rendez vous;
+};
+
+Clip clip;
+
+enum {
+	Chunk = 2048
+};
+
+static void
+xselect(XEvent *e)
+{
+	XSelectionRequestEvent *q;
+	XEvent r;
+	Atom a[4];
+	char *name;
+
+
+	if(e->type != SelectionRequest)
+		return;
+
+	/*
+	 * The lock is around the whole routine because we use the 
+	 * lock to make sure two people aren't sending on xkmcon
+	 * at once.
+	 */
+	q = (XSelectionRequestEvent*)e;
+
+    r.xselection.property = q->property;
+    if(q->target == targets) {
+        a[0] = XA_STRING;
+        a[1] = utf8string;
+        a[2] = text;
+        a[3] = compoundtext;
+
+        XChangeProperty(xkmcon, q->requestor, q->property, q->target,
+            8, PropModeReplace, (uchar*)a, sizeof a);
+    }else if(q->target == XA_STRING || q->target == utf8string || q->target == text || q->target == compoundtext){
+		qlock(&clip.lk);
+		XChangeProperty(xkmcon, q->requestor, q->property, q->target, 8,
+			PropModeReplace, (uchar*)clip.buf, strlen(clip.buf));
+		qunlock(&clip.lk);
+	}else {
+        name = XGetAtomName(xkmcon, q->target);
+        if(strcmp(name, "TIMESTAMP") != 0)
+            fprint(2, "%s: cannot handle selection request for '%s' (%d)\n", argv0, name, (int)q->target);
+		r.xselection.property = None;
+	}
+		
+	r.xselection.type = SelectionNotify;
+	r.xselection.display = q->display;
+	r.xselection.requestor = q->requestor;
+	r.xselection.selection = q->selection;
+	r.xselection.target = q->target;
+	r.xselection.time = q->time;
+	XSendEvent(xkmcon, q->requestor, False, 0, &r);
+	XFlush(xkmcon);
+}
+
+int
+haveclip(void *a)
+{
+	return clip.want == clip.have;
+}
+
+#undef long /* sic */
+char*
+clipread(void)
+{
+	Window w;
+	XEvent e;
+	Atom type;
+	unsigned long len, lleft, left, dummy;
+	int i, fmt, res;
+	uchar *data;
+
+	qlock(&clip.lk);
+	w = XGetSelectionOwner(xsnarfcon, XA_PRIMARY);
+	if(w == xdrawable)
+		data = (uchar*)strdup(clip.buf);
+	else if(w == None)
+		data = nil;
+	else {	
+		/*
+		 * we're supposed to get a notification, but we seem not to,
+		 * so let's just watch and see when the buffer stabilizes.
+		 * if you know how to fix this, mail rsc@plan9.bell-labs.com.
+		 */
+		XChangeProperty(xsnarfcon, xdrawable, XA_PRIMARY, XA_STRING, 8, PropModeReplace,
+		 	(uchar*)"", 0);
+		XConvertSelection(xsnarfcon, XA_PRIMARY, XA_STRING, None, xdrawable, CurrentTime);
+		XFlush(xsnarfcon);
+		for(i=0; i<30; i++){
+		 	osmsleep(100);
+			XGetWindowProperty(xsnarfcon, xdrawable, XA_STRING, 0, 0, 0, AnyPropertyType,
+				&type, &fmt, &len, &left, &data);
+			if(lleft == left && left > 0)
+				break;
+			lleft = left;
+		}
+		if(left > 0){
+			res = XGetWindowProperty(xsnarfcon, xdrawable, XA_STRING, 0, left, 0, 
+				AnyPropertyType, &type, &fmt, &len, &dummy, &data);
+			data = (uchar*)strdup(data);
+		}else
+			data = nil;
+	}
+	qunlock(&clip.lk);
+	return (char*)data;
+}
+
+int
+clipwrite(char *buf)
+{
+	int n;
+
+	n = strlen(buf);
+	qlock(&clip.lk);
+	if(n >= SnarfSize)
+		n = SnarfSize - 1;
+	memmove(clip.buf, buf, n);
+	clip.buf[n] = 0;
+	clip.n = n;
+	/*
+	 * xkmcon so that we get the event in the select loop.  
+	 * It seems to be okay to send a message and read an event
+	 * from a Display* at the same time.  Let's hope so.
+	 */
+	XSetSelectionOwner(xkmcon, XA_PRIMARY, xdrawable, CurrentTime);
+	XFlush(xkmcon);
+	qunlock(&clip.lk);
+	return n;
+}
+#define long int /* sic */
+
+int
+atlocalconsole(void)
+{
+	char *p, *q;
+	char buf[128];
+
+	p = getenv("DISPLAY");
+	if(p == nil)
+		return 0;
+
+	/* extract host part */
+	q = strchr(p, ':');
+	if(q == nil)
+		return 0;
+	*q = 0;
+
+	if(strcmp(p, "") == 0)
+		return 1;
+
+	/* try to match against system name (i.e. for ssh) */
+	if(gethostname(buf, sizeof buf) == 0){
+		if(strcmp(p, buf) == 0)
+			return 1;
+		if(strncmp(p, buf, strlen(p)) == 0 && buf[strlen(p)]=='.')
+			return 1;
+	}
+	
+	return 0;
+}
--- /dev/null
+++ b/gui-x11/xmem.h
@@ -1,0 +1,60 @@
+#define	Font	XXFont
+#define	Screen	XXScreen
+#define	Display	XXDisplay
+
+#include <X11/Xlib.h>
+/* #include <X11/Xlibint.h> */
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+
+#undef	Font
+#undef	Screen
+#undef	Display
+
+/*
+ * Structure pointed to by X field of Memimage
+ */
+typedef struct Xmem Xmem;
+
+enum
+{
+	PMundef	= ~0		/* undefined pixmap id */
+};
+
+
+struct Xmem
+{
+	int	pmid;	/* pixmap id for screen ldepth instance */
+	XImage *xi;	/* local image if we currenty have the data */
+	int	dirty;
+	Rectangle dirtyr;
+	Rectangle r;
+	ulong pc;	/* who wrote into xi */
+};
+
+extern	int		xtblbit;
+extern	int		x24bitswap;
+extern	int		plan9tox11[];
+extern  int		x11toplan9[];
+extern	int		xscreendepth;
+extern	XXDisplay	*xdisplay;
+extern	Drawable	xscreenid;
+extern	Visual		*xvis;
+extern	GC		xgcfill, xgcfill0;
+extern	int		xgcfillcolor, xgcfillcolor0;
+extern	GC		xgccopy, xgccopy0;
+extern	GC		xgczero, xgczero0;
+extern	int		xgczeropm, xgczeropm0;
+extern	GC		xgcsimplesrc, xgcsimplesrc0;
+extern	int		xgcsimplecolor, xgcsimplecolor0, xgcsimplepm, xgcsimplepm0;
+extern	GC		xgcreplsrc, xgcreplsrc0;
+extern	int		xgcreplsrcpm, xgcreplsrcpm0, xgcreplsrctile, xgcreplsrctile0;
+extern	XImage*		allocXdata(Memimage*, Rectangle);
+extern	void 		putXdata(Memimage*, Rectangle);
+extern	XImage*		getXdata(Memimage*, Rectangle);
+extern	void		freeXdata(Memimage*);
+extern	void	dirtyXdata(Memimage*, Rectangle);
+extern	ulong	xscreenchan;
+extern	void	xfillcolor(Memimage*, Rectangle, ulong);
binary files /dev/null b/include/a.out differ
--- /dev/null
+++ b/include/auth.h
@@ -1,0 +1,144 @@
+#pragma	src	"/sys/src/libauth"
+#pragma	lib	"libauth.a"
+
+/*
+ * Interface for typical callers.
+ */
+
+typedef struct	AuthInfo	AuthInfo;
+typedef struct	Chalstate	Chalstate;
+typedef struct	Chapreply	Chapreply;
+typedef struct	MSchapreply	MSchapreply;
+typedef struct	UserPasswd	UserPasswd;
+typedef struct	AuthRpc		AuthRpc;
+
+enum
+{
+	MAXCHLEN=	256,		/* max challenge length	*/
+	MAXNAMELEN=	256,		/* maximum name length */
+	MD5LEN=		16,
+
+	ARok = 0,			/* rpc return values */
+	ARdone,
+	ARerror,
+	ARneedkey,
+	ARbadkey,
+	ARwritenext,
+	ARtoosmall,
+	ARtoobig,
+	ARrpcfailure,
+	ARphase,
+
+	AuthRpcMax = 4096,
+};
+
+struct AuthRpc
+{
+	int afd;
+	char ibuf[AuthRpcMax];
+	char obuf[AuthRpcMax];
+	char *arg;
+	uint narg;
+};
+
+struct AuthInfo
+{
+	char	*cuid;		/* caller id */
+	char	*suid;		/* server id */
+	char	*cap;		/* capability (only valid on server side) */
+	int	nsecret;	/* length of secret */
+	uchar	*secret;	/* secret */
+};
+
+struct Chalstate
+{
+	char	*user;
+	char	chal[MAXCHLEN];
+	int	nchal;
+	void	*resp;
+	int	nresp;
+
+/* for implementation only */
+	int	afd;			/* to factotum */
+	AuthRpc	*rpc;			/* to factotum */
+	char	userbuf[MAXNAMELEN];	/* temp space if needed */
+	int	userinchal;		/* user was sent to obtain challenge */
+};
+
+struct	Chapreply		/* for protocol "chap" */
+{
+	uchar	id;
+	char	resp[MD5LEN];
+};
+
+struct	MSchapreply	/* for protocol "mschap" */
+{
+	char	LMresp[24];		/* Lan Manager response */
+	char	NTresp[24];		/* NT response */
+};
+
+struct	UserPasswd
+{
+	char	*user;
+	char	*passwd;
+};
+
+extern	int	newns(char*, char*);
+extern	int	addns(char*, char*);
+
+extern	int	noworld(char*);
+extern	int	amount(int, char*, int, char*);
+
+/* these two may get generalized away -rsc */
+extern	int	login(char*, char*, char*);
+extern	int	httpauth(char*, char*);
+
+typedef struct Attr Attr;
+typedef struct String String;
+enum {
+	AttrNameval,		/* name=val -- when matching, must have name=val */
+	AttrQuery,		/* name? -- when matching, must be present */
+	AttrDefault,		/* name:=val -- when matching, if present must match INTERNAL */
+};
+struct Attr
+{
+	int type;
+	Attr *next;
+	char *name;
+	char *val;
+};
+
+typedef int AuthGetkey(char*);
+
+int	_attrfmt(Fmt*);
+Attr	*_copyattr(Attr*);
+Attr	*_delattr(Attr*, char*);
+Attr	*_findattr(Attr*, char*);
+void	_freeattr(Attr*);
+Attr	*_mkattr(int, char*, char*, Attr*);
+Attr	*_parseattr(char*);
+char	*_strfindattr(Attr*, char*);
+#pragma varargck type "A" Attr*
+
+extern AuthInfo*	fauth_proxy(int, AuthRpc *rpc, AuthGetkey *getkey, char *params);
+extern AuthInfo*	auth_proxy(int fd, AuthGetkey *getkey, char *fmt, ...);
+extern int		auth_getkey(char*);
+extern int		(*amount_getkey)(char*);
+extern void		auth_freeAI(AuthInfo *ai);
+extern int		auth_chuid(AuthInfo *ai, char *ns);
+extern Chalstate	*auth_challenge(char*, ...);
+extern AuthInfo*	auth_response(Chalstate*);
+extern int		auth_respond(void*, uint, char*, uint, void*, uint, AuthGetkey *getkey, char*, ...);
+extern void		auth_freechal(Chalstate*);
+extern AuthInfo*	auth_userpasswd(char *user, char *passwd);
+extern UserPasswd*	auth_getuserpasswd(AuthGetkey *getkey, char*, ...);
+extern AuthInfo*	auth_getinfo(AuthRpc *rpc);
+extern AuthRpc*		auth_allocrpc(int afd);
+extern Attr*		auth_attr(AuthRpc *rpc);
+extern void		auth_freerpc(AuthRpc *rpc);
+extern uint		auth_rpc(AuthRpc *rpc, char *verb, void *a, int n);
+extern int		auth_wep(char*, char*, ...);
+#pragma varargck argpos auth_proxy 3
+#pragma varargck argpos auth_challenge 1
+#pragma varargck argpos auth_respond 3
+#pragma varargck argpos auth_getuserpasswd 2
--- /dev/null
+++ b/include/authsrv.h
@@ -1,0 +1,166 @@
+#pragma	src	"/sys/src/libauthsrv"
+#pragma	lib	"libauthsrv.a"
+
+/*
+ * Interface for talking to authentication server.
+ */
+typedef struct	Ticket		Ticket;
+typedef struct	Ticketreq	Ticketreq;
+typedef struct	Authenticator	Authenticator;
+typedef struct	Nvrsafe		Nvrsafe;
+typedef struct	Passwordreq	Passwordreq;
+typedef struct	OChapreply	OChapreply;
+typedef struct	OMSchapreply	OMSchapreply;
+
+enum
+{
+	ANAMELEN=	28,		/* maximum size of name in previous proto */
+	AERRLEN=	64,		/* maximum size of errstr in previous proto */
+	DOMLEN=		48,		/* length of an authentication domain name */
+	DESKEYLEN=	7,		/* length of a des key for encrypt/decrypt */
+	CHALLEN=	8,		/* length of a plan9 sk1 challenge */
+	NETCHLEN=	16,		/* max network challenge length (used in AS protocol) */
+	CONFIGLEN=	14,
+	SECRETLEN=	32,		/* max length of a secret */
+
+	KEYDBOFF=	8,		/* length of random data at the start of key file */
+	OKEYDBLEN=	ANAMELEN+DESKEYLEN+4+2,	/* length of an entry in old key file */
+	KEYDBLEN=	OKEYDBLEN+SECRETLEN,	/* length of an entry in key file */
+	OMD5LEN=	16,
+};
+
+/* encryption numberings (anti-replay) */
+enum
+{
+	AuthTreq=1,	/* ticket request */
+	AuthChal=2,	/* challenge box request */
+	AuthPass=3,	/* change password */
+	AuthOK=4,	/* fixed length reply follows */
+	AuthErr=5,	/* error follows */
+	AuthMod=6,	/* modify user */
+	AuthApop=7,	/* apop authentication for pop3 */
+	AuthOKvar=9,	/* variable length reply follows */
+	AuthChap=10,	/* chap authentication for ppp */
+	AuthMSchap=11,	/* MS chap authentication for ppp */
+	AuthCram=12,	/* CRAM verification for IMAP (RFC2195 & rfc2104) */
+	AuthHttp=13,	/* http domain login */
+	AuthVNC=14,	/* VNC server login (deprecated) */
+
+
+	AuthTs=64,	/* ticket encrypted with server's key */
+	AuthTc,		/* ticket encrypted with client's key */
+	AuthAs,		/* server generated authenticator */
+	AuthAc,		/* client generated authenticator */
+	AuthTp,		/* ticket encrypted with client's key for password change */
+	AuthHr,		/* http reply */
+};
+
+struct Ticketreq
+{
+	char	type;
+	char	authid[ANAMELEN];	/* server's encryption id */
+	char	authdom[DOMLEN];	/* server's authentication domain */
+	char	chal[CHALLEN];		/* challenge from server */
+	char	hostid[ANAMELEN];	/* host's encryption id */
+	char	uid[ANAMELEN];		/* uid of requesting user on host */
+};
+#define	TICKREQLEN	(3*ANAMELEN+CHALLEN+DOMLEN+1)
+
+struct Ticket
+{
+	char	num;			/* replay protection */
+	char	chal[CHALLEN];		/* server challenge */
+	char	cuid[ANAMELEN];		/* uid on client */
+	char	suid[ANAMELEN];		/* uid on server */
+	char	key[DESKEYLEN];		/* nonce DES key */
+};
+#define	TICKETLEN	(CHALLEN+2*ANAMELEN+DESKEYLEN+1)
+
+struct Authenticator
+{
+	char	num;			/* replay protection */
+	char	chal[CHALLEN];
+	ulong	id;			/* authenticator id, ++'d with each auth */
+};
+#define	AUTHENTLEN	(CHALLEN+4+1)
+
+struct Passwordreq
+{
+	char	num;
+	char	old[ANAMELEN];
+	char	new[ANAMELEN];
+	char	changesecret;
+	char	secret[SECRETLEN];	/* new secret */
+};
+#define	PASSREQLEN	(2*ANAMELEN+1+1+SECRETLEN)
+
+struct	OChapreply
+{
+	uchar	id;
+	char	uid[ANAMELEN];
+	char	resp[OMD5LEN];
+};
+
+struct	OMSchapreply
+{
+	char	uid[ANAMELEN];
+	char	LMresp[24];		/* Lan Manager response */
+	char	NTresp[24];		/* NT response */
+};
+
+/*
+ *  convert to/from wire format
+ */
+extern	int	convT2M(Ticket*, char*, char*);
+extern	void	convM2T(char*, Ticket*, char*);
+extern	void	convM2Tnoenc(char*, Ticket*);
+extern	int	convA2M(Authenticator*, char*, char*);
+extern	void	convM2A(char*, Authenticator*, char*);
+extern	int	convTR2M(Ticketreq*, char*);
+extern	void	convM2TR(char*, Ticketreq*);
+extern	int	convPR2M(Passwordreq*, char*, char*);
+extern	void	convM2PR(char*, Passwordreq*, char*);
+
+/*
+ *  convert ascii password to DES key
+ */
+extern	int	opasstokey(char*, char*);
+extern	int	passtokey(char*, char*);
+
+/*
+ *  Nvram interface
+ */
+enum {
+	NVwrite = 1<<0,		/* always prompt and rewrite nvram */
+	NVwriteonerr = 1<<1,	/* prompt and rewrite nvram when corrupt */
+};
+
+struct Nvrsafe
+{
+	char	machkey[DESKEYLEN];
+	uchar	machsum;
+	char	authkey[DESKEYLEN];
+	uchar	authsum;
+	char	config[CONFIGLEN];
+	uchar	configsum;
+	char	authid[ANAMELEN];
+	uchar	authidsum;
+	char	authdom[DOMLEN];
+	uchar	authdomsum;
+};
+
+extern	uchar	nvcsum(void*, int);
+extern int	readnvram(Nvrsafe*, int);
+
+/*
+ *  call up auth server
+ */
+extern	int	authdial(char *netroot, char *authdom);
+
+/*
+ *  exchange messages with auth server
+ */
+extern	int	_asgetticket(int, char*, char*);
+extern	int	_asrdresp(int, char*, int);
+extern	int	sslnegotiate(int, Ticket*, char**, char**);
+extern	int	srvsslnegotiate(int, Ticket*, char**, char**);
--- /dev/null
+++ b/include/cursor.h
@@ -1,0 +1,6 @@
+struct	Cursor
+{
+	Point	offset;
+	uchar	clr[2*16];
+	uchar	set[2*16];
+};
--- /dev/null
+++ b/include/draw.h
@@ -1,0 +1,519 @@
+#pragma src "/sys/src/libdraw"
+#pragma lib "libdraw.a"
+
+typedef struct	Cachefont Cachefont;
+typedef struct	Cacheinfo Cacheinfo;
+typedef struct	Cachesubf Cachesubf;
+typedef struct	Display Display;
+typedef struct	Font Font;
+typedef struct	Fontchar Fontchar;
+typedef struct	Image Image;
+typedef struct	Mouse Mouse;
+typedef struct	Point Point;
+typedef struct	Rectangle Rectangle;
+typedef struct	RGB RGB;
+typedef struct	Screen Screen;
+typedef struct	Subfont Subfont;
+
+#pragma varargck	type	"R"	Rectangle
+#pragma varargck	type	"P"	Point
+extern	int	Rfmt(Fmt*);
+extern	int	Pfmt(Fmt*);
+
+enum
+{
+	DOpaque		= 0xFFFFFFFF,
+	DTransparent	= 0x00000000,		/* only useful for allocimage, memfillcolor */
+	DBlack		= 0x000000FF,
+	DWhite		= 0xFFFFFFFF,
+	DRed		= 0xFF0000FF,
+	DGreen		= 0x00FF00FF,
+	DBlue		= 0x0000FFFF,
+	DCyan		= 0x00FFFFFF,
+	DMagenta		= 0xFF00FFFF,
+	DYellow		= 0xFFFF00FF,
+	DPaleyellow	= 0xFFFFAAFF,
+	DDarkyellow	= 0xEEEE9EFF,
+	DDarkgreen	= 0x448844FF,
+	DPalegreen	= 0xAAFFAAFF,
+	DMedgreen	= 0x88CC88FF,
+	DDarkblue	= 0x000055FF,
+	DPalebluegreen= 0xAAFFFFFF,
+	DPaleblue		= 0x0000BBFF,
+	DBluegreen	= 0x008888FF,
+	DGreygreen	= 0x55AAAAFF,
+	DPalegreygreen	= 0x9EEEEEFF,
+	DYellowgreen	= 0x99994CFF,
+	DMedblue		= 0x000099FF,
+	DGreyblue	= 0x005DBBFF,
+	DPalegreyblue	= 0x4993DDFF,
+	DPurpleblue	= 0x8888CCFF,
+
+	DNotacolor	= 0xFFFFFF00,
+	DNofill		= DNotacolor,
+	
+};
+
+enum
+{
+	Displaybufsize	= 8000,
+	ICOSSCALE	= 1024,
+	Borderwidth =	4,
+};
+
+enum
+{
+	/* refresh methods */
+	Refbackup	= 0,
+	Refnone		= 1,
+	Refmesg		= 2
+};
+#define	NOREFRESH	((void*)-1)
+
+enum
+{
+	/* line ends */
+	Endsquare	= 0,
+	Enddisc		= 1,
+	Endarrow	= 2,
+	Endmask		= 0x1F
+};
+
+#define	ARROW(a, b, c)	(Endarrow|((a)<<5)|((b)<<14)|((c)<<23))
+
+typedef enum
+{
+	/* Porter-Duff compositing operators */
+	Clear	= 0,
+
+	SinD	= 8,
+	DinS	= 4,
+	SoutD	= 2,
+	DoutS	= 1,
+
+	S		= SinD|SoutD,
+	SoverD	= SinD|SoutD|DoutS,
+	SatopD	= SinD|DoutS,
+	SxorD	= SoutD|DoutS,
+
+	D		= DinS|DoutS,
+	DoverS	= DinS|DoutS|SoutD,
+	DatopS	= DinS|SoutD,
+	DxorS	= DoutS|SoutD,	/* == SxorD */
+
+	Ncomp = 12,
+} Drawop;
+
+/*
+ * image channel descriptors 
+ */
+enum {
+	CRed = 0,
+	CGreen,
+	CBlue,
+	CGrey,
+	CAlpha,
+	CMap,
+	CIgnore,
+	NChan,
+};
+
+#define __DC(type, nbits)	((((type)&15)<<4)|((nbits)&15))
+#define CHAN1(a,b)	__DC(a,b)
+#define CHAN2(a,b,c,d)	(CHAN1((a),(b))<<8|__DC((c),(d)))
+#define CHAN3(a,b,c,d,e,f)	(CHAN2((a),(b),(c),(d))<<8|__DC((e),(f)))
+#define CHAN4(a,b,c,d,e,f,g,h)	(CHAN3((a),(b),(c),(d),(e),(f))<<8|__DC((g),(h)))
+
+#define NBITS(c) ((c)&15)
+#define TYPE(c) (((c)>>4)&15)
+
+enum {
+	GREY1	= CHAN1(CGrey, 1),
+	GREY2	= CHAN1(CGrey, 2),
+	GREY4	= CHAN1(CGrey, 4),
+	GREY8	= CHAN1(CGrey, 8),
+	CMAP8	= CHAN1(CMap, 8),
+	RGB15	= CHAN4(CIgnore, 1, CRed, 5, CGreen, 5, CBlue, 5),
+	RGB16	= CHAN3(CRed, 5, CGreen, 6, CBlue, 5),
+	RGB24	= CHAN3(CRed, 8, CGreen, 8, CBlue, 8),
+	BGR24	= CHAN3(CBlue, 8, CGreen, 8, CRed, 8),
+	RGBA32	= CHAN4(CRed, 8, CGreen, 8, CBlue, 8, CAlpha, 8),
+	ARGB32	= CHAN4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8),	/* stupid VGAs */
+	XRGB32  = CHAN4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8),
+	XBGR32  = CHAN4(CIgnore, 8, CBlue, 8, CGreen, 8, CRed, 8),
+};
+
+extern	char*	chantostr(char*, ulong);
+extern	ulong	strtochan(char*);
+extern	int		chantodepth(ulong);
+
+struct	Point
+{
+	int	x;
+	int	y;
+};
+
+struct Rectangle
+{
+	Point	min;
+	Point	max;
+};
+
+typedef void	(*Reffn)(Image*, Rectangle, void*);
+
+struct Screen
+{
+	Display	*display;	/* display holding data */
+	int	id;		/* id of system-held Screen */
+	Image	*image;		/* unused; for reference only */
+	Image	*fill;		/* color to paint behind windows */
+};
+
+struct Display
+{
+	QLock	qlock;
+	int		locking;	/*program is using lockdisplay */
+	int		dirno;
+	int		fd;
+	int		reffd;
+	int		ctlfd;
+	int		imageid;
+	int		local;
+	void		(*error)(Display*, char*);
+	char		*devdir;
+	char		*windir;
+	char		oldlabel[64];
+	ulong		dataqid;
+	Image		*white;
+	Image		*black;
+	Image		*opaque;
+	Image		*transparent;
+	Image		*image;
+	uchar		*buf;
+	int			bufsize;
+	uchar		*bufp;
+	Font		*defaultfont;
+	Subfont		*defaultsubfont;
+	Image		*windows;
+	Image		*screenimage;
+	int			_isnewdisplay;
+};
+
+struct Image
+{
+	Display		*display;	/* display holding data */
+	int		id;		/* id of system-held Image */
+	Rectangle	r;		/* rectangle in data area, local coords */
+	Rectangle 	clipr;		/* clipping region */
+	int		depth;		/* number of bits per pixel */
+	ulong	chan;
+	int		repl;		/* flag: data replicates to tile clipr */
+	Screen		*screen;	/* 0 if not a window */
+	Image		*next;	/* next in list of windows */
+};
+
+struct RGB
+{
+	ulong	red;
+	ulong	green;
+	ulong	blue;
+};
+
+/*
+ * Subfonts
+ *
+ * given char c, Subfont *f, Fontchar *i, and Point p, one says
+ *	i = f->info+c;
+ *	draw(b, Rect(p.x+i->left, p.y+i->top,
+ *		p.x+i->left+((i+1)->x-i->x), p.y+i->bottom),
+ *		color, f->bits, Pt(i->x, i->top));
+ *	p.x += i->width;
+ * to draw characters in the specified color (itself an Image) in Image b.
+ */
+
+struct	Fontchar
+{
+	int		x;		/* left edge of bits */
+	uchar		top;		/* first non-zero scan-line */
+	uchar		bottom;		/* last non-zero scan-line + 1 */
+	char		left;		/* offset of baseline */
+	uchar		width;		/* width of baseline */
+};
+
+struct	Subfont
+{
+	char		*name;
+	short		n;		/* number of chars in font */
+	uchar		height;		/* height of image */
+	char		ascent;		/* top of image to baseline */
+	Fontchar 	*info;		/* n+1 character descriptors */
+	Image		*bits;		/* of font */
+	int		ref;
+};
+
+enum
+{
+	/* starting values */
+	LOG2NFCACHE =	6,
+	NFCACHE =	(1<<LOG2NFCACHE),	/* #chars cached */
+	NFLOOK =	5,			/* #chars to scan in cache */
+	NFSUBF =	2,			/* #subfonts to cache */
+	/* max value */
+	MAXFCACHE =	1024+NFLOOK,		/* upper limit */
+	MAXSUBF =	50,			/* generous upper limit */
+	/* deltas */
+	DSUBF = 	4,
+	/* expiry ages */
+	SUBFAGE	=	10000,
+	CACHEAGE =	10000
+};
+
+struct Cachefont
+{
+	Rune		min;	/* lowest rune value to be taken from subfont */
+	Rune		max;	/* highest rune value+1 to be taken from subfont */
+	int		offset;	/* position in subfont of character at min */
+	char		*name;			/* stored in font */
+	char		*subfontname;		/* to access subfont */
+};
+
+struct Cacheinfo
+{
+	ushort		x;		/* left edge of bits */
+	uchar		width;		/* width of baseline */
+	schar		left;		/* offset of baseline */
+	Rune		value;	/* value of character at this slot in cache */
+	ushort		age;
+};
+
+struct Cachesubf
+{
+	ulong		age;	/* for replacement */
+	Cachefont	*cf;	/* font info that owns us */
+	Subfont		*f;	/* attached subfont */
+};
+
+struct Font
+{
+	char		*name;
+	Display		*display;
+	short		height;	/* max height of image, interline spacing */
+	short		ascent;	/* top of image to baseline */
+	short		width;	/* widest so far; used in caching only */	
+	short		nsub;	/* number of subfonts */
+	ulong		age;	/* increasing counter; used for LRU */
+	int		maxdepth;	/* maximum depth of all loaded subfonts */
+	int		ncache;	/* size of cache */
+	int		nsubf;	/* size of subfont list */
+	Cacheinfo	*cache;
+	Cachesubf	*subf;
+	Cachefont	**sub;	/* as read from file */
+	Image		*cacheimage;
+};
+
+#define	Dx(r)	((r).max.x-(r).min.x)
+#define	Dy(r)	((r).max.y-(r).min.y)
+
+/*
+ * Image management
+ */
+extern Image*	_allocimage(Image*, Display*, Rectangle, ulong, int, ulong, int, int);
+extern Image*	allocimage(Display*, Rectangle, ulong, int, ulong);
+extern uchar*	bufimage(Display*, int);
+extern int	bytesperline(Rectangle, int);
+extern void	closedisplay(Display*);
+extern void	drawerror(Display*, char*);
+extern int	flushimage(Display*, int);
+extern int	freeimage(Image*);
+extern int	_freeimage1(Image*);
+extern int	geninitdraw(char*, void(*)(Display*, char*), char*, char*, char*, int);
+extern int	initdraw(void(*)(Display*, char*), char*, char*);
+extern int	newwindow(char*);
+extern Display*	initdisplay(char*, char*, void(*)(Display*, char*));
+extern int	loadimage(Image*, Rectangle, uchar*, int);
+extern int	cloadimage(Image*, Rectangle, uchar*, int);
+extern int	getwindow(Display*, int);
+extern int	gengetwindow(Display*, char*, Image**, Screen**, int);
+extern Image* readimage(Display*, int, int);
+extern Image* creadimage(Display*, int, int);
+extern int	unloadimage(Image*, Rectangle, uchar*, int);
+extern int	wordsperline(Rectangle, int);
+extern int	writeimage(int, Image*, int);
+extern Image*	namedimage(Display*, char*);
+extern int	nameimage(Image*, char*, int);
+extern Image* allocimagemix(Display*, ulong, ulong);
+
+/*
+ * Colors
+ */
+extern	void	readcolmap(Display*, RGB*);
+extern	void	writecolmap(Display*, RGB*);
+extern	ulong	setalpha(ulong, uchar);
+
+/*
+ * Windows
+ */
+extern Screen*	allocscreen(Image*, Image*, int);
+extern Image*	_allocwindow(Image*, Screen*, Rectangle, int, ulong);
+extern Image*	allocwindow(Screen*, Rectangle, int, ulong);
+extern void	bottomnwindows(Image**, int);
+extern void	bottomwindow(Image*);
+extern int	freescreen(Screen*);
+extern Screen*	publicscreen(Display*, int, ulong);
+extern void	topnwindows(Image**, int);
+extern void	topwindow(Image*);
+extern int	originwindow(Image*, Point, Point);
+
+/*
+ * Geometry
+ */
+extern Point		Pt(int, int);
+extern Rectangle	Rect(int, int, int, int);
+extern Rectangle	Rpt(Point, Point);
+extern Point		addpt(Point, Point);
+extern Point		subpt(Point, Point);
+extern Point		divpt(Point, int);
+extern Point		mulpt(Point, int);
+extern int		eqpt(Point, Point);
+extern int		eqrect(Rectangle, Rectangle);
+extern Rectangle	insetrect(Rectangle, int);
+extern Rectangle	rectaddpt(Rectangle, Point);
+extern Rectangle	rectsubpt(Rectangle, Point);
+extern Rectangle	canonrect(Rectangle);
+extern int		rectXrect(Rectangle, Rectangle);
+extern int		rectinrect(Rectangle, Rectangle);
+extern void		combinerect(Rectangle*, Rectangle);
+extern int		rectclip(Rectangle*, Rectangle);
+extern int		ptinrect(Point, Rectangle);
+extern void		replclipr(Image*, int, Rectangle);
+extern int		drawreplxy(int, int, int);	/* used to be drawsetxy */
+extern Point	drawrepl(Rectangle, Point);
+extern int		rgb2cmap(int, int, int);
+extern int		cmap2rgb(int);
+extern int		cmap2rgba(int);
+extern void		icossin(int, int*, int*);
+extern void		icossin2(int, int, int*, int*);
+
+/*
+ * Graphics
+ */
+extern void	draw(Image*, Rectangle, Image*, Image*, Point);
+extern void	drawop(Image*, Rectangle, Image*, Image*, Point, Drawop);
+extern void	gendraw(Image*, Rectangle, Image*, Point, Image*, Point);
+extern void	gendrawop(Image*, Rectangle, Image*, Point, Image*, Point, Drawop);
+extern void	line(Image*, Point, Point, int, int, int, Image*, Point);
+extern void	lineop(Image*, Point, Point, int, int, int, Image*, Point, Drawop);
+extern void	poly(Image*, Point*, int, int, int, int, Image*, Point);
+extern void	polyop(Image*, Point*, int, int, int, int, Image*, Point, Drawop);
+extern void	fillpoly(Image*, Point*, int, int, Image*, Point);
+extern void	fillpolyop(Image*, Point*, int, int, Image*, Point, Drawop);
+extern Point	string(Image*, Point, Image*, Point, Font*, char*);
+extern Point	stringop(Image*, Point, Image*, Point, Font*, char*, Drawop);
+extern Point	stringn(Image*, Point, Image*, Point, Font*, char*, int);
+extern Point	stringnop(Image*, Point, Image*, Point, Font*, char*, int, Drawop);
+extern Point	runestring(Image*, Point, Image*, Point, Font*, Rune*);
+extern Point	runestringop(Image*, Point, Image*, Point, Font*, Rune*, Drawop);
+extern Point	runestringn(Image*, Point, Image*, Point, Font*, Rune*, int);
+extern Point	runestringnop(Image*, Point, Image*, Point, Font*, Rune*, int, Drawop);
+extern Point	stringbg(Image*, Point, Image*, Point, Font*, char*, Image*, Point);
+extern Point	stringbgop(Image*, Point, Image*, Point, Font*, char*, Image*, Point, Drawop);
+extern Point	stringnbg(Image*, Point, Image*, Point, Font*, char*, int, Image*, Point);
+extern Point	stringnbgop(Image*, Point, Image*, Point, Font*, char*, int, Image*, Point, Drawop);
+extern Point	runestringbg(Image*, Point, Image*, Point, Font*, Rune*, Image*, Point);
+extern Point	runestringbgop(Image*, Point, Image*, Point, Font*, Rune*, Image*, Point, Drawop);
+extern Point	runestringnbg(Image*, Point, Image*, Point, Font*, Rune*, int, Image*, Point);
+extern Point	runestringnbgop(Image*, Point, Image*, Point, Font*, Rune*, int, Image*, Point, Drawop);
+extern Point	_string(Image*, Point, Image*, Point, Font*, char*, Rune*, int, Rectangle, Image*, Point, Drawop);
+extern Point	stringsubfont(Image*, Point, Image*, Subfont*, char*);
+extern int		bezier(Image*, Point, Point, Point, Point, int, int, int, Image*, Point);
+extern int		bezierop(Image*, Point, Point, Point, Point, int, int, int, Image*, Point, Drawop);
+extern int		bezspline(Image*, Point*, int, int, int, int, Image*, Point);
+extern int		bezsplineop(Image*, Point*, int, int, int, int, Image*, Point, Drawop);
+extern int		bezsplinepts(Point*, int, Point**);
+extern int		fillbezier(Image*, Point, Point, Point, Point, int, Image*, Point);
+extern int		fillbezierop(Image*, Point, Point, Point, Point, int, Image*, Point, Drawop);
+extern int		fillbezspline(Image*, Point*, int, int, Image*, Point);
+extern int		fillbezsplineop(Image*, Point*, int, int, Image*, Point, Drawop);
+extern void	ellipse(Image*, Point, int, int, int, Image*, Point);
+extern void	ellipseop(Image*, Point, int, int, int, Image*, Point, Drawop);
+extern void	fillellipse(Image*, Point, int, int, Image*, Point);
+extern void	fillellipseop(Image*, Point, int, int, Image*, Point, Drawop);
+extern void	arc(Image*, Point, int, int, int, Image*, Point, int, int);
+extern void	arcop(Image*, Point, int, int, int, Image*, Point, int, int, Drawop);
+extern void	fillarc(Image*, Point, int, int, Image*, Point, int, int);
+extern void	fillarcop(Image*, Point, int, int, Image*, Point, int, int, Drawop);
+extern void	border(Image*, Rectangle, int, Image*, Point);
+extern void	borderop(Image*, Rectangle, int, Image*, Point, Drawop);
+
+/*
+ * Font management
+ */
+extern Font*	openfont(Display*, char*);
+extern Font*	buildfont(Display*, char*, char*);
+extern void	freefont(Font*);
+extern Font*	mkfont(Subfont*, Rune);
+extern int	cachechars(Font*, char**, Rune**, ushort*, int, int*, char**);
+extern void	agefont(Font*);
+extern Subfont*	allocsubfont(char*, int, int, int, Fontchar*, Image*);
+extern Subfont*	lookupsubfont(Display*, char*);
+extern void	installsubfont(char*, Subfont*);
+extern void	uninstallsubfont(Subfont*);
+extern void	freesubfont(Subfont*);
+extern Subfont*	readsubfont(Display*, char*, int, int);
+extern Subfont*	readsubfonti(Display*, char*, int, Image*, int);
+extern int	writesubfont(int, Subfont*);
+extern void	_unpackinfo(Fontchar*, uchar*, int);
+extern Point	stringsize(Font*, char*);
+extern int	stringwidth(Font*, char*);
+extern int	stringnwidth(Font*, char*, int);
+extern Point	runestringsize(Font*, Rune*);
+extern int	runestringwidth(Font*, Rune*);
+extern int	runestringnwidth(Font*, Rune*, int);
+extern Point	strsubfontwidth(Subfont*, char*);
+extern int	loadchar(Font*, Rune, Cacheinfo*, int, int, char**);
+extern char*	subfontname(char*, char*, int);
+extern Subfont*	_getsubfont(Display*, char*);
+extern Subfont*	getdefont(Display*);
+extern void		lockdisplay(Display*);
+extern void	unlockdisplay(Display*);
+extern int		drawlsetrefresh(ulong, int, void*, void*);
+
+/*
+ * Predefined 
+ */
+extern	uchar	defontdata[];
+extern	int		sizeofdefont;
+extern	Point		ZP;
+extern	Rectangle	ZR;
+
+/*
+ * Set up by initdraw()
+ */
+extern	Display	*display;
+extern	Font		*font;
+/* extern	Image	*screen; */
+extern	Screen	*_screen;
+extern	int	_cursorfd;
+extern	int	_drawdebug;	/* set to 1 to see errors from flushimage */
+extern	void	_setdrawop(Display*, Drawop);
+
+#define	BGSHORT(p)		(((p)[0]<<0) | ((p)[1]<<8))
+#define	BGLONG(p)		((BGSHORT(p)<<0) | (BGSHORT(p+2)<<16))
+#define	BPSHORT(p, v)		((p)[0]=(v), (p)[1]=((v)>>8))
+#define	BPLONG(p, v)		(BPSHORT(p, (v)), BPSHORT(p+2, (v)>>16))
+
+/*
+ * Compressed image file parameters and helper routines
+ */
+#define	NMATCH	3		/* shortest match possible */
+#define	NRUN	(NMATCH+31)	/* longest match possible */
+#define	NMEM	1024		/* window size */
+#define	NDUMP	128		/* maximum length of dump */
+#define	NCBLOCK	6000		/* size of compressed blocks */
+extern	void	_twiddlecompressed(uchar*, int);
+extern	int	_compblocksize(Rectangle, int);
+
+/* XXX backwards helps; should go */
+extern	int		log2[];
+extern	ulong	drawld2chan[];
+extern	void		drawsetdebug(int);
--- /dev/null
+++ b/include/dtos.h
@@ -1,0 +1,10 @@
+#if defined(linux) || defined(IRIX) || defined(SOLARIS) || defined(OSF1) || defined(__FreeBSD__) || defined(__APPLE__)
+#	include "unix.h"
+#	ifdef __APPLE__
+#		define panic dt_panic
+#	endif
+#elif defined(WINDOWS)
+#	include "winduhz.h"
+#else
+#	error "Define an OS"
+#endif
--- /dev/null
+++ b/include/fcall.h
@@ -1,0 +1,110 @@
+#define	VERSION9P	"9P2000"
+
+#define	MAXWELEM	16
+
+typedef
+struct	Fcall
+{
+	uchar	type;
+	u32int	fid;
+	ushort	tag;
+	u32int	msize;		/* Tversion, Rversion */
+	char	*version;	/* Tversion, Rversion */
+	ushort	oldtag;		/* Tflush */
+	char	*ename;		/* Rerror */
+	Qid	qid;		/* Rattach, Ropen, Rcreate */
+	u32int	iounit;		/* Ropen, Rcreate */
+	Qid	aqid;		/* Rauth */
+	u32int	afid;		/* Tauth, Tattach */
+	char	*uname;		/* Tauth, Tattach */
+	char	*aname;		/* Tauth, Tattach */
+	u32int	perm;		/* Tcreate */ 
+	char	*name;		/* Tcreate */
+	uchar	mode;		/* Tcreate, Topen */
+	u32int	newfid;		/* Twalk */
+	ushort	nwname;		/* Twalk */
+	char	*wname[MAXWELEM];	/* Twalk */
+	ushort	nwqid;		/* Rwalk */
+	Qid	wqid[MAXWELEM];		/* Rwalk */
+	vlong	offset;		/* Tread, Twrite */
+	u32int	count;		/* Tread, Twrite, Rread */
+	char	*data;		/* Twrite, Rread */
+	ushort	nstat;		/* Twstat, Rstat */
+	uchar	*stat;		/* Twstat, Rstat */
+} Fcall;
+
+
+#define	GBIT8(p)	((p)[0])
+#define	GBIT16(p)	((p)[0]|((p)[1]<<8))
+#define	GBIT32(p)	((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
+#define	GBIT64(p)	((vlong)((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24)) |\
+				((vlong)((p)[4]|((p)[5]<<8)|((p)[6]<<16)|((p)[7]<<24)) << 32))
+
+#define	PBIT8(p,v)	(p)[0]=(v)
+#define	PBIT16(p,v)	(p)[0]=(v);(p)[1]=(v)>>8
+#define	PBIT32(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24
+#define	PBIT64(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24;\
+			(p)[4]=(v)>>32;(p)[5]=(v)>>40;(p)[6]=(v)>>48;(p)[7]=(v)>>56
+
+#define	BIT8SZ		1
+#define	BIT16SZ		2
+#define	BIT32SZ		4
+#define	BIT64SZ		8
+#define	QIDSZ	(BIT8SZ+BIT32SZ+BIT64SZ)
+
+/* STATFIXLEN includes leading 16-bit count */
+/* The count, however, excludes itself; total size is BIT16SZ+count */
+#define STATFIXLEN	(BIT16SZ+QIDSZ+5*BIT16SZ+4*BIT32SZ+1*BIT64SZ)	/* amount of fixed length data in a stat buffer */
+
+#define	NOTAG		(ushort)~0U	/* Dummy tag */
+#define	NOFID		(u32int)~0U	/* Dummy fid */
+#define	IOHDRSZ		24	/* ample room for Twrite/Rread header (iounit) */
+
+enum
+{
+	Tversion =	100,
+	Rversion,
+	Tauth =		102,
+	Rauth,
+	Tattach =	104,
+	Rattach,
+	Terror =	106,	/* illegal */
+	Rerror,
+	Tflush =	108,
+	Rflush,
+	Twalk =		110,
+	Rwalk,
+	Topen =		112,
+	Ropen,
+	Tcreate =	114,
+	Rcreate,
+	Tread =		116,
+	Rread,
+	Twrite =	118,
+	Rwrite,
+	Tclunk =	120,
+	Rclunk,
+	Tremove =	122,
+	Rremove,
+	Tstat =		124,
+	Rstat,
+	Twstat =	126,
+	Rwstat,
+	Tmax,
+};
+
+uint	convM2S(uchar*, uint, Fcall*);
+uint	convS2M(Fcall*, uchar*, uint);
+uint	sizeS2M(Fcall*);
+
+int	statcheck(uchar *abuf, uint nbuf);
+uint	convM2D(uchar*, uint, Dir*, char*);
+uint	convD2M(Dir*, uchar*, uint);
+uint	sizeD2M(Dir*);
+
+int	fcallfmt(Fmt*);
+int	dirfmt(Fmt*);
+int	dirmodefmt(Fmt*);
+
+int	read9pmsg(int, void*, uint);
+
--- /dev/null
+++ b/include/keyboard.h
@@ -1,0 +1,42 @@
+#pragma src "/sys/src/libdraw"
+#pragma lib "libdraw.a"
+
+typedef struct 	Keyboardctl Keyboardctl;
+typedef struct	Channel	Channel;
+
+struct	Keyboardctl
+{
+	Channel	*c;	/* chan(Rune)[20] */
+
+	char		*file;
+	int		consfd;		/* to cons file */
+	int		ctlfd;		/* to ctl file */
+	int		pid;		/* of slave proc */
+};
+
+
+extern	Keyboardctl*	initkeyboard(char*);
+extern	int			ctlkeyboard(Keyboardctl*, char*);
+extern	void			closekeyboard(Keyboardctl*);
+
+enum {
+    KF= 0xF000, /* Rune: beginning of private Unicode space */
+    Spec=   0xF800,
+    /* KF|1, KF|2, ..., KF|0xC is F1, F2, ..., F12 */
+    Khome=  KF|0x0D,
+    Kup=    KF|0x0E,
+    Kpgup=  KF|0x0F,
+    Kprint= KF|0x10,
+    Kleft=  KF|0x11,
+    Kright= KF|0x12,
+    Kdown=  Spec|0x00,
+    Kview=  Spec|0x00,  
+    Kpgdown=    KF|0x13,
+    Kins=   KF|0x14,
+    Kend=   KF|0x18,
+
+    Kalt=       KF|0x15,
+    Kshift= KF|0x16,    
+    Kctl=       KF|0x17,
+};
+
--- /dev/null
+++ b/include/lib.h
@@ -1,0 +1,246 @@
+/* avoid name conflicts */
+#define accept	pm_accept
+#define listen  pm_listen
+#define sleep	ksleep
+#define wakeup	kwakeup
+#define strtod		libstrtod
+#define pow10	libpow10
+
+/* conflicts on some os's */
+#define encrypt	libencrypt
+#define decrypt libdecrypt
+#define oserror	liboserror
+#define clone	libclone
+#define atexit	libatexit
+#define log2	liblog2
+#define log	liblog
+#define reboot	libreboot
+#define srand	dtsrand
+#define rand	dtrand
+#define nrand	dtnrand
+#define lrand	dtlrand
+#define lnrand	dtlnrand
+#undef timeradd
+#define timeradd	xtimeradd
+
+
+#define	nil	((void*)0)
+
+typedef unsigned char	p9_uchar;
+typedef unsigned int	p9_uint;
+typedef unsigned int	p9_ulong;
+typedef int		p9_long;
+typedef signed char	p9_schar;
+typedef unsigned short	p9_ushort;
+typedef unsigned short	Rune;
+typedef unsigned int	p9_u32int;
+typedef p9_u32int mpdigit;
+
+/* make sure we don't conflict with predefined types */
+#define schar	p9_schar
+#define uchar	p9_uchar
+#define ushort	p9_ushort
+#define uint	p9_uint
+#define u32int	p9_u32int
+
+/* #define long int rather than p9_long so that "unsigned long" is valid */
+#define long	int
+#define ulong	p9_ulong
+#define vlong	p9_vlong
+#define uvlong	p9_uvlong
+
+#define	nelem(x)	(sizeof(x)/sizeof((x)[0]))
+#define	USED(x)		if(x);else
+#define	SET(x)
+
+enum
+{
+	UTFmax		= 3,		/* maximum bytes per rune */
+	Runesync	= 0x80,		/* cannot represent part of a UTF sequence (<) */
+	Runeself	= 0x80,		/* rune and UTF sequences are the same (<) */
+	Runeerror	= 0x80		/* decoding error in UTF */
+};
+
+/*
+ * new rune routines
+ */
+extern	int	runetochar(char*, Rune*);
+extern	int	chartorune(Rune*, char*);
+extern	int	runelen(long);
+extern	int	fullrune(char*, int);
+
+extern  int	wstrtoutf(char*, Rune*, int);
+extern  int	wstrutflen(Rune*);
+
+/*
+ * rune routines from converted str routines
+ */
+extern	long	utflen(char*);
+extern	char*	utfrune(char*, long);
+extern	char*	utfrrune(char*, long);
+
+/*
+ * Syscall data structures
+ */
+#define	MORDER	0x0003	/* mask for bits defining order of mounting */
+#define	MREPL	0x0000	/* mount replaces object */
+#define	MBEFORE	0x0001	/* mount goes before others in union directory */
+#define	MAFTER	0x0002	/* mount goes after others in union directory */
+#define	MCREATE	0x0004	/* permit creation in mounted directory */
+#define	MCACHE	0x0010	/* cache some data */
+#define	MMASK	0x0017	/* all bits on */
+
+#define	OREAD	0	/* open for read */
+#define	OWRITE	1	/* write */
+#define	ORDWR	2	/* read and write */
+#define	OEXEC	3	/* execute, == read but check execute permission */
+#define	OTRUNC	16	/* or'ed in (except for exec), truncate file first */
+#define	OCEXEC	32	/* or'ed in, close on exec */
+#define	ORCLOSE	64	/* or'ed in, remove on close */
+#define	OEXCL   0x1000	/* or'ed in, exclusive create */
+
+#define	NCONT	0	/* continue after note */
+#define	NDFLT	1	/* terminate after note */
+#define	NSAVE	2	/* clear note but hold state */
+#define	NRSTR	3	/* restore saved state */
+
+#define	ERRMAX			128	/* max length of error string */
+#define	KNAMELEN		28	/* max length of name held in kernel */
+
+/* bits in Qid.type */
+#define QTDIR		0x80		/* type bit for directories */
+#define QTAPPEND	0x40		/* type bit for append only files */
+#define QTEXCL		0x20		/* type bit for exclusive use files */
+#define QTMOUNT		0x10		/* type bit for mounted channel */
+#define QTAUTH		0x08		/* type bit for authentication file */
+#define QTFILE		0x00		/* plain file */
+
+/* bits in Dir.mode */
+#define DMDIR		0x80000000	/* mode bit for directories */
+#define DMAPPEND		0x40000000	/* mode bit for append only files */
+#define DMEXCL		0x20000000	/* mode bit for exclusive use files */
+#define DMMOUNT		0x10000000	/* mode bit for mounted channel */
+#define DMAUTH		0x08000000	/* mode bit for authentication files */
+#define DMREAD		0x4		/* mode bit for read permission */
+#define DMWRITE		0x2		/* mode bit for write permission */
+#define DMEXEC		0x1		/* mode bit for execute permission */
+
+typedef struct Lock
+{
+	long	key;
+} Lock;
+
+typedef struct QLock
+{
+	Lock	lk;
+	struct Proc	*hold;
+	struct Proc	*first;
+	struct Proc	*last;
+} QLock;
+
+typedef
+struct Qid
+{
+	uvlong	path;
+	ulong	vers;
+	uchar	type;
+} Qid;
+
+typedef
+struct Dir {
+	/* system-modified data */
+	ushort	type;	/* server type */
+	uint	dev;	/* server subtype */
+	/* file data */
+	Qid	qid;	/* unique id from server */
+	ulong	mode;	/* permissions */
+	ulong	atime;	/* last read time */
+	ulong	mtime;	/* last write time */
+	vlong	length;	/* file length */
+	char	*name;	/* last element of path */
+	char	*uid;	/* owner name */
+	char	*gid;	/* group name */
+	char	*muid;	/* last modifier name */
+} Dir;
+
+typedef
+struct Waitmsg
+{
+	int pid;	/* of loved one */
+	ulong time[3];	/* of loved one & descendants */
+	char	*msg;
+} Waitmsg;
+
+/*
+ * print routines
+ */
+typedef struct Fmt	Fmt;
+struct Fmt{
+	uchar	runes;			/* output buffer is runes or chars? */
+	void	*start;			/* of buffer */
+	void	*to;			/* current place in the buffer */
+	void	*stop;			/* end of the buffer; overwritten if flush fails */
+	int	(*flush)(Fmt *);	/* called when to == stop */
+	void	*farg;			/* to make flush a closure */
+	int	nfmt;			/* num chars formatted so far */
+	va_list	args;			/* args passed to dofmt */
+	int	r;			/* % format Rune */
+	int	width;
+	int	prec;
+	ulong	flags;
+};
+
+enum{
+	FmtWidth	= 1,
+	FmtLeft		= FmtWidth << 1,
+	FmtPrec		= FmtLeft << 1,
+	FmtSharp	= FmtPrec << 1,
+	FmtSpace	= FmtSharp << 1,
+	FmtSign		= FmtSpace << 1,
+	FmtZero		= FmtSign << 1,
+	FmtUnsigned	= FmtZero << 1,
+	FmtShort	= FmtUnsigned << 1,
+	FmtLong		= FmtShort << 1,
+	FmtVLong	= FmtLong << 1,
+	FmtComma	= FmtVLong << 1,
+	FmtByte	= FmtComma << 1,
+
+	FmtFlag		= FmtByte << 1
+};
+
+extern	int	print(char*, ...);
+extern	char*	seprint(char*, char*, char*, ...);
+extern	char*	vseprint(char*, char*, char*, va_list);
+extern	int	snprint(char*, int, char*, ...);
+extern	int	vsnprint(char*, int, char*, va_list);
+extern	char*	smprint(char*, ...);
+extern	char*	vsmprint(char*, va_list);
+extern	int	sprint(char*, char*, ...);
+extern	int	fprint(int, char*, ...);
+extern	int	vfprint(int, char*, va_list);
+
+extern	int	(*doquote)(int);
+extern	int	runesprint(Rune*, char*, ...);
+extern	int	runesnprint(Rune*, int, char*, ...);
+extern	int	runevsnprint(Rune*, int, char*, va_list);
+extern	Rune*	runeseprint(Rune*, Rune*, char*, ...);
+extern	Rune*	runevseprint(Rune*, Rune*, char*, va_list);
+extern	Rune*	runesmprint(char*, ...);
+extern	Rune*	runevsmprint(char*, va_list);
+
+extern	int	fmtfdinit(Fmt*, int, char*, int);
+extern	int	fmtfdflush(Fmt*);
+extern	int	fmtstrinit(Fmt*);
+extern	int	fmtinstall(int, int (*)(Fmt*));
+extern	char*	fmtstrflush(Fmt*);
+extern	int	runefmtstrinit(Fmt*);
+extern	Rune*	runefmtstrflush(Fmt*);
+
+extern	void*	mallocz(ulong, int);
+
+extern	void	srand(long);
+extern	int	rand(void);
+extern	int	nrand(int);
+extern	long	lrand(void);
+extern	long	lnrand(long);
+extern	double	frand(void);
--- /dev/null
+++ b/include/libc.h
@@ -1,0 +1,3 @@
+#include "lib.h"
+#include "user.h"
+
--- /dev/null
+++ b/include/libsec.h
@@ -1,0 +1,340 @@
+
+#ifndef _MPINT
+typedef struct mpint mpint;
+#endif
+
+/////////////////////////////////////////////////////////
+// AES definitions
+/////////////////////////////////////////////////////////
+
+enum
+{
+	AESbsize=	16,
+	AESmaxkey=	32,
+	AESmaxrounds=	14
+};
+
+typedef struct AESstate AESstate;
+struct AESstate
+{
+	ulong	setup;
+	int	rounds;
+	int	keybytes;
+	uchar	key[AESmaxkey];		/* unexpanded key */
+	u32int	ekey[4*(AESmaxrounds + 1)];	/* encryption key */
+	u32int	dkey[4*(AESmaxrounds + 1)];	/* decryption key */
+	uchar	ivec[AESbsize];	/* initialization vector */
+};
+
+void	setupAESstate(AESstate *s, uchar key[], int keybytes, uchar *ivec);
+void	aesCBCencrypt(uchar *p, int len, AESstate *s);
+void	aesCBCdecrypt(uchar *p, int len, AESstate *s);
+
+/////////////////////////////////////////////////////////
+// Blowfish Definitions
+/////////////////////////////////////////////////////////
+
+enum
+{
+	BFbsize	= 8,
+	BFrounds	= 16
+};
+
+// 16-round Blowfish
+typedef struct BFstate BFstate;
+struct BFstate
+{
+	ulong	setup;
+
+	uchar	key[56];
+	uchar	ivec[8];
+
+	u32int 	pbox[BFrounds+2];
+	u32int	sbox[1024];
+};
+
+void	setupBFstate(BFstate *s, uchar key[], int keybytes, uchar *ivec);
+void	bfCBCencrypt(uchar*, int, BFstate*);
+void	bfCBCdecrypt(uchar*, int, BFstate*);
+void	bfECBencrypt(uchar*, int, BFstate*);
+void	bfECBdecrypt(uchar*, int, BFstate*);
+
+/////////////////////////////////////////////////////////
+// DES definitions
+/////////////////////////////////////////////////////////
+
+enum
+{
+	DESbsize=	8
+};
+
+// single des
+typedef struct DESstate DESstate;
+struct DESstate
+{
+	ulong	setup;
+	uchar	key[8];		/* unexpanded key */
+	ulong	expanded[32];	/* expanded key */
+	uchar	ivec[8];	/* initialization vector */
+};
+
+void	setupDESstate(DESstate *s, uchar key[8], uchar *ivec);
+void	des_key_setup(uchar[8], ulong[32]);
+void	block_cipher(ulong*, uchar*, int);
+void	desCBCencrypt(uchar*, int, DESstate*);
+void	desCBCdecrypt(uchar*, int, DESstate*);
+void	desECBencrypt(uchar*, int, DESstate*);
+void	desECBdecrypt(uchar*, int, DESstate*);
+
+// for backward compatibility with 7 byte DES key format
+void	des56to64(uchar *k56, uchar *k64);
+void	des64to56(uchar *k64, uchar *k56);
+void	key_setup(uchar[7], ulong[32]);
+
+// triple des encrypt/decrypt orderings
+enum {
+	DES3E=		0,
+	DES3D=		1,
+	DES3EEE=	0,
+	DES3EDE=	2,
+	DES3DED=	5,
+	DES3DDD=	7
+};
+
+typedef struct DES3state DES3state;
+struct DES3state
+{
+	ulong	setup;
+	uchar	key[3][8];		/* unexpanded key */
+	ulong	expanded[3][32];	/* expanded key */
+	uchar	ivec[8];		/* initialization vector */
+};
+
+void	setupDES3state(DES3state *s, uchar key[3][8], uchar *ivec);
+void	triple_block_cipher(ulong keys[3][32], uchar*, int);
+void	des3CBCencrypt(uchar*, int, DES3state*);
+void	des3CBCdecrypt(uchar*, int, DES3state*);
+void	des3ECBencrypt(uchar*, int, DES3state*);
+void	des3ECBdecrypt(uchar*, int, DES3state*);
+
+/////////////////////////////////////////////////////////
+// digests
+/////////////////////////////////////////////////////////
+
+enum
+{
+	SHA1dlen=	20,	/* SHA digest length */
+	MD4dlen=	16,	/* MD4 digest length */
+	MD5dlen=	16	/* MD5 digest length */
+};
+
+typedef struct DigestState DigestState;
+struct DigestState
+{
+	ulong len;
+	u32int state[5];
+	uchar buf[128];
+	int blen;
+	char malloced;
+	char seeded;
+};
+typedef struct DigestState SHAstate;	/* obsolete name */
+typedef struct DigestState SHA1state;
+typedef struct DigestState MD5state;
+typedef struct DigestState MD4state;
+
+DigestState* md4(uchar*, ulong, uchar*, DigestState*);
+DigestState* md5(uchar*, ulong, uchar*, DigestState*);
+DigestState* sha1(uchar*, ulong, uchar*, DigestState*);
+DigestState* hmac_md5(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+DigestState* hmac_sha1(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+char* sha1pickle(SHA1state*);
+SHA1state* sha1unpickle(char*);
+
+/////////////////////////////////////////////////////////
+// random number generation
+/////////////////////////////////////////////////////////
+void	genrandom(uchar *buf, int nbytes);
+void	prng(uchar *buf, int nbytes);
+ulong	fastrand(void);
+ulong	nfastrand(ulong);
+
+/////////////////////////////////////////////////////////
+// primes
+/////////////////////////////////////////////////////////
+void	genprime(mpint *p, int n, int accuracy); // generate an n bit probable prime
+void	gensafeprime(mpint *p, mpint *alpha, int n, int accuracy);	// prime and generator
+void	genstrongprime(mpint *p, int n, int accuracy);	// generate an n bit strong prime
+void	DSAprimes(mpint *q, mpint *p, uchar seed[SHA1dlen]);
+int	probably_prime(mpint *n, int nrep);	// miller-rabin test
+int	smallprimetest(mpint *p);		// returns -1 if not prime, 0 otherwise
+
+/////////////////////////////////////////////////////////
+// rc4
+/////////////////////////////////////////////////////////
+typedef struct RC4state RC4state;
+struct RC4state
+{
+	 uchar state[256];
+	 uchar x;
+	 uchar y;
+};
+
+void	setupRC4state(RC4state*, uchar*, int);
+void	rc4(RC4state*, uchar*, int);
+void	rc4skip(RC4state*, int);
+void	rc4back(RC4state*, int);
+
+/////////////////////////////////////////////////////////
+// rsa
+/////////////////////////////////////////////////////////
+typedef struct RSApub RSApub;
+typedef struct RSApriv RSApriv;
+
+// public/encryption key
+struct RSApub
+{
+	mpint	*n;	// modulus
+	mpint	*ek;	// exp (encryption key)
+};
+
+// private/decryption key
+struct RSApriv
+{
+	RSApub	pub;
+
+	mpint	*dk;	// exp (decryption key)
+
+	// precomputed values to help with chinese remainder theorem calc
+	mpint	*p;
+	mpint	*q;
+	mpint	*kp;	// dk mod p-1
+	mpint	*kq;	// dk mod q-1
+	mpint	*c2;	// (inv p) mod q
+};
+
+RSApriv*	rsagen(int nlen, int elen, int rounds);
+RSApriv*	rsafill(mpint *n, mpint *e, mpint *d, mpint *p, mpint *q);
+mpint*		rsaencrypt(RSApub *k, mpint *in, mpint *out);
+mpint*		rsadecrypt(RSApriv *k, mpint *in, mpint *out);
+RSApub*		rsapuballoc(void);
+void		rsapubfree(RSApub*);
+RSApriv*	rsaprivalloc(void);
+void		rsaprivfree(RSApriv*);
+RSApub*		rsaprivtopub(RSApriv*);
+RSApub*		X509toRSApub(uchar*, int, char*, int);
+RSApriv*	asn1toRSApriv(uchar*, int);
+void		asn1dump(uchar *der, int len);
+uchar*		decodepem(char *s, char *type, int *len);
+uchar*		X509gen(RSApriv *priv, char *subj, ulong valid[2], int *certlen);
+uchar*		X509req(RSApriv *priv, char *subj, int *certlen);
+char*		X509verify(uchar *cert, int ncert, RSApub *pk);
+void		X509dump(uchar *cert, int ncert);
+/////////////////////////////////////////////////////////
+// elgamal
+/////////////////////////////////////////////////////////
+typedef struct EGpub EGpub;
+typedef struct EGpriv EGpriv;
+typedef struct EGsig EGsig;
+
+// public/encryption key
+struct EGpub
+{
+	mpint	*p;	// modulus
+	mpint	*alpha;	// generator
+	mpint	*key;	// (encryption key) alpha**secret mod p
+};
+
+// private/decryption key
+struct EGpriv
+{
+	EGpub	pub;
+	mpint	*secret; // (decryption key)
+};
+
+// signature
+struct EGsig
+{
+	mpint	*r, *s;
+};
+
+EGpriv*		eggen(int nlen, int rounds);
+mpint*		egencrypt(EGpub *k, mpint *in, mpint *out);
+mpint*		egdecrypt(EGpriv *k, mpint *in, mpint *out);
+EGsig*		egsign(EGpriv *k, mpint *m);
+int		egverify(EGpub *k, EGsig *sig, mpint *m);
+EGpub*		egpuballoc(void);
+void		egpubfree(EGpub*);
+EGpriv*		egprivalloc(void);
+void		egprivfree(EGpriv*);
+EGsig*		egsigalloc(void);
+void		egsigfree(EGsig*);
+EGpub*		egprivtopub(EGpriv*);
+
+/////////////////////////////////////////////////////////
+// dsa
+/////////////////////////////////////////////////////////
+typedef struct DSApub DSApub;
+typedef struct DSApriv DSApriv;
+typedef struct DSAsig DSAsig;
+
+// public/encryption key
+struct DSApub
+{
+	mpint	*p;	// modulus
+	mpint	*q;	// group order, q divides p-1
+	mpint	*alpha;	// group generator
+	mpint	*key;	// (encryption key) alpha**secret mod p
+};
+
+// private/decryption key
+struct DSApriv
+{
+	DSApub	pub;
+	mpint	*secret; // (decryption key)
+};
+
+// signature
+struct DSAsig
+{
+	mpint	*r, *s;
+};
+
+DSApriv*	dsagen(DSApub *opub);
+DSAsig*		dsasign(DSApriv *k, mpint *m);
+int		dsaverify(DSApub *k, DSAsig *sig, mpint *m);
+DSApub*		dsapuballoc(void);
+void		dsapubfree(DSApub*);
+DSApriv*	dsaprivalloc(void);
+void		dsaprivfree(DSApriv*);
+DSAsig*		dsasigalloc(void);
+void		dsasigfree(DSAsig*);
+DSApub*		dsaprivtopub(DSApriv*);
+
+/////////////////////////////////////////////////////////
+// TLS
+/////////////////////////////////////////////////////////
+typedef struct Thumbprint{
+	struct Thumbprint *next;
+	uchar sha1[SHA1dlen];
+} Thumbprint;
+
+typedef struct TLSconn{
+	char dir[40];  // connection directory
+	uchar *cert;   // certificate (local on input, remote on output)
+	uchar *sessionID;
+	int certlen, sessionIDlen;
+	int (*trace)(char*fmt, ...);
+} TLSconn;
+
+// tlshand.c
+extern int tlsClient(int fd, TLSconn *c);
+extern int tlsServer(int fd, TLSconn *c);
+
+// thumb.c
+extern Thumbprint* initThumbprints(char *ok, char *crl);
+extern void freeThumbprints(Thumbprint *ok);
+extern int okThumbprint(uchar *sha1, Thumbprint *ok);
+
+// readcert.c
+extern uchar *readcert(char *filename, int *pcertlen);
--- /dev/null
+++ b/include/memdraw.h
@@ -1,0 +1,200 @@
+#pragma	src	"/sys/src/libmemdraw"
+#pragma	lib	"libmemdraw.a"
+
+typedef struct	Memimage Memimage;
+typedef struct	Memdata Memdata;
+typedef struct	Memsubfont Memsubfont;
+typedef struct	Memlayer Memlayer;
+typedef struct	Memcmap Memcmap;
+typedef struct	Memdrawparam	Memdrawparam;
+
+/*
+ * Memdata is allocated from main pool, but .data from the image pool.
+ * Memdata is allocated separately to permit patching its pointer after
+ * compaction when windows share the image data.
+ * The first word of data is a back pointer to the Memdata, to find
+ * The word to patch.
+ */
+
+struct Memdata
+{
+	ulong	*base;	/* allocated data pointer */
+	uchar	*bdata;	/* pointer to first byte of actual data; word-aligned */
+	int		ref;		/* number of Memimages using this data */
+	void*	imref;
+	int		allocd;	/* is this malloc'd? */
+};
+
+enum {
+	Frepl		= 1<<0,	/* is replicated */
+	Fsimple	= 1<<1,	/* is 1x1 */
+	Fgrey	= 1<<2,	/* is grey */
+	Falpha	= 1<<3,	/* has explicit alpha */
+	Fcmap	= 1<<4,	/* has cmap channel */
+	Fbytes	= 1<<5,	/* has only 8-bit channels */
+};
+
+struct Memimage
+{
+	Rectangle	r;		/* rectangle in data area, local coords */
+	Rectangle	clipr;		/* clipping region */
+	int		depth;	/* number of bits of storage per pixel */
+	int		nchan;	/* number of channels */
+	ulong	chan;	/* channel descriptions */
+	Memcmap	*cmap;
+
+	Memdata	*data;	/* pointer to data; shared by windows in this image */
+	int		zero;		/* data->bdata+zero==&byte containing (0,0) */
+	ulong	width;	/* width in words of a single scan line */
+	Memlayer	*layer;	/* nil if not a layer*/
+	ulong	flags;
+
+	int		shift[NChan];
+	int		mask[NChan];
+	int		nbits[NChan];
+
+	void	*X;
+};
+
+struct Memcmap
+{
+	uchar	cmap2rgb[3*256];
+	uchar	rgb2cmap[16*16*16];
+};
+
+/*
+ * Subfonts
+ *
+ * given char c, Subfont *f, Fontchar *i, and Point p, one says
+ *	i = f->info+c;
+ *	draw(b, Rect(p.x+i->left, p.y+i->top,
+ *		p.x+i->left+((i+1)->x-i->x), p.y+i->bottom),
+ *		color, f->bits, Pt(i->x, i->top));
+ *	p.x += i->width;
+ * to draw characters in the specified color (itself a Memimage) in Memimage b.
+ */
+
+struct	Memsubfont
+{
+	char		*name;
+	short	n;		/* number of chars in font */
+	uchar	height;		/* height of bitmap */
+	char	ascent;		/* top of bitmap to baseline */
+	Fontchar *info;		/* n+1 character descriptors */
+	Memimage	*bits;		/* of font */
+};
+
+/*
+ * Encapsulated parameters and information for sub-draw routines.
+ */
+enum {
+	Simplesrc=1<<0,
+	Simplemask=1<<1,
+	Replsrc=1<<2,
+	Replmask=1<<3,
+	Fullmask=1<<4,
+};
+struct	Memdrawparam
+{
+	Memimage *dst;
+	Rectangle	r;
+	Memimage *src;
+	Rectangle sr;
+	Memimage *mask;
+	Rectangle mr;
+	int op;
+
+	ulong state;
+	ulong mval;	/* if Simplemask, the mask pixel in mask format */
+	ulong mrgba;	/* mval in rgba */
+	ulong sval;	/* if Simplesrc, the source pixel in src format */
+	ulong srgba;	/* sval in rgba */
+	ulong sdval;	/* sval in dst format */
+};
+
+/*
+ * Memimage management
+ */
+
+extern Memimage*	allocmemimage(Rectangle, ulong);
+extern Memimage*	_allocmemimage(Rectangle, ulong);
+extern Memimage*	allocmemimaged(Rectangle, ulong, Memdata*, void*);
+extern Memimage*	readmemimage(int);
+extern Memimage*	creadmemimage(int);
+extern int	writememimage(int, Memimage*);
+extern void	freememimage(Memimage*);
+extern int		_loadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int		_cloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int		_unloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int		loadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int		cloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int		unloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern ulong*	wordaddr(Memimage*, Point);
+extern uchar*	byteaddr(Memimage*, Point);
+extern int		drawclip(Memimage*, Rectangle*, Memimage*, Point*, Memimage*, Point*, Rectangle*, Rectangle*);
+extern void	memfillcolor(Memimage*, ulong);
+extern int		memsetchan(Memimage*, ulong);
+
+/*
+ * Graphics
+ */
+extern void	memdraw(Memimage*, Rectangle, Memimage*, Point, Memimage*, Point, int);
+extern void	memline(Memimage*, Point, Point, int, int, int, Memimage*, Point, int);
+extern void	mempoly(Memimage*, Point*, int, int, int, int, Memimage*, Point, int);
+extern void	memfillpoly(Memimage*, Point*, int, int, Memimage*, Point, int);
+extern void	_memfillpolysc(Memimage*, Point*, int, int, Memimage*, Point, int, int, int, int);
+extern Memdrawparam*	_memimagedrawsetup(Memimage*, Rectangle, Memimage*, Point, Memimage*, Point, int);
+extern void	_memimagedraw(Memdrawparam*);
+extern void	memimagedraw(Memimage*, Rectangle, Memimage*, Point, Memimage*, Point, int);
+extern int	hwdraw(Memdrawparam*);
+extern void	memimageline(Memimage*, Point, Point, int, int, int, Memimage*, Point, int);
+extern void	_memimageline(Memimage*, Point, Point, int, int, int, Memimage*, Point, Rectangle, int);
+extern Point	memimagestring(Memimage*, Point, Memimage*, Point, Memsubfont*, char*);
+extern void	memellipse(Memimage*, Point, int, int, int, Memimage*, Point, int);
+extern void	memarc(Memimage*, Point, int, int, int, Memimage*, Point, int, int, int);
+extern Rectangle	memlinebbox(Point, Point, int, int, int);
+extern int	memlineendsize(int);
+extern void	_memmkcmap(void);
+extern void	memimageinit(void);
+
+/*
+ * Subfont management
+ */
+extern Memsubfont*	allocmemsubfont(char*, int, int, int, Fontchar*, Memimage*);
+extern Memsubfont*	openmemsubfont(char*);
+extern void	freememsubfont(Memsubfont*);
+extern Point	memsubfontwidth(Memsubfont*, char*);
+extern Memsubfont*	getmemdefont(void);
+
+/*
+ * Predefined 
+ */
+extern	Memimage*	memwhite;
+extern	Memimage*	memblack;
+extern	Memimage*	memopaque;
+extern	Memimage*	memtransparent;
+extern	Memcmap	*memdefcmap;
+
+/*
+ * Kernel interface
+ */
+void		memimagemove(void*, void*);
+
+/*
+ * Kernel cruft
+ */
+extern void	rdb(void);
+extern int		iprint(char*, ...);
+#pragma varargck argpos iprint 1
+extern int		drawdebug;
+
+/*
+ * doprint interface: numbconv bit strings
+ */
+#pragma varargck type "llb" vlong
+#pragma varargck type "llb" uvlong
+#pragma varargck type "lb" long
+#pragma varargck type "lb" ulong
+#pragma varargck type "b" int
+#pragma varargck type "b" uint
+
--- /dev/null
+++ b/include/memlayer.h
@@ -1,0 +1,51 @@
+#pragma src "/sys/src/libmemlayer"
+#pragma lib "libmemlayer.a"
+
+typedef struct Memscreen Memscreen;
+typedef void (*Refreshfn)(Memimage*, Rectangle, void*);
+
+struct Memscreen
+{
+	Memimage	*frontmost;	/* frontmost layer on screen */
+	Memimage	*rearmost;	/* rearmost layer on screen */
+	Memimage	*image;		/* upon which all layers are drawn */
+	Memimage	*fill;			/* if non-zero, picture to use when repainting */
+};
+
+struct Memlayer
+{
+	Rectangle		screenr;	/* true position of layer on screen */
+	Point			delta;	/* add delta to go from image coords to screen */
+	Memscreen	*screen;	/* screen this layer belongs to */
+	Memimage	*front;	/* window in front of this one */
+	Memimage	*rear;	/* window behind this one*/
+	int		clear;	/* layer is fully visible */
+	Memimage	*save;	/* save area for obscured parts */
+	Refreshfn	refreshfn;		/* function to call to refresh obscured parts if save==nil */
+	void		*refreshptr;	/* argument to refreshfn */
+};
+
+/*
+ * These functions accept local coordinates
+ */
+int			memload(Memimage*, Rectangle, uchar*, int, int);
+int			memunload(Memimage*, Rectangle, uchar*, int);
+
+/*
+ * All these functions accept screen coordinates, not local ones.
+ */
+void			_memlayerop(void (*fn)(Memimage*, Rectangle, Rectangle, void*, int), Memimage*, Rectangle, Rectangle, void*);
+Memimage*	memlalloc(Memscreen*, Rectangle, Refreshfn, void*, ulong);
+void			memldelete(Memimage*);
+void			memlfree(Memimage*);
+void			memltofront(Memimage*);
+void			memltofrontn(Memimage**, int);
+void			_memltofrontfill(Memimage*, int);
+void			memltorear(Memimage*);
+void			memltorearn(Memimage**, int);
+int			memlsetrefresh(Memimage*, Refreshfn, void*);
+void			memlhide(Memimage*, Rectangle);
+void			memlexpose(Memimage*, Rectangle);
+void			_memlsetclear(Memscreen*);
+int			memlorigin(Memimage*, Point, Point);
+void			memlnorefresh(Memimage*, Rectangle, void*);
--- /dev/null
+++ b/include/mp.h
@@ -1,0 +1,134 @@
+#define _MPINT 1
+
+// the code assumes mpdigit to be at least an int
+// mpdigit must be an atomic type.  mpdigit is defined
+// in the architecture specific u.h
+
+typedef struct mpint mpint;
+
+struct mpint
+{
+	int	sign;	// +1 or -1
+	int	size;	// allocated digits
+	int	top;	// significant digits
+	mpdigit	*p;
+	char	flags;
+};
+
+enum
+{
+	MPstatic=	0x01,
+	Dbytes=		sizeof(mpdigit),	// bytes per digit
+	Dbits=		Dbytes*8		// bits per digit
+};
+
+// allocation
+void	mpsetminbits(int n);	// newly created mpint's get at least n bits
+mpint*	mpnew(int n);		// create a new mpint with at least n bits
+void	mpfree(mpint *b);
+void	mpbits(mpint *b, int n);	// ensure that b has at least n bits
+void	mpnorm(mpint *b);		// dump leading zeros
+mpint*	mpcopy(mpint *b);
+void	mpassign(mpint *old, mpint *new);
+
+// random bits
+mpint*	mprand(int bits, void (*gen)(uchar*, int), mpint *b);
+
+// conversion
+mpint*	strtomp(char*, char**, int, mpint*);	// ascii
+int	mpfmt(Fmt*);
+char*	mptoa(mpint*, int, char*, int);
+mpint*	letomp(uchar*, uint, mpint*);	// byte array, little-endian
+int	mptole(mpint*, uchar*, uint, uchar**);
+mpint*	betomp(uchar*, uint, mpint*);	// byte array, little-endian
+int	mptobe(mpint*, uchar*, uint, uchar**);
+uint	mptoui(mpint*);			// unsigned int
+mpint*	uitomp(uint, mpint*);
+int	mptoi(mpint*);			// int
+mpint*	itomp(int, mpint*);
+uvlong	mptouv(mpint*);			// unsigned vlong
+mpint*	uvtomp(uvlong, mpint*);
+vlong	mptov(mpint*);			// vlong
+mpint*	vtomp(vlong, mpint*);
+
+// divide 2 digits by one
+void	mpdigdiv(mpdigit *dividend, mpdigit divisor, mpdigit *quotient);
+
+// in the following, the result mpint may be
+// the same as one of the inputs.
+void	mpadd(mpint *b1, mpint *b2, mpint *sum);	// sum = b1+b2
+void	mpsub(mpint *b1, mpint *b2, mpint *diff);	// diff = b1-b2
+void	mpleft(mpint *b, int shift, mpint *res);	// res = b<<shift
+void	mpright(mpint *b, int shift, mpint *res);	// res = b>>shift
+void	mpmul(mpint *b1, mpint *b2, mpint *prod);	// prod = b1*b2
+void	mpexp(mpint *b, mpint *e, mpint *m, mpint *res);	// res = b**e mod m
+void	mpmod(mpint *b, mpint *m, mpint *remainder);	// remainder = b mod m
+
+// quotient = dividend/divisor, remainder = dividend % divisor
+void	mpdiv(mpint *dividend, mpint *divisor,  mpint *quotient, mpint *remainder);
+
+// return neg, 0, pos as b1-b2 is neg, 0, pos
+int	mpcmp(mpint *b1, mpint *b2);
+
+// extended gcd return d, x, and y, s.t. d = gcd(a,b) and ax+by = d
+void	mpextendedgcd(mpint *a, mpint *b, mpint *d, mpint *x, mpint *y);
+
+// res = b**-1 mod m
+void	mpinvert(mpint *b, mpint *m, mpint *res);
+
+// bit counting
+int	mpsignif(mpint*);	// number of sigificant bits in mantissa
+int	mplowbits0(mpint*);	// k, where n = 2**k * q for odd q
+
+// well known constants
+extern mpint	*mpzero, *mpone, *mptwo;
+
+// sum[0:alen] = a[0:alen-1] + b[0:blen-1]
+// prereq: alen >= blen, sum has room for alen+1 digits
+void	mpvecadd(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *sum);
+
+// diff[0:alen-1] = a[0:alen-1] - b[0:blen-1]
+// prereq: alen >= blen, diff has room for alen digits
+void	mpvecsub(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *diff);
+
+// p[0:n] += m * b[0:n-1]
+// prereq: p has room for n+1 digits
+void	mpvecdigmuladd(mpdigit *b, int n, mpdigit m, mpdigit *p);
+
+// p[0:n] -= m * b[0:n-1]
+// prereq: p has room for n+1 digits
+int	mpvecdigmulsub(mpdigit *b, int n, mpdigit m, mpdigit *p);
+
+// p[0:alen*blen-1] = a[0:alen-1] * b[0:blen-1]
+// prereq: alen >= blen, p has room for m*n digits
+void	mpvecmul(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *p);
+
+// sign of a - b or zero if the same
+int	mpveccmp(mpdigit *a, int alen, mpdigit *b, int blen);
+
+// divide the 2 digit dividend by the one digit divisor and stick in quotient
+// we assume that the result is one digit - overflow is all 1's
+void	mpdigdiv(mpdigit *dividend, mpdigit divisor, mpdigit *quotient);
+
+// playing with magnitudes
+int	mpmagcmp(mpint *b1, mpint *b2);
+void	mpmagadd(mpint *b1, mpint *b2, mpint *sum);	// sum = b1+b2
+void	mpmagsub(mpint *b1, mpint *b2, mpint *sum);	// sum = b1+b2
+
+// chinese remainder theorem
+typedef struct CRTpre	CRTpre;		// precomputed values for converting
+					//  twixt residues and mpint
+typedef struct CRTres	CRTres;		// residue form of an mpint
+
+struct CRTres
+{
+	int	n;		// number of residues
+	mpint	*r[1];		// residues
+};
+
+CRTpre*	crtpre(int, mpint**);			// precompute conversion values
+CRTres*	crtin(CRTpre*, mpint*);			// convert mpint to residues
+void	crtout(CRTpre*, CRTres*, mpint*);	// convert residues to mpint
+void	crtprefree(CRTpre*);
+void	crtresfree(CRTres*);
+
--- /dev/null
+++ b/include/u.h
@@ -1,0 +1,26 @@
+#include "dtos.h"
+
+/* avoid name conflicts */
+#undef accept
+#undef listen
+
+/* sys calls */
+#undef bind
+#undef chdir
+#undef close
+#undef create
+#undef dup
+#undef export
+#undef fstat
+#undef fwstat
+#undef mount
+#undef open
+#undef start
+#undef read
+#undef remove
+#undef seek
+#undef stat
+#undef write
+#undef wstat
+#undef unmount
+#undef pipe
--- /dev/null
+++ b/include/unix.h
@@ -1,0 +1,14 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <time.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+
+typedef long long		p9_vlong;
+typedef	unsigned long long p9_uvlong;
--- /dev/null
+++ b/include/user.h
@@ -1,0 +1,65 @@
+/* sys calls */
+#define	bind	sysbind
+#define	chdir	syschdir
+#define	close	sysclose
+#define create	syscreate
+#define dup	sysdup
+#define export	sysexport
+#define fstat	sysfstat
+#define fwstat	sysfwstat
+#define mount	sysmount
+#define	open	sysopen
+#define read	sysread
+#define remove	sysremove
+#define seek	sysseek
+#define stat	sysstat
+#define	write	syswrite
+#define wstat	syswstat
+#define unmount	sysunmount
+#define pipe	syspipe
+#define rendezvous	sysrendezvous
+#define getpid	sysgetpid
+#define time systime
+#define nsec sysnsec
+#define pread syspread
+#define pwrite syspwrite
+
+extern	int	bind(char*, char*, int);
+extern	int	chdir(char*);
+extern	int	close(int);
+extern	int	create(char*, int, ulong);
+extern	int	dup(int, int);
+extern  int	export(int);
+extern	int	fstat(int, uchar*, int);
+extern	int	fwstat(int, uchar*, int);
+extern	int	mount(int, int, char*, int, char*);
+extern	int	unmount(char*, char*);
+extern	int	open(char*, int);
+extern	int	pipe(int*);
+extern	long	read(int, void*, long);
+extern	long	readn(int, void*, long);
+extern	int	remove(char*);
+extern	vlong	seek(int, vlong, int);
+extern	int	stat(char*, uchar*, int);
+extern	long	write(int, void*, long);
+extern	int	wstat(char*, uchar*, int);
+
+extern	Dir	*dirstat(char*);
+extern	Dir	*dirfstat(int);
+extern	int	dirwstat(char*, Dir*);
+extern	int	dirfwstat(int, Dir*);
+extern	long	dirread(int, Dir*, long);
+
+/*
+ *  network dialing and authentication
+ */
+#define NETPATHLEN 40
+extern	int	accept(int, char*);
+extern	int	announce(char*, char*);
+extern	int	dial(char*, char*, char*, int*);
+extern	int	hangup(int);
+extern	int	listen(char*, char*);
+extern	char *netmkaddr(char*, char*, char*);
+extern	int	reject(int, char*, char*);
+
+extern 	char*	argv0;
--- /dev/null
+++ b/include/x.c
@@ -1,0 +1,6 @@
+#include <stdio.h>
+
+void
+main(void)
+{
+}
--- /dev/null
+++ b/kern/Makefile
@@ -1,0 +1,50 @@
+LIB=libkern.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+#CC=cl
+#CFLAGS=-c -nologo -W3 -YX -Zi -MT -Zl -I../include -DWINDOWS
+#O=obj
+
+OFILES=\
+	allocb.$O\
+	cache.$O\
+	chan.$O\
+	data.$O\
+	dev.$O\
+	devcons.$O\
+	devdraw.$O\
+	devfs.$O\
+	devip.$O\
+	devip-posix.$O\
+	devmnt.$O\
+	devmouse.$O\
+	devpipe.$O\
+	devroot.$O\
+	devssl.$O\
+	devtab.$O\
+	error.$O\
+	parse.$O\
+	pgrp.$O\
+	posix.$O\
+	procinit.$O\
+	rwlock.$O\
+	sleep.$O\
+	smalloc.$O\
+	stub.$O\
+	sysfile.$O\
+	sysproc.$O\
+	qio.$O\
+	qlock.$O\
+	term.$O\
+	todo.$O\
+	uart.$O\
+	waserror.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/kern/allocb.c
@@ -1,0 +1,165 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Hdrspc		= 64,		/* leave room for high-level headers */
+	Bdead		= 0x51494F42,	/* "QIOB" */
+};
+
+struct
+{
+	Lock	lk;
+	ulong	bytes;
+} ialloc;
+
+static Block*
+_allocb(int size)
+{
+	Block *b;
+	ulong addr;
+
+	if((b = mallocz(sizeof(Block)+size+Hdrspc, 0)) == nil)
+		return nil;
+
+	b->next = nil;
+	b->list = nil;
+	b->free = 0;
+	b->flag = 0;
+
+	/* align start of data portion by rounding up */
+	addr = (ulong)b;
+	addr = ROUND(addr + sizeof(Block), BLOCKALIGN);
+	b->base = (uchar*)addr;
+
+	/* align end of data portion by rounding down */
+	b->lim = ((uchar*)b) + sizeof(Block)+size+Hdrspc;
+	addr = (ulong)(b->lim);
+	addr = addr & ~(BLOCKALIGN-1);
+	b->lim = (uchar*)addr;
+
+	/* leave sluff at beginning for added headers */
+	b->rp = b->lim - ROUND(size, BLOCKALIGN);
+	if(b->rp < b->base)
+		panic("_allocb");
+	b->wp = b->rp;
+
+	return b;
+}
+
+Block*
+allocb(int size)
+{
+	Block *b;
+
+	/*
+	 * Check in a process and wait until successful.
+	 * Can still error out of here, though.
+	 */
+	if(up == nil)
+		panic("allocb without up: %luX\n", getcallerpc(&size));
+	if((b = _allocb(size)) == nil){
+		panic("allocb: no memory for %d bytes\n", size);
+	}
+	setmalloctag(b, getcallerpc(&size));
+
+	return b;
+}
+
+Block*
+iallocb(int size)
+{
+	Block *b;
+	static int m1, m2;
+
+	if(ialloc.bytes > conf.ialloc){
+		if((m1++%10000)==0)
+			print("iallocb: limited %lud/%lud\n",
+				ialloc.bytes, conf.ialloc);
+		return 0;
+	}
+
+	if((b = _allocb(size)) == nil){
+		if((m2++%10000)==0)
+			print("iallocb: no memory %lud/%lud\n",
+				ialloc.bytes, conf.ialloc);
+		return nil;
+	}
+	setmalloctag(b, getcallerpc(&size));
+	b->flag = BINTR;
+
+	ilock(&ialloc.lk);
+	ialloc.bytes += b->lim - b->base;
+	iunlock(&ialloc.lk);
+
+	return b;
+}
+
+void
+freeb(Block *b)
+{
+	void *dead = (void*)Bdead;
+
+	if(b == nil)
+		return;
+
+	/*
+	 * drivers which perform non cache coherent DMA manage their own buffer
+	 * pool of uncached buffers and provide their own free routine.
+	 */
+	if(b->free) {
+		b->free(b);
+		return;
+	}
+	if(b->flag & BINTR) {
+		ilock(&ialloc.lk);
+		ialloc.bytes -= b->lim - b->base;
+		iunlock(&ialloc.lk);
+	}
+
+	/* poison the block in case someone is still holding onto it */
+	b->next = dead;
+	b->rp = dead;
+	b->wp = dead;
+	b->lim = dead;
+	b->base = dead;
+
+	free(b);
+}
+
+void
+checkb(Block *b, char *msg)
+{
+	void *dead = (void*)Bdead;
+
+	if(b == dead)
+		panic("checkb b %s %lux", msg, b);
+	if(b->base == dead || b->lim == dead || b->next == dead
+	  || b->rp == dead || b->wp == dead){
+		print("checkb: base 0x%8.8luX lim 0x%8.8luX next 0x%8.8luX\n",
+			b->base, b->lim, b->next);
+		print("checkb: rp 0x%8.8luX wp 0x%8.8luX\n", b->rp, b->wp);
+		panic("checkb dead: %s\n", msg);
+	}
+
+	if(b->base > b->lim)
+		panic("checkb 0 %s %lux %lux", msg, b->base, b->lim);
+	if(b->rp < b->base)
+		panic("checkb 1 %s %lux %lux", msg, b->base, b->rp);
+	if(b->wp < b->base)
+		panic("checkb 2 %s %lux %lux", msg, b->base, b->wp);
+	if(b->rp > b->lim)
+		panic("checkb 3 %s %lux %lux", msg, b->rp, b->lim);
+	if(b->wp > b->lim)
+		panic("checkb 4 %s %lux %lux", msg, b->wp, b->lim);
+
+}
+
+void
+iallocsummary(void)
+{
+	print("ialloc %lud/%lud\n", ialloc.bytes, conf.ialloc);
+}
--- /dev/null
+++ b/kern/cache.c
@@ -1,0 +1,46 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+
+void
+cinit(void)
+{
+}
+
+void
+copen(Chan *c)
+{
+	USED(c);
+}
+
+int
+cread(Chan *c, uchar *buf, int len, vlong off)
+{
+	USED(c);
+	USED(buf);
+	USED(len);
+	USED(off);
+
+	return 0;
+}
+
+void
+cupdate(Chan *c, uchar *buf, int len, vlong off)
+{
+	USED(c);
+	USED(buf);
+	USED(len);
+	USED(off);
+}
+
+void
+cwrite(Chan* c, uchar *buf, int len, vlong off)
+{
+	USED(c);
+	USED(buf);
+	USED(len);
+	USED(off);
+}
--- /dev/null
+++ b/kern/chan.c
@@ -1,0 +1,1496 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+int chandebug=0;		/* toggled by sysr1 */
+QLock chanprint;		/* probably asking for trouble (deadlocks) -rsc */
+
+int domount(Chan**, Mhead**);
+
+void
+dumpmount(void)		/* DEBUGGING */
+{
+	Pgrp *pg;
+	Mount *t;
+	Mhead **h, **he, *f;
+
+	if(up == nil){
+		print("no process for dumpmount\n");
+		return;
+	}
+	pg = up->pgrp;
+	if(pg == nil){
+		print("no pgrp for dumpmount\n");
+		return;
+	}
+	rlock(&pg->ns);
+	if(waserror()) {
+		runlock(&pg->ns);
+		nexterror();
+	}
+
+	he = &pg->mnthash[MNTHASH];
+	for(h = pg->mnthash; h < he; h++) {
+		for(f = *h; f; f = f->hash) {
+			print("head: %p: %s 0x%llux.%lud %C %lud -> \n", f,
+				f->from->name->s, f->from->qid.path,
+				f->from->qid.vers, devtab[f->from->type]->dc,
+				f->from->dev);
+			for(t = f->mount; t; t = t->next)
+				print("\t%p: %s (umh %p) (path %.8llux dev %C %lud)\n", t, t->to->name->s, t->to->umh, t->to->qid.path, devtab[t->to->type]->dc, t->to->dev);
+		}
+	}
+	poperror();
+	runlock(&pg->ns);
+}
+
+
+char*
+c2name(Chan *c)		/* DEBUGGING */
+{
+	if(c == nil)
+		return "<nil chan>";
+	if(c->name == nil)
+		return "<nil name>";
+	if(c->name->s == nil)
+		return "<nil name.s>";
+	return c->name->s;
+}
+
+enum
+{
+	CNAMESLOP	= 20
+};
+
+struct
+{
+	Lock lk;
+	int	fid;
+	Chan	*free;
+	Chan	*list;
+}chanalloc;
+
+typedef struct Elemlist Elemlist;
+
+struct Elemlist
+{
+	char	*name;	/* copy of name, so '/' can be overwritten */
+	int	nelems;
+	char	**elems;
+	int	*off;
+	int	mustbedir;
+};
+
+#define SEP(c) ((c) == 0 || (c) == '/')
+void cleancname(Cname*);
+
+int
+isdotdot(char *p)
+{
+	return p[0]=='.' && p[1]=='.' && p[2]=='\0';
+}
+
+int
+incref(Ref *r)
+{
+	int x;
+
+	lock(&r->lk);
+	x = ++r->ref;
+	unlock(&r->lk);
+	return x;
+}
+
+int
+decref(Ref *r)
+{
+	int x;
+
+	lock(&r->lk);
+	x = --r->ref;
+	unlock(&r->lk);
+	if(x < 0)
+		panic("decref, pc=0x%lux", getcallerpc(&r));
+
+	return x;
+}
+
+/*
+ * Rather than strncpy, which zeros the rest of the buffer, kstrcpy
+ * truncates if necessary, always zero terminates, does not zero fill,
+ * and puts ... at the end of the string if it's too long.  Usually used to
+ * save a string in up->genbuf;
+ */
+void
+kstrcpy(char *s, char *t, int ns)
+{
+	int nt;
+
+	nt = strlen(t);
+	if(nt+1 <= ns){
+		memmove(s, t, nt+1);
+		return;
+	}
+	/* too long */
+	if(ns < 4){
+		/* but very short! */
+		strncpy(s, t, ns);
+		return;
+	}
+	/* truncate with ... at character boundary (very rare case) */
+	memmove(s, t, ns-4);
+	ns -= 4;
+	s[ns] = '\0';
+	/* look for first byte of UTF-8 sequence by skipping continuation bytes */
+	while(ns>0 && (s[--ns]&0xC0)==0x80)
+		;
+	strcpy(s+ns, "...");
+}
+
+int
+emptystr(char *s)
+{
+	if(s == nil)
+		return 1;
+	if(s[0] == '\0')
+		return 1;
+	return 0;
+}
+
+/*
+ * Atomically replace *p with copy of s
+ */
+void
+kstrdup(char **p, char *s)
+{
+	int n;
+	char *t, *prev;
+	static Lock l;
+
+	n = strlen(s)+1;
+	/* if it's a user, we can wait for memory; if not, something's very wrong */
+	if(up){
+		t = smalloc(n);
+		setmalloctag(t, getcallerpc(&p));
+	}else{
+		t = malloc(n);
+		if(t == nil)
+			panic("kstrdup: no memory");
+	}
+	memmove(t, s, n);
+	prev = *p;
+	*p = t;
+	free(prev);
+}
+
+void
+chandevreset(void)
+{
+	int i;
+
+	for(i=0; devtab[i] != nil; i++)
+		devtab[i]->reset();
+}
+
+void
+chandevinit(void)
+{
+	int i;
+
+	for(i=0; devtab[i] != nil; i++)
+		devtab[i]->init();
+}
+
+void
+chandevshutdown(void)
+{
+	int i;
+	
+	/* shutdown in reverse order */
+	for(i=0; devtab[i] != nil; i++)
+		;
+	for(i--; i >= 0; i--)
+		devtab[i]->shutdown();
+}
+
+Chan*
+newchan(void)
+{
+	Chan *c;
+
+	lock(&chanalloc.lk);
+	c = chanalloc.free;
+	if(c != 0)
+		chanalloc.free = c->next;
+	unlock(&chanalloc.lk);
+
+	if(c == nil) {
+		c = smalloc(sizeof(Chan));
+		lock(&chanalloc.lk);
+		c->fid = ++chanalloc.fid;
+		c->link = chanalloc.list;
+		chanalloc.list = c;
+		unlock(&chanalloc.lk);
+	}
+
+	/* if you get an error before associating with a dev,
+	   close calls rootclose, a nop */
+	c->type = 0;
+	c->flag = 0;
+	c->ref.ref = 1;
+	c->dev = 0;
+	c->offset = 0;
+	c->iounit = 0;
+	c->umh = 0;
+	c->uri = 0;
+	c->dri = 0;
+	c->aux = 0;
+	c->mchan = 0;
+	c->mcp = 0;
+	c->mux = 0;
+	memset(&c->mqid, 0, sizeof(c->mqid));
+	c->name = 0;
+	return c;
+}
+
+static Ref ncname;
+
+Cname*
+newcname(char *s)
+{
+	Cname *n;
+	int i;
+
+	n = smalloc(sizeof(Cname));
+	i = strlen(s);
+	n->len = i;
+	n->alen = i+CNAMESLOP;
+	n->s = smalloc(n->alen);
+	memmove(n->s, s, i+1);
+	n->ref.ref = 1;
+	incref(&ncname);
+	return n;
+}
+
+void
+cnameclose(Cname *n)
+{
+	if(n == nil)
+		return;
+	if(decref(&n->ref))
+		return;
+	decref(&ncname);
+	free(n->s);
+	free(n);
+}
+
+Cname*
+addelem(Cname *n, char *s)
+{
+	int i, a;
+	char *t;
+	Cname *new;
+
+	if(s[0]=='.' && s[1]=='\0')
+		return n;
+
+	if(n->ref.ref > 1){
+		/* copy on write */
+		new = newcname(n->s);
+		cnameclose(n);
+		n = new;
+	}
+
+	i = strlen(s);
+	if(n->len+1+i+1 > n->alen){
+		a = n->len+1+i+1 + CNAMESLOP;
+		t = smalloc(a);
+		memmove(t, n->s, n->len+1);
+		free(n->s);
+		n->s = t;
+		n->alen = a;
+	}
+	if(n->len>0 && n->s[n->len-1]!='/' && s[0]!='/')	/* don't insert extra slash if one is present */
+		n->s[n->len++] = '/';
+	memmove(n->s+n->len, s, i+1);
+	n->len += i;
+	if(isdotdot(s))
+		cleancname(n);
+	return n;
+}
+
+void
+chanfree(Chan *c)
+{
+	c->flag = CFREE;
+
+	if(c->umh != nil){
+		putmhead(c->umh);
+		c->umh = nil;
+	}
+	if(c->umc != nil){
+		cclose(c->umc);
+		c->umc = nil;
+	}
+	if(c->mux != nil){
+		muxclose(c->mux);
+		c->mux = nil;
+	}
+	if(c->mchan != nil){
+		cclose(c->mchan);
+		c->mchan = nil;
+	}
+
+	cnameclose(c->name);
+
+	lock(&chanalloc.lk);
+	c->next = chanalloc.free;
+	chanalloc.free = c;
+	unlock(&chanalloc.lk);
+}
+
+void
+cclose(Chan *c)
+{
+	if(c->flag&CFREE)
+		panic("cclose %lux", getcallerpc(&c));
+
+	if(decref(&c->ref))
+		return;
+
+	if(!waserror()){
+		devtab[c->type]->close(c);
+		poperror();
+	}
+	chanfree(c);
+}
+
+/*
+ * Make sure we have the only copy of c.  (Copy on write.)
+ */
+Chan*
+cunique(Chan *c)
+{
+	Chan *nc;
+
+	if(c->ref.ref != 1) {
+		nc = cclone(c);
+		cclose(c);
+		c = nc;
+	}
+
+	return c;
+}
+
+int
+eqqid(Qid a, Qid b)
+{
+	return a.path==b.path && a.vers==b.vers;
+}
+
+int
+eqchan(Chan *a, Chan *b, int pathonly)
+{
+	if(a->qid.path != b->qid.path)
+		return 0;
+	if(!pathonly && a->qid.vers!=b->qid.vers)
+		return 0;
+	if(a->type != b->type)
+		return 0;
+	if(a->dev != b->dev)
+		return 0;
+	return 1;
+}
+
+int
+eqchantdqid(Chan *a, int type, int dev, Qid qid, int pathonly)
+{
+	if(a->qid.path != qid.path)
+		return 0;
+	if(!pathonly && a->qid.vers!=qid.vers)
+		return 0;
+	if(a->type != type)
+		return 0;
+	if(a->dev != dev)
+		return 0;
+	return 1;
+}
+
+Mhead*
+newmhead(Chan *from)
+{
+	Mhead *mh;
+
+	mh = smalloc(sizeof(Mhead));
+	mh->ref.ref = 1;
+	mh->from = from;
+	incref(&from->ref);
+
+/*
+	n = from->name->len;
+	if(n >= sizeof(mh->fromname))
+		n = sizeof(mh->fromname)-1;
+	memmove(mh->fromname, from->name->s, n);
+	mh->fromname[n] = 0;
+*/
+	return mh;
+}
+
+int
+cmount(Chan **newp, Chan *old, int flag, char *spec)
+{
+	Pgrp *pg;
+	int order, flg;
+	Mhead *m, **l, *mh;
+	Mount *nm, *f, *um, **h;
+	Chan *new;
+
+	if(QTDIR & (old->qid.type^(*newp)->qid.type))
+		error(Emount);
+
+if(old->umh)print("cmount old extra umh\n");
+
+	order = flag&MORDER;
+
+	if((old->qid.type&QTDIR)==0 && order != MREPL)
+		error(Emount);
+
+	new = *newp;
+	mh = new->umh;
+
+	/*
+	 * Not allowed to bind when the old directory
+	 * is itself a union.  (Maybe it should be allowed, but I don't see
+	 * what the semantics would be.)
+	 *
+	 * We need to check mh->mount->next to tell unions apart from
+	 * simple mount points, so that things like
+	 *	mount -c fd /root
+	 *	bind -c /root /
+	 * work.  The check of mount->mflag catches things like
+	 *	mount fd /root
+	 *	bind -c /root /
+	 * 
+	 * This is far more complicated than it should be, but I don't
+	 * see an easier way at the moment.		-rsc
+	 */
+	if((flag&MCREATE) && mh && mh->mount
+	&& (mh->mount->next || !(mh->mount->mflag&MCREATE)))
+		error(Emount);
+
+	pg = up->pgrp;
+	wlock(&pg->ns);
+
+	l = &MOUNTH(pg, old->qid);
+	for(m = *l; m; m = m->hash) {
+		if(eqchan(m->from, old, 1))
+			break;
+		l = &m->hash;
+	}
+
+	if(m == nil) {
+		/*
+		 *  nothing mounted here yet.  create a mount
+		 *  head and add to the hash table.
+		 */
+		m = newmhead(old);
+		*l = m;
+
+		/*
+		 *  if this is a union mount, add the old
+		 *  node to the mount chain.
+		 */
+		if(order != MREPL)
+			m->mount = newmount(m, old, 0, 0);
+	}
+	wlock(&m->lock);
+	if(waserror()){
+		wunlock(&m->lock);
+		nexterror();
+	}
+	wunlock(&pg->ns);
+
+	nm = newmount(m, new, flag, spec);
+	if(mh != nil && mh->mount != nil) {
+		/*
+		 *  copy a union when binding it onto a directory
+		 */
+		flg = order;
+		if(order == MREPL)
+			flg = MAFTER;
+		h = &nm->next;
+		um = mh->mount;
+		for(um = um->next; um; um = um->next) {
+			f = newmount(m, um->to, flg, um->spec);
+			*h = f;
+			h = &f->next;
+		}
+	}
+
+	if(m->mount && order == MREPL) {
+		mountfree(m->mount);
+		m->mount = 0;
+	}
+
+	if(flag & MCREATE)
+		nm->mflag |= MCREATE;
+
+	if(m->mount && order == MAFTER) {
+		for(f = m->mount; f->next; f = f->next)
+			;
+		f->next = nm;
+	}
+	else {
+		for(f = nm; f->next; f = f->next)
+			;
+		f->next = m->mount;
+		m->mount = nm;
+	}
+
+	wunlock(&m->lock);
+	poperror();
+	return nm->mountid;
+}
+
+void
+cunmount(Chan *mnt, Chan *mounted)
+{
+	Pgrp *pg;
+	Mhead *m, **l;
+	Mount *f, **p;
+
+	if(mnt->umh)	/* should not happen */
+		print("cunmount newp extra umh %p has %p\n", mnt, mnt->umh);
+
+	/*
+	 * It _can_ happen that mounted->umh is non-nil, 
+	 * because mounted is the result of namec(Aopen)
+	 * (see sysfile.c:/^sysunmount).
+	 * If we open a union directory, it will have a umh.
+	 * Although surprising, this is okay, since the
+	 * cclose will take care of freeing the umh.
+	 */
+
+	pg = up->pgrp;
+	wlock(&pg->ns);
+
+	l = &MOUNTH(pg, mnt->qid);
+	for(m = *l; m; m = m->hash) {
+		if(eqchan(m->from, mnt, 1))
+			break;
+		l = &m->hash;
+	}
+
+	if(m == 0) {
+		wunlock(&pg->ns);
+		error(Eunmount);
+	}
+
+	wlock(&m->lock);
+	if(mounted == 0) {
+		*l = m->hash;
+		wunlock(&pg->ns);
+		mountfree(m->mount);
+		m->mount = nil;
+		cclose(m->from);
+		wunlock(&m->lock);
+		putmhead(m);
+		return;
+	}
+
+	p = &m->mount;
+	for(f = *p; f; f = f->next) {
+		/* BUG: Needs to be 2 pass */
+		if(eqchan(f->to, mounted, 1) ||
+		  (f->to->mchan && eqchan(f->to->mchan, mounted, 1))) {
+			*p = f->next;
+			f->next = 0;
+			mountfree(f);
+			if(m->mount == nil) {
+				*l = m->hash;
+				cclose(m->from);
+				wunlock(&m->lock);
+				wunlock(&pg->ns);
+				putmhead(m);
+				return;
+			}
+			wunlock(&m->lock);
+			wunlock(&pg->ns);
+			return;
+		}
+		p = &f->next;
+	}
+	wunlock(&m->lock);
+	wunlock(&pg->ns);
+	error(Eunion);
+}
+
+Chan*
+cclone(Chan *c)
+{
+	Chan *nc;
+	Walkqid *wq;
+
+	wq = devtab[c->type]->walk(c, nil, nil, 0);
+	if(wq == nil)
+		error("clone failed");
+	nc = wq->clone;
+	free(wq);
+	nc->name = c->name;
+	if(c->name)
+		incref(&c->name->ref);
+	return nc;
+}
+
+int
+findmount(Chan **cp, Mhead **mp, int type, int dev, Qid qid)
+{
+	Pgrp *pg;
+	Mhead *m;
+
+	pg = up->pgrp;
+	rlock(&pg->ns);
+	for(m = MOUNTH(pg, qid); m; m = m->hash){
+		rlock(&m->lock);
+if(m->from == nil){
+	print("m %p m->from 0\n", m);
+	runlock(&m->lock);
+	continue;
+}
+		if(eqchantdqid(m->from, type, dev, qid, 1)) {
+			runlock(&pg->ns);
+			if(mp != nil){
+				incref(&m->ref);
+				if(*mp != nil)
+					putmhead(*mp);
+				*mp = m;
+			}
+			if(*cp != nil)
+				cclose(*cp);
+			incref(&m->mount->to->ref);
+			*cp = m->mount->to;
+			runlock(&m->lock);
+			return 1;
+		}
+		runlock(&m->lock);
+	}
+
+	runlock(&pg->ns);
+	return 0;
+}
+
+int
+domount(Chan **cp, Mhead **mp)
+{
+	return findmount(cp, mp, (*cp)->type, (*cp)->dev, (*cp)->qid);
+}
+
+Chan*
+undomount(Chan *c, Cname *name)
+{
+	Chan *nc;
+	Pgrp *pg;
+	Mount *t;
+	Mhead **h, **he, *f;
+
+	pg = up->pgrp;
+	rlock(&pg->ns);
+	if(waserror()) {
+		runlock(&pg->ns);
+		nexterror();
+	}
+
+	he = &pg->mnthash[MNTHASH];
+	for(h = pg->mnthash; h < he; h++) {
+		for(f = *h; f; f = f->hash) {
+			if(strcmp(f->from->name->s, name->s) != 0)
+				continue;
+			for(t = f->mount; t; t = t->next) {
+				if(eqchan(c, t->to, 1)) {
+					/*
+					 * We want to come out on the left hand side of the mount
+					 * point using the element of the union that we entered on.
+					 * To do this, find the element that has a from name of
+					 * c->name->s.
+					 */
+					if(strcmp(t->head->from->name->s, name->s) != 0)
+						continue;
+					nc = t->head->from;
+					incref(&nc->ref);
+					cclose(c);
+					c = nc;
+					break;
+				}
+			}
+		}
+	}
+	poperror();
+	runlock(&pg->ns);
+	return c;
+}
+
+/*
+ * Either walks all the way or not at all.  No partial results in *cp.
+ * *nerror is the number of names to display in an error message.
+ */
+static char Edoesnotexist[] = "does not exist";
+int
+walk(Chan **cp, char **names, int nnames, int nomount, int *nerror)
+{
+	int dev, dotdot, i, n, nhave, ntry, type;
+	Chan *c, *nc;
+	Cname *cname;
+	Mount *f;
+	Mhead *mh, *nmh;
+	Walkqid *wq;
+
+	c = *cp;
+	incref(&c->ref);
+	cname = c->name;
+	incref(&cname->ref);
+	mh = nil;
+
+	/*
+	 * While we haven't gotten all the way down the path:
+	 *    1. step through a mount point, if any
+	 *    2. send a walk request for initial dotdot or initial prefix without dotdot
+	 *    3. move to the first mountpoint along the way.
+	 *    4. repeat.
+	 *
+	 * An invariant is that each time through the loop, c is on the undomount
+	 * side of the mount point, and c's name is cname.
+	 */
+	for(nhave=0; nhave<nnames; nhave+=n){
+		if((c->qid.type&QTDIR)==0){
+			if(nerror)
+				*nerror = nhave;
+			cnameclose(cname);
+			cclose(c);
+			strcpy(up->errstr, Enotdir);
+			if(mh != nil)
+{print("walk 1\n");
+				putmhead(mh);
+}
+			return -1;
+		}
+		ntry = nnames - nhave;
+		if(ntry > MAXWELEM)
+			ntry = MAXWELEM;
+		dotdot = 0;
+		for(i=0; i<ntry; i++){
+			if(isdotdot(names[nhave+i])){
+				if(i==0) {
+					dotdot = 1;
+					ntry = 1;
+				} else
+					ntry = i;
+				break;
+			}
+		}
+
+		if(!dotdot && !nomount)
+			domount(&c, &mh);
+
+		type = c->type;
+		dev = c->dev;
+
+		if((wq = devtab[type]->walk(c, nil, names+nhave, ntry)) == nil){
+			/* try a union mount, if any */
+			if(mh && !nomount){
+				/*
+				 * mh->mount == c, so start at mh->mount->next
+				 */
+				rlock(&mh->lock);
+				for(f = mh->mount->next; f; f = f->next)
+					if((wq = devtab[f->to->type]->walk(f->to, nil, names+nhave, ntry)) != nil)
+						break;
+				runlock(&mh->lock);
+				if(f != nil){
+					type = f->to->type;
+					dev = f->to->dev;
+				}
+			}
+			if(wq == nil){
+				cclose(c);
+				cnameclose(cname);
+				if(nerror)
+					*nerror = nhave+1;
+				if(mh != nil)
+					putmhead(mh);
+				return -1;
+			}
+		}
+
+		nmh = nil;
+		if(dotdot) {
+			assert(wq->nqid == 1);
+			assert(wq->clone != nil);
+
+			cname = addelem(cname, "..");
+			nc = undomount(wq->clone, cname);
+			n = 1;
+		} else {
+			nc = nil;
+			if(!nomount)
+				for(i=0; i<wq->nqid && i<ntry-1; i++)
+					if(findmount(&nc, &nmh, type, dev, wq->qid[i]))
+						break;
+			if(nc == nil){	/* no mount points along path */
+				if(wq->clone == nil){
+					cclose(c);
+					cnameclose(cname);
+					if(wq->nqid==0 || (wq->qid[wq->nqid-1].type&QTDIR)){
+						if(nerror)
+							*nerror = nhave+wq->nqid+1;
+						strcpy(up->errstr, Edoesnotexist);
+					}else{
+						if(nerror)
+							*nerror = nhave+wq->nqid;
+						strcpy(up->errstr, Enotdir);
+					}
+					free(wq);
+					if(mh != nil)
+						putmhead(mh);
+					return -1;
+				}
+				n = wq->nqid;
+				nc = wq->clone;
+			}else{		/* stopped early, at a mount point */
+				if(wq->clone != nil){
+					cclose(wq->clone);
+					wq->clone = nil;
+				}
+				n = i+1;
+			}
+			for(i=0; i<n; i++)
+				cname = addelem(cname, names[nhave+i]);
+		}
+		cclose(c);
+		c = nc;
+		putmhead(mh);
+		mh = nmh;
+		free(wq);
+	}
+
+	putmhead(mh);
+
+	c = cunique(c);
+
+	if(c->umh != nil){	//BUG
+		print("walk umh\n");
+		putmhead(c->umh);
+		c->umh = nil;
+	}
+
+	cnameclose(c->name);
+	c->name = cname;
+
+	cclose(*cp);
+	*cp = c;
+	if(nerror)
+		*nerror = 0;
+	return 0;
+}
+
+/*
+ * c is a mounted non-creatable directory.  find a creatable one.
+ */
+Chan*
+createdir(Chan *c, Mhead *m)
+{
+	Chan *nc;
+	Mount *f;
+
+	rlock(&m->lock);
+	if(waserror()) {
+		runlock(&m->lock);
+		nexterror();
+	}
+	for(f = m->mount; f; f = f->next) {
+		if(f->mflag&MCREATE) {
+			nc = cclone(f->to);
+			runlock(&m->lock);
+			poperror();
+			cclose(c);
+			return nc;
+		}
+	}
+	error(Enocreate);
+	return 0;
+}
+
+void
+saveregisters(void)
+{
+}
+
+/*
+ * In place, rewrite name to compress multiple /, eliminate ., and process ..
+ */
+void
+cleancname(Cname *n)
+{
+	char *p;
+
+	if(n->s[0] == '#'){
+		p = strchr(n->s, '/');
+		if(p == nil)
+			return;
+		cleanname(p);
+
+		/*
+		 * The correct name is #i rather than #i/,
+		 * but the correct name of #/ is #/.
+		 */
+		if(strcmp(p, "/")==0 && n->s[1] != '/')
+			*p = '\0';
+	}else
+		cleanname(n->s);
+	n->len = strlen(n->s);
+}
+
+static void
+growparse(Elemlist *e)
+{
+	char **new;
+	int *inew;
+	enum { Delta = 8 };
+
+	if(e->nelems % Delta == 0){
+		new = smalloc((e->nelems+Delta) * sizeof(char*));
+		memmove(new, e->elems, e->nelems*sizeof(char*));
+		free(e->elems);
+		e->elems = new;
+		inew = smalloc((e->nelems+Delta+1) * sizeof(int));
+		memmove(inew, e->off, e->nelems*sizeof(int));
+		free(e->off);
+		e->off = inew;
+	}
+}
+
+/*
+ * The name is known to be valid.
+ * Copy the name so slashes can be overwritten.
+ * An empty string will set nelem=0.
+ * A path ending in / or /. or /.//./ etc. will have
+ * e.mustbedir = 1, so that we correctly
+ * reject, e.g., "/adm/users/." when /adm/users is a file
+ * rather than a directory.
+ */
+static void
+parsename(char *name, Elemlist *e)
+{
+	char *slash;
+
+	kstrdup(&e->name, name);
+	name = e->name;
+	e->nelems = 0;
+	e->elems = nil;
+	e->off = smalloc(sizeof(int));
+	e->off[0] = skipslash(name) - name;
+	for(;;){
+		name = skipslash(name);
+		if(*name=='\0'){
+			e->mustbedir = 1;
+			break;
+		}
+		growparse(e);
+		e->elems[e->nelems++] = name;
+		slash = utfrune(name, '/');
+		if(slash == nil){
+			e->off[e->nelems] = name+strlen(name) - e->name;
+			e->mustbedir = 0;
+			break;
+		}
+		e->off[e->nelems] = slash - e->name;
+		*slash++ = '\0';
+		name = slash;
+	}
+}
+
+void*
+memrchr(void *va, int c, long n)
+{
+	uchar *a, *e;
+
+	a = va;
+	for(e=a+n-1; e>a; e--)
+		if(*e == c)
+			return e;
+	return nil;
+}
+
+/*
+ * Turn a name into a channel.
+ * &name[0] is known to be a valid address.  It may be a kernel address.
+ *
+ * Opening with amode Aopen, Acreate, or Aremove guarantees
+ * that the result will be the only reference to that particular fid.
+ * This is necessary since we might pass the result to
+ * devtab[]->remove().
+ *
+ * Opening Atodir, Amount, or Aaccess does not guarantee this.
+ *
+ * Opening Aaccess can, under certain conditions, return a
+ * correct Chan* but with an incorrect Cname attached.
+ * Since the functions that open Aaccess (sysstat, syswstat, sys_stat)
+ * do not use the Cname*, this avoids an unnecessary clone.
+ */
+Chan*
+namec(char *aname, int amode, int omode, ulong perm)
+{
+	int n, prefix, len, t, nomount, npath;
+	Chan *c, *cnew;
+	Cname *cname;
+	Elemlist e;
+	Rune r;
+	Mhead *m;
+	char *createerr, tmperrbuf[ERRMAX];
+	char *name;
+
+	name = aname;
+	if(name[0] == '\0')
+		error("empty file name");
+	validname(name, 1);
+
+	/*
+	 * Find the starting off point (the current slash, the root of
+	 * a device tree, or the current dot) as well as the name to
+	 * evaluate starting there.
+	 */
+	nomount = 0;
+	switch(name[0]){
+	case '/':
+		c = up->slash;
+		incref(&c->ref);
+		break;
+	
+	case '#':
+		nomount = 1;
+		up->genbuf[0] = '\0';
+		n = 0;
+		while(*name!='\0' && (*name != '/' || n < 2)){
+			if(n >= sizeof(up->genbuf)-1)
+				error(Efilename);
+			up->genbuf[n++] = *name++;
+		}
+		up->genbuf[n] = '\0';
+		/*
+		 *  noattach is sandboxing.
+		 *
+		 *  the OK exceptions are:
+		 *	|  it only gives access to pipes you create
+		 *	d  this process's file descriptors
+		 *	e  this process's environment
+		 *  the iffy exceptions are:
+		 *	c  time and pid, but also cons and consctl
+		 *	p  control of your own processes (and unfortunately
+		 *	   any others left unprotected)
+		 */
+		n = chartorune(&r, up->genbuf+1)+1;
+		/* actually / is caught by parsing earlier */
+		if(utfrune("M", r))
+			error(Enoattach);
+		if(up->pgrp->noattach && utfrune("|decp", r)==nil)
+			error(Enoattach);
+		t = devno(r, 1);
+		if(t == -1)
+			error(Ebadsharp);
+		c = devtab[t]->attach(up->genbuf+n);
+		break;
+
+	default:
+		c = up->dot;
+		incref(&c->ref);
+		break;
+	}
+	prefix = name - aname;
+
+	e.name = nil;
+	e.elems = nil;
+	e.off = nil;
+	e.nelems = 0;
+	if(waserror()){
+		cclose(c);
+		free(e.name);
+		free(e.elems);
+		free(e.off);
+//dumpmount();
+		nexterror();
+	}
+
+	/*
+	 * Build a list of elements in the path.
+	 */
+	parsename(name, &e);
+
+	/*
+	 * On create, ....
+	 */
+	if(amode == Acreate){
+		/* perm must have DMDIR if last element is / or /. */
+		if(e.mustbedir && !(perm&DMDIR)){
+			npath = e.nelems;
+			strcpy(tmperrbuf, "create without DMDIR");
+			goto NameError;
+		}
+
+		/* don't try to walk the last path element just yet. */
+		if(e.nelems == 0)
+			error(Eexist);
+		e.nelems--;
+	}
+
+	if(walk(&c, e.elems, e.nelems, nomount, &npath) < 0){
+		if(npath < 0 || npath > e.nelems){
+			print("namec %s walk error npath=%d\n", aname, npath);
+			nexterror();
+		}
+		strcpy(tmperrbuf, up->errstr);
+	NameError:
+		len = prefix+e.off[npath];
+		if(len < ERRMAX/3 || (name=memrchr(aname, '/', len))==nil || name==aname)
+			snprint(up->genbuf, sizeof up->genbuf, "%.*s", len, aname);
+		else
+			snprint(up->genbuf, sizeof up->genbuf, "...%.*s", (int)(len-(name-aname)), name);
+		snprint(up->errstr, ERRMAX, "%#q %s", up->genbuf, tmperrbuf);
+		nexterror();
+	}
+
+	if(e.mustbedir && !(c->qid.type&QTDIR)){
+		npath = e.nelems;
+		strcpy(tmperrbuf, "not a directory");
+		goto NameError;
+	}
+
+	if(amode == Aopen && (omode&3) == OEXEC && (c->qid.type&QTDIR)){
+		npath = e.nelems;
+		error("cannot exec directory");
+	}
+
+	switch(amode){
+	case Aaccess:
+		if(!nomount)
+			domount(&c, nil);
+		break;
+
+	case Abind:
+		m = nil;
+		if(!nomount)
+			domount(&c, &m);
+		if(c->umh != nil)
+			putmhead(c->umh);
+		c->umh = m;
+		break;
+
+	case Aremove:
+	case Aopen:
+	Open:
+		/* save the name; domount might change c */
+		cname = c->name;
+		incref(&cname->ref);
+		m = nil;
+		if(!nomount)
+			domount(&c, &m);
+
+		/* our own copy to open or remove */
+		c = cunique(c);
+
+		/* now it's our copy anyway, we can put the name back */
+		cnameclose(c->name);
+		c->name = cname;
+
+		switch(amode){
+		case Aremove:
+			putmhead(m);
+			break;
+
+		case Aopen:
+		case Acreate:
+if(c->umh != nil){
+	print("cunique umh Open\n");
+	putmhead(c->umh);
+	c->umh = nil;
+}
+
+			/* only save the mount head if it's a multiple element union */
+			if(m && m->mount && m->mount->next)
+				c->umh = m;
+			else
+				putmhead(m);
+
+			/* save registers else error() in open has wrong value of c saved */
+			saveregisters();
+
+			if(omode == OEXEC)
+				c->flag &= ~CCACHE;
+
+			c = devtab[c->type]->open(c, omode&~OCEXEC);
+
+			if(omode & OCEXEC)
+				c->flag |= CCEXEC;
+			if(omode & ORCLOSE)
+				c->flag |= CRCLOSE;
+			break;
+		}
+		break;
+
+	case Atodir:
+		/*
+		 * Directories (e.g. for cd) are left before the mount point,
+		 * so one may mount on / or . and see the effect.
+		 */
+		if(!(c->qid.type & QTDIR))
+			error(Enotdir);
+		break;
+
+	case Amount:
+		/*
+		 * When mounting on an already mounted upon directory,
+		 * one wants subsequent mounts to be attached to the
+		 * original directory, not the replacement.  Don't domount.
+		 */
+		break;
+
+	case Acreate:
+		/*
+		 * We've already walked all but the last element.
+		 * If the last exists, try to open it OTRUNC.
+		 * If omode&OEXCL is set, just give up.
+		 */
+		e.nelems++;
+		if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) == 0){
+			if(omode&OEXCL)
+				error(Eexist);
+			omode |= OTRUNC;
+			goto Open;
+		}
+
+		/*
+		 * The semantics of the create(2) system call are that if the
+		 * file exists and can be written, it is to be opened with truncation.
+		 * On the other hand, the create(5) message fails if the file exists.
+		 * If we get two create(2) calls happening simultaneously, 
+		 * they might both get here and send create(5) messages, but only 
+		 * one of the messages will succeed.  To provide the expected create(2)
+		 * semantics, the call with the failed message needs to try the above
+		 * walk again, opening for truncation.  This correctly solves the 
+		 * create/create race, in the sense that any observable outcome can
+		 * be explained as one happening before the other.
+		 * The create/create race is quite common.  For example, it happens
+		 * when two rc subshells simultaneously update the same
+		 * environment variable.
+		 *
+		 * The implementation still admits a create/create/remove race:
+		 * (A) walk to file, fails
+		 * (B) walk to file, fails
+		 * (A) create file, succeeds, returns 
+		 * (B) create file, fails
+		 * (A) remove file, succeeds, returns
+		 * (B) walk to file, return failure.
+		 *
+		 * This is hardly as common as the create/create race, and is really
+		 * not too much worse than what might happen if (B) got a hold of a
+		 * file descriptor and then the file was removed -- either way (B) can't do
+		 * anything with the result of the create call.  So we don't care about this race.
+		 *
+		 * Applications that care about more fine-grained decision of the races
+		 * can use the OEXCL flag to get at the underlying create(5) semantics;
+		 * by default we provide the common case.
+		 *
+		 * We need to stay behind the mount point in case we
+		 * need to do the first walk again (should the create fail).
+		 *
+		 * We also need to cross the mount point and find the directory
+		 * in the union in which we should be creating.
+		 *
+		 * The channel staying behind is c, the one moving forward is cnew.
+		 */
+		m = nil;
+		cnew = nil;	/* is this assignment necessary? */
+		if(!waserror()){	/* try create */
+			if(!nomount && findmount(&cnew, &m, c->type, c->dev, c->qid))
+				cnew = createdir(cnew, m);
+			else{
+				cnew = c;
+				incref(&cnew->ref);
+			}
+
+			/*
+			 * We need our own copy of the Chan because we're
+			 * about to send a create, which will move it.  Once we have
+			 * our own copy, we can fix the name, which might be wrong
+			 * if findmount gave us a new Chan.
+			 */
+			cnew = cunique(cnew);
+			cnameclose(cnew->name);
+			cnew->name = c->name;
+			incref(&cnew->name->ref);
+
+			devtab[cnew->type]->create(cnew, e.elems[e.nelems-1], omode&~(OEXCL|OCEXEC), perm);
+			poperror();
+			if(omode & OCEXEC)
+				cnew->flag |= CCEXEC;
+			if(omode & ORCLOSE)
+				cnew->flag |= CRCLOSE;
+			if(m)
+				putmhead(m);
+			cclose(c);
+			c = cnew;
+			c->name = addelem(c->name, e.elems[e.nelems-1]);
+			break;
+		}else{		/* create failed */
+			cclose(cnew);
+			if(m)
+				putmhead(m);
+			if(omode & OEXCL)
+				nexterror();
+			/* save error */
+			createerr = up->errstr;
+			up->errstr = tmperrbuf;
+			/* note: we depend that walk does not error */
+			if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) < 0){
+				up->errstr = createerr;
+				error(createerr);	/* report true error */
+			}
+			up->errstr = createerr;
+			omode |= OTRUNC;
+			goto Open;
+		}
+		panic("namec: not reached");				
+
+	default:
+		panic("unknown namec access %d\n", amode);
+	}
+
+	poperror();
+
+	/* place final element in genbuf for e.g. exec */
+	if(e.nelems > 0)
+		kstrcpy(up->genbuf, e.elems[e.nelems-1], sizeof up->genbuf);
+	else
+		kstrcpy(up->genbuf, ".", sizeof up->genbuf);
+	free(e.name);
+	free(e.elems);
+	free(e.off);
+
+	return c;
+}
+
+/*
+ * name is valid. skip leading / and ./ as much as possible
+ */
+char*
+skipslash(char *name)
+{
+	while(name[0]=='/' || (name[0]=='.' && (name[1]==0 || name[1]=='/')))
+		name++;
+	return name;
+}
+
+char isfrog[256]={
+	/*NUL*/	1, 1, 1, 1, 1, 1, 1, 1,	/* 0 */
+	/*BKS*/	1, 1, 1, 1, 1, 1, 1, 1, /* 0x08 */
+	/*DLE*/	1, 1, 1, 1, 1, 1, 1, 1, /* 0x10 */
+	/*CAN*/	1, 1, 1, 1, 1, 1, 1, 1, /* 0x18 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */
+		0, 0, 0, 0, 0, 0, 0, 1, /* 0x28 (1 is '/', 0x2F) */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x38 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 */
+		0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */
+		0, 0, 0, 0, 0, 0, 0, 1, /* 0x78 (1 is DEL, 0x7F) */
+};
+
+/*
+ * Check that the name
+ *  a) is in valid memory.
+ *  b) is shorter than 2^16 bytes, so it can fit in a 9P string field.
+ *  c) contains no frogs.
+ * The first byte is known to be addressible by the requester, so the
+ * routine works for kernel and user memory both.
+ * The parameter slashok flags whether a slash character is an error
+ * or a valid character.
+ */
+void
+validname(char *aname, int slashok)
+{
+	char *p, *ename, *name;
+	uint t;
+	int c;
+	Rune r;
+
+	name = aname;
+/*
+	if(((ulong)name & KZERO) != KZERO) {
+		p = name;
+		t = BY2PG-((ulong)p&(BY2PG-1));
+		while((ename=vmemchr(p, 0, t)) == nil) {
+			p += t;
+			t = BY2PG;
+		}
+	}else
+*/
+		ename = memchr(name, 0, (1<<16));
+
+	if(ename==nil || ename-name>=(1<<16))
+		error("name too long");
+
+	while(*name){
+		/* all characters above '~' are ok */
+		c = *(uchar*)name;
+		if(c >= Runeself)
+			name += chartorune(&r, name);
+		else{
+			if(isfrog[c])
+				if(!slashok || c!='/'){
+					snprint(up->genbuf, sizeof(up->genbuf), "%s: %q", Ebadchar, aname);
+					error(up->genbuf);
+			}
+			name++;
+		}
+	}
+}
+
+void
+isdir(Chan *c)
+{
+	if(c->qid.type & QTDIR)
+		return;
+	error(Enotdir);
+}
+
+/*
+ * This is necessary because there are many
+ * pointers to the top of a given mount list:
+ *
+ *	- the mhead in the namespace hash table
+ *	- the mhead in chans returned from findmount:
+ *	  used in namec and then by unionread.
+ *	- the mhead in chans returned from createdir:
+ *	  used in the open/create race protect, which is gone.
+ *
+ * The RWlock in the Mhead protects the mount list it contains.
+ * The mount list is deleted when we cunmount.
+ * The RWlock ensures that nothing is using the mount list at that time.
+ *
+ * It is okay to replace c->mh with whatever you want as 
+ * long as you are sure you have a unique reference to it.
+ *
+ * This comment might belong somewhere else.
+ */
+void
+putmhead(Mhead *m)
+{
+	if(m && decref(&m->ref) == 0){
+		m->mount = (Mount*)0xCafeBeef;
+		free(m);
+	}
+}
--- /dev/null
+++ b/kern/dat.h
@@ -1,0 +1,519 @@
+#define	KNAMELEN		28	/* max length of name held in kernel */
+#define	DOMLEN			64
+
+#define	BLOCKALIGN		8
+
+typedef struct Alarms	Alarms;
+typedef struct Block	Block;
+typedef struct CSN	CSN;
+typedef struct Chan	Chan;
+typedef struct Cmdbuf	Cmdbuf;
+typedef struct Cmdtab	Cmdtab;
+typedef struct Cname	Cname;
+typedef struct Conf	Conf;
+typedef struct Dev	Dev;
+typedef struct Dirtab	Dirtab;
+typedef struct Edfinterface	Edfinterface;
+typedef struct Egrp	Egrp;
+typedef struct Evalue	Evalue;
+typedef struct Fgrp	Fgrp;
+typedef struct FPsave	FPsave;
+typedef struct DevConf	DevConf;
+typedef struct Label	Label;
+typedef struct List	List;
+typedef struct Log	Log;
+typedef struct Logflag	Logflag;
+typedef struct Mntcache Mntcache;
+typedef struct Mount	Mount;
+typedef struct Mntrpc	Mntrpc;
+typedef struct Mntwalk	Mntwalk;
+typedef struct Mnt	Mnt;
+typedef struct Mhead	Mhead;
+typedef struct Note	Note;
+typedef struct Page	Page;
+typedef struct Palloc	Palloc;
+typedef struct Perf	Perf;
+typedef struct Pgrps	Pgrps;
+typedef struct PhysUart	PhysUart;
+typedef struct Pgrp	Pgrp;
+typedef struct Physseg	Physseg;
+typedef struct Proc	Proc;
+typedef struct Pte	Pte;
+typedef struct Pthash	Pthash;
+typedef struct Queue	Queue;
+typedef struct Ref	Ref;
+typedef struct Rendez	Rendez;
+typedef struct Rgrp	Rgrp;
+typedef struct RWlock	RWlock;
+typedef struct Schedq	Schedq;
+typedef struct Segment	Segment;
+typedef struct Session	Session;
+typedef struct Task	Task;
+typedef struct Talarm	Talarm;
+typedef struct Timer	Timer;
+typedef struct Uart	Uart;
+typedef struct Ureg Ureg;
+typedef struct Waitq	Waitq;
+typedef struct Walkqid	Walkqid;
+typedef int    Devgen(Chan*, char*, Dirtab*, int, int, Dir*);
+
+#include "fcall.h"
+
+enum
+{
+	SnarfSize = 64*1024,
+};
+
+struct Conf
+{
+	ulong	nmach;		/* processors */
+	ulong	nproc;		/* processes */
+	ulong	monitor;	/* has monitor? */
+	ulong	npage0;		/* total physical pages of memory */
+	ulong	npage1;		/* total physical pages of memory */
+	ulong	npage;		/* total physical pages of memory */
+	ulong	upages;		/* user page pool */
+	ulong	nimage;		/* number of page cache image headers */
+	ulong	nswap;		/* number of swap pages */
+	int	nswppo;		/* max # of pageouts per segment pass */
+	ulong	base0;		/* base of bank 0 */
+	ulong	base1;		/* base of bank 1 */
+	ulong	copymode;	/* 0 is copy on write, 1 is copy on reference */
+	ulong	ialloc;		/* max interrupt time allocation in bytes */
+	ulong	pipeqsize;	/* size in bytes of pipe queues */
+	int	nuart;		/* number of uart devices */
+};
+
+struct Label
+{
+	jmp_buf	buf;
+};
+
+struct Ref
+{
+	Lock lk;
+	long	ref;
+};
+
+struct Rendez
+{
+	Lock lk;
+	Proc	*p;
+};
+
+struct RWlock	/* changed from kernel */
+{
+	int	readers;
+	Lock	lk;
+	QLock	x;
+	QLock	k;
+};
+
+struct Talarm
+{
+	Lock lk;
+	Proc	*list;
+};
+
+struct Alarms
+{
+	QLock lk;
+	Proc	*head;
+};
+
+/*
+ * Access types in namec & channel flags
+ */
+enum
+{
+	Aaccess,			/* as in stat, wstat */
+	Abind,			/* for left-hand-side of bind */
+	Atodir,				/* as in chdir */
+	Aopen,				/* for i/o */
+	Amount,				/* to be mounted or mounted upon */
+	Acreate,			/* is to be created */
+	Aremove,			/* will be removed by caller */
+
+	COPEN	= 0x0001,		/* for i/o */
+	CMSG	= 0x0002,		/* the message channel for a mount */
+/*rsc	CCREATE	= 0x0004,		/* permits creation if c->mnt */
+	CCEXEC	= 0x0008,		/* close on exec */
+	CFREE	= 0x0010,		/* not in use */
+	CRCLOSE	= 0x0020,		/* remove on close */
+	CCACHE	= 0x0080,		/* client cache */
+};
+
+/* flag values */
+enum
+{
+	BINTR	=	(1<<0),
+	BFREE	=	(1<<1),
+	Bipck	=	(1<<2),		/* ip checksum */
+	Budpck	=	(1<<3),		/* udp checksum */
+	Btcpck	=	(1<<4),		/* tcp checksum */
+	Bpktck	=	(1<<5),		/* packet checksum */
+};
+
+struct Block
+{
+	Block*	next;
+	Block*	list;
+	uchar*	rp;			/* first unconsumed byte */
+	uchar*	wp;			/* first empty byte */
+	uchar*	lim;			/* 1 past the end of the buffer */
+	uchar*	base;			/* start of the buffer */
+	void	(*free)(Block*);
+	ushort	flag;
+	ushort	checksum;		/* IP checksum of complete packet (minus media header) */
+};
+#define BLEN(s)	((s)->wp - (s)->rp)
+#define BALLOC(s) ((s)->lim - (s)->base)
+
+struct Chan
+{
+	Ref ref;
+	Chan*	next;			/* allocation */
+	Chan*	link;
+	vlong	offset;			/* in file */
+	ushort	type;
+	ulong	dev;
+	ushort	mode;			/* read/write */
+	ushort	flag;
+	Qid	qid;
+	int	fid;			/* for devmnt */
+	ulong	iounit;	/* chunk size for i/o; 0==default */
+	Mhead*	umh;			/* mount point that derived Chan; used in unionread */
+	Chan*	umc;			/* channel in union; held for union read */
+	QLock	umqlock;		/* serialize unionreads */
+	int	uri;			/* union read index */
+	int	dri;			/* devdirread index */
+	ulong	mountid;
+	Mntcache *mcp;			/* Mount cache pointer */
+	Mnt		*mux;		/* Mnt for clients using me for messages */
+	void*	aux;
+	Qid	pgrpid;		/* for #p/notepg */
+	ulong	mid;		/* for ns in devproc */
+	Chan*	mchan;			/* channel to mounted server */
+	Qid	mqid;			/* qid of root of mount point */
+	Session*session;
+	Cname	*name;
+};
+
+struct Cname
+{
+	Ref ref;
+	int	alen;			/* allocated length */
+	int	len;			/* strlen(s) */
+	char	*s;
+};
+
+struct Dev
+{
+	int	dc;
+	char*	name;
+
+	void	(*reset)(void);
+	void	(*init)(void);
+	void	(*shutdown)(void);
+	Chan*	(*attach)(char*);
+	Walkqid*	(*walk)(Chan*, Chan*, char**, int);
+	int	(*stat)(Chan*, uchar*, int);
+	Chan*	(*open)(Chan*, int);
+	void	(*create)(Chan*, char*, int, ulong);
+	void	(*close)(Chan*);
+	long	(*read)(Chan*, void*, long, vlong);
+	Block*	(*bread)(Chan*, long, ulong);
+	long	(*write)(Chan*, void*, long, vlong);
+	long	(*bwrite)(Chan*, Block*, ulong);
+	void	(*remove)(Chan*);
+	int	(*wstat)(Chan*, uchar*, int);
+	void	(*power)(int);	/* power mgt: power(1) => on, power (0) => off */
+	int	(*config)(int, char*, DevConf*);	// returns nil on error
+};
+
+struct Dirtab
+{
+	char	name[KNAMELEN];
+	Qid	qid;
+	vlong length;
+	long	perm;
+};
+
+struct Walkqid
+{
+	Chan	*clone;
+	int	nqid;
+	Qid	qid[1];
+};
+
+enum
+{
+	NSMAX	=	1000,
+	NSLOG	=	7,
+	NSCACHE	=	(1<<NSLOG),
+};
+
+struct Mntwalk				/* state for /proc/#/ns */
+{
+	int		cddone;
+	ulong	id;
+	Mhead*	mh;
+	Mount*	cm;
+};
+
+struct Mount
+{
+	ulong	mountid;
+	Mount*	next;
+	Mhead*	head;
+	Mount*	copy;
+	Mount*	order;
+	Chan*	to;			/* channel replacing channel */
+	int	mflag;
+	char	*spec;
+};
+
+struct Mhead
+{
+	Ref ref;
+	RWlock	lock;
+	Chan*	from;			/* channel mounted upon */
+	Mount*	mount;			/* what's mounted upon it */
+	Mhead*	hash;			/* Hash chain */
+};
+
+struct Mnt
+{
+	Lock lk;
+	/* references are counted using c->ref; channels on this mount point incref(c->mchan) == Mnt.c */
+	Chan	*c;		/* Channel to file service */
+	Proc	*rip;		/* Reader in progress */
+	Mntrpc	*queue;		/* Queue of pending requests on this channel */
+	ulong	id;		/* Multiplexer id for channel check */
+	Mnt	*list;		/* Free list */
+	int	flags;		/* cache */
+	int	msize;		/* data + IOHDRSZ */
+	char	*version;			/* 9P version */
+	Queue	*q;		/* input queue */
+};
+
+enum
+{
+	NUser,				/* note provided externally */
+	NExit,				/* deliver note quietly */
+	NDebug,				/* print debug message */
+};
+
+struct Note
+{
+	char	msg[ERRMAX];
+	int	flag;			/* whether system posted it */
+};
+
+enum
+{
+	RENDLOG	=	5,
+	RENDHASH =	1<<RENDLOG,		/* Hash to lookup rendezvous tags */
+	MNTLOG	=	5,
+	MNTHASH =	1<<MNTLOG,		/* Hash to walk mount table */
+	NFD =		100,		/* per process file descriptors */
+	PGHLOG  =	9,
+	PGHSIZE	=	1<<PGHLOG,	/* Page hash for image lookup */
+};
+#define REND(p,s)	((p)->rendhash[(s)&((1<<RENDLOG)-1)])
+#define MOUNTH(p,qid)	((p)->mnthash[(qid).path&((1<<MNTLOG)-1)])
+
+struct Pgrp
+{
+	Ref ref;				/* also used as a lock when mounting */
+	int	noattach;
+	ulong	pgrpid;
+	QLock	debug;			/* single access via devproc.c */
+	RWlock	ns;			/* Namespace n read/one write lock */
+	Mhead	*mnthash[MNTHASH];
+};
+
+struct Rgrp
+{
+	Ref ref;
+	Proc	*rendhash[RENDHASH];	/* Rendezvous tag hash */
+};
+
+struct Egrp
+{
+	Ref ref;
+	RWlock lk;
+	Evalue	**ent;
+	int nent;
+	int ment;
+	ulong	path;	/* qid.path of next Evalue to be allocated */
+	ulong	vers;	/* of Egrp */
+};
+
+struct Evalue
+{
+	char	*name;
+	char	*value;
+	int	len;
+	Evalue	*link;
+	Qid	qid;
+};
+
+struct Fgrp
+{
+	Ref ref;
+	Chan	**fd;
+	int	nfd;			/* number allocated */
+	int	maxfd;			/* highest fd in use */
+	int	exceed;			/* debugging */
+};
+
+enum
+{
+	DELTAFD	= 20,		/* incremental increase in Fgrp.fd's */
+	NERR = 20
+};
+
+typedef uvlong	Ticks;
+
+enum
+{
+	Running,
+	Rendezvous,
+	Wakeme,
+};
+
+struct Proc
+{
+	uint		state;
+	uint		mach;
+
+	ulong	pid;
+	ulong	parentpid;
+
+	Pgrp	*pgrp;		/* Process group for namespace */
+	Fgrp	*fgrp;		/* File descriptor group */
+	Rgrp *rgrp;
+
+	Lock	rlock;		/* sync sleep/wakeup with postnote */
+	Rendez	*r;		/* rendezvous point slept on */
+	Rendez	sleep;		/* place for syssleep/debug */
+	int	notepending;	/* note issued but not acted on */
+	int	kp;		/* true if a kernel process */
+
+	ulong	rendtag;	/* Tag for rendezvous */
+	ulong	rendval;	/* Value for rendezvous */
+	Proc	*rendhash;	/* Hash list for tag values */
+
+	int	nerrlab;
+	Label	errlab[NERR];
+	char user[KNAMELEN];
+	char	*syserrstr;	/* last error from a system call, errbuf0 or 1 */
+	char	*errstr;	/* reason we're unwinding the error stack, errbuf1 or 0 */
+	char	errbuf0[ERRMAX];
+	char	errbuf1[ERRMAX];
+	char	genbuf[128];	/* buffer used e.g. for last name element from namec */
+	char text[KNAMELEN];
+
+	Chan	*slash;
+	Chan	*dot;
+
+	Proc		*qnext;
+
+	void	(*fn)(void*);
+	void *arg;
+
+	char oproc[1024];	/* reserved for os */
+
+};
+
+enum
+{
+	PRINTSIZE =	256,
+	MAXCRYPT = 	127,
+	NUMSIZE	=	12,		/* size of formatted number */
+	MB =		(1024*1024),
+	READSTR =	1000,		/* temporary buffer size for device reads */
+};
+
+extern	char*	conffile;
+extern	int	cpuserver;
+extern	Dev*	devtab[];
+extern  char	*eve;
+extern	char	hostdomain[];
+extern	uchar	initcode[];
+extern  Queue*	kbdq;
+extern  Queue*	kprintoq;
+extern  Ref	noteidalloc;
+extern	Palloc	palloc;
+extern  Queue	*serialoq;
+extern	char*	statename[];
+extern	int	nsyscall;
+extern	char	*sysname;
+extern	uint	qiomaxatomic;
+extern	Conf	conf;
+enum
+{
+	LRESPROF	= 3,
+};
+
+/*
+ *  action log
+ */
+struct Log {
+	Lock lk;
+	int	opens;
+	char*	buf;
+	char	*end;
+	char	*rptr;
+	int	len;
+	int	nlog;
+	int	minread;
+
+	int	logmask;	/* mask of things to debug */
+
+	QLock	readq;
+	Rendez	readr;
+};
+
+struct Logflag {
+	char*	name;
+	int	mask;
+};
+
+enum
+{
+	NCMDFIELD = 128
+};
+
+struct Cmdbuf
+{
+	char	*buf;
+	char	**f;
+	int	nf;
+};
+
+struct Cmdtab
+{
+	int	index;	/* used by client to switch on result */
+	char	*cmd;	/* command name */
+	int	narg;	/* expected #args; 0 ==> variadic */
+};
+
+/* queue state bits,  Qmsg, Qcoalesce, and Qkick can be set in qopen */
+enum
+{
+	/* Queue.state */
+	Qstarve		= (1<<0),	/* consumer starved */
+	Qmsg		= (1<<1),	/* message stream */
+	Qclosed		= (1<<2),	/* queue has been closed/hungup */
+	Qflow		= (1<<3),	/* producer flow controlled */
+	Qcoalesce	= (1<<4),	/* coallesce packets on read */
+	Qkick		= (1<<5),	/* always call the kick routine after qwrite */
+};
+
+#define DEVDOTDOT -1
+
+extern Proc *_getproc(void);
+extern void _setproc(Proc*);
+#define	up	(_getproc())
--- /dev/null
+++ b/kern/data.c
@@ -1,0 +1,31 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Proc *up;
+Conf conf = 
+{
+	1,
+	100,
+	0,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	1024*1024*1024,
+	0,
+};
+
+char *eve = "eve";
+ulong kerndate;
+int cpuserver;
+char hostdomain[] = "drawterm.net";
--- /dev/null
+++ b/kern/dev.c
@@ -1,0 +1,468 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+extern ulong	kerndate;
+
+void
+mkqid(Qid *q, vlong path, ulong vers, int type)
+{
+	q->type = type;
+	q->vers = vers;
+	q->path = path;
+}
+
+int
+devno(int c, int user)
+{
+	int i;
+
+	for(i = 0; devtab[i] != nil; i++) {
+		if(devtab[i]->dc == c)
+			return i;
+	}
+	if(user == 0)
+		panic("devno %C 0x%ux", c, c);
+
+	return -1;
+}
+
+void
+devdir(Chan *c, Qid qid, char *n, vlong length, char *user, long perm, Dir *db)
+{
+	db->name = n;
+	if(c->flag&CMSG)
+		qid.type |= QTMOUNT;
+	db->qid = qid;
+	db->type = devtab[c->type]->dc;
+	db->dev = c->dev;
+	db->mode = perm;
+	db->mode |= qid.type << 24;
+	db->atime = seconds();
+	db->mtime = kerndate;
+	db->length = length;
+	db->uid = user;
+	db->gid = eve;
+	db->muid = user;
+}
+
+/*
+ * (here, Devgen is the prototype; devgen is the function in dev.c.)
+ * 
+ * a Devgen is expected to return the directory entry for ".."
+ * if you pass it s==DEVDOTDOT (-1).  otherwise...
+ * 
+ * there are two contradictory rules.
+ * 
+ * (i) if c is a directory, a Devgen is expected to list its children
+ * as you iterate s.
+ * 
+ * (ii) whether or not c is a directory, a Devgen is expected to list
+ * its siblings as you iterate s.
+ * 
+ * devgen always returns the list of children in the root
+ * directory.  thus it follows (i) when c is the root and (ii) otherwise.
+ * many other Devgens follow (i) when c is a directory and (ii) otherwise.
+ * 
+ * devwalk assumes (i).  it knows that devgen breaks (i)
+ * for children that are themselves directories, and explicitly catches them.
+ * 
+ * devstat assumes (ii).  if the Devgen in question follows (i)
+ * for this particular c, devstat will not find the necessary info.
+ * with our particular Devgen functions, this happens only for
+ * directories, so devstat makes something up, assuming
+ * c->name, c->qid, eve, DMDIR|0555.
+ * 
+ * devdirread assumes (i).  the callers have to make sure
+ * that the Devgen satisfies (i) for the chan being read.
+ */
+/*
+ * the zeroth element of the table MUST be the directory itself for ..
+*/
+int
+devgen(Chan *c, char *name, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+	if(tab == 0)
+		return -1;
+	if(i == DEVDOTDOT){
+		/* nothing */
+	}else if(name){
+		for(i=1; i<ntab; i++)
+			if(strcmp(tab[i].name, name) == 0)
+				break;
+		if(i==ntab)
+			return -1;
+		tab += i;
+	}else{
+		/* skip over the first element, that for . itself */
+		i++;
+		if(i >= ntab)
+			return -1;
+		tab += i;
+	}
+	devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
+	return 1;
+}
+
+void
+devreset(void)
+{
+}
+
+void
+devinit(void)
+{
+}
+
+void
+devshutdown(void)
+{
+}
+
+Chan*
+devattach(int tc, char *spec)
+{
+	Chan *c;
+	char *buf;
+
+	c = newchan();
+	mkqid(&c->qid, 0, 0, QTDIR);
+	c->type = devno(tc, 0);
+	if(spec == nil)
+		spec = "";
+	buf = smalloc(4+strlen(spec)+1);
+	sprint(buf, "#%C%s", tc, spec);
+	c->name = newcname(buf);
+	free(buf);
+	return c;
+}
+
+
+Chan*
+devclone(Chan *c)
+{
+	Chan *nc;
+
+	if(c->flag & COPEN)
+		panic("clone of open file type %C\n", devtab[c->type]->dc);
+
+	nc = newchan();
+
+	nc->type = c->type;
+	nc->dev = c->dev;
+	nc->mode = c->mode;
+	nc->qid = c->qid;
+	nc->offset = c->offset;
+	nc->umh = nil;
+	nc->mountid = c->mountid;
+	nc->aux = c->aux;
+	nc->pgrpid = c->pgrpid;
+	nc->mid = c->mid;
+	nc->mqid = c->mqid;
+	nc->mcp = c->mcp;
+	return nc;
+}
+
+Walkqid*
+devwalk(Chan *c, Chan *nc, char **name, int nname, Dirtab *tab, int ntab, Devgen *gen)
+{
+	int i, j, alloc;
+	Walkqid *wq;
+	char *n;
+	Dir dir;
+
+	if(nname > 0)
+		isdir(c);
+
+	alloc = 0;
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	if(waserror()){
+		if(alloc && wq->clone!=nil)
+			cclose(wq->clone);
+		free(wq);
+		return nil;
+	}
+	if(nc == nil){
+		nc = devclone(c);
+		nc->type = 0;	/* device doesn't know about this channel yet */
+		alloc = 1;
+	}
+	wq->clone = nc;
+
+	for(j=0; j<nname; j++){
+		if(!(nc->qid.type&QTDIR)){
+			if(j==0)
+				error(Enotdir);
+			goto Done;
+		}
+		n = name[j];
+		if(strcmp(n, ".") == 0){
+    Accept:
+			wq->qid[wq->nqid++] = nc->qid;
+			continue;
+		}
+		if(strcmp(n, "..") == 0){
+			if((*gen)(nc, nil, tab, ntab, DEVDOTDOT, &dir) != 1){
+				print("devgen walk .. in dev%s %llux broken\n",
+					devtab[nc->type]->name, nc->qid.path);
+				error("broken devgen");
+			}
+			nc->qid = dir.qid;
+			goto Accept;
+		}
+		/*
+		 * Ugly problem: If we're using devgen, make sure we're
+		 * walking the directory itself, represented by the first
+		 * entry in the table, and not trying to step into a sub-
+		 * directory of the table, e.g. /net/net. Devgen itself
+		 * should take care of the problem, but it doesn't have
+		 * the necessary information (that we're doing a walk).
+		 */
+		if(gen==devgen && nc->qid.path!=tab[0].qid.path)
+			goto Notfound;
+		for(i=0;; i++) {
+			switch((*gen)(nc, n, tab, ntab, i, &dir)){
+			case -1:
+			Notfound:
+				if(j == 0)
+					error(Enonexist);
+				kstrcpy(up->errstr, Enonexist, ERRMAX);
+				goto Done;
+			case 0:
+				continue;
+			case 1:
+				if(strcmp(n, dir.name) == 0){
+					nc->qid = dir.qid;
+					goto Accept;
+				}
+				continue;
+			}
+		}
+	}
+	/*
+	 * We processed at least one name, so will return some data.
+	 * If we didn't process all nname entries succesfully, we drop
+	 * the cloned channel and return just the Qids of the walks.
+	 */
+Done:
+	poperror();
+	if(wq->nqid < nname){
+		if(alloc)
+			cclose(wq->clone);
+		wq->clone = nil;
+	}else if(wq->clone){
+		/* attach cloned channel to same device */
+		wq->clone->type = c->type;
+	}
+	return wq;
+}
+
+int
+devstat(Chan *c, uchar *db, int n, Dirtab *tab, int ntab, Devgen *gen)
+{
+	int i;
+	Dir dir;
+	char *p, *elem;
+
+	for(i=0;; i++)
+		switch((*gen)(c, nil, tab, ntab, i, &dir)){
+		case -1:
+			if(c->qid.type & QTDIR){
+				if(c->name == nil)
+					elem = "???";
+				else if(strcmp(c->name->s, "/") == 0)
+					elem = "/";
+				else
+					for(elem=p=c->name->s; *p; p++)
+						if(*p == '/')
+							elem = p+1;
+				devdir(c, c->qid, elem, 0, eve, DMDIR|0555, &dir);
+				n = convD2M(&dir, db, n);
+				if(n == 0)
+					error(Ebadarg);
+				return n;
+			}
+			print("devstat %C %llux\n", devtab[c->type]->dc, c->qid.path);
+
+			error(Enonexist);
+		case 0:
+			break;
+		case 1:
+			if(c->qid.path == dir.qid.path) {
+				if(c->flag&CMSG)
+					dir.mode |= DMMOUNT;
+				n = convD2M(&dir, db, n);
+				if(n == 0)
+					error(Ebadarg);
+				return n;
+			}
+			break;
+		}
+	error(Egreg);	/* not reached? */
+	return -1;
+}
+
+long
+devdirread(Chan *c, char *d, long n, Dirtab *tab, int ntab, Devgen *gen)
+{
+	long m, dsz;
+	struct{
+		Dir d;
+		char slop[100];
+	}dir;
+
+	for(m=0; m<n; c->dri++) {
+		switch((*gen)(c, nil, tab, ntab, c->dri, &dir.d)){
+		case -1:
+			return m;
+
+		case 0:
+			break;
+
+		case 1:
+			dsz = convD2M(&dir.d, (uchar*)d, n-m);
+			if(dsz <= BIT16SZ){	/* <= not < because this isn't stat; read is stuck */
+				if(m == 0)
+					error(Eshort);
+				return m;
+			}
+			m += dsz;
+			d += dsz;
+			break;
+		}
+	}
+
+	return m;
+}
+
+/*
+ * error(Eperm) if open permission not granted for up->user.
+ */
+void
+devpermcheck(char *fileuid, ulong perm, int omode)
+{
+	ulong t;
+	static int access[] = { 0400, 0200, 0600, 0100 };
+
+	if(strcmp(up->user, fileuid) == 0)
+		perm <<= 0;
+	else
+	if(strcmp(up->user, eve) == 0)
+		perm <<= 3;
+	else
+		perm <<= 6;
+
+	t = access[omode&3];
+	if((t&perm) != t)
+		error(Eperm);
+}
+
+Chan*
+devopen(Chan *c, int omode, Dirtab *tab, int ntab, Devgen *gen)
+{
+	int i;
+	Dir dir;
+
+	for(i=0;; i++) {
+		switch((*gen)(c, nil, tab, ntab, i, &dir)){
+		case -1:
+			goto Return;
+		case 0:
+			break;
+		case 1:
+			if(c->qid.path == dir.qid.path) {
+				devpermcheck(dir.uid, dir.mode, omode);
+				goto Return;
+			}
+			break;
+		}
+	}
+Return:
+	c->offset = 0;
+	if((c->qid.type&QTDIR) && omode!=OREAD)
+		error(Eperm);
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	return c;
+}
+
+void
+devcreate(Chan *c, char *name, int mode, ulong perm)
+{
+	USED(c);
+	USED(name);
+	USED(mode);
+	USED(perm);
+
+	error(Eperm);
+}
+
+Block*
+devbread(Chan *c, long n, ulong offset)
+{
+	Block *bp;
+
+	bp = allocb(n);
+	if(bp == 0)
+		error(Enomem);
+	if(waserror()) {
+		freeb(bp);
+		nexterror();
+	}
+	bp->wp += devtab[c->type]->read(c, bp->wp, n, offset);
+	poperror();
+	return bp;
+}
+
+long
+devbwrite(Chan *c, Block *bp, ulong offset)
+{
+	long n;
+
+	if(waserror()) {
+		freeb(bp);
+		nexterror();
+	}
+	n = devtab[c->type]->write(c, bp->rp, BLEN(bp), offset);
+	poperror();
+	freeb(bp);
+
+	return n;
+}
+
+void
+devremove(Chan *c)
+{
+	USED(c);
+	error(Eperm);
+}
+
+int
+devwstat(Chan *c, uchar *a, int n)
+{
+	USED(c);
+	USED(a);
+	USED(n);
+
+	error(Eperm);
+	return 0;
+}
+
+void
+devpower(int a)
+{
+	USED(a);
+	error(Eperm);
+}
+
+int
+devconfig(int a, char *b, DevConf *c)
+{
+	USED(a);
+	USED(b);
+	USED(c);
+	error(Eperm);
+	return 0;
+}
--- /dev/null
+++ b/kern/devcons.c
@@ -1,0 +1,1238 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include 	"keyboard.h"
+
+void	(*consdebug)(void) = nil;
+void	(*screenputs)(char*, int) = nil;
+
+Queue*	kbdq;			/* unprocessed console input */
+Queue*	lineq;			/* processed console input */
+Queue*	serialoq;		/* serial console output */
+Queue*	kprintoq;		/* console output, for /dev/kprint */
+ulong	kprintinuse;		/* test and set whether /dev/kprint is open */
+int	iprintscreenputs = 0;
+
+int	panicking;
+
+struct
+{
+	int exiting;
+	int machs;
+} active;
+
+static struct
+{
+	QLock lk;
+
+	int	raw;		/* true if we shouldn't process input */
+	int	ctl;		/* number of opens to the control file */
+	int	x;		/* index into line */
+	char	line[1024];	/* current input line */
+
+	int	count;
+	int	ctlpoff;
+
+	/* a place to save up characters at interrupt time before dumping them in the queue */
+	Lock	lockputc;
+	char	istage[1024];
+	char	*iw;
+	char	*ir;
+	char	*ie;
+} kbd = {
+	{ 0 },
+	0,
+	0,
+	0,
+	{ 0 },
+	0,
+	0,
+	{ 0 },
+	{ 0 },
+	kbd.istage,
+	kbd.istage,
+	kbd.istage + sizeof(kbd.istage),
+};
+
+char	*sysname;
+vlong	fasthz;
+
+static void	seedrand(void);
+static int	readtime(ulong, char*, int);
+static int	readbintime(char*, int);
+static int	writetime(char*, int);
+static int	writebintime(char*, int);
+
+enum
+{
+	CMreboot,
+	CMpanic,
+};
+
+Cmdtab rebootmsg[] =
+{
+	CMreboot,	"reboot",	0,
+	CMpanic,	"panic",	0,
+};
+
+int
+return0(void *v)
+{
+	return 0;
+}
+
+void
+printinit(void)
+{
+	lineq = qopen(2*1024, 0, nil, nil);
+	if(lineq == nil)
+		panic("printinit");
+	qnoblock(lineq, 1);
+
+	kbdq = qopen(4*1024, 0, 0, 0);
+	if(kbdq == nil)
+		panic("kbdinit");
+	qnoblock(kbdq, 1);
+}
+
+int
+consactive(void)
+{
+	if(serialoq)
+		return qlen(serialoq) > 0;
+	return 0;
+}
+
+void
+prflush(void)
+{
+/*
+	ulong now;
+
+	now = m->ticks;
+	while(consactive())
+		if(m->ticks - now >= HZ)
+			break;
+*/
+}
+
+/*
+ *   Print a string on the console.  Convert \n to \r\n for serial
+ *   line consoles.  Locking of the queues is left up to the screen
+ *   or uart code.  Multi-line messages to serial consoles may get
+ *   interspersed with other messages.
+ */
+static void
+putstrn0(char *str, int n, int usewrite)
+{
+	int m;
+	char *t;
+
+	/*
+	 *  if someone is reading /dev/kprint,
+	 *  put the message there.
+	 *  if not and there's an attached bit mapped display,
+	 *  put the message there.
+	 *
+	 *  if there's a serial line being used as a console,
+	 *  put the message there.
+	 */
+	if(kprintoq != nil && !qisclosed(kprintoq)){
+		if(usewrite)
+			qwrite(kprintoq, str, n);
+		else
+			qiwrite(kprintoq, str, n);
+	}else if(screenputs != nil)
+		screenputs(str, n);
+
+	if(serialoq == nil){
+		uartputs(str, n);
+		return;
+	}
+
+	while(n > 0) {
+		t = memchr(str, '\n', n);
+		if(t && !kbd.raw) {
+			m = t-str;
+			if(usewrite){
+				qwrite(serialoq, str, m);
+				qwrite(serialoq, "\r\n", 2);
+			} else {
+				qiwrite(serialoq, str, m);
+				qiwrite(serialoq, "\r\n", 2);
+			}
+			n -= m+1;
+			str = t+1;
+		} else {
+			if(usewrite)
+				qwrite(serialoq, str, n);
+			else
+				qiwrite(serialoq, str, n);
+			break;
+		}
+	}
+}
+
+void
+putstrn(char *str, int n)
+{
+	putstrn0(str, n, 0);
+}
+
+int noprint;
+
+int
+print(char *fmt, ...)
+{
+	int n;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	if(noprint)
+		return -1;
+
+	va_start(arg, fmt);
+	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	putstrn(buf, n);
+
+	return n;
+}
+
+int
+iprint(char *fmt, ...)
+{
+	int n, s;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	s = splhi();
+	va_start(arg, fmt);
+	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	if(screenputs != nil && iprintscreenputs)
+		screenputs(buf, n);
+	uartputs(buf, n);
+	splx(s);
+
+	return n;
+}
+
+void
+panic(char *fmt, ...)
+{
+	int n;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	kprintoq = nil;	/* don't try to write to /dev/kprint */
+
+	if(panicking)
+		for(;;);
+	panicking = 1;
+
+	splhi();
+	strcpy(buf, "panic: ");
+	va_start(arg, fmt);
+	n = vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	buf[n] = '\n';
+	uartputs(buf, n+1);
+	if(consdebug)
+		(*consdebug)();
+	spllo();
+	prflush();
+	putstrn(buf, n+1);
+	dumpstack();
+
+	exit(1);
+}
+
+int
+pprint(char *fmt, ...)
+{
+	int n;
+	Chan *c;
+	va_list arg;
+	char buf[2*PRINTSIZE];
+
+	if(up == nil || up->fgrp == nil)
+		return 0;
+
+	c = up->fgrp->fd[2];
+	if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR))
+		return 0;
+	n = sprint(buf, "%s %lud: ", up->text, up->pid);
+	va_start(arg, fmt);
+	n = vseprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+
+	if(waserror())
+		return 0;
+	devtab[c->type]->write(c, buf, n, c->offset);
+	poperror();
+
+	lock(&c->ref.lk);
+	c->offset += n;
+	unlock(&c->ref.lk);
+
+	return n;
+}
+
+static void
+echoscreen(char *buf, int n)
+{
+	char *e, *p;
+	char ebuf[128];
+	int x;
+
+	p = ebuf;
+	e = ebuf + sizeof(ebuf) - 4;
+	while(n-- > 0){
+		if(p >= e){
+			screenputs(ebuf, p - ebuf);
+			p = ebuf;
+		}
+		x = *buf++;
+		if(x == 0x15){
+			*p++ = '^';
+			*p++ = 'U';
+			*p++ = '\n';
+		} else
+			*p++ = x;
+	}
+	if(p != ebuf)
+		screenputs(ebuf, p - ebuf);
+}
+
+static void
+echoserialoq(char *buf, int n)
+{
+	char *e, *p;
+	char ebuf[128];
+	int x;
+
+	p = ebuf;
+	e = ebuf + sizeof(ebuf) - 4;
+	while(n-- > 0){
+		if(p >= e){
+			qiwrite(serialoq, ebuf, p - ebuf);
+			p = ebuf;
+		}
+		x = *buf++;
+		if(x == '\n'){
+			*p++ = '\r';
+			*p++ = '\n';
+		} else if(x == 0x15){
+			*p++ = '^';
+			*p++ = 'U';
+			*p++ = '\n';
+		} else
+			*p++ = x;
+	}
+	if(p != ebuf)
+		qiwrite(serialoq, ebuf, p - ebuf);
+}
+
+static void
+echo(char *buf, int n)
+{
+	static int ctrlt, pid;
+	extern ulong etext;
+	int x;
+	char *e, *p;
+
+	e = buf+n;
+	for(p = buf; p < e; p++){
+		switch(*p){
+		case 0x10:	/* ^P */
+			if(cpuserver && !kbd.ctlpoff){
+				active.exiting = 1;
+				return;
+			}
+			break;
+		case 0x14:	/* ^T */
+			ctrlt++;
+			if(ctrlt > 2)
+				ctrlt = 2;
+			continue;
+		}
+
+		if(ctrlt != 2)
+			continue;
+
+		/* ^T escapes */
+		ctrlt = 0;
+		switch(*p){
+		case 'S':
+			x = splhi();
+			dumpstack();
+			procdump();
+			splx(x);
+			return;
+		case 's':
+			dumpstack();
+			return;
+		case 'x':
+			xsummary();
+			ixsummary();
+			mallocsummary();
+			pagersummary();
+			return;
+		case 'd':
+			if(consdebug == nil)
+				consdebug = rdb;
+			else
+				consdebug = nil;
+			print("consdebug now 0x%p\n", consdebug);
+			return;
+		case 'D':
+			if(consdebug == nil)
+				consdebug = rdb;
+			consdebug();
+			return;
+		case 'p':
+			x = spllo();
+			procdump();
+			splx(x);
+			return;
+		case 'q':
+			scheddump();
+			return;
+		case 'k':
+			killbig();
+			return;
+		case 'r':
+			exit(0);
+			return;
+		}
+	}
+
+	qproduce(kbdq, buf, n);
+	if(kbd.raw)
+		return;
+	if(screenputs != nil)
+		echoscreen(buf, n);
+	if(serialoq)
+		echoserialoq(buf, n);
+}
+
+/*
+ *  Called by a uart interrupt for console input.
+ *
+ *  turn '\r' into '\n' before putting it into the queue.
+ */
+int
+kbdcr2nl(Queue *q, int ch)
+{
+	char *next;
+
+	USED(q);
+	ilock(&kbd.lockputc);		/* just a mutex */
+	if(ch == '\r' && !kbd.raw)
+		ch = '\n';
+	next = kbd.iw+1;
+	if(next >= kbd.ie)
+		next = kbd.istage;
+	if(next != kbd.ir){
+		*kbd.iw = ch;
+		kbd.iw = next;
+	}
+	iunlock(&kbd.lockputc);
+	return 0;
+}
+static
+void
+_kbdputc(int c)
+{
+	Rune r;
+	char buf[UTFmax];
+	int n;
+
+	r = c;
+	n = runetochar(buf, &r);
+	if(n == 0)
+		return;
+	echo(buf, n);
+//	kbd.c = r;
+//	qproduce(kbdq, buf, n);
+}
+
+/* _kbdputc, but with compose translation */
+int
+kbdputc(Queue *q, int c)
+{
+	int	i;
+	static int collecting, nk;
+	static Rune kc[5];
+
+	 if(c == Kalt){
+		 collecting = 1;
+		 nk = 0;
+		 return;
+	 }
+
+	 if(!collecting){
+		 _kbdputc(c);
+		 return;
+	 }
+
+	kc[nk++] = c;
+	c = latin1(kc, nk);
+	if(c < -1)  /* need more keystrokes */
+		return;
+	if(c != -1) /* valid sequence */
+		_kbdputc(c);
+	else
+		for(i=0; i<nk; i++)
+		 	_kbdputc(kc[i]);
+	nk = 0;
+	collecting = 0;
+
+	return 0;
+}
+
+
+enum{
+	Qdir,
+	Qbintime,
+	Qcons,
+	Qconsctl,
+	Qcpunote,
+	Qcputime,
+	Qdrivers,
+	Qkprint,
+	Qhostdomain,
+	Qhostowner,
+	Qnull,
+	Qosversion,
+	Qpgrpid,
+	Qpid,
+	Qppid,
+	Qrandom,
+	Qreboot,
+	Qsecstore,
+	Qsnarf,
+	Qswap,
+	Qsysname,
+	Qsysstat,
+	Qtime,
+	Quser,
+	Qzero,
+};
+
+enum
+{
+	VLNUMSIZE=	22,
+};
+
+static Dirtab consdir[]={
+	".",	{Qdir, 0, QTDIR},	0,		DMDIR|0555,
+	"bintime",	{Qbintime},	24,		0664,
+	"cons",		{Qcons},	0,		0660,
+	"consctl",	{Qconsctl},	0,		0220,
+	"cpunote",	{Qcpunote},	0,		0444,
+	"cputime",	{Qcputime},	6*NUMSIZE,	0444,
+	"drivers",	{Qdrivers},	0,		0444,
+	"hostdomain",	{Qhostdomain},	DOMLEN,		0664,
+	"hostowner",	{Qhostowner},	0,	0664,
+	"kprint",		{Qkprint, 0, QTEXCL},	0,	DMEXCL|0440,
+	"null",		{Qnull},	0,		0666,
+	"osversion",	{Qosversion},	0,		0444,
+	"pgrpid",	{Qpgrpid},	NUMSIZE,	0444,
+	"pid",		{Qpid},		NUMSIZE,	0444,
+	"ppid",		{Qppid},	NUMSIZE,	0444,
+	"random",	{Qrandom},	0,		0444,
+	"reboot",	{Qreboot},	0,		0664,
+	"secstore",	{Qsecstore},	0,		0666,
+	"snarf",	{Qsnarf},		0,		0666,
+	"swap",		{Qswap},	0,		0664,
+	"sysname",	{Qsysname},	0,		0664,
+	"sysstat",	{Qsysstat},	0,		0666,
+	"time",		{Qtime},	NUMSIZE+3*VLNUMSIZE,	0664,
+	"user",		{Quser},	0,	0666,
+	"zero",		{Qzero},	0,		0444,
+};
+
+char secstorebuf[65536];
+Dirtab *secstoretab = &consdir[Qsecstore];
+Dirtab *snarftab = &consdir[Qsnarf];
+
+int
+readnum(ulong off, char *buf, ulong n, ulong val, int size)
+{
+	char tmp[64];
+
+	snprint(tmp, sizeof(tmp), "%*.0lud", size-1, val);
+	tmp[size-1] = ' ';
+	if(off >= size)
+		return 0;
+	if(off+n > size)
+		n = size-off;
+	memmove(buf, tmp+off, n);
+	return n;
+}
+
+int
+readstr(ulong off, char *buf, ulong n, char *str)
+{
+	int size;
+
+	size = strlen(str);
+	if(off >= size)
+		return 0;
+	if(off+n > size)
+		n = size-off;
+	memmove(buf, str+off, n);
+	return n;
+}
+
+static void
+consinit(void)
+{
+	todinit();
+	randominit();
+	/*
+	 * at 115200 baud, the 1024 char buffer takes 56 ms to process,
+	 * processing it every 22 ms should be fine
+	 */
+/*	addclock0link(kbdputcclock, 22); */
+}
+
+static Chan*
+consattach(char *spec)
+{
+	return devattach('c', spec);
+}
+
+static Walkqid*
+conswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
+}
+
+static int
+consstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, consdir, nelem(consdir), devgen);
+}
+
+static Chan*
+consopen(Chan *c, int omode)
+{
+	c->aux = nil;
+	c = devopen(c, omode, consdir, nelem(consdir), devgen);
+	switch((ulong)c->qid.path){
+	case Qconsctl:
+		qlock(&kbd.lk);
+		kbd.ctl++;
+		qunlock(&kbd.lk);
+		break;
+
+	case Qkprint:
+		if(tas(&kprintinuse) != 0){
+			c->flag &= ~COPEN;
+			error(Einuse);
+		}
+		if(kprintoq == nil){
+			kprintoq = qopen(8*1024, Qcoalesce, 0, 0);
+			if(kprintoq == nil){
+				c->flag &= ~COPEN;
+				error(Enomem);
+			}
+			qnoblock(kprintoq, 1);
+		}else
+			qreopen(kprintoq);
+		c->iounit = qiomaxatomic;
+		break;
+
+	case Qsecstore:
+		if(omode == ORDWR)
+			error(Eperm);
+		if(omode != OREAD)
+			memset(secstorebuf, 0, sizeof secstorebuf);
+		break;
+
+	case Qsnarf:
+		if(omode == ORDWR)
+			error(Eperm);
+		if(omode == OREAD)
+			c->aux = strdup("");
+		else
+			c->aux = mallocz(SnarfSize, 1);
+		break;
+	}
+	return c;
+}
+
+static void
+consclose(Chan *c)
+{
+	switch((ulong)c->qid.path){
+	/* last close of control file turns off raw */
+	case Qconsctl:
+		if(c->flag&COPEN){
+			qlock(&kbd.lk);
+			if(--kbd.ctl == 0)
+				kbd.raw = 0;
+			qunlock(&kbd.lk);
+		}
+		break;
+
+	/* close of kprint allows other opens */
+	case Qkprint:
+		if(c->flag & COPEN){
+			kprintinuse = 0;
+			qhangup(kprintoq, nil);
+		}
+		break;
+
+	case Qsnarf:
+		if(c->mode == OWRITE)
+			clipwrite(c->aux);
+		free(c->aux);
+		break;
+	}
+}
+
+static long
+consread(Chan *c, void *buf, long n, vlong off)
+{
+	ulong l;
+	char *b, *bp;
+	char tmp[128];		/* must be >= 6*NUMSIZE */
+	char *cbuf = buf;
+	int ch, i, k, id, eol;
+	vlong offset = off;
+
+	if(n <= 0)
+		return n;
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);
+
+	case Qcons:
+		qlock(&kbd.lk);
+		if(waserror()) {
+			qunlock(&kbd.lk);
+			nexterror();
+		}
+		if(kbd.raw) {
+			if(qcanread(lineq))
+				n = qread(lineq, buf, n);
+			else {
+				/* read as much as possible */
+				do {
+					i = qread(kbdq, cbuf, n);
+					cbuf += i;
+					n -= i;
+				} while (n>0 && qcanread(kbdq));
+				n = cbuf - (char*)buf;
+			}
+		} else {
+			while(!qcanread(lineq)) {
+				qread(kbdq, &kbd.line[kbd.x], 1);
+				ch = kbd.line[kbd.x];
+				eol = 0;
+				switch(ch){
+				case '\b':
+					if(kbd.x)
+						kbd.x--;
+					break;
+				case 0x15:
+					kbd.x = 0;
+					break;
+				case '\n':
+				case 0x04:
+					eol = 1;
+				default:
+					kbd.line[kbd.x++] = ch;
+					break;
+				}
+				if(kbd.x == sizeof(kbd.line) || eol){
+					if(ch == 0x04)
+						kbd.x--;
+					qwrite(lineq, kbd.line, kbd.x);
+					kbd.x = 0;
+				}
+			}
+			n = qread(lineq, buf, n);
+		}
+		qunlock(&kbd.lk);
+		poperror();
+		return n;
+
+	case Qcpunote:
+		sleep(&up->sleep, return0, nil);
+
+	case Qcputime:
+		return 0;
+
+	case Qkprint:
+		return qread(kprintoq, buf, n);
+
+	case Qpgrpid:
+		return readnum((ulong)offset, buf, n, up->pgrp->pgrpid, NUMSIZE);
+
+	case Qpid:
+		return readnum((ulong)offset, buf, n, up->pid, NUMSIZE);
+
+	case Qppid:
+		return readnum((ulong)offset, buf, n, up->parentpid, NUMSIZE);
+
+	case Qtime:
+		return readtime((ulong)offset, buf, n);
+
+	case Qbintime:
+		return readbintime(buf, n);
+
+	case Qhostowner:
+		return readstr((ulong)offset, buf, n, eve);
+
+	case Qhostdomain:
+		return readstr((ulong)offset, buf, n, hostdomain);
+
+	case Quser:
+		return readstr((ulong)offset, buf, n, up->user);
+
+	case Qnull:
+		return 0;
+
+	case Qsnarf:
+		if(offset == 0){
+			free(c->aux);
+			c->aux = clipread();
+		}
+		if(c->aux == nil)
+			return 0;
+		return readstr(offset, buf, n, c->aux);
+
+	case Qsecstore:
+		return readstr(offset, buf, n, secstorebuf);
+
+	case Qsysstat:
+		return 0;
+
+	case Qswap:
+		return 0;
+
+	case Qsysname:
+		if(sysname == nil)
+			return 0;
+		return readstr((ulong)offset, buf, n, sysname);
+
+	case Qrandom:
+		return randomread(buf, n);
+
+	case Qdrivers:
+		b = malloc(READSTR);
+		if(b == nil)
+			error(Enomem);
+		n = 0;
+		for(i = 0; devtab[i] != nil; i++)
+			n += snprint(b+n, READSTR-n, "#%C %s\n", devtab[i]->dc,  devtab[i]->name);
+		if(waserror()){
+			free(b);
+			nexterror();
+		}
+		n = readstr((ulong)offset, buf, n, b);
+		free(b);
+		poperror();
+		return n;
+
+	case Qzero:
+		memset(buf, 0, n);
+		return n;
+
+	case Qosversion:
+		snprint(tmp, sizeof tmp, "2000");
+		n = readstr((ulong)offset, buf, n, tmp);
+		return n;
+
+	default:
+		print("consread 0x%llux\n", c->qid.path);
+		error(Egreg);
+	}
+	return -1;		/* never reached */
+}
+
+static long
+conswrite(Chan *c, void *va, long n, vlong off)
+{
+	char buf[256];
+	long l, bp;
+	char *a = va;
+	int id, fd;
+	Chan *swc;
+	ulong offset = off;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	switch((ulong)c->qid.path){
+	case Qcons:
+		/*
+		 * Can't page fault in putstrn, so copy the data locally.
+		 */
+		l = n;
+		while(l > 0){
+			bp = l;
+			if(bp > sizeof buf)
+				bp = sizeof buf;
+			memmove(buf, a, bp);
+			putstrn0(buf, bp, 1);
+			a += bp;
+			l -= bp;
+		}
+		break;
+
+	case Qconsctl:
+		if(n >= sizeof(buf))
+			n = sizeof(buf)-1;
+		strncpy(buf, a, n);
+		buf[n] = 0;
+		for(a = buf; a;){
+			if(strncmp(a, "rawon", 5) == 0){
+				qlock(&kbd.lk);
+				if(kbd.x){
+					qwrite(kbdq, kbd.line, kbd.x);
+					kbd.x = 0;
+				}
+				kbd.raw = 1;
+				qunlock(&kbd.lk);
+			} else if(strncmp(a, "rawoff", 6) == 0){
+				qlock(&kbd.lk);
+				kbd.raw = 0;
+				kbd.x = 0;
+				qunlock(&kbd.lk);
+			} else if(strncmp(a, "ctlpon", 6) == 0){
+				kbd.ctlpoff = 0;
+			} else if(strncmp(a, "ctlpoff", 7) == 0){
+				kbd.ctlpoff = 1;
+			}
+			if(a = strchr(a, ' '))
+				a++;
+		}
+		break;
+
+	case Qtime:
+		if(!iseve())
+			error(Eperm);
+		return writetime(a, n);
+
+	case Qbintime:
+		if(!iseve())
+			error(Eperm);
+		return writebintime(a, n);
+
+	case Qhostowner:
+		return hostownerwrite(a, n);
+
+	case Qhostdomain:
+		return hostdomainwrite(a, n);
+
+	case Quser:
+		return userwrite(a, n);
+
+	case Qnull:
+		break;
+
+	case Qreboot:
+		if(!iseve())
+			error(Eperm);
+		cb = parsecmd(a, n);
+
+		if(waserror()) {
+			free(cb);
+			nexterror();
+		}
+		ct = lookupcmd(cb, rebootmsg, nelem(rebootmsg));
+		switch(ct->index) {
+		case CMreboot:
+			rebootcmd(cb->nf-1, cb->f+1);
+			break;
+		case CMpanic:
+			panic("/dev/reboot");
+		}
+		poperror();
+		free(cb);
+		break;
+
+	case Qsecstore:
+		if(offset >= sizeof secstorebuf || offset+n+1 >= sizeof secstorebuf)
+			error(Etoobig);
+		secstoretab->qid.vers++;
+		memmove(secstorebuf+offset, va, n);
+		return n;
+
+	case Qsnarf:
+		if(offset >= SnarfSize || offset+n >= SnarfSize)
+			error(Etoobig);
+		snarftab->qid.vers++;
+		memmove((uchar*)c->aux+offset, va, n);
+		return n;
+
+	case Qsysstat:
+		n = 0;
+		break;
+
+	case Qswap:
+		if(n >= sizeof buf)
+			error(Egreg);
+		memmove(buf, va, n);	/* so we can NUL-terminate */
+		buf[n] = 0;
+		/* start a pager if not already started */
+		if(strncmp(buf, "start", 5) == 0){
+			kickpager();
+			break;
+		}
+		if(cpuserver && !iseve())
+			error(Eperm);
+		if(buf[0]<'0' || '9'<buf[0])
+			error(Ebadarg);
+		fd = strtoul(buf, 0, 0);
+		swc = fdtochan(fd, -1, 1, 1);
+		setswapchan(swc);
+		break;
+
+	case Qsysname:
+		if(offset != 0)
+			error(Ebadarg);
+		if(n <= 0 || n >= sizeof buf)
+			error(Ebadarg);
+		strncpy(buf, a, n);
+		buf[n] = 0;
+		if(buf[n-1] == '\n')
+			buf[n-1] = 0;
+		kstrdup(&sysname, buf);
+		break;
+
+	default:
+		print("conswrite: 0x%llux\n", c->qid.path);
+		error(Egreg);
+	}
+	return n;
+}
+
+Dev consdevtab = {
+	'c',
+	"cons",
+
+	devreset,
+	consinit,
+	devshutdown,
+	consattach,
+	conswalk,
+	consstat,
+	consopen,
+	devcreate,
+	consclose,
+	consread,
+	devbread,
+	conswrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+static	ulong	randn;
+
+static void
+seedrand(void)
+{
+	randomread((void*)&randn, sizeof(randn));
+}
+
+// int
+// nrand(int n)
+// {
+// 	if(randn == 0)
+// 		seedrand();
+// 	randn = randn*1103515245 + 12345 + fastticks(0);
+// 	return (randn>>16) % n;
+// }
+
+int
+rand(void)
+{
+	nrand(1);
+	return randn;
+}
+
+/* static uvlong uvorder = 0x0001020304050607ULL; */
+static uvlong uvorder = (uvlong)0x0001020304050607;
+
+static uchar*
+le2vlong(vlong *to, uchar *f)
+{
+	uchar *t, *o;
+	int i;
+
+	t = (uchar*)to;
+	o = (uchar*)&uvorder;
+	for(i = 0; i < sizeof(vlong); i++)
+		t[o[i]] = f[i];
+	return f+sizeof(vlong);
+}
+
+static uchar*
+vlong2le(uchar *t, vlong from)
+{
+	uchar *f, *o;
+	int i;
+
+	f = (uchar*)&from;
+	o = (uchar*)&uvorder;
+	for(i = 0; i < sizeof(vlong); i++)
+		t[i] = f[o[i]];
+	return t+sizeof(vlong);
+}
+
+static long order = 0x00010203;
+
+static uchar*
+le2long(long *to, uchar *f)
+{
+	uchar *t, *o;
+	int i;
+
+	t = (uchar*)to;
+	o = (uchar*)&order;
+	for(i = 0; i < sizeof(long); i++)
+		t[o[i]] = f[i];
+	return f+sizeof(long);
+}
+
+static uchar*
+long2le(uchar *t, long from)
+{
+	uchar *f, *o;
+	int i;
+
+	f = (uchar*)&from;
+	o = (uchar*)&order;
+	for(i = 0; i < sizeof(long); i++)
+		t[i] = f[o[i]];
+	return t+sizeof(long);
+}
+
+char *Ebadtimectl = "bad time control";
+
+/*
+ *  like the old #c/time but with added info.  Return
+ *
+ *	secs	nanosecs	fastticks	fasthz
+ */
+static int
+readtime(ulong off, char *buf, int n)
+{
+	vlong	nsec, ticks;
+	long sec;
+	char str[7*NUMSIZE];
+
+	nsec = todget(&ticks);
+	if(fasthz == (vlong)0)
+		fastticks((uvlong*)&fasthz);
+	sec = nsec/((uvlong) 1000000000);
+	snprint(str, sizeof(str), "%*.0lud %*.0llud %*.0llud %*.0llud ",
+		NUMSIZE-1, sec,
+		VLNUMSIZE-1, nsec,
+		VLNUMSIZE-1, ticks,
+		VLNUMSIZE-1, fasthz);
+	return readstr(off, buf, n, str);
+}
+
+/*
+ *  set the time in seconds
+ */
+static int
+writetime(char *buf, int n)
+{
+	char b[13];
+	long i;
+	vlong now;
+
+	if(n >= sizeof(b))
+		error(Ebadtimectl);
+	strncpy(b, buf, n);
+	b[n] = 0;
+	i = strtol(b, 0, 0);
+	if(i <= 0)
+		error(Ebadtimectl);
+	now = i*((vlong) 1000000000);
+	todset(now, 0, 0);
+	return n;
+}
+
+/*
+ *  read binary time info.  all numbers are little endian.
+ *  ticks and nsec are syncronized.
+ */
+static int
+readbintime(char *buf, int n)
+{
+	int i;
+	vlong nsec, ticks;
+	uchar *b = (uchar*)buf;
+
+	i = 0;
+	if(fasthz == (vlong)0)
+		fastticks((uvlong*)&fasthz);
+	nsec = todget(&ticks);
+	if(n >= 3*sizeof(uvlong)){
+		vlong2le(b+2*sizeof(uvlong), fasthz);
+		i += sizeof(uvlong);
+	}
+	if(n >= 2*sizeof(uvlong)){
+		vlong2le(b+sizeof(uvlong), ticks);
+		i += sizeof(uvlong);
+	}
+	if(n >= 8){
+		vlong2le(b, nsec);
+		i += sizeof(vlong);
+	}
+	return i;
+}
+
+/*
+ *  set any of the following
+ *	- time in nsec
+ *	- nsec trim applied over some seconds
+ *	- clock frequency
+ */
+static int
+writebintime(char *buf, int n)
+{
+	uchar *p;
+	vlong delta;
+	long period;
+
+	n--;
+	p = (uchar*)buf + 1;
+	switch(*buf){
+	case 'n':
+		if(n < sizeof(vlong))
+			error(Ebadtimectl);
+		le2vlong(&delta, p);
+		todset(delta, 0, 0);
+		break;
+	case 'd':
+		if(n < sizeof(vlong)+sizeof(long))
+			error(Ebadtimectl);
+		p = le2vlong(&delta, p);
+		le2long(&period, p);
+		todset(-1, delta, period);
+		break;
+	case 'f':
+		if(n < sizeof(uvlong))
+			error(Ebadtimectl);
+		le2vlong(&fasthz, p);
+		todsetfreq(fasthz);
+		break;
+	}
+	return n;
+}
+
+
--- /dev/null
+++ b/kern/devdraw.c
@@ -1,0 +1,2100 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#define	Image	IMAGE
+#include	<draw.h>
+#include	<memdraw.h>
+#include	<memlayer.h>
+#include	<cursor.h>
+#include	"screen.h"
+
+enum
+{
+	Qtopdir		= 0,
+	Qnew,
+	Q3rd,
+	Q2nd,
+	Qcolormap,
+	Qctl,
+	Qdata,
+	Qrefresh,
+};
+
+/*
+ * Qid path is:
+ *	 4 bits of file type (qids above)
+ *	24 bits of mux slot number +1; 0 means not attached to client
+ */
+#define	QSHIFT	4	/* location in qid of client # */
+
+#define	QID(q)		((((ulong)(q).path)&0x0000000F)>>0)
+#define	CLIENTPATH(q)	((((ulong)q)&0x7FFFFFF0)>>QSHIFT)
+#define	CLIENT(q)	CLIENTPATH((q).path)
+
+#define	NHASH		(1<<5)
+#define	HASHMASK	(NHASH-1)
+#define	IOUNIT	(64*1024)
+
+typedef struct Client Client;
+typedef struct Draw Draw;
+typedef struct DImage DImage;
+typedef struct DScreen DScreen;
+typedef struct CScreen CScreen;
+typedef struct FChar FChar;
+typedef struct Refresh Refresh;
+typedef struct Refx Refx;
+typedef struct DName DName;
+
+ulong blanktime = 30;	/* in minutes; a half hour */
+
+struct Draw
+{
+	QLock	lk;
+	int		clientid;
+	int		nclient;
+	Client**	client;
+	int		nname;
+	DName*	name;
+	int		vers;
+	int		softscreen;
+	int		blanked;	/* screen turned off */
+	ulong		blanktime;	/* time of last operation */
+	ulong		savemap[3*256];
+};
+
+struct Client
+{
+	Ref		r;
+	DImage*		dimage[NHASH];
+	CScreen*	cscreen;
+	Refresh*	refresh;
+	Rendez		refrend;
+	uchar*		readdata;
+	int		nreaddata;
+	int		busy;
+	int		clientid;
+	int		slot;
+	int		refreshme;
+	int		infoid;
+	int		op;
+};
+
+struct Refresh
+{
+	DImage*		dimage;
+	Rectangle	r;
+	Refresh*	next;
+};
+
+struct Refx
+{
+	Client*		client;
+	DImage*		dimage;
+};
+
+struct DName
+{
+	char			*name;
+	Client	*client;
+	DImage*		dimage;
+	int			vers;
+};
+
+struct FChar
+{
+	int		minx;	/* left edge of bits */
+	int		maxx;	/* right edge of bits */
+	uchar		miny;	/* first non-zero scan-line */
+	uchar		maxy;	/* last non-zero scan-line + 1 */
+	schar		left;	/* offset of baseline */
+	uchar		width;	/* width of baseline */
+};
+
+/*
+ * Reference counts in DImages:
+ *	one per open by original client
+ *	one per screen image or fill
+ * 	one per image derived from this one by name
+ */
+struct DImage
+{
+	int		id;
+	int		ref;
+	char		*name;
+	int		vers;
+	Memimage*	image;
+	int		ascent;
+	int		nfchar;
+	FChar*		fchar;
+	DScreen*	dscreen;	/* 0 if not a window */
+	DImage*	fromname;	/* image this one is derived from, by name */
+	DImage*		next;
+};
+
+struct CScreen
+{
+	DScreen*	dscreen;
+	CScreen*	next;
+};
+
+struct DScreen
+{
+	int		id;
+	int		public;
+	int		ref;
+	DImage	*dimage;
+	DImage	*dfill;
+	Memscreen*	screen;
+	Client*		owner;
+	DScreen*	next;
+};
+
+static	Draw		sdraw;
+static	Memimage	*screenimage;
+static	Memdata	screendata;
+static	Rectangle	flushrect;
+static	int		waste;
+static	DScreen*	dscreen;
+extern	void		flushmemscreen(Rectangle);
+	void		drawmesg(Client*, void*, int);
+	void		drawuninstall(Client*, int);
+	void		drawfreedimage(DImage*);
+	Client*		drawclientofpath(ulong);
+
+static	char Enodrawimage[] =	"unknown id for draw image";
+static	char Enodrawscreen[] =	"unknown id for draw screen";
+static	char Eshortdraw[] =	"short draw message";
+static	char Eshortread[] =	"draw read too short";
+static	char Eimageexists[] =	"image id in use";
+static	char Escreenexists[] =	"screen id in use";
+static	char Edrawmem[] =	"image memory allocation failed";
+static	char Ereadoutside[] =	"readimage outside image";
+static	char Ewriteoutside[] =	"writeimage outside image";
+static	char Enotfont[] =	"image not a font";
+static	char Eindex[] =		"character index out of range";
+static	char Enoclient[] =	"no such draw client";
+static	char Edepth[] =	"image has bad depth";
+static	char Enameused[] =	"image name in use";
+static	char Enoname[] =	"no image with that name";
+static	char Eoldname[] =	"named image no longer valid";
+static	char Enamed[] = 	"image already has name";
+static	char Ewrongname[] = 	"wrong name for image";
+
+static int
+drawgen(Chan *c, char *name, Dirtab *dt, int ndt, int s, Dir *dp)
+{
+	int t;
+	Qid q;
+	ulong path;
+	Client *cl;
+
+	USED(name);
+	USED(dt);
+	USED(ndt);
+
+	q.vers = 0;
+
+	if(s == DEVDOTDOT){
+		switch(QID(c->qid)){
+		case Qtopdir:
+		case Q2nd:
+			mkqid(&q, Qtopdir, 0, QTDIR);
+			devdir(c, q, "#i", 0, eve, 0500, dp);
+			break;
+		case Q3rd:
+			cl = drawclientofpath(c->qid.path);
+			if(cl == nil)
+				strcpy(up->genbuf, "??");
+			else
+				sprint(up->genbuf, "%d", cl->clientid);
+			mkqid(&q, Q2nd, 0, QTDIR);
+			devdir(c, q, up->genbuf, 0, eve, 0500, dp);
+			break;
+		default:
+			panic("drawwalk %llux", c->qid.path);
+		}
+		return 1;
+	}
+
+	/*
+	 * Top level directory contains the name of the device.
+	 */
+	t = QID(c->qid);
+	if(t == Qtopdir){
+		switch(s){
+		case 0:
+			mkqid(&q, Q2nd, 0, QTDIR);
+			devdir(c, q, "draw", 0, eve, 0555, dp);
+			break;
+		default:
+			return -1;
+		}
+		return 1;
+	}
+
+	/*
+	 * Second level contains "new" plus all the clients.
+	 */
+	if(t == Q2nd || t == Qnew){
+		if(s == 0){
+			mkqid(&q, Qnew, 0, QTFILE);
+			devdir(c, q, "new", 0, eve, 0666, dp);
+		}
+		else if(s <= sdraw.nclient){
+			cl = sdraw.client[s-1];
+			if(cl == 0)
+				return 0;
+			sprint(up->genbuf, "%d", cl->clientid);
+			mkqid(&q, (s<<QSHIFT)|Q3rd, 0, QTDIR);
+			devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+			return 1;
+		}
+		else
+			return -1;
+		return 1;
+	}
+
+	/*
+	 * Third level.
+	 */
+	path = c->qid.path&~((1<<QSHIFT)-1);	/* slot component */
+	q.vers = c->qid.vers;
+	q.type = QTFILE;
+	switch(s){
+	case 0:
+		q.path = path|Qcolormap;
+		devdir(c, q, "colormap", 0, eve, 0600, dp);
+		break;
+	case 1:
+		q.path = path|Qctl;
+		devdir(c, q, "ctl", 0, eve, 0600, dp);
+		break;
+	case 2:
+		q.path = path|Qdata;
+		devdir(c, q, "data", 0, eve, 0600, dp);
+		break;
+	case 3:
+		q.path = path|Qrefresh;
+		devdir(c, q, "refresh", 0, eve, 0400, dp);
+		break;
+	default:
+		return -1;
+	}
+	return 1;
+}
+
+static
+int
+drawrefactive(void *a)
+{
+	Client *c;
+
+	c = a;
+	return c->refreshme || c->refresh!=0;
+}
+
+static
+void
+drawrefreshscreen(DImage *l, Client *client)
+{
+	while(l != nil && l->dscreen == nil)
+		l = l->fromname;
+	if(l != nil && l->dscreen->owner != client)
+		l->dscreen->owner->refreshme = 1;
+}
+
+static
+void
+drawrefresh(Memimage *m, Rectangle r, void *v)
+{
+	Refx *x;
+	DImage *d;
+	Client *c;
+	Refresh *ref;
+
+	USED(m);
+
+	if(v == 0)
+		return;
+	x = v;
+	c = x->client;
+	d = x->dimage;
+	for(ref=c->refresh; ref; ref=ref->next)
+		if(ref->dimage == d){
+			combinerect(&ref->r, r);
+			return;
+		}
+	ref = malloc(sizeof(Refresh));
+	if(ref){
+		ref->dimage = d;
+		ref->r = r;
+		ref->next = c->refresh;
+		c->refresh = ref;
+	}
+}
+
+static void
+addflush(Rectangle r)
+{
+	int abb, ar, anbb;
+	Rectangle nbb;
+
+	if(sdraw.softscreen==0 || !rectclip(&r, screenimage->r))
+		return;
+
+	if(flushrect.min.x >= flushrect.max.x){
+		flushrect = r;
+		waste = 0;
+		return;
+	}
+	nbb = flushrect;
+	combinerect(&nbb, r);
+	ar = Dx(r)*Dy(r);
+	abb = Dx(flushrect)*Dy(flushrect);
+	anbb = Dx(nbb)*Dy(nbb);
+	/*
+	 * Area of new waste is area of new bb minus area of old bb,
+	 * less the area of the new segment, which we assume is not waste.
+	 * This could be negative, but that's OK.
+	 */
+	waste += anbb-abb - ar;
+	if(waste < 0)
+		waste = 0;
+	/*
+	 * absorb if:
+	 *	total area is small
+	 *	waste is less than half total area
+	 * 	rectangles touch
+	 */
+	if(anbb<=1024 || waste*2<anbb || rectXrect(flushrect, r)){
+		flushrect = nbb;
+		return;
+	}
+	/* emit current state */
+	if(flushrect.min.x < flushrect.max.x)
+		flushmemscreen(flushrect);
+	flushrect = r;
+	waste = 0;
+}
+
+static
+void
+dstflush(int dstid, Memimage *dst, Rectangle r)
+{
+	Memlayer *l;
+
+	if(dstid == 0){
+		combinerect(&flushrect, r);
+		return;
+	}
+	/* how can this happen? -rsc, dec 12 2002 */
+	if(dst == 0){
+		print("nil dstflush\n");
+		return;
+	}
+	l = dst->layer;
+	if(l == nil)
+		return;
+	do{
+		if(l->screen->image->data != screenimage->data)
+			return;
+		r = rectaddpt(r, l->delta);
+		l = l->screen->image->layer;
+	}while(l);
+	addflush(r);
+}
+
+void
+drawflush(void)
+{
+	if(flushrect.min.x < flushrect.max.x)
+		flushmemscreen(flushrect);
+	flushrect = Rect(10000, 10000, -10000, -10000);
+}
+
+void
+drawflushr(Rectangle r)
+{
+	qlock(&sdraw.lk);
+	flushmemscreen(r);
+	qunlock(&sdraw.lk);
+}
+
+static
+int
+drawcmp(char *a, char *b, int n)
+{
+	if(strlen(a) != n)
+		return 1;
+	return memcmp(a, b, n);
+}
+
+DName*
+drawlookupname(int n, char *str)
+{
+	DName *name, *ename;
+
+	name = sdraw.name;
+	ename = &name[sdraw.nname];
+	for(; name<ename; name++)
+		if(drawcmp(name->name, str, n) == 0)
+			return name;
+	return 0;
+}
+
+int
+drawgoodname(DImage *d)
+{
+	DName *n;
+
+	/* if window, validate the screen's own images */
+	if(d->dscreen)
+		if(drawgoodname(d->dscreen->dimage) == 0
+		|| drawgoodname(d->dscreen->dfill) == 0)
+			return 0;
+	if(d->name == nil)
+		return 1;
+	n = drawlookupname(strlen(d->name), d->name);
+	if(n==nil || n->vers!=d->vers)
+		return 0;
+	return 1;
+}
+
+DImage*
+drawlookup(Client *client, int id, int checkname)
+{
+	DImage *d;
+
+	d = client->dimage[id&HASHMASK];
+	while(d){
+		if(d->id == id){
+			if(checkname && !drawgoodname(d))
+				error(Eoldname);
+			return d;
+		}
+		d = d->next;
+	}
+	return 0;
+}
+
+DScreen*
+drawlookupdscreen(int id)
+{
+	DScreen *s;
+
+	s = dscreen;
+	while(s){
+		if(s->id == id)
+			return s;
+		s = s->next;
+	}
+	return 0;
+}
+
+DScreen*
+drawlookupscreen(Client *client, int id, CScreen **cs)
+{
+	CScreen *s;
+
+	s = client->cscreen;
+	while(s){
+		if(s->dscreen->id == id){
+			*cs = s;
+			return s->dscreen;
+		}
+		s = s->next;
+	}
+	error(Enodrawscreen);
+	return 0;
+}
+
+Memimage*
+drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen)
+{
+	DImage *d;
+
+	d = malloc(sizeof(DImage));
+	if(d == 0)
+		return 0;
+	d->id = id;
+	d->ref = 1;
+	d->name = 0;
+	d->vers = 0;
+	d->image = i;
+	d->nfchar = 0;
+	d->fchar = 0;
+	d->fromname = 0;
+	d->dscreen = dscreen;
+	d->next = client->dimage[id&HASHMASK];
+	client->dimage[id&HASHMASK] = d;
+	return i;
+}
+
+Memscreen*
+drawinstallscreen(Client *client, DScreen *d, int id, DImage *dimage, DImage *dfill, int public)
+{
+	Memscreen *s;
+	CScreen *c;
+
+	c = malloc(sizeof(CScreen));
+	if(dimage && dimage->image && dimage->image->chan == 0)
+		panic("bad image %p in drawinstallscreen", dimage->image);
+
+	if(c == 0)
+		return 0;
+	if(d == 0){
+		d = malloc(sizeof(DScreen));
+		if(d == 0){
+			free(c);
+			return 0;
+		}
+		s = malloc(sizeof(Memscreen));
+		if(s == 0){
+			free(c);
+			free(d);
+			return 0;
+		}
+		s->frontmost = 0;
+		s->rearmost = 0;
+		d->dimage = dimage;
+		if(dimage){
+			s->image = dimage->image;
+			dimage->ref++;
+		}
+		d->dfill = dfill;
+		if(dfill){
+			s->fill = dfill->image;
+			dfill->ref++;
+		}
+		d->ref = 0;
+		d->id = id;
+		d->screen = s;
+		d->public = public;
+		d->next = dscreen;
+		d->owner = client;
+		dscreen = d;
+	}
+	c->dscreen = d;
+	d->ref++;
+	c->next = client->cscreen;
+	client->cscreen = c;
+	return d->screen;
+}
+
+void
+drawdelname(DName *name)
+{
+	int i;
+
+	i = name-sdraw.name;
+	memmove(name, name+1, (sdraw.nname-(i+1))*sizeof(DName));
+	sdraw.nname--;
+}
+
+void
+drawfreedscreen(DScreen *this)
+{
+	DScreen *ds, *next;
+
+	this->ref--;
+	if(this->ref < 0)
+		print("negative ref in drawfreedscreen\n");
+	if(this->ref > 0)
+		return;
+	ds = dscreen;
+	if(ds == this){
+		dscreen = this->next;
+		goto Found;
+	}
+	while(next = ds->next){	/* assign = */
+		if(next == this){
+			ds->next = this->next;
+			goto Found;
+		}
+		ds = next;
+	}
+	error(Enodrawimage);
+
+    Found:
+	if(this->dimage)
+		drawfreedimage(this->dimage);
+	if(this->dfill)
+		drawfreedimage(this->dfill);
+	free(this->screen);
+	free(this);
+}
+
+void
+drawfreedimage(DImage *dimage)
+{
+	int i;
+	Memimage *l;
+	DScreen *ds;
+
+	dimage->ref--;
+	if(dimage->ref < 0)
+		print("negative ref in drawfreedimage\n");
+	if(dimage->ref > 0)
+		return;
+
+	/* any names? */
+	for(i=0; i<sdraw.nname; )
+		if(sdraw.name[i].dimage == dimage)
+			drawdelname(sdraw.name+i);
+		else
+			i++;
+	if(dimage->fromname){	/* acquired by name; owned by someone else*/
+		drawfreedimage(dimage->fromname);
+		goto Return;
+	}
+	if(dimage->image == screenimage)	/* don't free the display */
+		goto Return;
+	ds = dimage->dscreen;
+	if(ds){
+		l = dimage->image;
+		if(l->data == screenimage->data)
+			addflush(l->layer->screenr);
+		if(l->layer->refreshfn == drawrefresh)	/* else true owner will clean up */
+			free(l->layer->refreshptr);
+		l->layer->refreshptr = nil;
+		if(drawgoodname(dimage))
+			memldelete(l);
+		else
+			memlfree(l);
+		drawfreedscreen(ds);
+	}else
+		freememimage(dimage->image);
+    Return:
+	free(dimage->fchar);
+	free(dimage);
+}
+
+void
+drawuninstallscreen(Client *client, CScreen *this)
+{
+	CScreen *cs, *next;
+
+	cs = client->cscreen;
+	if(cs == this){
+		client->cscreen = this->next;
+		drawfreedscreen(this->dscreen);
+		free(this);
+		return;
+	}
+	while(next = cs->next){	/* assign = */
+		if(next == this){
+			cs->next = this->next;
+			drawfreedscreen(this->dscreen);
+			free(this);
+			return;
+		}
+		cs = next;
+	}
+}
+
+void
+drawuninstall(Client *client, int id)
+{
+	DImage *d, *next;
+
+	d = client->dimage[id&HASHMASK];
+	if(d == 0)
+		error(Enodrawimage);
+	if(d->id == id){
+		client->dimage[id&HASHMASK] = d->next;
+		drawfreedimage(d);
+		return;
+	}
+	while(next = d->next){	/* assign = */
+		if(next->id == id){
+			d->next = next->next;
+			drawfreedimage(next);
+			return;
+		}
+		d = next;
+	}
+	error(Enodrawimage);
+}
+
+void
+drawaddname(Client *client, DImage *di, int n, char *str)
+{
+	DName *name, *ename, *new, *t;
+
+	name = sdraw.name;
+	ename = &name[sdraw.nname];
+	for(; name<ename; name++)
+		if(drawcmp(name->name, str, n) == 0)
+			error(Enameused);
+	t = smalloc((sdraw.nname+1)*sizeof(DName));
+	memmove(t, sdraw.name, sdraw.nname*sizeof(DName));
+	free(sdraw.name);
+	sdraw.name = t;
+	new = &sdraw.name[sdraw.nname++];
+	new->name = smalloc(n+1);
+	memmove(new->name, str, n);
+	new->name[n] = 0;
+	new->dimage = di;
+	new->client = client;
+	new->vers = ++sdraw.vers;
+}
+
+Client*
+drawnewclient(void)
+{
+	Client *cl, **cp;
+	int i;
+
+	for(i=0; i<sdraw.nclient; i++){
+		cl = sdraw.client[i];
+		if(cl == 0)
+			break;
+	}
+	if(i == sdraw.nclient){
+		cp = malloc((sdraw.nclient+1)*sizeof(Client*));
+		if(cp == 0)
+			return 0;
+		memmove(cp, sdraw.client, sdraw.nclient*sizeof(Client*));
+		free(sdraw.client);
+		sdraw.client = cp;
+		sdraw.nclient++;
+		cp[i] = 0;
+	}
+	cl = malloc(sizeof(Client));
+	if(cl == 0)
+		return 0;
+	memset(cl, 0, sizeof(Client));
+	cl->slot = i;
+	cl->clientid = ++sdraw.clientid;
+	cl->op = SoverD;
+	sdraw.client[i] = cl;
+	return cl;
+}
+
+static int
+drawclientop(Client *cl)
+{
+	int op;
+
+	op = cl->op;
+	cl->op = SoverD;
+	return op;
+}
+
+int
+drawhasclients(void)
+{
+	/*
+	 * if draw has ever been used, we can't resize the frame buffer,
+	 * even if all clients have exited (nclients is cumulative); it's too
+	 * hard to make work.
+	 */
+	return sdraw.nclient != 0;
+}
+
+Client*
+drawclientofpath(ulong path)
+{
+	Client *cl;
+	int slot;
+
+	slot = CLIENTPATH(path);
+	if(slot == 0)
+		return nil;
+	cl = sdraw.client[slot-1];
+	if(cl==0 || cl->clientid==0)
+		return nil;
+	return cl;
+}
+
+
+Client*
+drawclient(Chan *c)
+{
+	Client *client;
+
+	client = drawclientofpath(c->qid.path);
+	if(client == nil)
+		error(Enoclient);
+	return client;
+}
+
+Memimage*
+drawimage(Client *client, uchar *a)
+{
+	DImage *d;
+
+	d = drawlookup(client, BGLONG(a), 1);
+	if(d == nil)
+		error(Enodrawimage);
+	return d->image;
+}
+
+void
+drawrectangle(Rectangle *r, uchar *a)
+{
+	r->min.x = BGLONG(a+0*4);
+	r->min.y = BGLONG(a+1*4);
+	r->max.x = BGLONG(a+2*4);
+	r->max.y = BGLONG(a+3*4);
+}
+
+void
+drawpoint(Point *p, uchar *a)
+{
+	p->x = BGLONG(a+0*4);
+	p->y = BGLONG(a+1*4);
+}
+
+Point
+drawchar(Memimage *dst, Point p, Memimage *src, Point *sp, DImage *font, int index, int op)
+{
+	FChar *fc;
+	Rectangle r;
+	Point sp1;
+
+	fc = &font->fchar[index];
+	r.min.x = p.x+fc->left;
+	r.min.y = p.y-(font->ascent-fc->miny);
+	r.max.x = r.min.x+(fc->maxx-fc->minx);
+	r.max.y = r.min.y+(fc->maxy-fc->miny);
+	sp1.x = sp->x+fc->left;
+	sp1.y = sp->y+fc->miny;
+	memdraw(dst, r, src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+	p.x += fc->width;
+	sp->x += fc->width;
+	return p;
+}
+
+static int
+initscreenimage(void)
+{
+	int width, depth;
+	ulong chan;
+	void *X;
+	Rectangle r;
+
+	if(screenimage != nil)
+		return 1;
+
+	screendata.base = nil;
+	screendata.bdata = attachscreen(&r, &chan, &depth, &width, &sdraw.softscreen, &X);
+	if(screendata.bdata == nil && X == nil)
+		return 0;
+	screendata.ref = 1;
+
+	screenimage = allocmemimaged(r, chan, &screendata, X);
+	if(screenimage == nil){
+		/* RSC: BUG: detach screen */
+		return 0;
+	}
+
+	screenimage->width = width;
+	screenimage->clipr = r;
+	return 1;
+}
+
+void
+deletescreenimage(void)
+{
+	qlock(&sdraw.lk);
+	/* RSC: BUG: detach screen */
+	if(screenimage)
+		freememimage(screenimage);
+	screenimage = nil;
+	qunlock(&sdraw.lk);
+}
+
+static Chan*
+drawattach(char *spec)
+{
+	qlock(&sdraw.lk);
+	if(!initscreenimage()){
+		qunlock(&sdraw.lk);
+		error("no frame buffer");
+	}
+	qunlock(&sdraw.lk);
+	return devattach('i', spec);
+}
+
+static Walkqid*
+drawwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	if(screendata.bdata == nil)
+		error("no frame buffer");
+	return devwalk(c, nc, name, nname, 0, 0, drawgen);
+}
+
+static int
+drawstat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, 0, 0, drawgen);
+}
+
+static Chan*
+drawopen(Chan *c, int omode)
+{
+	Client *cl;
+
+	if(c->qid.type & QTDIR){
+		c = devopen(c, omode, 0, 0, drawgen);
+		c->iounit = IOUNIT;
+	}
+
+	qlock(&sdraw.lk);
+	if(waserror()){
+		qunlock(&sdraw.lk);
+		nexterror();
+	}
+
+	if(QID(c->qid) == Qnew){
+		cl = drawnewclient();
+		if(cl == 0)
+			error(Enodev);
+		c->qid.path = Qctl|((cl->slot+1)<<QSHIFT);
+	}
+
+	switch(QID(c->qid)){
+	case Qnew:
+		break;
+
+	case Qctl:
+		cl = drawclient(c);
+		if(cl->busy)
+			error(Einuse);
+		cl->busy = 1;
+		flushrect = Rect(10000, 10000, -10000, -10000);
+		drawinstall(cl, 0, screenimage, 0);
+		incref(&cl->r);
+		break;
+	case Qcolormap:
+	case Qdata:
+	case Qrefresh:
+		cl = drawclient(c);
+		incref(&cl->r);
+		break;
+	}
+	qunlock(&sdraw.lk);
+	poperror();
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	c->iounit = IOUNIT;
+	return c;
+}
+
+static void
+drawclose(Chan *c)
+{
+	int i;
+	DImage *d, **dp;
+	Client *cl;
+	Refresh *r;
+
+	if(QID(c->qid) < Qcolormap)	/* Qtopdir, Qnew, Q3rd, Q2nd have no client */
+		return;
+	qlock(&sdraw.lk);
+	if(waserror()){
+		qunlock(&sdraw.lk);
+		nexterror();
+	}
+
+	cl = drawclient(c);
+	if(QID(c->qid) == Qctl)
+		cl->busy = 0;
+	if((c->flag&COPEN) && (decref(&cl->r)==0)){
+		while(r = cl->refresh){	/* assign = */
+			cl->refresh = r->next;
+			free(r);
+		}
+		/* free names */
+		for(i=0; i<sdraw.nname; )
+			if(sdraw.name[i].client == cl)
+				drawdelname(sdraw.name+i);
+			else
+				i++;
+		while(cl->cscreen)
+			drawuninstallscreen(cl, cl->cscreen);
+		/* all screens are freed, so now we can free images */
+		dp = cl->dimage;
+		for(i=0; i<NHASH; i++){
+			while((d = *dp) != nil){
+				*dp = d->next;
+				drawfreedimage(d);
+			}
+			dp++;
+		}
+		sdraw.client[cl->slot] = 0;
+		drawflush();	/* to erase visible, now dead windows */
+		free(cl);
+	}
+	qunlock(&sdraw.lk);
+	poperror();
+}
+
+long
+drawread(Chan *c, void *a, long n, vlong off)
+{
+	int index, m;
+	ulong red, green, blue;
+	Client *cl;
+	uchar *p;
+	Refresh *r;
+	DImage *di;
+	Memimage *i;
+	ulong offset = off;
+	char buf[16];
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, a, n, 0, 0, drawgen);
+	cl = drawclient(c);
+	qlock(&sdraw.lk);
+	if(waserror()){
+		qunlock(&sdraw.lk);
+		nexterror();
+	}
+	switch(QID(c->qid)){
+	case Qctl:
+		if(n < 12*12)
+			error(Eshortread);
+		if(cl->infoid < 0)
+			error(Enodrawimage);
+		if(cl->infoid == 0){
+			i = screenimage;
+			if(i == nil)
+				error(Enodrawimage);
+		}else{
+			di = drawlookup(cl, cl->infoid, 1);
+			if(di == nil)
+				error(Enodrawimage);
+			i = di->image;
+		}
+		n = sprint(a, "%11d %11d %11s %11d %11d %11d %11d %11d %11d %11d %11d %11d ",
+			cl->clientid, cl->infoid, chantostr(buf, i->chan), (i->flags&Frepl)==Frepl,
+			i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y,
+			i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
+		cl->infoid = -1;
+		break;
+
+	case Qcolormap:
+		drawactive(1);	/* to restore map from backup */
+		p = malloc(4*12*256+1);
+		if(p == 0)
+			error(Enomem);
+		m = 0;
+		for(index = 0; index < 256; index++){
+			getcolor(index, &red, &green, &blue);
+			m += sprint((char*)p+m, "%11d %11lud %11lud %11lud\n", index, red>>24, green>>24, blue>>24);
+		}
+		n = readstr(offset, a, n, (char*)p);
+		free(p);
+		break;
+
+	case Qdata:
+		if(cl->readdata == nil)
+			error("no draw data");
+		if(n < cl->nreaddata)
+			error(Eshortread);
+		n = cl->nreaddata;
+		memmove(a, cl->readdata, cl->nreaddata);
+		free(cl->readdata);
+		cl->readdata = nil;
+		break;
+
+	case Qrefresh:
+		if(n < 5*4)
+			error(Ebadarg);
+		for(;;){
+			if(cl->refreshme || cl->refresh)
+				break;
+			qunlock(&sdraw.lk);
+			if(waserror()){
+				qlock(&sdraw.lk);	/* restore lock for waserror() above */
+				nexterror();
+			}
+			sleep(&cl->refrend, drawrefactive, cl);
+			poperror();
+			qlock(&sdraw.lk);
+		}
+		p = a;
+		while(cl->refresh && n>=5*4){
+			r = cl->refresh;
+			BPLONG(p+0*4, r->dimage->id);
+			BPLONG(p+1*4, r->r.min.x);
+			BPLONG(p+2*4, r->r.min.y);
+			BPLONG(p+3*4, r->r.max.x);
+			BPLONG(p+4*4, r->r.max.y);
+			cl->refresh = r->next;
+			free(r);
+			p += 5*4;
+			n -= 5*4;
+		}
+		cl->refreshme = 0;
+		n = p-(uchar*)a;
+	}
+	qunlock(&sdraw.lk);
+	poperror();
+	return n;
+}
+
+void
+drawwakeall(void)
+{
+	Client *cl;
+	int i;
+
+	for(i=0; i<sdraw.nclient; i++){
+		cl = sdraw.client[i];
+		if(cl && (cl->refreshme || cl->refresh))
+			wakeup(&cl->refrend);
+	}
+}
+
+static long
+drawwrite(Chan *c, void *a, long n, vlong offset)
+{
+	char buf[128], *fields[4], *q;
+	Client *cl;
+	int i, m, red, green, blue, x;
+
+	USED(offset);
+
+	if(c->qid.type & QTDIR)
+		error(Eisdir);
+	cl = drawclient(c);
+	qlock(&sdraw.lk);
+	if(waserror()){
+		drawwakeall();
+		qunlock(&sdraw.lk);
+		nexterror();
+	}
+	switch(QID(c->qid)){
+	case Qctl:
+		if(n != 4)
+			error("unknown draw control request");
+		cl->infoid = BGLONG((uchar*)a);
+		break;
+
+	case Qcolormap:
+		drawactive(1);	/* to restore map from backup */
+		m = n;
+		n = 0;
+		while(m > 0){
+			x = m;
+			if(x > sizeof(buf)-1)
+				x = sizeof(buf)-1;
+			q = memccpy(buf, a, '\n', x);
+			if(q == 0)
+				break;
+			i = q-buf;
+			n += i;
+			a = (char*)a + i;
+			m -= i;
+			*q = 0;
+			if(tokenize(buf, fields, nelem(fields)) != 4)
+				error(Ebadarg);
+			i = strtoul(fields[0], 0, 0);
+			red = strtoul(fields[1], 0, 0);
+			green = strtoul(fields[2], 0, 0);
+			blue = strtoul(fields[3], &q, 0);
+			if(fields[3] == q)
+				error(Ebadarg);
+			if(red>255 || green>255 || blue>255 || i<0 || i>255)
+				error(Ebadarg);
+			red |= red<<8;
+			red |= red<<16;
+			green |= green<<8;
+			green |= green<<16;
+			blue |= blue<<8;
+			blue |= blue<<16;
+			setcolor(i, red, green, blue);
+		}
+		break;
+
+	case Qdata:
+		drawmesg(cl, a, n);
+		drawwakeall();
+		break;
+
+	default:
+		error(Ebadusefd);
+	}
+	qunlock(&sdraw.lk);
+	poperror();
+	return n;
+}
+
+uchar*
+drawcoord(uchar *p, uchar *maxp, int oldx, int *newx)
+{
+	int b, x;
+
+	if(p >= maxp)
+		error(Eshortdraw);
+	b = *p++;
+	x = b & 0x7F;
+	if(b & 0x80){
+		if(p+1 >= maxp)
+			error(Eshortdraw);
+		x |= *p++ << 7;
+		x |= *p++ << 15;
+		if(x & (1<<22))
+			x |= ~0<<23;
+	}else{
+		if(b & 0x40)
+			x |= ~0<<7;
+		x += oldx;
+	}
+	*newx = x;
+	return p;
+}
+
+static void
+printmesg(char *fmt, uchar *a, int plsprnt)
+{
+	char buf[256];
+	char *p, *q;
+	int s;
+
+	if(1|| plsprnt==0){
+		SET(s);
+		SET(q);
+		SET(p);
+		USED(fmt);
+		USED(a);
+		USED(buf);
+		USED(p);
+		USED(q);
+		USED(s);
+		return;
+	}
+	q = buf;
+	*q++ = *a++;
+	for(p=fmt; *p; p++){
+		switch(*p){
+		case 'l':
+			q += sprint(q, " %ld", (long)BGLONG(a));
+			a += 4;
+			break;
+		case 'L':
+			q += sprint(q, " %.8lux", (ulong)BGLONG(a));
+			a += 4;
+			break;
+		case 'R':
+			q += sprint(q, " [%d %d %d %d]", BGLONG(a), BGLONG(a+4), BGLONG(a+8), BGLONG(a+12));
+			a += 16;
+			break;
+		case 'P':
+			q += sprint(q, " [%d %d]", BGLONG(a), BGLONG(a+4));
+			a += 8;
+			break;
+		case 'b':
+			q += sprint(q, " %d", *a++);
+			break;
+		case 's':
+			q += sprint(q, " %d", BGSHORT(a));
+			a += 2;
+			break;
+		case 'S':
+			q += sprint(q, " %.4ux", BGSHORT(a));
+			a += 2;
+			break;
+		}
+	}
+	*q++ = '\n';
+	*q = 0;
+	iprint("%.*s", (int)(q-buf), buf);
+}
+
+void
+drawmesg(Client *client, void *av, int n)
+{
+	int c, repl, m, y, dstid, scrnid, ni, ci, j, nw, e0, e1, op, ox, oy, oesize, esize, doflush;
+	uchar *u, *a, refresh;
+	char *fmt;
+	ulong value, chan;
+	Rectangle r, clipr;
+	Point p, q, *pp, sp;
+	Memimage *i, *dst, *src, *mask;
+	Memimage *l, **lp;
+	Memscreen *scrn;
+	DImage *font, *ll, *di, *ddst, *dsrc;
+	DName *dn;
+	DScreen *dscrn;
+	FChar *fc;
+	Refx *refx;
+	CScreen *cs;
+	Refreshfn reffn;
+
+	a = av;
+	m = 0;
+	fmt = nil;
+	if(waserror()){
+		if(fmt) printmesg(fmt, a, 1);
+	/*	iprint("error: %s\n", up->errstr);	*/
+		nexterror();
+	}
+	while((n-=m) > 0){
+		USED(fmt);
+		a += m;
+		switch(*a){
+		default:
+			error("bad draw command");
+		/* new allocate: 'b' id[4] screenid[4] refresh[1] chan[4] repl[1] R[4*4] clipR[4*4] rrggbbaa[4] */
+		case 'b':
+			printmesg(fmt="LLbLbRRL", a, 0);
+			m = 1+4+4+1+4+1+4*4+4*4+4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			scrnid = BGSHORT(a+5);
+			refresh = a[9];
+			chan = BGLONG(a+10);
+			repl = a[14];
+			drawrectangle(&r, a+15);
+			drawrectangle(&clipr, a+31);
+			value = BGLONG(a+47);
+			if(drawlookup(client, dstid, 0))
+				error(Eimageexists);
+			if(scrnid){
+				dscrn = drawlookupscreen(client, scrnid, &cs);
+				scrn = dscrn->screen;
+				if(repl || chan!=scrn->image->chan)
+					error("image parameters incompatible with screen");
+				reffn = nil;
+				switch(refresh){
+				case Refbackup:
+					break;
+				case Refnone:
+					reffn = memlnorefresh;
+					break;
+				case Refmesg:
+					reffn = drawrefresh;
+					break;
+				default:
+					error("unknown refresh method");
+				}
+				l = memlalloc(scrn, r, reffn, 0, value);
+				if(l == 0)
+					error(Edrawmem);
+				addflush(l->layer->screenr);
+				l->clipr = clipr;
+				rectclip(&l->clipr, r);
+				if(drawinstall(client, dstid, l, dscrn) == 0){
+					memldelete(l);
+					error(Edrawmem);
+				}
+				dscrn->ref++;
+				if(reffn){
+					refx = nil;
+					if(reffn == drawrefresh){
+						refx = malloc(sizeof(Refx));
+						if(refx == 0){
+							drawuninstall(client, dstid);
+							error(Edrawmem);
+						}
+						refx->client = client;
+						refx->dimage = drawlookup(client, dstid, 1);
+					}
+					memlsetrefresh(l, reffn, refx);
+				}
+				continue;
+			}
+			i = allocmemimage(r, chan);
+			if(i == 0)
+				error(Edrawmem);
+			if(repl)
+				i->flags |= Frepl;
+			i->clipr = clipr;
+			if(!repl)
+				rectclip(&i->clipr, r);
+			if(drawinstall(client, dstid, i, 0) == 0){
+				freememimage(i);
+				error(Edrawmem);
+			}
+			memfillcolor(i, value);
+			continue;
+
+		/* allocate screen: 'A' id[4] imageid[4] fillid[4] public[1] */
+		case 'A':
+			printmesg(fmt="LLLb", a, 1);
+			m = 1+4+4+4+1;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(dstid == 0)
+				error(Ebadarg);
+			if(drawlookupdscreen(dstid))
+				error(Escreenexists);
+			ddst = drawlookup(client, BGLONG(a+5), 1);
+			dsrc = drawlookup(client, BGLONG(a+9), 1);
+			if(ddst==0 || dsrc==0)
+				error(Enodrawimage);
+			if(drawinstallscreen(client, 0, dstid, ddst, dsrc, a[13]) == 0)
+				error(Edrawmem);
+			continue;
+
+		/* set repl and clip: 'c' dstid[4] repl[1] clipR[4*4] */
+		case 'c':
+			printmesg(fmt="LbR", a, 0);
+			m = 1+4+1+4*4;
+			if(n < m)
+				error(Eshortdraw);
+			ddst = drawlookup(client, BGLONG(a+1), 1);
+			if(ddst == nil)
+				error(Enodrawimage);
+			if(ddst->name)
+				error("can't change repl/clipr of shared image");
+			dst = ddst->image;
+			if(a[5])
+				dst->flags |= Frepl;
+			drawrectangle(&dst->clipr, a+6);
+			continue;
+
+		/* draw: 'd' dstid[4] srcid[4] maskid[4] R[4*4] P[2*4] P[2*4] */
+		case 'd':
+			printmesg(fmt="LLLRPP", a, 0);
+			m = 1+4+4+4+4*4+2*4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			src = drawimage(client, a+5);
+			mask = drawimage(client, a+9);
+			drawrectangle(&r, a+13);
+			drawpoint(&p, a+29);
+			drawpoint(&q, a+37);
+			op = drawclientop(client);
+			memdraw(dst, r, src, p, mask, q, op);
+			dstflush(dstid, dst, r);
+			continue;
+
+		/* toggle debugging: 'D' val[1] */
+		case 'D':
+			printmesg(fmt="b", a, 0);
+			m = 1+1;
+			if(n < m)
+				error(Eshortdraw);
+			drawdebug = a[1];
+			continue;
+
+		/* ellipse: 'e' dstid[4] srcid[4] center[2*4] a[4] b[4] thick[4] sp[2*4] alpha[4] phi[4]*/
+		case 'e':
+		case 'E':
+			printmesg(fmt="LLPlllPll", a, 0);
+			m = 1+4+4+2*4+4+4+4+2*4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			src = drawimage(client, a+5);
+			drawpoint(&p, a+9);
+			e0 = BGLONG(a+17);
+			e1 = BGLONG(a+21);
+			if(e0<0 || e1<0)
+				error("invalid ellipse semidiameter");
+			j = BGLONG(a+25);
+			if(j < 0)
+				error("negative ellipse thickness");
+			drawpoint(&sp, a+29);
+			c = j;
+			if(*a == 'E')
+				c = -1;
+			ox = BGLONG(a+37);
+			oy = BGLONG(a+41);
+			op = drawclientop(client);
+			/* high bit indicates arc angles are present */
+			if(ox & (1<<31)){
+				if((ox & (1<<30)) == 0)
+					ox &= ~(1<<31);
+				memarc(dst, p, e0, e1, c, src, sp, ox, oy, op);
+			}else
+				memellipse(dst, p, e0, e1, c, src, sp, op);
+			dstflush(dstid, dst, Rect(p.x-e0-j, p.y-e1-j, p.x+e0+j+1, p.y+e1+j+1));
+			continue;
+
+		/* free: 'f' id[4] */
+		case 'f':
+			printmesg(fmt="L", a, 1);
+			m = 1+4;
+			if(n < m)
+				error(Eshortdraw);
+			ll = drawlookup(client, BGLONG(a+1), 0);
+			if(ll && ll->dscreen && ll->dscreen->owner != client)
+				ll->dscreen->owner->refreshme = 1;
+			drawuninstall(client, BGLONG(a+1));
+			continue;
+
+		/* free screen: 'F' id[4] */
+		case 'F':
+			printmesg(fmt="L", a, 1);
+			m = 1+4;
+			if(n < m)
+				error(Eshortdraw);
+			drawlookupscreen(client, BGLONG(a+1), &cs);
+			drawuninstallscreen(client, cs);
+			continue;
+
+		/* initialize font: 'i' fontid[4] nchars[4] ascent[1] */
+		case 'i':
+			printmesg(fmt="Llb", a, 1);
+			m = 1+4+4+1;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(dstid == 0)
+				error("can't use display as font");
+			font = drawlookup(client, dstid, 1);
+			if(font == 0)
+				error(Enodrawimage);
+			if(font->image->layer)
+				error("can't use window as font");
+			ni = BGLONG(a+5);
+			if(ni<=0 || ni>4096)
+				error("bad font size (4096 chars max)");
+			free(font->fchar);	/* should we complain if non-zero? */
+			font->fchar = malloc(ni*sizeof(FChar));
+			if(font->fchar == 0)
+				error("no memory for font");
+			memset(font->fchar, 0, ni*sizeof(FChar));
+			font->nfchar = ni;
+			font->ascent = a[9];
+			continue;
+
+		/* load character: 'l' fontid[4] srcid[4] index[2] R[4*4] P[2*4] left[1] width[1] */
+		case 'l':
+			printmesg(fmt="LLSRPbb", a, 0);
+			m = 1+4+4+2+4*4+2*4+1+1;
+			if(n < m)
+				error(Eshortdraw);
+			font = drawlookup(client, BGLONG(a+1), 1);
+			if(font == 0)
+				error(Enodrawimage);
+			if(font->nfchar == 0)
+				error(Enotfont);
+			src = drawimage(client, a+5);
+			ci = BGSHORT(a+9);
+			if(ci >= font->nfchar)
+				error(Eindex);
+			drawrectangle(&r, a+11);
+			drawpoint(&p, a+27);
+			memdraw(font->image, r, src, p, memopaque, p, S);
+			fc = &font->fchar[ci];
+			fc->minx = r.min.x;
+			fc->maxx = r.max.x;
+			fc->miny = r.min.y;
+			fc->maxy = r.max.y;
+			fc->left = a[35];
+			fc->width = a[36];
+			continue;
+
+		/* draw line: 'L' dstid[4] p0[2*4] p1[2*4] end0[4] end1[4] radius[4] srcid[4] sp[2*4] */
+		case 'L':
+			printmesg(fmt="LPPlllLP", a, 0);
+			m = 1+4+2*4+2*4+4+4+4+4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			drawpoint(&p, a+5);
+			drawpoint(&q, a+13);
+			e0 = BGLONG(a+21);
+			e1 = BGLONG(a+25);
+			j = BGLONG(a+29);
+			if(j < 0)
+				error("negative line width");
+			src = drawimage(client, a+33);
+			drawpoint(&sp, a+37);
+			op = drawclientop(client);
+			memline(dst, p, q, e0, e1, j, src, sp, op);
+			/* avoid memlinebbox if possible */
+			if(dstid==0 || dst->layer!=nil){
+				/* BUG: this is terribly inefficient: update maximal containing rect*/
+				r = memlinebbox(p, q, e0, e1, j);
+				dstflush(dstid, dst, insetrect(r, -(1+1+j)));
+			}
+			continue;
+
+		/* create image mask: 'm' newid[4] id[4] */
+/*
+ *
+		case 'm':
+			printmesg("LL", a, 0);
+			m = 4+4;
+			if(n < m)
+				error(Eshortdraw);
+			break;
+ *
+ */
+
+		/* attach to a named image: 'n' dstid[4] j[1] name[j] */
+		case 'n':
+			printmesg(fmt="Lz", a, 0);
+			m = 1+4+1;
+			if(n < m)
+				error(Eshortdraw);
+			j = a[5];
+			if(j == 0)	/* give me a non-empty name please */
+				error(Eshortdraw);
+			m += j;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(drawlookup(client, dstid, 0))
+				error(Eimageexists);
+			dn = drawlookupname(j, (char*)a+6);
+			if(dn == nil)
+				error(Enoname);
+			if(drawinstall(client, dstid, dn->dimage->image, 0) == 0)
+				error(Edrawmem);
+			di = drawlookup(client, dstid, 0);
+			if(di == 0)
+				error("draw: can't happen");
+			di->vers = dn->vers;
+			di->name = smalloc(j+1);
+			di->fromname = dn->dimage;
+			di->fromname->ref++;
+			memmove(di->name, a+6, j);
+			di->name[j] = 0;
+			client->infoid = dstid;
+			continue;
+
+		/* name an image: 'N' dstid[4] in[1] j[1] name[j] */
+		case 'N':
+			printmesg(fmt="Lbz", a, 0);
+			m = 1+4+1+1;
+			if(n < m)
+				error(Eshortdraw);
+			c = a[5];
+			j = a[6];
+			if(j == 0)	/* give me a non-empty name please */
+				error(Eshortdraw);
+			m += j;
+			if(n < m)
+				error(Eshortdraw);
+			di = drawlookup(client, BGLONG(a+1), 0);
+			if(di == 0)
+				error(Enodrawimage);
+			if(di->name)
+				error(Enamed);
+			if(c)
+				drawaddname(client, di, j, (char*)a+7);
+			else{
+				dn = drawlookupname(j, (char*)a+7);
+				if(dn == nil)
+					error(Enoname);
+				if(dn->dimage != di)
+					error(Ewrongname);
+				drawdelname(dn);
+			}
+			continue;
+
+		/* position window: 'o' id[4] r.min [2*4] screenr.min [2*4] */
+		case 'o':
+			printmesg(fmt="LPP", a, 0);
+			m = 1+4+2*4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			if(dst->layer){
+				drawpoint(&p, a+5);
+				drawpoint(&q, a+13);
+				r = dst->layer->screenr;
+				ni = memlorigin(dst, p, q);
+				if(ni < 0)
+					error("image origin failed");
+				if(ni > 0){
+					addflush(r);
+					addflush(dst->layer->screenr);
+					ll = drawlookup(client, BGLONG(a+1), 1);
+					drawrefreshscreen(ll, client);
+				}
+			}
+			continue;
+
+		/* set compositing operator for next draw operation: 'O' op */
+		case 'O':
+			printmesg(fmt="b", a, 0);
+			m = 1+1;
+			if(n < m)
+				error(Eshortdraw);
+			client->op = a[1];
+			continue;
+
+		/* filled polygon: 'P' dstid[4] n[2] wind[4] ignore[2*4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+		/* polygon: 'p' dstid[4] n[2] end0[4] end1[4] radius[4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+		case 'p':
+		case 'P':
+			printmesg(fmt="LslllLPP", a, 0);
+			m = 1+4+2+4+4+4+4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			dst = drawimage(client, a+1);
+			ni = BGSHORT(a+5);
+			if(ni < 0)
+				error("negative count in polygon");
+			e0 = BGLONG(a+7);
+			e1 = BGLONG(a+11);
+			j = 0;
+			if(*a == 'p'){
+				j = BGLONG(a+15);
+				if(j < 0)
+					error("negative polygon line width");
+			}
+			src = drawimage(client, a+19);
+			drawpoint(&sp, a+23);
+			drawpoint(&p, a+31);
+			ni++;
+			pp = malloc(ni*sizeof(Point));
+			if(pp == nil)
+				error(Enomem);
+			doflush = 0;
+			if(dstid==0 || (dst->layer && dst->layer->screen->image->data == screenimage->data))
+				doflush = 1;	/* simplify test in loop */
+			ox = oy = 0;
+			esize = 0;
+			u = a+m;
+			for(y=0; y<ni; y++){
+				q = p;
+				oesize = esize;
+				u = drawcoord(u, a+n, ox, &p.x);
+				u = drawcoord(u, a+n, oy, &p.y);
+				ox = p.x;
+				oy = p.y;
+				if(doflush){
+					esize = j;
+					if(*a == 'p'){
+						if(y == 0){
+							c = memlineendsize(e0);
+							if(c > esize)
+								esize = c;
+						}
+						if(y == ni-1){
+							c = memlineendsize(e1);
+							if(c > esize)
+								esize = c;
+						}
+					}
+					if(*a=='P' && e0!=1 && e0 !=~0)
+						r = dst->clipr;
+					else if(y > 0){
+						r = Rect(q.x-oesize, q.y-oesize, q.x+oesize+1, q.y+oesize+1);
+						combinerect(&r, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+					}
+					if(rectclip(&r, dst->clipr))		/* should perhaps be an arg to dstflush */
+						dstflush(dstid, dst, r);
+				}
+				pp[y] = p;
+			}
+			if(y == 1)
+				dstflush(dstid, dst, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+			op = drawclientop(client);
+			if(*a == 'p')
+				mempoly(dst, pp, ni, e0, e1, j, src, sp, op);
+			else
+				memfillpoly(dst, pp, ni, e0, src, sp, op);
+			free(pp);
+			m = u-a;
+			continue;
+
+		/* read: 'r' id[4] R[4*4] */
+		case 'r':
+			printmesg(fmt="LR", a, 0);
+			m = 1+4+4*4;
+			if(n < m)
+				error(Eshortdraw);
+			i = drawimage(client, a+1);
+			drawrectangle(&r, a+5);
+			if(!rectinrect(r, i->r))
+				error(Ereadoutside);
+			c = bytesperline(r, i->depth);
+			c *= Dy(r);
+			free(client->readdata);
+			client->readdata = mallocz(c, 0);
+			if(client->readdata == nil)
+				error("readimage malloc failed");
+			client->nreaddata = memunload(i, r, client->readdata, c);
+			if(client->nreaddata < 0){
+				free(client->readdata);
+				client->readdata = nil;
+				error("bad readimage call");
+			}
+			continue;
+
+		/* string: 's' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] ni*(index[2]) */
+		/* stringbg: 'x' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] bgid[4] bgpt[2*4] ni*(index[2]) */
+		case 's':
+		case 'x':
+			printmesg(fmt="LLLPRPs", a, 0);
+			m = 1+4+4+4+2*4+4*4+2*4+2;
+			if(*a == 'x')
+				m += 4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			src = drawimage(client, a+5);
+			font = drawlookup(client, BGLONG(a+9), 1);
+			if(font == 0)
+				error(Enodrawimage);
+			if(font->nfchar == 0)
+				error(Enotfont);
+			drawpoint(&p, a+13);
+			drawrectangle(&r, a+21);
+			drawpoint(&sp, a+37);
+			ni = BGSHORT(a+45);
+			u = a+m;
+			m += ni*2;
+			if(n < m)
+				error(Eshortdraw);
+			clipr = dst->clipr;
+			dst->clipr = r;
+			op = drawclientop(client);
+			if(*a == 'x'){
+				/* paint background */
+				l = drawimage(client, a+47);
+				drawpoint(&q, a+51);
+				r.min.x = p.x;
+				r.min.y = p.y-font->ascent;
+				r.max.x = p.x;
+				r.max.y = r.min.y+Dy(font->image->r);
+				j = ni;
+				while(--j >= 0){
+					ci = BGSHORT(u);
+					if(ci<0 || ci>=font->nfchar){
+						dst->clipr = clipr;
+						error(Eindex);
+					}
+					r.max.x += font->fchar[ci].width;
+					u += 2;
+				}
+				memdraw(dst, r, l, q, memopaque, ZP, op);
+				u -= 2*ni;
+			}
+			q = p;
+			while(--ni >= 0){
+				ci = BGSHORT(u);
+				if(ci<0 || ci>=font->nfchar){
+					dst->clipr = clipr;
+					error(Eindex);
+				}
+				q = drawchar(dst, q, src, &sp, font, ci, op);
+				u += 2;
+			}
+			dst->clipr = clipr;
+			p.y -= font->ascent;
+			dstflush(dstid, dst, Rect(p.x, p.y, q.x, p.y+Dy(font->image->r)));
+			continue;
+
+		/* use public screen: 'S' id[4] chan[4] */
+		case 'S':
+			printmesg(fmt="Ll", a, 0);
+			m = 1+4+4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(dstid == 0)
+				error(Ebadarg);
+			dscrn = drawlookupdscreen(dstid);
+			if(dscrn==0 || (dscrn->public==0 && dscrn->owner!=client))
+				error(Enodrawscreen);
+			if(dscrn->screen->image->chan != BGLONG(a+5))
+				error("inconsistent chan");
+			if(drawinstallscreen(client, dscrn, 0, 0, 0, 0) == 0)
+				error(Edrawmem);
+			continue;
+
+		/* top or bottom windows: 't' top[1] nw[2] n*id[4] */
+		case 't':
+			printmesg(fmt="bsL", a, 0);
+			m = 1+1+2;
+			if(n < m)
+				error(Eshortdraw);
+			nw = BGSHORT(a+2);
+			if(nw < 0)
+				error(Ebadarg);
+			if(nw == 0)
+				continue;
+			m += nw*4;
+			if(n < m)
+				error(Eshortdraw);
+			lp = malloc(nw*sizeof(Memimage*));
+			if(lp == 0)
+				error(Enomem);
+			if(waserror()){
+				free(lp);
+				nexterror();
+			}
+			for(j=0; j<nw; j++)
+				lp[j] = drawimage(client, a+1+1+2+j*4);
+			if(lp[0]->layer == 0)
+				error("images are not windows");
+			for(j=1; j<nw; j++)
+				if(lp[j]->layer->screen != lp[0]->layer->screen)
+					error("images not on same screen");
+			if(a[1])
+				memltofrontn(lp, nw);
+			else
+				memltorearn(lp, nw);
+			if(lp[0]->layer->screen->image->data == screenimage->data)
+				for(j=0; j<nw; j++)
+					addflush(lp[j]->layer->screenr);
+			ll = drawlookup(client, BGLONG(a+1+1+2), 1);
+			drawrefreshscreen(ll, client);
+			poperror();
+			free(lp);
+			continue;
+
+		/* visible: 'v' */
+		case 'v':
+			printmesg(fmt="", a, 0);
+			m = 1;
+			drawflush();
+			continue;
+
+		/* write: 'y' id[4] R[4*4] data[x*1] */
+		/* write from compressed data: 'Y' id[4] R[4*4] data[x*1] */
+		case 'y':
+		case 'Y':
+			printmesg(fmt="LR", a, 0);
+		//	iprint("load %c\n", *a);
+			m = 1+4+4*4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			dst = drawimage(client, a+1);
+			drawrectangle(&r, a+5);
+			if(!rectinrect(r, dst->r))
+				error(Ewriteoutside);
+			y = memload(dst, r, a+m, n-m, *a=='Y');
+			if(y < 0)
+				error("bad writeimage call");
+			dstflush(dstid, dst, r);
+			m += y;
+			continue;
+		}
+	}
+	poperror();
+}
+
+Dev drawdevtab = {
+	'i',
+	"draw",
+
+	devreset,
+	devinit,
+	devshutdown,
+	drawattach,
+	drawwalk,
+	drawstat,
+	drawopen,
+	devcreate,
+	drawclose,
+	drawread,
+	devbread,
+	drawwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+/*
+ * On 8 bit displays, load the default color map
+ */
+void
+drawcmap(void)
+{
+	int r, g, b, cr, cg, cb, v;
+	int num, den;
+	int i, j;
+
+	drawactive(1);	/* to restore map from backup */
+	for(r=0,i=0; r!=4; r++)
+	    for(v=0; v!=4; v++,i+=16){
+		for(g=0,j=v-r; g!=4; g++)
+		    for(b=0;b!=4;b++,j++){
+			den = r;
+			if(g > den)
+				den = g;
+			if(b > den)
+				den = b;
+			if(den == 0)	/* divide check -- pick grey shades */
+				cr = cg = cb = v*17;
+			else{
+				num = 17*(4*den+v);
+				cr = r*num/den;
+				cg = g*num/den;
+				cb = b*num/den;
+			}
+			setcolor(i+(j&15),
+				cr*0x01010101, cg*0x01010101, cb*0x01010101);
+		    }
+	}
+}
+
+void
+drawblankscreen(int blank)
+{
+	int i, nc;
+	ulong *p;
+
+	if(blank == sdraw.blanked)
+		return;
+	if(!canqlock(&sdraw.lk))
+		return;
+	if(!initscreenimage()){
+		qunlock(&sdraw.lk);
+		return;
+	}
+	p = sdraw.savemap;
+	nc = screenimage->depth > 8 ? 256 : 1<<screenimage->depth;
+
+	/*
+	 * blankscreen uses the hardware to blank the screen
+	 * when possible.  to help in cases when it is not possible,
+	 * we set the color map to be all black.
+	 */
+	if(blank == 0){	/* turn screen on */
+		for(i=0; i<nc; i++, p+=3)
+			setcolor(i, p[0], p[1], p[2]);
+	//	blankscreen(0);
+	}else{	/* turn screen off */
+	//	blankscreen(1);
+		for(i=0; i<nc; i++, p+=3){
+			getcolor(i, &p[0], &p[1], &p[2]);
+			setcolor(i, 0, 0, 0);
+		}
+	}
+	sdraw.blanked = blank;
+	qunlock(&sdraw.lk);
+}
+
+/*
+ * record activity on screen, changing blanking as appropriate
+ */
+void
+drawactive(int active)
+{
+/*
+	if(active){
+		drawblankscreen(0);
+		sdraw.blanktime = MACHP(0)->ticks;
+	}else{
+		if(blanktime && sdraw.blanktime && TK2SEC(MACHP(0)->ticks - sdraw.blanktime)/60 >= blanktime)
+			drawblankscreen(1);
+	}
+*/
+}
+
+int
+drawidletime(void)
+{
+	return 0;
+/*	return TK2SEC(MACHP(0)->ticks - sdraw.blanktime)/60; */
+}
+
--- /dev/null
+++ b/kern/devfs.c
@@ -1,0 +1,638 @@
+#include	<sys/types.h>
+#include	<sys/stat.h>
+#include	<dirent.h>
+#include	<fcntl.h>
+#include	<errno.h>
+#include	<stdio.h> /* for remove, rename */
+#include	<limits.h>
+
+#ifndef NAME_MAX
+#	define NAME_MAX 256
+#endif
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+
+typedef	struct Ufsinfo	Ufsinfo;
+
+enum
+{
+	NUID	= 256,
+	NGID	= 256,
+	MAXPATH	= 1024,
+	MAXCOMP	= 128
+};
+
+struct Ufsinfo
+{
+	int	mode;
+	int	fd;
+	int	uid;
+	int	gid;
+	DIR*	dir;
+	ulong	offset;
+	QLock	oq;
+	char nextname[NAME_MAX];
+};
+
+char	*base = "/";
+
+static	Qid	fsqid(char*, struct stat *);
+static	void	fspath(Chan*, char*, char*);
+static	ulong	fsdirread(Chan*, uchar*, int, ulong);
+static	int	fsomode(int);
+
+/* clumsy hack, but not worse than the Path stuff in the last one */
+static char*
+uc2name(Chan *c)
+{
+	char *s;
+
+	if(c->name == nil)
+		return "/";
+	s = c2name(c);
+	if(s[0]=='#' && s[1]=='U')
+		return s+2;
+	return s;
+}
+
+static char*
+lastelem(Chan *c)
+{
+	char *s, *t;
+
+	s = uc2name(c);
+	if((t = strrchr(s, '/')) == nil)
+		return s;
+	if(t[1] == 0)
+		return t;
+	return t+1;
+}
+	
+static Chan*
+fsattach(char *spec)
+{
+	Chan *c;
+	struct stat stbuf;
+	static int devno;
+	Ufsinfo *uif;
+
+	if(stat(base, &stbuf) < 0)
+		error(strerror(errno));
+
+	c = devattach('U', spec);
+
+	uif = mallocz(sizeof(Ufsinfo), 1);
+	uif->mode = stbuf.st_mode;
+	uif->uid = stbuf.st_uid;
+	uif->gid = stbuf.st_gid;
+
+	c->aux = uif;
+	c->dev = devno++;
+	c->qid.type = QTDIR;
+/*print("fsattach %s\n", c2name(c));*/
+
+	return c;
+}
+
+static Chan*
+fsclone(Chan *c, Chan *nc)
+{
+	Ufsinfo *uif;
+
+	uif = mallocz(sizeof(Ufsinfo), 1);
+	*uif = *(Ufsinfo*)c->aux;
+	nc->aux = uif;
+
+	return nc;
+}
+
+static int
+fswalk1(Chan *c, char *name)
+{
+	struct stat stbuf;
+	char path[MAXPATH];
+	Ufsinfo *uif;
+
+	fspath(c, name, path);
+
+	/*print("** fs walk '%s' -> %s\n", path, name);  */
+
+	if(stat(path, &stbuf) < 0)
+		return 0;
+
+	uif = c->aux;
+
+	uif->mode = stbuf.st_mode;
+	uif->uid = stbuf.st_uid;
+	uif->gid = stbuf.st_gid;
+
+	c->qid = fsqid(path, &stbuf);
+
+	return 1;
+}
+
+extern Cname* addelem(Cname*, char*);
+
+static Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	int i;
+	Cname *cname;
+	Walkqid *wq;
+
+	if(nc != nil)
+		panic("fswalk: nc != nil");
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	nc = devclone(c);
+	cname = c->name;
+	incref(&cname->ref);
+
+	fsclone(c, nc);
+	wq->clone = nc;
+	for(i=0; i<nname; i++){
+		nc->name = cname;
+		if(fswalk1(nc, name[i]) == 0)
+			break;
+		cname = addelem(cname, name[i]);
+		wq->qid[i] = nc->qid;
+	}
+	nc->name = nil;
+	cnameclose(cname);
+	if(i != nname){
+		cclose(nc);
+		wq->clone = nil;
+	}
+	wq->nqid = i;
+	return wq;
+}
+	
+static int
+fsstat(Chan *c, uchar *buf, int n)
+{
+	Dir d;
+	struct stat stbuf;
+	char path[MAXPATH];
+
+	if(n < BIT16SZ)
+		error(Eshortstat);
+
+	fspath(c, 0, path);
+	if(stat(path, &stbuf) < 0)
+		error(strerror(errno));
+
+	d.name = lastelem(c);
+	d.uid = "unknown";
+	d.gid = "unknown";
+	d.muid = "unknown";
+	d.qid = c->qid;
+	d.mode = (c->qid.type<<24)|(stbuf.st_mode&0777);
+	d.atime = stbuf.st_atime;
+	d.mtime = stbuf.st_mtime;
+	d.length = stbuf.st_size;
+	d.type = 'U';
+	d.dev = c->dev;
+	return convD2M(&d, buf, n);
+}
+
+static Chan*
+fsopen(Chan *c, int mode)
+{
+	char path[MAXPATH];
+	int m, isdir;
+	Ufsinfo *uif;
+
+/*print("fsopen %s\n", c2name(c));*/
+	m = mode & (OTRUNC|3);
+	switch(m) {
+	case 0:
+		break;
+	case 1:
+	case 1|16:
+		break;
+	case 2:	
+	case 0|16:
+	case 2|16:
+		break;
+	case 3:
+		break;
+	default:
+		error(Ebadarg);
+	}
+
+	isdir = c->qid.type & QTDIR;
+
+	if(isdir && mode != OREAD)
+		error(Eperm);
+
+	m = fsomode(m & 3);
+	c->mode = openmode(mode);
+
+	uif = c->aux;
+
+	fspath(c, 0, path);
+	if(isdir) {
+		uif->dir = opendir(path);
+		if(uif->dir == 0)
+			error(strerror(errno));
+	}	
+	else {
+		if(mode & OTRUNC)
+			m |= O_TRUNC;
+		uif->fd = open(path, m, 0666);
+
+		if(uif->fd < 0)
+			error(strerror(errno));
+	}
+	uif->offset = 0;
+
+	c->offset = 0;
+	c->flag |= COPEN;
+	return c;
+}
+
+static void
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+	int fd, m;
+	char path[MAXPATH];
+	struct stat stbuf;
+	Ufsinfo *uif;
+
+	m = fsomode(mode&3);
+
+	fspath(c, name, path);
+
+	uif = c->aux;
+
+	if(perm & DMDIR) {
+		if(m)
+			error(Eperm);
+
+		if(mkdir(path, perm & 0777) < 0)
+			error(strerror(errno));
+
+		fd = open(path, 0);
+		if(fd >= 0) {
+			chmod(path, perm & 0777);
+			chown(path, uif->uid, uif->uid);
+		}
+		close(fd);
+
+		uif->dir = opendir(path);
+		if(uif->dir == 0)
+			error(strerror(errno));
+	}
+	else {
+		fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+		if(fd >= 0) {
+			if(m != 1) {
+				close(fd);
+				fd = open(path, m);
+			}
+			chmod(path, perm & 0777);
+			chown(path, uif->uid, uif->gid);
+		}
+		if(fd < 0)
+			error(strerror(errno));
+		uif->fd = fd;
+	}
+
+	if(stat(path, &stbuf) < 0)
+		error(strerror(errno));
+	c->qid = fsqid(path, &stbuf);
+	c->offset = 0;
+	c->flag |= COPEN;
+	c->mode = openmode(mode);
+}
+
+static void
+fsclose(Chan *c)
+{
+	Ufsinfo *uif;
+
+	uif = c->aux;
+
+	if(c->flag & COPEN) {
+		if(c->qid.type & QTDIR)
+			closedir(uif->dir);
+		else
+			close(uif->fd);
+	}
+
+	free(uif);
+}
+
+static long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+	int fd, r;
+	Ufsinfo *uif;
+
+/*print("fsread %s\n", c2name(c));*/
+	if(c->qid.type & QTDIR)
+		return fsdirread(c, va, n, offset);
+
+	uif = c->aux;
+	qlock(&uif->oq);
+	if(waserror()) {
+		qunlock(&uif->oq);
+		nexterror();
+	}
+	fd = uif->fd;
+	if(uif->offset != offset) {
+		r = lseek(fd, offset, 0);
+		if(r < 0)
+			error(strerror(errno));
+		uif->offset = offset;
+	}
+
+	n = read(fd, va, n);
+	if(n < 0)
+		error(strerror(errno));
+
+	uif->offset += n;
+	qunlock(&uif->oq);
+	poperror();
+
+	return n;
+}
+
+static long
+fswrite(Chan *c, void *va, long n, vlong offset)
+{
+	int fd, r;
+	Ufsinfo *uif;
+
+	uif = c->aux;
+
+	qlock(&uif->oq);
+	if(waserror()) {
+		qunlock(&uif->oq);
+		nexterror();
+	}
+	fd = uif->fd;
+	if(uif->offset != offset) {
+		r = lseek(fd, offset, 0);
+		if(r < 0)
+			error(strerror(errno));
+		uif->offset = offset;
+	}
+
+	n = write(fd, va, n);
+	if(n < 0)
+		error(strerror(errno));
+
+	uif->offset += n;
+	qunlock(&uif->oq);
+	poperror();
+
+	return n;
+}
+
+static void
+fsremove(Chan *c)
+{
+	int n;
+	char path[MAXPATH];
+
+	fspath(c, 0, path);
+	if(c->qid.type & QTDIR)
+		n = rmdir(path);
+	else
+		n = remove(path);
+	if(n < 0)
+		error(strerror(errno));
+}
+
+int
+fswstat(Chan *c, uchar *buf, int n)
+{
+	Dir d;
+	struct stat stbuf;
+	char old[MAXPATH], new[MAXPATH], dir[MAXPATH];
+	char strs[MAXPATH*3], *p;
+	Ufsinfo *uif;
+
+	if(convM2D(buf, n, &d, strs) != n)
+		error(Ebadstat);
+	
+	fspath(c, 0, old);
+	if(stat(old, &stbuf) < 0)
+		error(strerror(errno));
+
+	uif = c->aux;
+
+	if(d.name[0] && strcmp(d.name, lastelem(c)) != 0) {
+		fspath(c, 0, old);
+		strcpy(new, old);
+		p = strrchr(new, '/');
+		strcpy(p+1, d.name);
+		if(rename(old, new) < 0)
+			error(strerror(errno));
+	}
+
+	fspath(c, 0, old);
+	if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
+		if(chmod(old, d.mode&0777) < 0)
+			error(strerror(errno));
+		uif->mode &= ~0777;
+		uif->mode |= d.mode&0777;
+	}
+/*
+	p = name2pass(gid, d.gid);
+	if(p == 0)
+		error(Eunknown);
+
+	if(p->id != stbuf.st_gid) {
+		if(chown(old, stbuf.st_uid, p->id) < 0)
+			error(strerror(errno));
+
+		uif->gid = p->id;
+	}
+*/
+	return n;
+}
+
+static Qid
+fsqid(char *p, struct stat *st)
+{
+	Qid q;
+	int dev;
+	ulong h;
+	static int nqdev;
+	static uchar *qdev;
+
+	if(qdev == 0)
+		qdev = mallocz(65536U, 1);
+
+	q.type = 0;
+	if((st->st_mode&S_IFMT) ==  S_IFDIR)
+		q.type = QTDIR;
+
+	dev = st->st_dev & 0xFFFFUL;
+	if(qdev[dev] == 0)
+		qdev[dev] = ++nqdev;
+
+	h = 0;
+	while(*p != '\0')
+		h += *p++ * 13;
+	
+	q.path = (vlong)qdev[dev]<<32;
+	q.path |= h;
+	q.vers = st->st_mtime;
+
+	return q;
+}
+
+static void
+fspath(Chan *c, char *ext, char *path)
+{
+	int i, n;
+	char *comp[MAXCOMP];
+
+	strcpy(path, base);
+	strcat(path, "/");
+	strcat(path, uc2name(c));
+	if(ext){
+		strcat(path, "/");
+		strcat(path, ext);
+	}
+	cleanname(path);
+}
+
+static int
+isdots(char *name)
+{
+	if(name[0] != '.')
+		return 0;
+	if(name[1] == '\0')
+		return 1;
+	if(name[1] != '.')
+		return 0;
+	if(name[2] == '\0')
+		return 1;
+	return 0;
+}
+
+static int
+p9readdir(char *name, Ufsinfo *uif)
+{
+	struct dirent *de;
+	
+	if(uif->nextname[0]){
+		strcpy(name, uif->nextname);
+		uif->nextname[0] = 0;
+		return 1;
+	}
+
+	de = readdir(uif->dir);
+	if(de == NULL)
+		return 0;
+		
+	strcpy(name, de->d_name);
+	return 1;
+}
+
+static ulong
+fsdirread(Chan *c, uchar *va, int count, ulong offset)
+{
+	int i;
+	Dir d;
+	long n;
+	char de[NAME_MAX];
+	struct stat stbuf;
+	char path[MAXPATH], dirpath[MAXPATH];
+	Ufsinfo *uif;
+
+/*print("fsdirread %s\n", c2name(c));*/
+	i = 0;
+	uif = c->aux;
+
+	errno = 0;
+	if(uif->offset != offset) {
+		if(offset != 0)
+			error("bad offset in fsdirread");
+		uif->offset = offset;  /* sync offset */
+		uif->nextname[0] = 0;
+		rewinddir(uif->dir);
+	}
+
+	fspath(c, 0, dirpath);
+
+	while(i+BIT16SZ < count) {
+		if(!p9readdir(de, uif))
+			break;
+
+		if(de[0]==0 || isdots(de))
+			continue;
+
+		d.name = de;
+		sprint(path, "%s/%s", dirpath, de);
+		memset(&stbuf, 0, sizeof stbuf);
+
+		if(stat(path, &stbuf) < 0) {
+			print("dir: bad path %s\n", path);
+			/* but continue... probably a bad symlink */
+		}
+
+		d.uid = "unknown";
+		d.gid = "unknown";
+		d.qid = fsqid(path, &stbuf);
+		d.mode = (d.qid.type<<24)|(stbuf.st_mode&0777);
+		d.atime = stbuf.st_atime;
+		d.mtime = stbuf.st_mtime;
+		d.length = stbuf.st_size;
+		d.type = 'U';
+		d.dev = c->dev;
+		n = convD2M(&d, (char*)va+i, count-i);
+		if(n == BIT16SZ){
+			strcpy(uif->nextname, de);
+			break;
+		}
+		i += n;
+	}
+/*print("got %d\n", i);*/
+	uif->offset += i;
+	return i;
+}
+
+static int
+fsomode(int m)
+{
+	switch(m) {
+	case 0:			/* OREAD */
+	case 3:			/* OEXEC */
+		return 0;
+	case 1:			/* OWRITE */
+		return 1;
+	case 2:			/* ORDWR */
+		return 2;
+	}
+	error(Ebadarg);
+	return 0;
+}
+
+Dev fsdevtab = {
+	'U',
+	"fs",
+
+	devreset,
+	devinit,
+	devshutdown,
+	fsattach,
+	fswalk,
+	fsstat,
+	fsopen,
+	fscreate,
+	fsclose,
+	fsread,
+	devbread,
+	fswrite,
+	devbwrite,
+	fsremove,
+	fswstat,
+};
--- /dev/null
+++ b/kern/devip-posix.c
@@ -1,0 +1,209 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "devip.h"
+
+#undef listen
+#undef accept
+#undef bind
+
+void
+osipinit(void)
+{
+	char buf[1024];
+	gethostname(buf, sizeof(buf));
+	kstrdup(&sysname, buf);
+
+}
+
+int
+so_socket(int type)
+{
+	int fd, one;
+
+	switch(type) {
+	default:
+		error("bad protocol type");
+	case S_TCP:
+		type = SOCK_STREAM;
+		break;
+	case S_UDP:
+		type = SOCK_DGRAM;
+		break;
+	}
+
+	fd = socket(AF_INET, type, 0);
+	if(fd < 0)
+		oserror();
+
+	one = 1;
+	if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) > 0){
+		oserrstr();
+		print("setsockopt: %r");
+	}
+
+	return fd;
+}
+
+
+void
+so_connect(int fd, unsigned long raddr, unsigned short rport)
+{
+	struct sockaddr_in sin;
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	hnputs(&sin.sin_port, rport);
+	hnputl(&sin.sin_addr.s_addr, raddr);
+
+	if(connect(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+		oserror();
+}
+
+void
+so_getsockname(int fd, unsigned long *laddr, unsigned short *lport)
+{
+	int len;
+	struct sockaddr_in sin;
+
+	len = sizeof(sin);
+	if(getsockname(fd, (struct sockaddr*)&sin, &len) < 0)
+		oserror();
+
+	if(sin.sin_family != AF_INET || len != sizeof(sin))
+		error("not AF_INET");
+
+	*laddr = nhgetl(&sin.sin_addr.s_addr);
+	*lport = nhgets(&sin.sin_port);
+}
+
+void
+so_listen(int fd)
+{
+	if(listen(fd, 5) < 0)
+		oserror();
+}
+
+int
+so_accept(int fd, unsigned long *raddr, unsigned short *rport)
+{
+	int nfd, len;
+	struct sockaddr_in sin;
+
+	len = sizeof(sin);
+	nfd = accept(fd, (struct sockaddr*)&sin, &len);
+	if(nfd < 0)
+		oserror();
+
+	if(sin.sin_family != AF_INET || len != sizeof(sin))
+		error("not AF_INET");
+
+	*raddr = nhgetl(&sin.sin_addr.s_addr);
+	*rport = nhgets(&sin.sin_port);
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port)
+{
+	int i, one;
+	struct sockaddr_in sin;
+
+	one = 1;
+	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0){
+		oserrstr();
+		print("setsockopt: %r");
+	}
+
+	if(su) {
+		for(i = 600; i < 1024; i++) {
+			memset(&sin, 0, sizeof(sin));
+			sin.sin_family = AF_INET;
+			sin.sin_port = i;
+
+			if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) >= 0)	
+				return;
+		}
+		oserror();
+	}
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	hnputs(&sin.sin_port, port);
+
+	if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+		oserror();
+}
+
+int
+so_gethostbyname(char *host, char**hostv, int n)
+{
+	int i;
+	char buf[32];
+	unsigned char *p;
+	struct hostent *hp;
+
+	hp = gethostbyname(host);
+	if(hp == 0)
+		return 0;
+
+	for(i = 0; hp->h_addr_list[i] && i < n; i++) {
+		p = (unsigned char*)hp->h_addr_list[i];
+		sprint(buf, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+		hostv[i] = strdup(buf);
+		if(hostv[i] == 0)
+			break;
+	}
+	return i;
+}
+
+char*
+hostlookup(char *host)
+{
+	char buf[100];
+	uchar *p;
+	struct hostent *he;
+
+	he = gethostbyname(host);
+	if(he != 0 && he->h_addr_list[0]) {
+		p = (uchar*)he->h_addr_list[0];
+		sprint(buf, "%ud.%ud.%ud.%ud", p[0], p[1], p[2], p[3]);
+	} else
+		strcpy(buf, host);
+
+	return strdup(buf);
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+	struct servent *s;
+
+	s = getservbyname(service, net);
+	if(s == 0)
+		return -1;
+
+	sprint(port, "%d", nhgets(&s->s_port));
+	return 0;
+}
+
+int
+so_send(int fd, void *d, int n, int f)
+{
+	send(fd, d, n, f);
+}
+
+int
+so_recv(int fd, void *d, int n, int f)
+{
+	return recv(fd, d, n, f);
+}
--- /dev/null
+++ b/kern/devip-win.c
@@ -1,0 +1,208 @@
+#include <windows.h>
+#include "winduhz.h"
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "devip.h"
+
+#pragma comment(lib, "wsock32.lib")
+
+#undef listen
+#undef accept
+#undef bind
+
+void
+osipinit(void)
+{
+	WSADATA wasdat;
+	char buf[1024];
+
+	if(WSAStartup(MAKEWORD(1, 1), &wasdat) != 0)
+		panic("no winsock.dll");
+
+	gethostname(buf, sizeof(buf));
+	kstrdup(&sysname, buf);
+}
+
+int
+so_socket(int type)
+{
+	int fd, one;
+
+	switch(type) {
+	default:
+		error("bad protocol type");
+	case S_TCP:
+		type = SOCK_STREAM;
+		break;
+	case S_UDP:
+		type = SOCK_DGRAM;
+		break;
+	}
+
+	fd = socket(AF_INET, type, 0);
+	if(fd < 0)
+		error(sys_errlist[errno]);
+
+	one = 1;
+	if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) > 0)
+		print("setsockopt: %s", sys_errlist[errno]);
+
+
+	return fd;
+}
+
+
+void
+so_connect(int fd, unsigned long raddr, unsigned short rport)
+{
+	struct sockaddr_in sin;
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	hnputs(&sin.sin_port, rport);
+	hnputl(&sin.sin_addr.s_addr, raddr);
+
+	if(connect(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+		error(sys_errlist[errno]);
+}
+
+void
+so_getsockname(int fd, unsigned long *laddr, unsigned short *lport)
+{
+	int len;
+	struct sockaddr_in sin;
+
+	len = sizeof(sin);
+	if(getsockname(fd, (struct sockaddr*)&sin, &len) < 0)
+		error(sys_errlist[errno]);
+
+	if(sin.sin_family != AF_INET || len != sizeof(sin))
+		error("not AF_INET");
+
+	*laddr = nhgetl(&sin.sin_addr.s_addr);
+	*lport = nhgets(&sin.sin_port);
+}
+
+void
+so_listen(int fd)
+{
+	if(listen(fd, 5) < 0)
+		error(sys_errlist[errno]);
+}
+
+int
+so_accept(int fd, unsigned long *raddr, unsigned short *rport)
+{
+	int nfd, len;
+	struct sockaddr_in sin;
+
+	len = sizeof(sin);
+	nfd = accept(fd, (struct sockaddr*)&sin, &len);
+	if(nfd < 0)
+		error(sys_errlist[errno]);
+
+	if(sin.sin_family != AF_INET || len != sizeof(sin))
+		error("not AF_INET");
+
+	*raddr = nhgetl(&sin.sin_addr.s_addr);
+	*rport = nhgets(&sin.sin_port);
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port)
+{
+	int i, one;
+	struct sockaddr_in sin;
+
+	one = 1;
+	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0)
+		print("setsockopt: %s", sys_errlist[errno]);
+
+	if(su) {
+		for(i = 600; i < 1024; i++) {
+			memset(&sin, 0, sizeof(sin));
+			sin.sin_family = AF_INET;
+			sin.sin_port = i;
+
+			if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) >= 0)	
+				return;
+		}
+		error(sys_errlist[errno]);
+	}
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	hnputs(&sin.sin_port, port);
+
+	if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
+		error(sys_errlist[errno]);
+}
+
+int
+so_gethostbyname(char *host, char**hostv, int n)
+{
+	int i;
+	char buf[32];
+	unsigned char *p;
+	struct hostent *hp;
+
+	hp = gethostbyname(host);
+	if(hp == 0)
+		return 0;
+
+	for(i = 0; hp->h_addr_list[i] && i < n; i++) {
+		p = (unsigned char*)hp->h_addr_list[i];
+		sprint(buf, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+		hostv[i] = strdup(buf);
+		if(hostv[i] == 0)
+			break;
+	}
+	return i;
+}
+
+char*
+hostlookup(char *host)
+{
+	char buf[100];
+	uchar *p;
+	HOSTENT *he;
+
+	he = gethostbyname(host);
+	if(he != 0 && he->h_addr_list[0]) {
+		p = he->h_addr_list[0];
+		sprint(buf, "%ud.%ud.%ud.%ud", p[0], p[1], p[2], p[3]);
+	} else
+		strcpy(buf, host);
+
+	return strdup(buf);
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+	struct servent *s;
+
+	s = getservbyname(service, net);
+	if(s == 0)
+		return -1;
+
+	sprint(port, "%d", nhgets(&s->s_port));
+	return 0;
+}
+
+int
+so_send(int fd, void *d, int n, int f)
+{
+	return send(fd, d, n, f);
+}
+
+int
+so_recv(int fd, void *d, int n, int f)
+{
+	return recv(fd, d, n, f);
+}
--- /dev/null
+++ b/kern/devip.c
@@ -1,0 +1,936 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "devip.h"
+
+void		hnputl(void *p, unsigned long v);
+void		hnputs(void *p, unsigned short v);
+unsigned long	nhgetl(void *p);
+unsigned short	nhgets(void *p);
+unsigned long	parseip(char *to, char *from);
+void	csclose(Chan*);
+long	csread(Chan*, void*, long, vlong);
+long	cswrite(Chan*, void*, long, vlong);
+
+void osipinit(void);
+
+enum
+{
+	Qtopdir		= 1,	/* top level directory */
+	Qcs,
+	Qprotodir,		/* directory for a protocol */
+	Qclonus,
+	Qconvdir,		/* directory for a conversation */
+	Qdata,
+	Qctl,
+	Qstatus,
+	Qremote,
+	Qlocal,
+	Qlisten,
+
+	MAXPROTO	= 4
+};
+#define TYPE(x) 	((int)((x).path & 0xf))
+#define CONV(x) 	((int)(((x).path >> 4)&0xfff))
+#define PROTO(x) 	((int)(((x).path >> 16)&0xff))
+#define QID(p, c, y) 	(((p)<<16) | ((c)<<4) | (y))
+
+typedef struct Proto	Proto;
+typedef struct Conv	Conv;
+struct Conv
+{
+	int	x;
+	Ref	r;
+	int	sfd;
+	int	perm;
+	char	owner[KNAMELEN];
+	char*	state;
+	ulong	laddr;
+	ushort	lport;
+	ulong	raddr;
+	ushort	rport;
+	int	restricted;
+	char	cerr[KNAMELEN];
+	Proto*	p;
+};
+
+struct Proto
+{
+	Lock	l;
+	int	x;
+	int	stype;
+	char	name[KNAMELEN];
+	int	nc;
+	int	maxconv;
+	Conv**	conv;
+	Qid	qid;
+};
+
+static	int	np;
+static	Proto	proto[MAXPROTO];
+int	eipfmt(Fmt*);
+
+static	Conv*	protoclone(Proto*, char*, int);
+static	void	setladdr(Conv*);
+
+int
+ipgen(Chan *c, char *nname, Dirtab *d, int nd, int s, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+	char *p;
+
+	USED(nname);
+	q.vers = 0;
+	q.type = 0;
+	switch(TYPE(c->qid)) {
+	case Qtopdir:
+		if(s >= 1+np)
+			return -1;
+
+		if(s == 0){
+			q.path = QID(s, 0, Qcs);
+			devdir(c, q, "cs", 0, "network", 0666, dp);
+		}else{
+			s--;
+			q.path = QID(s, 0, Qprotodir);
+			q.type = QTDIR;
+			devdir(c, q, proto[s].name, 0, "network", DMDIR|0555, dp);
+		}
+		return 1;
+	case Qprotodir:
+		if(s < proto[PROTO(c->qid)].nc) {
+			cv = proto[PROTO(c->qid)].conv[s];
+			sprint(up->genbuf, "%d", s);
+			q.path = QID(PROTO(c->qid), s, Qconvdir);
+			q.type = QTDIR;
+			devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
+			return 1;
+		}
+		s -= proto[PROTO(c->qid)].nc;
+		switch(s) {
+		default:
+			return -1;
+		case 0:
+			p = "clone";
+			q.path = QID(PROTO(c->qid), 0, Qclonus);
+			break;
+		}
+		devdir(c, q, p, 0, "network", 0555, dp);
+		return 1;
+	case Qconvdir:
+		cv = proto[PROTO(c->qid)].conv[CONV(c->qid)];
+		switch(s) {
+		default:
+			return -1;
+		case 0:
+			q.path = QID(PROTO(c->qid), CONV(c->qid), Qdata);
+			devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
+			return 1;
+		case 1:
+			q.path = QID(PROTO(c->qid), CONV(c->qid), Qctl);
+			devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
+			return 1;
+		case 2:
+			p = "status";
+			q.path = QID(PROTO(c->qid), CONV(c->qid), Qstatus);
+			break;
+		case 3:
+			p = "remote";
+			q.path = QID(PROTO(c->qid), CONV(c->qid), Qremote);
+			break;
+		case 4:
+			p = "local";
+			q.path = QID(PROTO(c->qid), CONV(c->qid), Qlocal);
+			break;
+		case 5:
+			p = "listen";
+			q.path = QID(PROTO(c->qid), CONV(c->qid), Qlisten);
+			break;
+		}
+		devdir(c, q, p, 0, cv->owner, 0444, dp);
+		return 1;
+	}
+	return -1;
+}
+
+static void
+newproto(char *name, int type, int maxconv)
+{
+	int l;
+	Proto *p;
+
+	if(np >= MAXPROTO) {
+		print("no %s: increase MAXPROTO", name);
+		return;
+	}
+
+	p = &proto[np];
+	strcpy(p->name, name);
+	p->stype = type;
+	p->qid.path = QID(np, 0, Qprotodir);
+	p->qid.type = QTDIR;
+	p->x = np++;
+	p->maxconv = maxconv;
+	l = sizeof(Conv*)*(p->maxconv+1);
+	p->conv = mallocz(l, 1);
+	if(p->conv == 0)
+		panic("no memory");
+}
+
+void
+ipinit(void)
+{
+	osipinit();
+
+	newproto("udp", S_UDP, 10);
+	newproto("tcp", S_TCP, 30);
+
+	fmtinstall('I', eipfmt);
+	fmtinstall('E', eipfmt);
+	
+}
+
+Chan *
+ipattach(char *spec)
+{
+	Chan *c;
+
+	c = devattach('I', spec);
+	c->qid.path = QID(0, 0, Qtopdir);
+	c->qid.type = QTDIR;
+	c->qid.vers = 0;
+	return c;
+}
+
+static Walkqid*
+ipwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, 0, 0, ipgen);
+}
+
+int
+ipstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, 0, 0, ipgen);
+}
+
+Chan *
+ipopen(Chan *c, int omode)
+{
+	Proto *p;
+	ulong raddr;
+	ushort rport;
+	int perm, sfd;
+	Conv *cv, *lcv;
+
+	omode &= 3;
+	switch(omode) {
+	case OREAD:
+		perm = 4;
+		break;
+	case OWRITE:
+		perm = 2;
+		break;
+	case ORDWR:
+		perm = 6;
+		break;
+	}
+
+	switch(TYPE(c->qid)) {
+	default:
+		break;
+	case Qtopdir:
+	case Qprotodir:
+	case Qconvdir:
+	case Qstatus:
+	case Qremote:
+	case Qlocal:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qclonus:
+		p = &proto[PROTO(c->qid)];
+		cv = protoclone(p, up->user, -1);
+		if(cv == 0)
+			error(Enodev);
+		c->qid.path = QID(p->x, cv->x, Qctl);
+		c->qid.vers = 0;
+		break;
+	case Qdata:
+	case Qctl:
+		p = &proto[PROTO(c->qid)];
+		lock(&p->l);
+		cv = p->conv[CONV(c->qid)];
+		lock(&cv->r.lk);
+		if((perm & (cv->perm>>6)) != perm) {
+			if(strcmp(up->user, cv->owner) != 0 ||
+		 	  (perm & cv->perm) != perm) {
+				unlock(&cv->r.lk);
+				unlock(&p->l);
+				error(Eperm);
+			}
+		}
+		cv->r.ref++;
+		if(cv->r.ref == 1) {
+			memmove(cv->owner, up->user, KNAMELEN);
+			cv->perm = 0660;
+		}
+		unlock(&cv->r.lk);
+		unlock(&p->l);
+		break;
+	case Qlisten:
+		p = &proto[PROTO(c->qid)];
+		lcv = p->conv[CONV(c->qid)];
+		sfd = so_accept(lcv->sfd, &raddr, &rport);
+		cv = protoclone(p, up->user, sfd);
+		if(cv == 0) {
+			close(sfd);
+			error(Enodev);
+		}
+		cv->raddr = raddr;
+		cv->rport = rport;
+		setladdr(cv);
+		cv->state = "Established";
+		c->qid.path = QID(p->x, cv->x, Qctl);
+		break;
+	}
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+void
+ipclose(Chan *c)
+{
+	Conv *cc;
+
+	switch(TYPE(c->qid)) {
+	case Qcs:
+		csclose(c);
+		break;
+	case Qdata:
+	case Qctl:
+		if((c->flag & COPEN) == 0)
+			break;
+		cc = proto[PROTO(c->qid)].conv[CONV(c->qid)];
+		if(decref(&cc->r) != 0)
+			break;
+		strcpy(cc->owner, "network");
+		cc->perm = 0666;
+		cc->state = "Closed";
+		cc->laddr = 0;
+		cc->raddr = 0;
+		cc->lport = 0;
+		cc->rport = 0;
+		close(cc->sfd);
+		break;
+	}
+}
+
+long
+ipread(Chan *ch, void *a, long n, vlong offset)
+{
+	int r;
+	Conv *c;
+	Proto *x;
+	uchar ip[4];
+	char buf[128], *p;
+
+/*print("ipread %s %lux\n", c2name(ch), (long)ch->qid.path);*/
+	p = a;
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qcs:
+		return csread(ch, a, n, offset);
+	case Qprotodir:
+	case Qtopdir:
+	case Qconvdir:
+		return devdirread(ch, a, n, 0, 0, ipgen);
+	case Qctl:
+		sprint(buf, "%d", CONV(ch->qid));
+		return readstr(offset, p, n, buf);
+	case Qremote:
+		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+		hnputl(ip, c->raddr);
+		sprint(buf, "%I!%d\n", ip, c->rport);
+		return readstr(offset, p, n, buf);
+	case Qlocal:
+		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+		hnputl(ip, c->laddr);
+		sprint(buf, "%I!%d\n", ip, c->lport);
+		return readstr(offset, p, n, buf);
+	case Qstatus:
+		x = &proto[PROTO(ch->qid)];
+		c = x->conv[CONV(ch->qid)];
+		sprint(buf, "%s/%d %d %s \n",
+			c->p->name, c->x, c->r.ref, c->state);
+		return readstr(offset, p, n, buf);
+	case Qdata:
+		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
+		r = so_recv(c->sfd, a, n, 0);
+		if(r < 0){
+			oserrstr();
+			nexterror();
+		}
+		return r;
+	}
+}
+
+static void
+setladdr(Conv *c)
+{
+	so_getsockname(c->sfd, &c->laddr, &c->lport);
+}
+
+static void
+setlport(Conv *c)
+{
+	if(c->restricted == 0 && c->lport == 0)
+		return;
+
+	so_bind(c->sfd, c->restricted, c->lport);
+}
+
+static void
+setladdrport(Conv *c, char *str)
+{
+	char *p, addr[4];
+
+	p = strchr(str, '!');
+	if(p == 0) {
+		p = str;
+		c->laddr = 0;
+	}
+	else {
+		*p++ = 0;
+		parseip(addr, str);
+		c->laddr = nhgetl((uchar*)addr);
+	}
+	if(*p == '*')
+		c->lport = 0;
+	else
+		c->lport = atoi(p);
+
+	setlport(c);
+}
+
+static char*
+setraddrport(Conv *c, char *str)
+{
+	char *p, addr[4];
+
+	p = strchr(str, '!');
+	if(p == 0)
+		return "malformed address";
+	*p++ = 0;
+	parseip(addr, str);
+	c->raddr = nhgetl((uchar*)addr);
+	c->rport = atoi(p);
+	p = strchr(p, '!');
+	if(p) {
+		if(strcmp(p, "!r") == 0)
+			c->restricted = 1;
+	}
+	return 0;
+}
+
+long
+ipwrite(Chan *ch, void *a, long n, vlong offset)
+{
+	Conv *c;
+	Proto *x;
+	int r, nf;
+	char *p, *fields[3], buf[128];
+
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qcs:
+		return cswrite(ch, a, n, offset);
+	case Qctl:
+		x = &proto[PROTO(ch->qid)];
+		c = x->conv[CONV(ch->qid)];
+		if(n > sizeof(buf)-1)
+			n = sizeof(buf)-1;
+		memmove(buf, a, n);
+		buf[n] = '\0';
+
+		nf = tokenize(buf, fields, 3);
+		if(strcmp(fields[0], "connect") == 0){
+			switch(nf) {
+			default:
+				error("bad args to connect");
+			case 2:
+				p = setraddrport(c, fields[1]);
+				if(p != 0)
+					error(p);
+				break;
+			case 3:
+				p = setraddrport(c, fields[1]);
+				if(p != 0)
+					error(p);
+				c->lport = atoi(fields[2]);
+				setlport(c);
+				break;
+			}
+			so_connect(c->sfd, c->raddr, c->rport);
+			setladdr(c);
+			c->state = "Established";
+			return n;
+		}
+		if(strcmp(fields[0], "announce") == 0) {
+			switch(nf){
+			default:
+				error("bad args to announce");
+			case 2:
+				setladdrport(c, fields[1]);
+				break;
+			}
+			so_listen(c->sfd);
+			c->state = "Announced";
+			return n;
+		}
+		if(strcmp(fields[0], "bind") == 0){
+			switch(nf){
+			default:
+				error("bad args to bind");
+			case 2:
+				c->lport = atoi(fields[1]);
+				break;
+			}
+			setlport(c);
+			return n;
+		}
+		error("bad control message");
+	case Qdata:
+		x = &proto[PROTO(ch->qid)];
+		c = x->conv[CONV(ch->qid)];
+		r = so_send(c->sfd, a, n, 0);
+		if(r < 0){
+			oserrstr();
+			nexterror();
+		}
+		return r;
+	}
+	return n;
+}
+
+static Conv*
+protoclone(Proto *p, char *user, int nfd)
+{
+	Conv *c, **pp, **ep;
+
+	c = 0;
+	lock(&p->l);
+	if(waserror()) {
+		unlock(&p->l);
+		nexterror();
+	}
+	ep = &p->conv[p->maxconv];
+	for(pp = p->conv; pp < ep; pp++) {
+		c = *pp;
+		if(c == 0) {
+			c = mallocz(sizeof(Conv), 1);
+			if(c == 0)
+				error(Enomem);
+			lock(&c->r.lk);
+			c->r.ref = 1;
+			c->p = p;
+			c->x = pp - p->conv;
+			p->nc++;
+			*pp = c;
+			break;
+		}
+		lock(&c->r.lk);
+		if(c->r.ref == 0) {
+			c->r.ref++;
+			break;
+		}
+		unlock(&c->r.lk);
+	}
+	if(pp >= ep) {
+		unlock(&p->l);
+		poperror();
+		return 0;
+	}
+
+	strcpy(c->owner, user);
+	c->perm = 0660;
+	c->state = "Closed";
+	c->restricted = 0;
+	c->laddr = 0;
+	c->raddr = 0;
+	c->lport = 0;
+	c->rport = 0;
+	c->sfd = nfd;
+	if(nfd == -1)
+		c->sfd = so_socket(p->stype);
+
+	unlock(&c->r.lk);
+	unlock(&p->l);
+	poperror();
+	return c;
+}
+
+enum
+{
+	Isprefix= 16,
+};
+
+uchar prefixvals[256] =
+{
+/*0x00*/ 0 | Isprefix,
+		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x10*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x20*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x30*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x40*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x50*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x60*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x70*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x80*/ 1 | Isprefix,
+		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0x90*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xA0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xB0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xC0*/ 2 | Isprefix,
+		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xD0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xE0*/ 3 | Isprefix,
+		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/*0xF0*/ 4 | Isprefix,
+		   0, 0, 0, 0, 0, 0, 0, 
+/*0xF8*/ 5 | Isprefix,
+		   0, 0, 0, 
+/*0xFC*/ 6 | Isprefix,
+		   0,
+/*0xFE*/ 7 | Isprefix,
+/*0xFF*/ 8 | Isprefix,
+};
+
+int
+eipfmt(Fmt *f)
+{
+	char buf[5*8];
+	static char *efmt = "%.2lux%.2lux%.2lux%.2lux%.2lux%.2lux";
+	static char *ifmt = "%d.%d.%d.%d";
+	uchar *p, ip[16];
+	ulong *lp;
+	ushort s;
+	int i, j, n, eln, eli;
+	ulong ul;
+
+	switch(f->r) {
+	case 'E':		/* Ethernet address */
+		p = va_arg(f->args, uchar*);
+		snprint(buf, sizeof buf, efmt, p[0], p[1], p[2], p[3], p[4], p[5]);
+		return fmtstrcpy(f, buf);
+
+	case 'I':
+		ul = va_arg(f->args, ulong);
+		hnputl(ip, ul);
+		snprint(buf, sizeof buf, ifmt, ip[0], ip[1], ip[2], ip[3]);
+		return fmtstrcpy(f, buf);
+	}
+	return fmtstrcpy(f, "(eipfmt)");
+}
+
+void
+hnputl(void *p, unsigned long v)
+{
+	unsigned char *a;
+
+	a = p;
+	a[0] = v>>24;
+	a[1] = v>>16;
+	a[2] = v>>8;
+	a[3] = v;
+}
+
+void
+hnputs(void *p, unsigned short v)
+{
+	unsigned char *a;
+
+	a = p;
+	a[0] = v>>8;
+	a[1] = v;
+}
+
+unsigned long
+nhgetl(void *p)
+{
+	unsigned char *a;
+	a = p;
+	return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
+}
+
+unsigned short
+nhgets(void *p)
+{
+	unsigned char *a;
+	a = p;
+	return (a[0]<<8)|(a[1]<<0);
+}
+
+#define CLASS(p) ((*(unsigned char*)(p))>>6)
+
+unsigned long
+parseip(char *to, char *from)
+{
+	int i;
+	char *p;
+
+	p = from;
+	memset(to, 0, 4);
+	for(i = 0; i < 4 && *p; i++){
+		to[i] = strtoul(p, &p, 0);
+		if(*p == '.')
+			p++;
+	}
+	switch(CLASS(to)){
+	case 0:	/* class A - 1 byte net */
+	case 1:
+		if(i == 3){
+			to[3] = to[2];
+			to[2] = to[1];
+			to[1] = 0;
+		} else if (i == 2){
+			to[3] = to[1];
+			to[1] = 0;
+		}
+		break;
+	case 2:	/* class B - 2 byte net */
+		if(i == 3){
+			to[3] = to[2];
+			to[2] = 0;
+		}
+		break;
+	}
+	return nhgetl(to);
+}
+
+void
+csclose(Chan *c)
+{
+	free(c->aux);
+}
+
+long
+csread(Chan *c, void *a, long n, vlong offset)
+{
+	if(c->aux == nil)
+		return 0;
+	return readstr(offset, a, n, c->aux);
+}
+
+static struct
+{
+	char *name;
+	uint num;
+} tab[] = {
+	"cs", 1,
+	"echo", 7,
+	"discard", 9,
+	"systat", 11,
+	"daytime", 13,
+	"netstat", 15,
+	"chargen", 19,
+	"ftp-data", 20,
+	"ftp", 21,
+	"ssh", 22,
+	"telnet", 23,
+	"smtp", 25,
+	"time", 37,
+	"whois", 43,
+	"dns", 53,
+	"domain", 53,
+	"uucp", 64,
+	"gopher", 70,
+	"rje", 77,
+	"finger", 79,
+	"http", 80,
+	"link", 87,
+	"supdup", 95,
+	"hostnames", 101,
+	"iso-tsap", 102,
+	"x400", 103,
+	"x400-snd", 104,
+	"csnet-ns", 105,
+	"pop-2", 109,
+	"pop3", 110,
+	"portmap", 111,
+	"uucp-path", 117,
+	"nntp", 119,
+	"netbios", 139,
+	"imap4", 143,
+	"NeWS", 144,
+	"print-srv", 170,
+	"z39.50", 210,
+	"fsb", 400,
+	"sysmon", 401,
+	"proxy", 402,
+	"proxyd", 404,
+	"https", 443,
+	"cifs", 445,
+	"ssmtp", 465,
+	"rexec", 512,
+	"login", 513,
+	"shell", 514,
+	"printer", 515,
+	"courier", 530,
+	"cscan", 531,
+	"uucp", 540,
+	"snntp", 563,
+	"9fs", 564,
+	"whoami", 565,
+	"guard", 566,
+	"ticket", 567,
+	"dlsftp", 666,
+	"fmclient", 729,
+	"imaps", 993,
+	"pop3s", 995,
+	"ingreslock", 1524,
+	"pptp", 1723,
+	"nfs", 2049,
+	"webster", 2627,
+	"weather", 3000,
+	"secstore", 5356,
+	"Xdisplay", 6000,
+	"styx", 6666,
+	"mpeg", 6667,
+	"rstyx", 6668,
+	"infdb", 6669,
+	"infsigner", 6671,
+	"infcsigner", 6672,
+	"inflogin", 6673,
+	"bandt", 7330,
+	"face", 32000,
+	"dhashgate", 11978,
+	"exportfs", 17007,
+	"rexexec", 17009,
+	"ncpu", 17010,
+	"cpu", 17013,
+	"glenglenda1", 17020,
+	"glenglenda2", 17021,
+	"glenglenda3", 17022,
+	"glenglenda4", 17023,
+	"glenglenda5", 17024,
+	"glenglenda6", 17025,
+	"glenglenda7", 17026,
+	"glenglenda8", 17027,
+	"glenglenda9", 17028,
+	"glenglenda10", 17029,
+	"flyboy", 17032,
+	"dlsftp", 17033,
+	"venti", 17034,
+	"wiki", 17035,
+	"vica", 17036,
+	0
+};
+
+static int
+lookupport(char *s)
+{
+	int i;
+	char buf[10], *p;
+
+	i = strtol(s, &p, 0);
+	if(*s && *p == 0)
+		return i;
+
+	i = so_getservbyname(s, "tcp", buf);
+	if(i != -1)
+		return atoi(buf);
+	for(i=0; tab[i].name; i++)
+		if(strcmp(s, tab[i].name) == 0)
+			return tab[i].num;
+	return 0;
+}
+
+static ulong
+lookuphost(char *s)
+{
+	char to[4];
+	ulong ip;
+
+	memset(to, 0, sizeof to);
+	parseip(to, s);
+	ip = nhgetl(to);
+	if(ip != 0)
+		return ip;
+	if((s = hostlookup(s)) == nil)
+		return 0;
+	parseip(to, s);
+	ip = nhgetl(to);
+	free(s);
+	return ip;
+}
+
+long
+cswrite(Chan *c, void *a, long n, vlong offset)
+{
+	char *f[4];
+	char *s, *ns;
+	ulong ip;
+	int nf, port;
+
+	s = malloc(n+1);
+	if(s == nil)
+		error(Enomem);
+	if(waserror()){
+		free(s);
+		nexterror();
+	}
+	memmove(s, a, n);
+	s[n] = 0;
+	nf = getfields(s, f, nelem(f), 0, "!");
+	if(nf != 3)
+		error("can't translate");
+
+	port = lookupport(f[2]);
+	if(port <= 0)
+		error("no translation for port found");
+
+	ip = lookuphost(f[1]);
+	if(ip == 0)
+		error("no translation for host found");
+
+	ns = smprint("/net/%s/clone %I!%d", f[0], ip, port);
+	if(ns == nil)
+		error(Enomem);
+	free(c->aux);
+	c->aux = ns;
+	poperror();
+	free(s);
+	return n;
+}
+
+Dev ipdevtab = 
+{
+	'I',
+	"ip",
+
+	devreset,
+	ipinit,
+	devshutdown,
+	ipattach,
+	ipwalk,
+	ipstat,
+	ipopen,
+	devcreate,
+	ipclose,
+	ipread,
+	devbread,
+	ipwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
--- /dev/null
+++ b/kern/devip.h
@@ -1,0 +1,17 @@
+enum
+{
+	S_TCP,
+	S_UDP
+};
+
+int		so_socket(int type);
+void		so_connect(int, unsigned long, unsigned short);
+void		so_getsockname(int, unsigned long*, unsigned short*);
+void		so_bind(int, int, unsigned short);
+void		so_listen(int);
+int		so_accept(int, unsigned long*, unsigned short*);
+int		so_getservbyname(char*, char*, char*);
+int		so_gethostbyname(char*, char**, int);
+
+char*	hostlookup(char*);
+
--- /dev/null
+++ b/kern/devmnt.c
@@ -1,0 +1,1214 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+/*
+ * References are managed as follows:
+ * The channel to the server - a network connection or pipe - has one
+ * reference for every Chan open on the server.  The server channel has
+ * c->mux set to the Mnt used for muxing control to that server.  Mnts
+ * have no reference count; they go away when c goes away.
+ * Each channel derived from the mount point has mchan set to c,
+ * and increfs/decrefs mchan to manage references on the server
+ * connection.
+ */
+
+#define MAXRPC (IOHDRSZ+8192)
+
+struct Mntrpc
+{
+	Chan*	c;		/* Channel for whom we are working */
+	Mntrpc*	list;		/* Free/pending list */
+	Fcall	request;	/* Outgoing file system protocol message */
+	Fcall 	reply;		/* Incoming reply */
+	Mnt*	m;		/* Mount device during rpc */
+	Rendez	r;		/* Place to hang out */
+	uchar*	rpc;		/* I/O Data buffer */
+	uint		rpclen;	/* len of buffer */
+	Block	*b;		/* reply blocks */
+	char	done;		/* Rpc completed */
+	uvlong	stime;		/* start time for mnt statistics */
+	ulong	reqlen;		/* request length for mnt statistics */
+	ulong	replen;		/* reply length for mnt statistics */
+	Mntrpc*	flushed;	/* message this one flushes */
+};
+
+enum
+{
+	TAGSHIFT = 5,			/* ulong has to be 32 bits */
+	TAGMASK = (1<<TAGSHIFT)-1,
+	NMASK = (64*1024)>>TAGSHIFT,
+};
+
+struct Mntalloc
+{
+	Lock lk;
+	Mnt*	list;		/* Mount devices in use */
+	Mnt*	mntfree;	/* Free list */
+	Mntrpc*	rpcfree;
+	int	nrpcfree;
+	int	nrpcused;
+	ulong	id;
+	ulong	tagmask[NMASK];
+}mntalloc;
+
+void	mattach(Mnt*, Chan*, char*);
+Mnt*	mntchk(Chan*);
+void	mntdirfix(uchar*, Chan*);
+Mntrpc*	mntflushalloc(Mntrpc*, ulong);
+void	mntflushfree(Mnt*, Mntrpc*);
+void	mntfree(Mntrpc*);
+void	mntgate(Mnt*);
+void	mntpntfree(Mnt*);
+void	mntqrm(Mnt*, Mntrpc*);
+Mntrpc*	mntralloc(Chan*, ulong);
+long	mntrdwr(int, Chan*, void*, long, vlong);
+int	mntrpcread(Mnt*, Mntrpc*);
+void	mountio(Mnt*, Mntrpc*);
+void	mountmux(Mnt*, Mntrpc*);
+void	mountrpc(Mnt*, Mntrpc*);
+int	rpcattn(void*);
+Chan*	mntchan(void);
+
+char	Esbadstat[] = "invalid directory entry received from server";
+char Enoversion[] = "version not established for mount channel";
+
+
+void (*mntstats)(int, Chan*, uvlong, ulong);
+
+static void
+mntreset(void)
+{
+	mntalloc.id = 1;
+	mntalloc.tagmask[0] = 1;			/* don't allow 0 as a tag */
+	mntalloc.tagmask[NMASK-1] = 0x80000000UL;	/* don't allow NOTAG */
+	fmtinstall('F', fcallfmt);
+	fmtinstall('D', dirfmt);
+/* We can't install %M since eipfmt does and is used in the kernel [sape] */
+
+	cinit();
+}
+
+/*
+ * Version is not multiplexed: message sent only once per connection.
+ */
+long
+mntversion(Chan *c, char *version, int msize, int returnlen)
+{
+	Fcall f;
+	uchar *msg;
+	Mnt *m;
+	char *v;
+	long k, l;
+	uvlong oo;
+	char buf[128];
+
+	qlock(&c->umqlock);	/* make sure no one else does this until we've established ourselves */
+	if(waserror()){
+		qunlock(&c->umqlock);
+		nexterror();
+	}
+
+	/* defaults */
+	if(msize == 0)
+		msize = MAXRPC;
+	if(msize > c->iounit && c->iounit != 0)
+		msize = c->iounit;
+	v = version;
+	if(v == nil || v[0] == '\0')
+		v = VERSION9P;
+
+	/* validity */
+	if(msize < 0)
+		error("bad iounit in version call");
+	if(strncmp(v, VERSION9P, strlen(VERSION9P)) != 0)
+		error("bad 9P version specification");
+
+	m = c->mux;
+
+	if(m != nil){
+		qunlock(&c->umqlock);
+		poperror();
+
+		strecpy(buf, buf+sizeof buf, m->version);
+		k = strlen(buf);
+		if(strncmp(buf, v, k) != 0){
+			snprint(buf, sizeof buf, "incompatible 9P versions %s %s", m->version, v);
+			error(buf);
+		}
+		if(returnlen > 0){
+			if(returnlen < k)
+				error(Eshort);
+			memmove(version, buf, k);
+		}
+		return k;
+	}
+
+	f.type = Tversion;
+	f.tag = NOTAG;
+	f.msize = msize;
+	f.version = v;
+	msg = malloc(8192+IOHDRSZ);
+	if(msg == nil)
+		exhausted("version memory");
+	if(waserror()){
+		free(msg);
+		nexterror();
+	}
+	k = convS2M(&f, msg, 8192+IOHDRSZ);
+	if(k == 0)
+		error("bad fversion conversion on send");
+
+	lock(&c->ref.lk);
+	oo = c->offset;
+	c->offset += k;
+	unlock(&c->ref.lk);
+
+	l = devtab[c->type]->write(c, msg, k, oo);
+
+	if(l < k){
+		lock(&c->ref.lk);
+		c->offset -= k - l;
+		unlock(&c->ref.lk);
+		error("short write in fversion");
+	}
+
+	/* message sent; receive and decode reply */
+	k = devtab[c->type]->read(c, msg, 8192+IOHDRSZ, c->offset);
+	if(k <= 0)
+		error("EOF receiving fversion reply");
+
+	lock(&c->ref.lk);
+	c->offset += k;
+	unlock(&c->ref.lk);
+
+	l = convM2S(msg, k, &f);
+	if(l != k)
+		error("bad fversion conversion on reply");
+	if(f.type != Rversion){
+		if(f.type == Rerror)
+			error(f.ename);
+		error("unexpected reply type in fversion");
+	}
+	if(f.msize > msize)
+		error("server tries to increase msize in fversion");
+	if(f.msize<256 || f.msize>1024*1024)
+		error("nonsense value of msize in fversion");
+	if(strncmp(f.version, v, strlen(f.version)) != 0)
+		error("bad 9P version returned from server");
+
+	/* now build Mnt associated with this connection */
+	lock(&mntalloc.lk);
+	m = mntalloc.mntfree;
+	if(m != 0)
+		mntalloc.mntfree = m->list;
+	else {
+		m = malloc(sizeof(Mnt));
+		if(m == 0) {
+			unlock(&mntalloc.lk);
+			exhausted("mount devices");
+		}
+	}
+	m->list = mntalloc.list;
+	mntalloc.list = m;
+	m->version = nil;
+	kstrdup(&m->version, f.version);
+	m->id = mntalloc.id++;
+	m->q = qopen(10*MAXRPC, 0, nil, nil);
+	m->msize = f.msize;
+	unlock(&mntalloc.lk);
+
+	poperror();	/* msg */
+	free(msg);
+
+	lock(&m->lk);
+	m->queue = 0;
+	m->rip = 0;
+
+	c->flag |= CMSG;
+	c->mux = m;
+	m->c = c;
+	unlock(&m->lk);
+
+	poperror();	/* c */
+	qunlock(&c->umqlock);
+
+	k = strlen(f.version);
+	if(returnlen > 0){
+		if(returnlen < k)
+			error(Eshort);
+		memmove(version, f.version, k);
+	}
+
+	return k;
+}
+
+Chan*
+mntauth(Chan *c, char *spec)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	m = c->mux;
+
+	if(m == nil){
+		mntversion(c, VERSION9P, MAXRPC, 0);
+		m = c->mux;
+		if(m == nil)
+			error(Enoversion);
+	}
+
+	c = mntchan();
+	if(waserror()) {
+		/* Close must not be called since it will
+		 * call mnt recursively
+		 */
+		chanfree(c);
+		nexterror();
+	}
+
+	r = mntralloc(0, m->msize);
+
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+
+	r->request.type = Tauth;
+	r->request.afid = c->fid;
+	r->request.uname = up->user;
+	r->request.aname = spec;
+	mountrpc(m, r);
+
+	c->qid = r->reply.aqid;
+	c->mchan = m->c;
+	incref(&m->c->ref);
+	c->mqid = c->qid;
+	c->mode = ORDWR;
+
+	poperror();	/* r */
+	mntfree(r);
+
+	poperror();	/* c */
+
+	return c;
+
+}
+
+static Chan*
+mntattach(char *muxattach)
+{
+	Mnt *m;
+	Chan *c;
+	Mntrpc *r;
+	struct bogus{
+		Chan	*chan;
+		Chan	*authchan;
+		char	*spec;
+		int	flags;
+	}bogus;
+
+	bogus = *((struct bogus *)muxattach);
+	c = bogus.chan;
+
+	m = c->mux;
+
+	if(m == nil){
+		mntversion(c, nil, 0, 0);
+		m = c->mux;
+		if(m == nil)
+			error(Enoversion);
+	}
+
+	c = mntchan();
+	if(waserror()) {
+		/* Close must not be called since it will
+		 * call mnt recursively
+		 */
+		chanfree(c);
+		nexterror();
+	}
+
+	r = mntralloc(0, m->msize);
+
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+
+	r->request.type = Tattach;
+	r->request.fid = c->fid;
+	if(bogus.authchan == nil)
+		r->request.afid = NOFID;
+	else
+		r->request.afid = bogus.authchan->fid;
+	r->request.uname = up->user;
+	r->request.aname = bogus.spec;
+	mountrpc(m, r);
+
+	c->qid = r->reply.qid;
+	c->mchan = m->c;
+	incref(&m->c->ref);
+	c->mqid = c->qid;
+
+	poperror();	/* r */
+	mntfree(r);
+
+	poperror();	/* c */
+
+	if(bogus.flags&MCACHE)
+		c->flag |= CCACHE;
+	return c;
+}
+
+Chan*
+mntchan(void)
+{
+	Chan *c;
+
+	c = devattach('M', 0);
+	lock(&mntalloc.lk);
+	c->dev = mntalloc.id++;
+	unlock(&mntalloc.lk);
+
+	if(c->mchan)
+		panic("mntchan non-zero %p", c->mchan);
+	return c;
+}
+
+static Walkqid*
+mntwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	int i, alloc;
+	Mnt *m;
+	Mntrpc *r;
+	Walkqid *wq;
+
+	if(nc != nil)
+		print("mntwalk: nc != nil\n");
+	if(nname > MAXWELEM)
+		error("devmnt: too many name elements");
+	alloc = 0;
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	if(waserror()){
+		if(alloc && wq->clone!=nil)
+			cclose(wq->clone);
+		free(wq);
+		return nil;
+	}
+
+	alloc = 0;
+	m = mntchk(c);
+	r = mntralloc(c, m->msize);
+	if(nc == nil){
+		nc = devclone(c);
+		/*
+		 * Until the other side accepts this fid, we can't mntclose it.
+		 * Therefore set type to 0 for now; rootclose is known to be safe.
+		 */
+		nc->type = 0;
+		alloc = 1;
+	}
+	wq->clone = nc;
+
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+	r->request.type = Twalk;
+	r->request.fid = c->fid;
+	r->request.newfid = nc->fid;
+	r->request.nwname = nname;
+	memmove(r->request.wname, name, nname*sizeof(char*));
+
+	mountrpc(m, r);
+
+	if(r->reply.nwqid > nname)
+		error("too many QIDs returned by walk");
+	if(r->reply.nwqid < nname){
+		if(alloc)
+			cclose(nc);
+		wq->clone = nil;
+		if(r->reply.nwqid == 0){
+			free(wq);
+			wq = nil;
+			goto Return;
+		}
+	}
+
+	/* move new fid onto mnt device and update its qid */
+	if(wq->clone != nil){
+		if(wq->clone != c){
+			wq->clone->type = c->type;
+			wq->clone->mchan = c->mchan;
+			incref(&c->mchan->ref);
+		}
+		if(r->reply.nwqid > 0)
+			wq->clone->qid = r->reply.wqid[r->reply.nwqid-1];
+	}
+	wq->nqid = r->reply.nwqid;
+	for(i=0; i<wq->nqid; i++)
+		wq->qid[i] = r->reply.wqid[i];
+
+    Return:
+	poperror();
+	mntfree(r);
+	poperror();
+	return wq;
+}
+
+static int
+mntstat(Chan *c, uchar *dp, int n)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	if(n < BIT16SZ)
+		error(Eshortstat);
+	m = mntchk(c);
+	r = mntralloc(c, m->msize);
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+	r->request.type = Tstat;
+	r->request.fid = c->fid;
+	mountrpc(m, r);
+
+	if(r->reply.nstat >= 1<<(8*BIT16SZ))
+		error("returned stat buffer count too large");
+
+	if(r->reply.nstat > n){
+		/*
+		 * 12/31/2002 RSC
+		 * 
+		 * This should be nstat-2, which is the first two
+		 * bytes of the stat buffer.  But dirstat and dirfstat
+		 * depended on getting the full nstat (they didn't
+		 * add BIT16SZ themselves).  I fixed dirstat and dirfstat
+		 * but am leaving this unchanged for now.  After a
+		 * few months, once enough of the relevant binaries
+		 * have been recompiled for other reasons, we can
+		 * change this to nstat-2.  Devstat gets this right
+		 * (via convD2M).
+		 */
+		/* doesn't fit; just patch the count and return */
+		PBIT16((uchar*)dp, r->reply.nstat);
+		n = BIT16SZ;
+	}else{
+		n = r->reply.nstat;
+		memmove(dp, r->reply.stat, n);
+		validstat(dp, n);
+		mntdirfix(dp, c);
+	}
+	poperror();
+	mntfree(r);
+	return n;
+}
+
+static Chan*
+mntopencreate(int type, Chan *c, char *name, int omode, ulong perm)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	m = mntchk(c);
+	r = mntralloc(c, m->msize);
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+	r->request.type = type;
+	r->request.fid = c->fid;
+	r->request.mode = omode;
+	if(type == Tcreate){
+		r->request.perm = perm;
+		r->request.name = name;
+	}
+	mountrpc(m, r);
+
+	c->qid = r->reply.qid;
+	c->offset = 0;
+	c->mode = openmode(omode);
+	c->iounit = r->reply.iounit;
+	if(c->iounit == 0 || c->iounit > m->msize-IOHDRSZ)
+		c->iounit = m->msize-IOHDRSZ;
+	c->flag |= COPEN;
+	poperror();
+	mntfree(r);
+
+	if(c->flag & CCACHE)
+		copen(c);
+
+	return c;
+}
+
+static Chan*
+mntopen(Chan *c, int omode)
+{
+	return mntopencreate(Topen, c, nil, omode, 0);
+}
+
+static void
+mntcreate(Chan *c, char *name, int omode, ulong perm)
+{
+	mntopencreate(Tcreate, c, name, omode, perm);
+}
+
+static void
+mntclunk(Chan *c, int t)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	m = mntchk(c);
+	r = mntralloc(c, m->msize);
+	if(waserror()){
+		mntfree(r);
+		nexterror();
+	}
+
+	r->request.type = t;
+	r->request.fid = c->fid;
+	mountrpc(m, r);
+	mntfree(r);
+	poperror();
+}
+
+void
+muxclose(Mnt *m)
+{
+	Mntrpc *q, *r;
+
+	for(q = m->queue; q; q = r) {
+		r = q->list;
+		mntfree(q);
+	}
+	m->id = 0;
+	free(m->version);
+	m->version = nil;
+	mntpntfree(m);
+}
+
+void
+mntpntfree(Mnt *m)
+{
+	Mnt *f, **l;
+	Queue *q;
+
+	lock(&mntalloc.lk);
+	l = &mntalloc.list;
+	for(f = *l; f; f = f->list) {
+		if(f == m) {
+			*l = m->list;
+			break;
+		}
+		l = &f->list;
+	}
+	m->list = mntalloc.mntfree;
+	mntalloc.mntfree = m;
+	q = m->q;
+	unlock(&mntalloc.lk);
+
+	qfree(q);
+}
+
+static void
+mntclose(Chan *c)
+{
+	mntclunk(c, Tclunk);
+}
+
+static void
+mntremove(Chan *c)
+{
+	mntclunk(c, Tremove);
+}
+
+static int
+mntwstat(Chan *c, uchar *dp, int n)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	m = mntchk(c);
+	r = mntralloc(c, m->msize);
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+	r->request.type = Twstat;
+	r->request.fid = c->fid;
+	r->request.nstat = n;
+	r->request.stat = dp;
+	mountrpc(m, r);
+	poperror();
+	mntfree(r);
+	return n;
+}
+
+static long
+mntread(Chan *c, void *buf, long n, vlong off)
+{
+	uchar *p, *e;
+	int nc, cache, isdir, dirlen;
+
+	isdir = 0;
+	cache = c->flag & CCACHE;
+	if(c->qid.type & QTDIR) {
+		cache = 0;
+		isdir = 1;
+	}
+
+	p = buf;
+	if(cache) {
+		nc = cread(c, buf, n, off);
+		if(nc > 0) {
+			n -= nc;
+			if(n == 0)
+				return nc;
+			p += nc;
+			off += nc;
+		}
+		n = mntrdwr(Tread, c, p, n, off);
+		cupdate(c, p, n, off);
+		return n + nc;
+	}
+
+	n = mntrdwr(Tread, c, buf, n, off);
+	if(isdir) {
+		for(e = &p[n]; p+BIT16SZ < e; p += dirlen){
+			dirlen = BIT16SZ+GBIT16(p);
+			if(p+dirlen > e)
+				break;
+			validstat(p, dirlen);
+			mntdirfix(p, c);
+		}
+		if(p != e)
+			error(Esbadstat);
+	}
+	return n;
+}
+
+static long
+mntwrite(Chan *c, void *buf, long n, vlong off)
+{
+	return mntrdwr(Twrite, c, buf, n, off);
+}
+
+long
+mntrdwr(int type, Chan *c, void *buf, long n, vlong off)
+{
+	Mnt *m;
+ 	Mntrpc *r;
+	char *uba;
+	int cache;
+	ulong cnt, nr, nreq;
+
+	m = mntchk(c);
+	uba = buf;
+	cnt = 0;
+	cache = c->flag & CCACHE;
+	if(c->qid.type & QTDIR)
+		cache = 0;
+	for(;;) {
+		r = mntralloc(c, m->msize);
+		if(waserror()) {
+			mntfree(r);
+			nexterror();
+		}
+		r->request.type = type;
+		r->request.fid = c->fid;
+		r->request.offset = off;
+		r->request.data = uba;
+		nr = n;
+		if(nr > m->msize-IOHDRSZ)
+			nr = m->msize-IOHDRSZ;
+		r->request.count = nr;
+		mountrpc(m, r);
+		nreq = r->request.count;
+		nr = r->reply.count;
+		if(nr > nreq)
+			nr = nreq;
+
+		if(type == Tread)
+			r->b = bl2mem((uchar*)uba, r->b, nr);
+		else if(cache)
+			cwrite(c, (uchar*)uba, nr, off);
+
+		poperror();
+		mntfree(r);
+		off += nr;
+		uba += nr;
+		cnt += nr;
+		n -= nr;
+		if(nr != nreq || n == 0)
+			break;
+	}
+	return cnt;
+}
+
+void
+mountrpc(Mnt *m, Mntrpc *r)
+{
+	char *sn, *cn;
+	int t;
+
+	r->reply.tag = 0;
+	r->reply.type = Tmax;	/* can't ever be a valid message type */
+
+	mountio(m, r);
+
+	t = r->reply.type;
+	switch(t) {
+	case Rerror:
+		error(r->reply.ename);
+	case Rflush:
+		error(Eintr);
+	default:
+		if(t == r->request.type+1)
+			break;
+		sn = "?";
+		if(m->c->name != nil)
+			sn = m->c->name->s;
+		cn = "?";
+		if(r->c != nil && r->c->name != nil)
+			cn = r->c->name->s;
+		print("mnt: proc %lud: mismatch from %s %s rep 0x%lux tag %d fid %d T%d R%d rp %d\n",
+			up->pid, sn, cn,
+			r, r->request.tag, r->request.fid, r->request.type,
+			r->reply.type, r->reply.tag);
+		error(Emountrpc);
+	}
+}
+
+void
+mountio(Mnt *m, Mntrpc *r)
+{
+	int n;
+
+	while(waserror()) {
+		if(m->rip == up)
+			mntgate(m);
+		if(strcmp(up->errstr, Eintr) != 0){
+			mntflushfree(m, r);
+			nexterror();
+		}
+		r = mntflushalloc(r, m->msize);
+	}
+
+	lock(&m->lk);
+	r->m = m;
+	r->list = m->queue;
+	m->queue = r;
+	unlock(&m->lk);
+
+	/* Transmit a file system rpc */
+	if(m->msize == 0)
+		panic("msize");
+	n = convS2M(&r->request, r->rpc, m->msize);
+	if(n < 0)
+		panic("bad message type in mountio");
+	if(devtab[m->c->type]->write(m->c, r->rpc, n, 0) != n)
+		error(Emountrpc);
+	r->stime = fastticks(nil);
+	r->reqlen = n;
+
+	/* Gate readers onto the mount point one at a time */
+	for(;;) {
+		lock(&m->lk);
+		if(m->rip == 0)
+			break;
+		unlock(&m->lk);
+		sleep(&r->r, rpcattn, r);
+		if(r->done){
+			poperror();
+			mntflushfree(m, r);
+			return;
+		}
+	}
+	m->rip = up;
+	unlock(&m->lk);
+	while(r->done == 0) {
+		if(mntrpcread(m, r) < 0)
+			error(Emountrpc);
+		mountmux(m, r);
+	}
+	mntgate(m);
+	poperror();
+	mntflushfree(m, r);
+}
+
+static int
+doread(Mnt *m, int len)
+{
+	Block *b;
+
+	while(qlen(m->q) < len){
+		b = devtab[m->c->type]->bread(m->c, m->msize, 0);
+		if(b == nil)
+			return -1;
+		if(BLEN(b) == 0){
+			freeblist(b);
+			return -1;
+		}
+		qaddlist(m->q, b);
+	}
+	return 0;
+}
+
+int
+mntrpcread(Mnt *m, Mntrpc *r)
+{
+	int i, t, len, hlen;
+	Block *b, **l, *nb;
+
+	r->reply.type = 0;
+	r->reply.tag = 0;
+
+	/* read at least length, type, and tag and pullup to a single block */
+	if(doread(m, BIT32SZ+BIT8SZ+BIT16SZ) < 0)
+		return -1;
+	nb = pullupqueue(m->q, BIT32SZ+BIT8SZ+BIT16SZ);
+
+	/* read in the rest of the message, avoid rediculous (for now) message sizes */
+	len = GBIT32(nb->rp);
+	if(len > m->msize){
+		qdiscard(m->q, qlen(m->q));
+		return -1;
+	}
+	if(doread(m, len) < 0)
+		return -1;
+
+	/* pullup the header (i.e. everything except data) */
+	t = nb->rp[BIT32SZ];
+	switch(t){
+	case Rread:
+		hlen = BIT32SZ+BIT8SZ+BIT16SZ+BIT32SZ;
+		break;
+	default:
+		hlen = len;
+		break;
+	}
+	nb = pullupqueue(m->q, hlen);
+
+	if(convM2S(nb->rp, len, &r->reply) <= 0){
+		/* bad message, dump it */
+		print("mntrpcread: convM2S failed\n");
+		qdiscard(m->q, len);
+		return -1;
+	}
+
+	/* hang the data off of the fcall struct */
+	l = &r->b;
+	*l = nil;
+	do {
+		b = qremove(m->q);
+		if(hlen > 0){
+			b->rp += hlen;
+			len -= hlen;
+			hlen = 0;
+		}
+		i = BLEN(b);
+		if(i <= len){
+			len -= i;
+			*l = b;
+			l = &(b->next);
+		} else {
+			/* split block and put unused bit back */
+			nb = allocb(i-len);
+			memmove(nb->wp, b->rp+len, i-len);
+			b->wp = b->rp+len;
+			nb->wp += i-len;
+			qputback(m->q, nb);
+			*l = b;
+			return 0;
+		}
+	}while(len > 0);
+
+	return 0;
+}
+
+void
+mntgate(Mnt *m)
+{
+	Mntrpc *q;
+
+	lock(&m->lk);
+	m->rip = 0;
+	for(q = m->queue; q; q = q->list) {
+		if(q->done == 0)
+		if(wakeup(&q->r))
+			break;
+	}
+	unlock(&m->lk);
+}
+
+void
+mountmux(Mnt *m, Mntrpc *r)
+{
+	Mntrpc **l, *q;
+
+	lock(&m->lk);
+	l = &m->queue;
+	for(q = *l; q; q = q->list) {
+		/* look for a reply to a message */
+		if(q->request.tag == r->reply.tag) {
+			*l = q->list;
+			if(q != r) {
+				/*
+				 * Completed someone else.
+				 * Trade pointers to receive buffer.
+				 */
+				q->reply = r->reply;
+				q->b = r->b;
+				r->b = nil;
+			}
+			q->done = 1;
+			unlock(&m->lk);
+			if(mntstats != nil)
+				(*mntstats)(q->request.type,
+					m->c, q->stime,
+					q->reqlen + r->replen);
+			if(q != r)
+				wakeup(&q->r);
+			return;
+		}
+		l = &q->list;
+	}
+	unlock(&m->lk);
+	print("unexpected reply tag %ud; type %d\n", r->reply.tag, r->reply.type);
+}
+
+/*
+ * Create a new flush request and chain the previous
+ * requests from it
+ */
+Mntrpc*
+mntflushalloc(Mntrpc *r, ulong iounit)
+{
+	Mntrpc *fr;
+
+	fr = mntralloc(0, iounit);
+
+	fr->request.type = Tflush;
+	if(r->request.type == Tflush)
+		fr->request.oldtag = r->request.oldtag;
+	else
+		fr->request.oldtag = r->request.tag;
+	fr->flushed = r;
+
+	return fr;
+}
+
+/*
+ *  Free a chain of flushes.  Remove each unanswered
+ *  flush and the original message from the unanswered
+ *  request queue.  Mark the original message as done
+ *  and if it hasn't been answered set the reply to to
+ *  Rflush.
+ */
+void
+mntflushfree(Mnt *m, Mntrpc *r)
+{
+	Mntrpc *fr;
+
+	while(r){
+		fr = r->flushed;
+		if(!r->done){
+			r->reply.type = Rflush;
+			mntqrm(m, r);
+		}
+		if(fr)
+			mntfree(r);
+		r = fr;
+	}
+}
+
+int
+alloctag(void)
+{
+	int i, j;
+	ulong v;
+
+	for(i = 0; i < NMASK; i++){
+		v = mntalloc.tagmask[i];
+		if(v == ~0UL)
+			continue;
+		for(j = 0; j < 1<<TAGSHIFT; j++)
+			if((v & (1<<j)) == 0){
+				mntalloc.tagmask[i] |= 1<<j;
+				return (i<<TAGSHIFT) + j;
+			}
+	}
+	panic("no friggin tags left");
+	return NOTAG;
+}
+
+void
+freetag(int t)
+{
+	mntalloc.tagmask[t>>TAGSHIFT] &= ~(1<<(t&TAGMASK));
+}
+
+Mntrpc*
+mntralloc(Chan *c, ulong msize)
+{
+	Mntrpc *new;
+
+	lock(&mntalloc.lk);
+	new = mntalloc.rpcfree;
+	if(new == nil){
+		new = malloc(sizeof(Mntrpc));
+		if(new == nil) {
+			unlock(&mntalloc.lk);
+			exhausted("mount rpc header");
+		}
+		/*
+		 * The header is split from the data buffer as
+		 * mountmux may swap the buffer with another header.
+		 */
+		new->rpc = mallocz(msize, 0);
+		if(new->rpc == nil){
+			free(new);
+			unlock(&mntalloc.lk);
+			exhausted("mount rpc buffer");
+		}
+		new->rpclen = msize;
+		new->request.tag = alloctag();
+	}
+	else {
+		mntalloc.rpcfree = new->list;
+		mntalloc.nrpcfree--;
+		if(new->rpclen < msize){
+			free(new->rpc);
+			new->rpc = mallocz(msize, 0);
+			if(new->rpc == nil){
+				free(new);
+				mntalloc.nrpcused--;
+				unlock(&mntalloc.lk);
+				exhausted("mount rpc buffer");
+			}
+			new->rpclen = msize;
+		}
+	}
+	mntalloc.nrpcused++;
+	unlock(&mntalloc.lk);
+	new->c = c;
+	new->done = 0;
+	new->flushed = nil;
+	new->b = nil;
+	return new;
+}
+
+void
+mntfree(Mntrpc *r)
+{
+	if(r->b != nil)
+		freeblist(r->b);
+	lock(&mntalloc.lk);
+	if(mntalloc.nrpcfree >= 10){
+		free(r->rpc);
+		free(r);
+		freetag(r->request.tag);
+	}
+	else{
+		r->list = mntalloc.rpcfree;
+		mntalloc.rpcfree = r;
+		mntalloc.nrpcfree++;
+	}
+	mntalloc.nrpcused--;
+	unlock(&mntalloc.lk);
+}
+
+void
+mntqrm(Mnt *m, Mntrpc *r)
+{
+	Mntrpc **l, *f;
+
+	lock(&m->lk);
+	r->done = 1;
+
+	l = &m->queue;
+	for(f = *l; f; f = f->list) {
+		if(f == r) {
+			*l = r->list;
+			break;
+		}
+		l = &f->list;
+	}
+	unlock(&m->lk);
+}
+
+Mnt*
+mntchk(Chan *c)
+{
+	Mnt *m;
+
+	/* This routine is mostly vestiges of prior lives; now it's just sanity checking */
+
+	if(c->mchan == nil)
+		panic("mntchk 1: nil mchan c %s\n", c2name(c));
+
+	m = c->mchan->mux;
+
+	if(m == nil)
+		print("mntchk 2: nil mux c %s c->mchan %s \n", c2name(c), c2name(c->mchan));
+
+	/*
+	 * Was it closed and reused (was error(Eshutdown); now, it can't happen)
+	 */
+	if(m->id == 0 || m->id >= c->dev)
+		panic("mntchk 3: can't happen");
+
+	return m;
+}
+
+/*
+ * Rewrite channel type and dev for in-flight data to
+ * reflect local values.  These entries are known to be
+ * the first two in the Dir encoding after the count.
+ */
+void
+mntdirfix(uchar *dirbuf, Chan *c)
+{
+	uint r;
+
+	r = devtab[c->type]->dc;
+	dirbuf += BIT16SZ;	/* skip count */
+	PBIT16(dirbuf, r);
+	dirbuf += BIT16SZ;
+	PBIT32(dirbuf, c->dev);
+}
+
+int
+rpcattn(void *v)
+{
+	Mntrpc *r;
+
+	r = v;
+	return r->done || r->m->rip == 0;
+}
+
+Dev mntdevtab = {
+	'M',
+	"mnt",
+
+	mntreset,
+	devinit,
+	devshutdown,
+	mntattach,
+	mntwalk,
+	mntstat,
+	mntopen,
+	mntcreate,
+	mntclose,
+	mntread,
+	devbread,
+	mntwrite,
+	devbwrite,
+	mntremove,
+	mntwstat,
+};
--- /dev/null
+++ b/kern/devmouse.c
@@ -1,0 +1,237 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	"draw.h"
+#include	"memdraw.h"
+#include	"screen.h"
+
+int	mousequeue;
+
+Mouseinfo	mouse;
+Cursorinfo	cursor;
+
+static int	mousechanged(void*);
+
+enum{
+	Qdir,
+	Qcursor,
+	Qmouse
+};
+
+Dirtab mousedir[]={
+	".",		{Qdir, 0, QTDIR},	0,	DMDIR|0555,	
+	"cursor",	{Qcursor},	0,			0666,
+	"mouse",	{Qmouse},	0,			0666,
+};
+
+#define	NMOUSE	(sizeof(mousedir)/sizeof(Dirtab))
+
+static Chan*
+mouseattach(char *spec)
+{
+	return devattach('m', spec);
+}
+
+static Walkqid*
+mousewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, mousedir, NMOUSE, devgen);
+}
+
+static int
+mousestat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, mousedir, NMOUSE, devgen);
+}
+
+static Chan*
+mouseopen(Chan *c, int omode)
+{
+	switch((long)c->qid.path){
+	case Qdir:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qmouse:
+		lock(&mouse.lk);
+		if(mouse.open){
+			unlock(&mouse.lk);
+			error(Einuse);
+		}
+		mouse.open = 1;
+		unlock(&mouse.lk);
+		break;
+	}
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+void
+mouseclose(Chan *c)
+{
+	if(!(c->flag&COPEN))
+		return;
+
+	switch((long)c->qid.path) {
+	case Qmouse:
+		lock(&mouse.lk);
+		mouse.open = 0;
+		unlock(&mouse.lk);
+		cursorarrow();
+	}
+}
+
+
+long
+mouseread(Chan *c, void *va, long n, vlong offset)
+{
+	char buf[4*12+1];
+	uchar *p;
+	int i, nn;
+	ulong msec;
+/*	static int map[8] = {0, 4, 2, 6, 1, 5, 3, 7 };	*/
+
+	p = va;
+	switch((long)c->qid.path){
+	case Qdir:
+		return devdirread(c, va, n, mousedir, NMOUSE, devgen);
+
+	case Qcursor:
+		if(offset != 0)
+			return 0;
+		if(n < 2*4+2*2*16)
+			error(Eshort);
+		n = 2*4+2*2*16;
+		lock(&cursor.lk);
+		BPLONG(p+0, cursor.offset.x);
+		BPLONG(p+4, cursor.offset.y);
+		memmove(p+8, cursor.clr, 2*16);
+		memmove(p+40, cursor.set, 2*16);
+		unlock(&cursor.lk);
+		return n;
+
+	case Qmouse:
+		while(mousechanged(0) == 0)
+			sleep(&mouse.r, mousechanged, 0);
+
+		lock(&screen.lk);
+		if(screen.reshaped) {
+			screen.reshaped = 0;
+			sprint(buf, "t%11d %11d", 0, ticks());
+			if(n > 1+2*12)
+				n = 1+2*12;
+			memmove(va, buf, n);
+			unlock(&screen.lk);
+			return n;
+		}
+		unlock(&screen.lk);
+
+		lock(&mouse.lk);
+		i = mouse.ri;
+		nn = (mouse.wi + Mousequeue - i) % Mousequeue;
+		if(nn < 1)
+			panic("empty mouse queue");
+		msec = ticks();
+		while(nn > 1) {
+			if(mouse.queue[i].msec + Mousewindow > msec)
+				break;
+			i = (i+1)%Mousequeue;
+			nn--;
+		}
+		sprint(buf, "m%11d %11d %11d %11d",
+			mouse.queue[i].xy.x,
+			mouse.queue[i].xy.y,
+			mouse.queue[i].buttons,
+			mouse.queue[i].msec);
+		mouse.ri = (i+1)%Mousequeue;
+		unlock(&mouse.lk);
+		if(n > 1+4*12)
+			n = 1+4*12;
+		memmove(va, buf, n);
+		return n;
+	}
+	return 0;
+}
+
+long
+mousewrite(Chan *c, void *va, long n, vlong offset)
+{
+	char *p;
+	Point pt;
+	char buf[64];
+
+	USED(offset);
+
+	p = va;
+	switch((long)c->qid.path){
+	case Qdir:
+		error(Eisdir);
+
+	case Qcursor:
+		if(n < 2*4+2*2*16){
+			cursorarrow();
+		}else{
+			n = 2*4+2*2*16;
+			lock(&cursor.lk);
+			cursor.offset.x = BGLONG(p+0);
+			cursor.offset.y = BGLONG(p+4);
+			memmove(cursor.clr, p+8, 2*16);
+			memmove(cursor.set, p+40, 2*16);
+			unlock(&cursor.lk);
+			setcursor();
+		}
+		return n;
+
+	case Qmouse:
+		if(n > sizeof buf-1)
+			n = sizeof buf -1;
+		memmove(buf, va, n);
+		buf[n] = 0;
+		p = 0;
+		pt.x = strtoul(buf+1, &p, 0);
+		if(p == 0)
+			error(Eshort);
+		pt.y = strtoul(p, 0, 0);
+		if(ptinrect(pt, gscreen->r))
+			mouseset(pt);
+		return n;
+	}
+
+	error(Egreg);
+	return -1;
+}
+
+int
+mousechanged(void *a)
+{
+	USED(a);
+
+	return mouse.ri != mouse.wi || screen.reshaped;
+}
+
+Dev mousedevtab = {
+	'm',
+	"mouse",
+
+	devreset,
+	devinit,
+	devshutdown,
+	mouseattach,
+	mousewalk,
+	mousestat,
+	mouseopen,
+	devcreate,
+	mouseclose,
+	mouseread,
+	devbread,
+	mousewrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
--- /dev/null
+++ b/kern/devntfs.c
@@ -1,0 +1,704 @@
+#include	<windows.h>
+#include	<sys/types.h>
+#include	<sys/stat.h>
+#include	<fcntl.h>
+
+#ifndef NAME_MAX
+#	define NAME_MAX 256
+#endif
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+typedef struct DIR	DIR;
+typedef	struct Ufsinfo	Ufsinfo;
+
+enum
+{
+	NUID	= 256,
+	NGID	= 256,
+	MAXPATH	= 1024,
+	MAXCOMP	= 128
+};
+
+struct DIR
+{
+	HANDLE	handle;
+	char*	path;
+	int	index;
+	WIN32_FIND_DATA	wfd;
+};
+
+struct Ufsinfo
+{
+	int	mode;
+	int	fd;
+	int	uid;
+	int	gid;
+	DIR*	dir;
+	ulong	offset;
+	QLock	oq;
+	char nextname[NAME_MAX];
+};
+
+DIR*	opendir(char*);
+int	readdir(char*, DIR*);
+void	closedir(DIR*);
+void	rewinddir(DIR*);
+
+char	*base = "c:/.";
+
+static	Qid	fsqid(char*, struct stat *);
+static	void	fspath(Chan*, char*, char*);
+// static	void	fsperm(Chan*, int);
+static	ulong	fsdirread(Chan*, uchar*, int, ulong);
+static	int	fsomode(int);
+static  int	chown(char *path, int uid, int);
+static	int	link(char *path, char *next);
+
+/* clumsy hack, but not worse than the Path stuff in the last one */
+static char*
+uc2name(Chan *c)
+{
+	char *s;
+
+	if(c->name == nil)
+		return "/";
+	s = c2name(c);
+	if(s[0]=='#' && s[1]=='U')
+		return s+2;
+	return s;
+}
+
+static char*
+lastelem(Chan *c)
+{
+	char *s, *t;
+
+	s = uc2name(c);
+	if((t = strrchr(s, '/')) == nil)
+		return s;
+	if(t[1] == 0)
+		return t;
+	return t+1;
+}
+	
+static Chan*
+fsattach(void *spec)
+{
+	Chan *c;
+	struct stat stbuf;
+	static int devno;
+	Ufsinfo *uif;
+
+	if(stat(base, &stbuf) < 0)
+		error(strerror(errno));
+
+	c = devattach('U', spec);
+
+	uif = mallocz(sizeof(Ufsinfo), 1);
+	uif->gid = stbuf.st_gid;
+	uif->uid = stbuf.st_uid;
+	uif->mode = stbuf.st_mode;
+
+	c->aux = uif;
+	c->dev = devno++;
+	c->qid.type = QTDIR;
+/*print("fsattach %s\n", c2name(c));*/
+
+	return c;
+}
+
+static Chan*
+fsclone(Chan *c, Chan *nc)
+{
+	Ufsinfo *uif;
+
+	uif = mallocz(sizeof(Ufsinfo), 1);
+	*uif = *(Ufsinfo*)c->aux;
+	nc->aux = uif;
+
+	return nc;
+}
+
+static int
+fswalk1(Chan *c, char *name)
+{
+	struct stat stbuf;
+	char path[MAXPATH];
+	Ufsinfo *uif;
+
+	fspath(c, name, path);
+
+	/*	print("** fs walk '%s' -> %s\n", path, name); /**/
+
+	if(stat(path, &stbuf) < 0)
+		return 0;
+
+	uif = c->aux;
+
+	uif->gid = stbuf.st_gid;
+	uif->uid = stbuf.st_uid;
+	uif->mode = stbuf.st_mode;
+
+	c->qid = fsqid(path, &stbuf);
+
+	return 1;
+}
+
+extern Cname* addelem(Cname*, char*);
+
+static Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	int i;
+	Cname *cname;
+	Walkqid *wq;
+
+	if(nc != nil)
+		panic("fswalk: nc != nil");
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	nc = devclone(c);
+	cname = c->name;
+	incref(&cname->ref);
+
+	fsclone(c, nc);
+	wq->clone = nc;
+	for(i=0; i<nname; i++){
+		nc->name = cname;
+		if(fswalk1(nc, name[i]) == 0)
+			break;
+		cname = addelem(cname, name[i]);
+		wq->qid[i] = nc->qid;
+	}
+	nc->name = nil;
+	cnameclose(cname);
+	if(i != nname){
+		cclose(nc);
+		wq->clone = nil;
+	}
+	wq->nqid = i;
+	return wq;
+}
+	
+static int
+fsstat(Chan *c, uchar *buf, int n)
+{
+	Dir d;
+	struct stat stbuf;
+	char path[MAXPATH];
+
+	if(n < BIT16SZ)
+		error(Eshortstat);
+
+	fspath(c, 0, path);
+	if(stat(path, &stbuf) < 0)
+		error(strerror(errno));
+
+	d.name = lastelem(c);
+	d.uid = "unknown";
+	d.gid = "unknown";
+	d.muid = "unknown";
+	d.qid = c->qid;
+	d.mode = (c->qid.type<<24)|(stbuf.st_mode&0777);
+	d.atime = stbuf.st_atime;
+	d.mtime = stbuf.st_mtime;
+	d.length = stbuf.st_size;
+	d.type = 'U';
+	d.dev = c->dev;
+	return convD2M(&d, buf, n);
+}
+
+static Chan*
+fsopen(Chan *c, int mode)
+{
+	char path[MAXPATH];
+	int m, isdir;
+	Ufsinfo *uif;
+
+/*print("fsopen %s\n", c2name(c));*/
+	m = mode & (OTRUNC|3);
+	switch(m) {
+	case 0:
+		break;
+	case 1:
+	case 1|16:
+		break;
+	case 2:	
+	case 0|16:
+	case 2|16:
+		break;
+	case 3:
+		break;
+	default:
+		error(Ebadarg);
+	}
+
+	isdir = c->qid.type & QTDIR;
+
+	if(isdir && mode != OREAD)
+		error(Eperm);
+
+	m = fsomode(m & 3);
+	c->mode = openmode(mode);
+
+	uif = c->aux;
+
+	fspath(c, 0, path);
+	if(isdir) {
+		uif->dir = opendir(path);
+		if(uif->dir == 0)
+			error(strerror(errno));
+	}	
+	else {
+		if(mode & OTRUNC)
+			m |= O_TRUNC;
+		uif->fd = open(path, m|_O_BINARY, 0666);
+
+		if(uif->fd < 0)
+			error(strerror(errno));
+	}
+	uif->offset = 0;
+
+	c->offset = 0;
+	c->flag |= COPEN;
+	return c;
+}
+
+static void
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+	int fd, m;
+	char path[MAXPATH];
+	struct stat stbuf;
+	Ufsinfo *uif;
+
+	m = fsomode(mode&3);
+
+	fspath(c, name, path);
+
+	uif = c->aux;
+
+	if(perm & DMDIR) {
+		if(m)
+			error(Eperm);
+
+		if(mkdir(path) < 0)
+			error(strerror(errno));
+
+		fd = open(path, 0);
+		if(fd >= 0) {
+			chmod(path, perm & 0777);
+			chown(path, uif->uid, uif->uid);
+		}
+		close(fd);
+
+		uif->dir = opendir(path);
+		if(uif->dir == 0)
+			error(strerror(errno));
+	}
+	else {
+		fd = open(path, _O_WRONLY|_O_BINARY|_O_CREAT|_O_TRUNC, 0666);
+		if(fd >= 0) {
+			if(m != 1) {
+				close(fd);
+				fd = open(path, m|_O_BINARY);
+			}
+			chmod(path, perm & 0777);
+			chown(path, uif->uid, uif->gid);
+		}
+		if(fd < 0)
+			error(strerror(errno));
+		uif->fd = fd;
+	}
+
+	if(stat(path, &stbuf) < 0)
+		error(strerror(errno));
+	c->qid = fsqid(path, &stbuf);
+	c->offset = 0;
+	c->flag |= COPEN;
+	c->mode = openmode(mode);
+}
+
+static void
+fsclose(Chan *c)
+{
+	Ufsinfo *uif;
+
+	uif = c->aux;
+
+	if(c->flag & COPEN) {
+		if(c->qid.type & QTDIR)
+			closedir(uif->dir);
+		else
+			close(uif->fd);
+	}
+
+	free(uif);
+}
+
+static long
+fsread(Chan *c, void *va, long n, ulong offset)
+{
+	int fd, r;
+	Ufsinfo *uif;
+
+/*print("fsread %s\n", c2name(c));*/
+	if(c->qid.type & QTDIR)
+		return fsdirread(c, va, n, offset);
+
+	uif = c->aux;
+	qlock(&uif->oq);
+	if(waserror()) {
+		qunlock(&uif->oq);
+		nexterror();
+	}
+	fd = uif->fd;
+	if(uif->offset != offset) {
+		r = lseek(fd, offset, 0);
+		if(r < 0)
+			error(strerror(errno));
+		uif->offset = offset;
+	}
+
+	n = read(fd, va, n);
+	if(n < 0)
+		error(strerror(errno));
+
+	uif->offset += n;
+	qunlock(&uif->oq);
+	poperror();
+
+	return n;
+}
+
+static long
+fswrite(Chan *c, void *va, long n, ulong offset)
+{
+	int fd, r;
+	Ufsinfo *uif;
+
+	uif = c->aux;
+
+	qlock(&uif->oq);
+	if(waserror()) {
+		qunlock(&uif->oq);
+		nexterror();
+	}
+	fd = uif->fd;
+	if(uif->offset != offset) {
+		r = lseek(fd, offset, 0);
+		if(r < 0)
+			error(strerror(errno));
+		uif->offset = offset;
+	}
+
+	n = write(fd, va, n);
+	if(n < 0)
+		error(strerror(errno));
+
+	uif->offset += n;
+	qunlock(&uif->oq);
+	poperror();
+
+	return n;
+}
+
+static void
+fsremove(Chan *c)
+{
+	int n;
+	char path[MAXPATH];
+
+	fspath(c, 0, path);
+	if(c->qid.type & QTDIR)
+		n = rmdir(path);
+	else
+		n = remove(path);
+	if(n < 0)
+		error(strerror(errno));
+}
+
+static int
+fswstat(Chan *c, uchar *buf, int n)
+{
+	Dir d;
+	struct stat stbuf;
+	char old[MAXPATH], new[MAXPATH];
+	char strs[MAXPATH*3], *p;
+	Ufsinfo *uif;
+
+	if (convM2D(buf, n, &d, strs) != n)
+		error(Ebadstat);
+	
+	fspath(c, 0, old);
+	if(stat(old, &stbuf) < 0)
+		error(strerror(errno));
+
+	uif = c->aux;
+
+//	if(uif->uid != stbuf.st_uid)
+//		error(Eowner);
+
+	if(d.name[0] && strcmp(d.name, lastelem(c)) != 0) {
+		fspath(c, 0, old);
+		strcpy(new, old);
+		p = strrchr(new, '/');
+		strcpy(p+1, d.name);
+		if(rename(old, new) < 0)
+			error(strerror(errno));
+	}
+
+	fspath(c, 0, old);
+	if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
+		if(chmod(old, d.mode&0777) < 0)
+			error(strerror(errno));
+		uif->mode &= ~0777;
+		uif->mode |= d.mode&0777;
+	}
+/*
+	p = name2pass(gid, d.gid);
+	if(p == 0)
+		error(Eunknown);
+
+	if(p->id != stbuf.st_gid) {
+		if(chown(old, stbuf.st_uid, p->id) < 0)
+			error(sys_errlist[errno]);
+
+		uif->gid = p->id;
+	}
+*/
+	return n;
+}
+
+static Qid
+fsqid(char *p, struct stat *st)
+{
+	Qid q;
+	int dev;
+	ulong h;
+	static int nqdev;
+	static uchar *qdev;
+
+	if(qdev == 0)
+		qdev = mallocz(65536U, 1);
+
+	q.type = 0;
+	if((st->st_mode&S_IFMT) ==  S_IFDIR)
+		q.type = QTDIR;
+
+	dev = st->st_dev & 0xFFFFUL;
+	if(qdev[dev] == 0)
+		qdev[dev] = ++nqdev;
+
+	h = 0;
+	while(*p != '\0')
+		h += *p++ * 13;
+	
+	q.path = (vlong)qdev[dev]<<32;
+	q.path |= h;
+	q.vers = st->st_mtime;
+
+	return q;
+}
+
+static void
+fspath(Chan *c, char *ext, char *path)
+{
+	strcpy(path, base);
+	strcat(path, "/");
+	strcat(path, uc2name(c));
+	if(ext) {
+		strcat(path, "/");
+		strcat(path, ext);
+	}
+	cleanname(path);
+}
+
+static int
+isdots(char *name)
+{
+	if(name[0] != '.')
+		return 0;
+	if(name[1] == '\0')
+		return 1;
+	if(name[1] != '.')
+		return 0;
+	if(name[2] == '\0')
+		return 1;
+	return 0;
+}
+
+static int
+p9readdir(char *name, Ufsinfo *uif)
+{
+	if(uif->nextname[0]){
+		strcpy(name, uif->nextname);
+		uif->nextname[0] = 0;
+		return 1;
+	}
+
+	return readdir(name, uif->dir);
+}
+
+static ulong
+fsdirread(Chan *c, uchar *va, int count, ulong offset)
+{
+	int i;
+	Dir d;
+	long n;
+	char de[NAME_MAX];
+	struct stat stbuf;
+	char path[MAXPATH], dirpath[MAXPATH];
+	Ufsinfo *uif;
+
+/*print("fsdirread %s\n", c2name(c));*/
+	i = 0;
+	uif = c->aux;
+
+	errno = 0;
+	if(uif->offset != offset) {
+		if(offset != 0)
+			error("bad offset in fsdirread");
+		uif->offset = offset;  /* sync offset */
+		uif->nextname[0] = 0;
+		rewinddir(uif->dir);
+	}
+
+	fspath(c, 0, dirpath);
+
+	while(i+BIT16SZ < count) {
+		if(!p9readdir(de, uif))
+			break;
+
+		if(de[0]==0 || isdots(de))
+			continue;
+
+		d.name = de;
+		sprint(path, "%s/%s", dirpath, de);
+		memset(&stbuf, 0, sizeof stbuf);
+
+		if(stat(path, &stbuf) < 0) {
+			print("dir: bad path %s\n", path);
+			/* but continue... probably a bad symlink */
+		}
+
+		d.uid = "unknown";
+		d.gid = "unknown";
+		d.muid = "unknown";
+		d.qid = fsqid(path, &stbuf);
+		d.mode = (d.qid.type<<24)|(stbuf.st_mode&0777);
+		d.atime = stbuf.st_atime;
+		d.mtime = stbuf.st_mtime;
+		d.length = stbuf.st_size;
+		d.type = 'U';
+		d.dev = c->dev;
+		n = convD2M(&d, (char*)va+i, count-i);
+		if(n == BIT16SZ){
+			strcpy(uif->nextname, de);
+			break;
+		}
+		i += n;
+	}
+/*print("got %d\n", i);*/
+	uif->offset += i;
+	return i;
+}
+
+static int
+fsomode(int m)
+{
+	switch(m) {
+	case 0:			/* OREAD */
+	case 3:			/* OEXEC */
+		return 0;
+	case 1:			/* OWRITE */
+		return 1;
+	case 2:			/* ORDWR */
+		return 2;
+	}
+	error(Ebadarg);
+	return 0;
+}
+void
+closedir(DIR *d)
+{
+	FindClose(d->handle);
+	free(d->path);
+}
+
+int
+readdir(char *name, DIR *d)
+{
+	if(d->index != 0) {
+		if(FindNextFile(d->handle, &d->wfd) == FALSE)
+			return 0;
+	}
+	strcpy(name, d->wfd.cFileName);
+	d->index++;
+
+	return 1;
+}
+
+void
+rewinddir(DIR *d)
+{
+	FindClose(d->handle);
+	d->handle = FindFirstFile(d->path, &d->wfd);
+	d->index = 0;
+}
+
+static int
+chown(char *path, int uid, int perm)
+{
+/*	panic("chown"); */
+	return 0;
+}
+
+DIR*
+opendir(char *p)
+{
+	DIR *d;
+	char path[MAX_PATH];
+
+	
+	snprint(path, sizeof(path), "%s/*.*", p);
+
+	d = mallocz(sizeof(DIR), 1);
+	if(d == 0)
+		return 0;
+
+	d->index = 0;
+
+	d->handle = FindFirstFile(path, &d->wfd);
+	if(d->handle == INVALID_HANDLE_VALUE) {
+		free(d);
+		return 0;
+	}
+
+	d->path = strdup(path);
+	return d;
+}
+
+Dev fsdevtab = {
+	'U',
+	"fs",
+
+	devreset,
+	devinit,
+	devshutdown,
+	fsattach,
+	fswalk,
+	fsstat,
+	fsopen,
+	fscreate,
+	fsclose,
+	fsread,
+	devbread,
+	fswrite,
+	devbwrite,
+	fsremove,
+	fswstat,
+};
--- /dev/null
+++ b/kern/devpipe.c
@@ -1,0 +1,398 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	"netif.h"
+
+typedef struct Pipe	Pipe;
+struct Pipe
+{
+	QLock lk;
+	Pipe	*next;
+	int	ref;
+	ulong	path;
+	Queue	*q[2];
+	int	qref[2];
+};
+
+struct
+{
+	Lock lk;
+	ulong	path;
+} pipealloc;
+
+enum
+{
+	Qdir,
+	Qdata0,
+	Qdata1,
+};
+
+Dirtab pipedir[] =
+{
+	".",		{Qdir,0,QTDIR},	0,		DMDIR|0500,
+	"data",		{Qdata0},	0,		0600,
+	"data1",	{Qdata1},	0,		0600,
+};
+#define NPIPEDIR 3
+
+static void
+pipeinit(void)
+{
+	if(conf.pipeqsize == 0){
+		if(conf.nmach > 1)
+			conf.pipeqsize = 256*1024;
+		else
+			conf.pipeqsize = 32*1024;
+	}
+}
+
+/*
+ *  create a pipe, no streams are created until an open
+ */
+static Chan*
+pipeattach(char *spec)
+{
+	Pipe *p;
+	Chan *c;
+
+	c = devattach('|', spec);
+	p = malloc(sizeof(Pipe));
+	if(p == 0)
+		exhausted("memory");
+	p->ref = 1;
+
+	p->q[0] = qopen(conf.pipeqsize, 0, 0, 0);
+	if(p->q[0] == 0){
+		free(p);
+		exhausted("memory");
+	}
+	p->q[1] = qopen(conf.pipeqsize, 0, 0, 0);
+	if(p->q[1] == 0){
+		free(p->q[0]);
+		free(p);
+		exhausted("memory");
+	}
+
+	lock(&pipealloc.lk);
+	p->path = ++pipealloc.path;
+	unlock(&pipealloc.lk);
+
+	mkqid(&c->qid, NETQID(2*p->path, Qdir), 0, QTDIR);
+	c->aux = p;
+	c->dev = 0;
+	return c;
+}
+
+static int
+pipegen(Chan *c, char *name, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+	Qid q;
+	int len;
+	Pipe *p;
+
+	USED(name);
+
+	if(i == DEVDOTDOT){
+		devdir(c, c->qid, "#|", 0, eve, DMDIR|0555, dp);
+		return 1;
+	}
+	i++;	/* skip . */
+	if(tab==0 || i>=ntab)
+		return -1;
+
+	tab += i;
+	p = c->aux;
+	switch((ulong)tab->qid.path){
+	case Qdata0:
+		len = qlen(p->q[0]);
+		break;
+	case Qdata1:
+		len = qlen(p->q[1]);
+		break;
+	default:
+		len = tab->length;
+		break;
+	}
+	mkqid(&q, NETQID(NETID(c->qid.path), tab->qid.path), 0, QTFILE);
+	devdir(c, q, tab->name, len, eve, tab->perm, dp);
+	return 1;
+}
+
+
+static Walkqid*
+pipewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	Walkqid *wq;
+	Pipe *p;
+
+	wq = devwalk(c, nc, name, nname, pipedir, NPIPEDIR, pipegen);
+	if(wq != nil && wq->clone != nil && wq->clone != c){
+		p = c->aux;
+		qlock(&p->lk);
+		p->ref++;
+		if(c->flag & COPEN){
+			print("channel open in pipewalk\n");
+			switch(NETTYPE(c->qid.path)){
+			case Qdata0:
+				p->qref[0]++;
+				break;
+			case Qdata1:
+				p->qref[1]++;
+				break;
+			}
+		}
+		qunlock(&p->lk);
+	}
+	return wq;
+}
+
+static int
+pipestat(Chan *c, uchar *db, int n)
+{
+	Pipe *p;
+	Dir dir;
+
+	p = c->aux;
+
+	switch(NETTYPE(c->qid.path)){
+	case Qdir:
+		devdir(c, c->qid, ".", 0, eve, DMDIR|0555, &dir);
+		break;
+	case Qdata0:
+		devdir(c, c->qid, "data", qlen(p->q[0]), eve, 0600, &dir);
+		break;
+	case Qdata1:
+		devdir(c, c->qid, "data1", qlen(p->q[1]), eve, 0600, &dir);
+		break;
+	default:
+		panic("pipestat");
+	}
+	n = convD2M(&dir, db, n);
+	if(n < BIT16SZ)
+		error(Eshortstat);
+	return n;
+}
+
+/*
+ *  if the stream doesn't exist, create it
+ */
+static Chan*
+pipeopen(Chan *c, int omode)
+{
+	Pipe *p;
+
+	if(c->qid.type & QTDIR){
+		if(omode != OREAD)
+			error(Ebadarg);
+		c->mode = omode;
+		c->flag |= COPEN;
+		c->offset = 0;
+		return c;
+	}
+
+	p = c->aux;
+	qlock(&p->lk);
+	switch(NETTYPE(c->qid.path)){
+	case Qdata0:
+		p->qref[0]++;
+		break;
+	case Qdata1:
+		p->qref[1]++;
+		break;
+	}
+	qunlock(&p->lk);
+
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	c->iounit = qiomaxatomic;
+	return c;
+}
+
+static void
+pipeclose(Chan *c)
+{
+	Pipe *p;
+
+	p = c->aux;
+	qlock(&p->lk);
+
+	if(c->flag & COPEN){
+		/*
+		 *  closing either side hangs up the stream
+		 */
+		switch(NETTYPE(c->qid.path)){
+		case Qdata0:
+			p->qref[0]--;
+			if(p->qref[0] == 0){
+				qhangup(p->q[1], 0);
+				qclose(p->q[0]);
+			}
+			break;
+		case Qdata1:
+			p->qref[1]--;
+			if(p->qref[1] == 0){
+				qhangup(p->q[0], 0);
+				qclose(p->q[1]);
+			}
+			break;
+		}
+	}
+
+
+	/*
+	 *  if both sides are closed, they are reusable
+	 */
+	if(p->qref[0] == 0 && p->qref[1] == 0){
+		qreopen(p->q[0]);
+		qreopen(p->q[1]);
+	}
+
+	/*
+	 *  free the structure on last close
+	 */
+	p->ref--;
+	if(p->ref == 0){
+		qunlock(&p->lk);
+		free(p->q[0]);
+		free(p->q[1]);
+		free(p);
+	} else
+		qunlock(&p->lk);
+}
+
+static long
+piperead(Chan *c, void *va, long n, vlong offset)
+{
+	Pipe *p;
+
+	USED(offset);
+
+	p = c->aux;
+
+	switch(NETTYPE(c->qid.path)){
+	case Qdir:
+		return devdirread(c, va, n, pipedir, NPIPEDIR, pipegen);
+	case Qdata0:
+		return qread(p->q[0], va, n);
+	case Qdata1:
+		return qread(p->q[1], va, n);
+	default:
+		panic("piperead");
+	}
+	return -1;	/* not reached */
+}
+
+static Block*
+pipebread(Chan *c, long n, ulong offset)
+{
+	Pipe *p;
+
+	p = c->aux;
+
+	switch(NETTYPE(c->qid.path)){
+	case Qdata0:
+		return qbread(p->q[0], n);
+	case Qdata1:
+		return qbread(p->q[1], n);
+	}
+
+	return devbread(c, n, offset);
+}
+
+/*
+ *  a write to a closed pipe causes a note to be sent to
+ *  the process.
+ */
+static long
+pipewrite(Chan *c, void *va, long n, vlong offset)
+{
+	Pipe *p;
+
+	USED(offset);
+	if(!islo())
+		print("pipewrite hi %lux\n", getcallerpc(&c));
+
+	if(waserror()) {
+		/* avoid notes when pipe is a mounted queue */
+		if((c->flag & CMSG) == 0)
+			postnote(up, 1, "sys: write on closed pipe", NUser);
+		nexterror();
+	}
+
+	p = c->aux;
+
+	switch(NETTYPE(c->qid.path)){
+	case Qdata0:
+		n = qwrite(p->q[1], va, n);
+		break;
+
+	case Qdata1:
+		n = qwrite(p->q[0], va, n);
+		break;
+
+	default:
+		panic("pipewrite");
+	}
+
+	poperror();
+	return n;
+}
+
+static long
+pipebwrite(Chan *c, Block *bp, ulong offset)
+{
+	long n;
+	Pipe *p;
+
+	USED(offset);
+
+	if(waserror()) {
+		/* avoid notes when pipe is a mounted queue */
+		if((c->flag & CMSG) == 0)
+			postnote(up, 1, "sys: write on closed pipe", NUser);
+		nexterror();
+	}
+
+	p = c->aux;
+	switch(NETTYPE(c->qid.path)){
+	case Qdata0:
+		n = qbwrite(p->q[1], bp);
+		break;
+
+	case Qdata1:
+		n = qbwrite(p->q[0], bp);
+		break;
+
+	default:
+		n = 0;
+		panic("pipebwrite");
+	}
+
+	poperror();
+	return n;
+}
+
+Dev pipedevtab = {
+	'|',
+	"pipe",
+
+	devreset,
+	pipeinit,
+	devshutdown,
+	pipeattach,
+	pipewalk,
+	pipestat,
+	pipeopen,
+	devcreate,
+	pipeclose,
+	piperead,
+	pipebread,
+	pipewrite,
+	pipebwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/kern/devroot.c
@@ -1,0 +1,268 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Qdir = 0,
+	Qboot = 0x1000,
+
+	Nrootfiles = 32,
+	Nbootfiles = 32,
+};
+
+typedef struct Dirlist Dirlist;
+struct Dirlist
+{
+	uint base;
+	Dirtab *dir;
+	uchar **data;
+	int ndir;
+	int mdir;
+};
+
+static Dirtab rootdir[Nrootfiles] = {
+	"#/",		{Qdir, 0, QTDIR},	0,		DMDIR|0555,
+	"boot",	{Qboot, 0, QTDIR},	0,		DMDIR|0555,
+};
+static uchar *rootdata[Nrootfiles];
+static Dirlist rootlist = 
+{
+	0,
+	rootdir,
+	rootdata,
+	2,
+	Nrootfiles
+};
+
+static Dirtab bootdir[Nbootfiles] = {
+	"boot",	{Qboot, 0, QTDIR},	0,		DMDIR|0555,
+};
+static uchar *bootdata[Nbootfiles];
+static Dirlist bootlist =
+{
+	Qboot,
+	bootdir,
+	bootdata,
+	1,
+	Nbootfiles
+};
+
+/*
+ *  add a file to the list
+ */
+static void
+addlist(Dirlist *l, char *name, uchar *contents, ulong len, int perm)
+{
+	Dirtab *d;
+
+	if(l->ndir >= l->mdir)
+		panic("too many root files");
+	l->data[l->ndir] = contents;
+	d = &l->dir[l->ndir];
+	strcpy(d->name, name);
+	d->length = len;
+	d->perm = perm;
+	d->qid.type = 0;
+	d->qid.vers = 0;
+	d->qid.path = ++l->ndir + l->base;
+	if(perm & DMDIR)
+		d->qid.type |= QTDIR;
+}
+
+/*
+ *  add a root file
+ */
+void
+addbootfile(char *name, uchar *contents, ulong len)
+{
+	addlist(&bootlist, name, contents, len, 0555);
+}
+
+/*
+ *  add a root directory
+ */
+static void
+addrootdir(char *name)
+{
+	addlist(&rootlist, name, nil, 0, DMDIR|0555);
+}
+
+static void
+rootreset(void)
+{
+	addrootdir("bin");
+	addrootdir("dev");
+	addrootdir("env");
+	addrootdir("fd");
+	addrootdir("mnt");
+	addrootdir("net");
+	addrootdir("net.alt");
+	addrootdir("proc");
+	addrootdir("root");
+	addrootdir("srv");
+}
+
+static Chan*
+rootattach(char *spec)
+{
+	return devattach('/', spec);
+}
+
+static int
+rootgen(Chan *c, char *name, Dirtab *dirt, int ndirt, int s, Dir *dp)
+{
+	int t;
+	Dirtab *d;
+	Dirlist *l;
+
+	USED(dirt);
+	USED(ndirt);
+
+	switch((int)c->qid.path){
+	case Qdir:
+		if(s == DEVDOTDOT){
+			Qid tqiddir = {Qdir, 0, QTDIR};
+			devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+			return 1;
+		}
+		return devgen(c, name, rootlist.dir, rootlist.ndir, s, dp);
+	case Qboot:
+		if(s == DEVDOTDOT){
+			Qid tqiddir = {Qdir, 0, QTDIR};
+			devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+			return 1;
+		}
+		return devgen(c, name, bootlist.dir, bootlist.ndir, s, dp);
+	default:
+		if(s == DEVDOTDOT){
+			Qid tqiddir = {Qdir, 0, QTDIR};
+			if((int)c->qid.path < Qboot)
+				devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+			else {
+				tqiddir.path = Qboot;
+				devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+			}
+			return 1;
+		}
+		if(s != 0)
+			return -1;
+		if((int)c->qid.path < Qboot){
+			t = c->qid.path-1;
+			l = &rootlist;
+		}else{
+			t = c->qid.path - Qboot - 1;
+			l = &bootlist;
+		}
+		if(t >= l->ndir)
+			return -1;
+if(t < 0){
+print("rootgen %llud %d %d\n", c->qid.path, s, t);
+panic("whoops");
+}
+		d = &l->dir[t];
+		devdir(c, d->qid, d->name, d->length, eve, d->perm, dp);
+		return 1;
+	}
+	return -1;
+}
+
+static Walkqid*
+rootwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c,  nc, name, nname, nil, 0, rootgen);
+}
+
+static int
+rootstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, nil, 0, rootgen);
+}
+
+static Chan*
+rootopen(Chan *c, int omode)
+{
+	return devopen(c, omode, nil, 0, devgen);
+}
+
+/*
+ * sysremove() knows this is a nop
+ */
+static void
+rootclose(Chan *c)
+{
+	USED(c);
+}
+
+static long
+rootread(Chan *c, void *buf, long n, vlong off)
+{
+	ulong t;
+	Dirtab *d;
+	Dirlist *l;
+	uchar *data;
+	ulong offset = off;
+
+	t = c->qid.path;
+	switch(t){
+	case Qdir:
+	case Qboot:
+		return devdirread(c, buf, n, nil, 0, rootgen);
+	}
+
+	if(t<Qboot)
+		l = &rootlist;
+	else{
+		t -= Qboot;
+		l = &bootlist;
+	}
+
+	t--;
+	if(t >= l->ndir)
+		error(Egreg);
+
+	d = &l->dir[t];
+	data = l->data[t];
+	if(offset >= d->length)
+		return 0;
+	if(offset+n > d->length)
+		n = d->length - offset;
+	memmove(buf, data+offset, n);
+	return n;
+}
+
+static long
+rootwrite(Chan *c, void *v, long n, vlong o)
+{
+	USED(c);
+	USED(v);
+	USED(n);
+	USED(o);
+
+	error(Egreg);
+	return 0;
+}
+
+Dev rootdevtab = {
+	'/',
+	"root",
+
+	rootreset,
+	devinit,
+	devshutdown,
+	rootattach,
+	rootwalk,
+	rootstat,
+	rootopen,
+	devcreate,
+	rootclose,
+	rootread,
+	devbread,
+	rootwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
--- /dev/null
+++ b/kern/devssl.c
@@ -1,0 +1,1512 @@
+/*
+ *  devssl - secure sockets layer
+ */
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	"libsec.h"
+
+#define NOSPOOKS 1
+
+typedef struct OneWay OneWay;
+struct OneWay
+{
+	QLock	q;
+	QLock	ctlq;
+
+	void	*state;		/* encryption state */
+	int	slen;		/* hash data length */
+	uchar	*secret;	/* secret */
+	ulong	mid;		/* message id */
+};
+
+enum
+{
+	/* connection states */
+	Sincomplete=	0,
+	Sclear=		1,
+	Sencrypting=	2,
+	Sdigesting=	4,
+	Sdigenc=	Sencrypting|Sdigesting,
+
+	/* encryption algorithms */
+	Noencryption=	0,
+	DESCBC=		1,
+	DESECB=		2,
+	RC4=		3
+};
+
+typedef struct Dstate Dstate;
+struct Dstate
+{
+	Chan	*c;		/* io channel */
+	uchar	state;		/* state of connection */
+	int	ref;		/* serialized by dslock for atomic destroy */
+
+	uchar	encryptalg;	/* encryption algorithm */
+	ushort	blocklen;	/* blocking length */
+
+	ushort	diglen;		/* length of digest */
+	DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*);	/* hash func */
+
+	/* for SSL format */
+	int	max;		/* maximum unpadded data per msg */
+	int	maxpad;		/* maximum padded data per msg */
+
+	/* input side */
+	OneWay	in;
+	Block	*processed;
+	Block	*unprocessed;
+
+	/* output side */
+	OneWay	out;
+
+	/* protections */
+	char	*user;
+	int	perm;
+};
+
+enum
+{
+	Maxdmsg=	1<<16,
+	Maxdstate=	128,	/* must be a power of 2 */
+};
+
+Lock	dslock;
+int	dshiwat;
+char	*dsname[Maxdstate];
+Dstate	*dstate[Maxdstate];
+char	*encalgs;
+char	*hashalgs;
+
+enum{
+	Qtopdir		= 1,	/* top level directory */
+	Qprotodir,
+	Qclonus,
+	Qconvdir,		/* directory for a conversation */
+	Qdata,
+	Qctl,
+	Qsecretin,
+	Qsecretout,
+	Qencalgs,
+	Qhashalgs,
+};
+
+#define TYPE(x) 	((x).path & 0xf)
+#define CONV(x) 	(((x).path >> 5)&(Maxdstate-1))
+#define QID(c, y) 	(((c)<<5) | (y))
+
+static void	ensure(Dstate*, Block**, int);
+static void	consume(Block**, uchar*, int);
+static void	setsecret(OneWay*, uchar*, int);
+static Block*	encryptb(Dstate*, Block*, int);
+static Block*	decryptb(Dstate*, Block*);
+static Block*	digestb(Dstate*, Block*, int);
+static void	checkdigestb(Dstate*, Block*);
+static Chan*	buftochan(char*);
+static void	sslhangup(Dstate*);
+static Dstate*	dsclone(Chan *c);
+static void	dsnew(Chan *c, Dstate **);
+static long	sslput(Dstate *s, Block * volatile b);
+
+char *sslnames[] = {
+	/* unused */ 0,
+	/* topdir */ 0,
+	/* protodir */ 0,
+	"clone",
+	/* convdir */ 0,
+	"data",
+	"ctl",
+	"secretin",
+	"secretout",
+	"encalgs",
+	"hashalgs",
+};
+
+static int
+sslgen(Chan *c, char *n, Dirtab *d, int nd, int s, Dir *dp)
+{
+	Qid q;
+	Dstate *ds;
+	char name[16], *p, *nm;
+	int ft;
+
+	USED(n);
+	USED(nd);
+	USED(d);
+
+	q.type = QTFILE;
+	q.vers = 0;
+
+	ft = TYPE(c->qid);
+	switch(ft) {
+	case Qtopdir:
+		if(s == DEVDOTDOT){
+			q.path = QID(0, Qtopdir);
+			q.type = QTDIR;
+			devdir(c, q, "#D", 0, eve, 0555, dp);
+			return 1;
+		}
+		if(s > 0)
+			return -1;
+		q.path = QID(0, Qprotodir);
+		q.type = QTDIR;
+		devdir(c, q, "ssl", 0, eve, 0555, dp);
+		return 1;
+	case Qprotodir:
+		if(s == DEVDOTDOT){
+			q.path = QID(0, Qtopdir);
+			q.type = QTDIR;
+			devdir(c, q, ".", 0, eve, 0555, dp);
+			return 1;
+		}
+		if(s < dshiwat) {
+			q.path = QID(s, Qconvdir);
+			q.type = QTDIR;
+			ds = dstate[s];
+			if(ds != 0)
+				nm = ds->user;
+			else
+				nm = eve;
+			if(dsname[s] == nil){
+				sprint(name, "%d", s);
+				kstrdup(&dsname[s], name);
+			}
+			devdir(c, q, dsname[s], 0, nm, 0555, dp);
+			return 1;
+		}
+		if(s > dshiwat)
+			return -1;
+		q.path = QID(0, Qclonus);
+		devdir(c, q, "clone", 0, eve, 0555, dp);
+		return 1;
+	case Qconvdir:
+		if(s == DEVDOTDOT){
+			q.path = QID(0, Qprotodir);
+			q.type = QTDIR;
+			devdir(c, q, "ssl", 0, eve, 0555, dp);
+			return 1;
+		}
+		ds = dstate[CONV(c->qid)];
+		if(ds != 0)
+			nm = ds->user;
+		else
+			nm = eve;
+		switch(s) {
+		default:
+			return -1;
+		case 0:
+			q.path = QID(CONV(c->qid), Qctl);
+			p = "ctl";
+			break;
+		case 1:
+			q.path = QID(CONV(c->qid), Qdata);
+			p = "data";
+			break;
+		case 2:
+			q.path = QID(CONV(c->qid), Qsecretin);
+			p = "secretin";
+			break;
+		case 3:
+			q.path = QID(CONV(c->qid), Qsecretout);
+			p = "secretout";
+			break;
+		case 4:
+			q.path = QID(CONV(c->qid), Qencalgs);
+			p = "encalgs";
+			break;
+		case 5:
+			q.path = QID(CONV(c->qid), Qhashalgs);
+			p = "hashalgs";
+			break;
+		}
+		devdir(c, q, p, 0, nm, 0660, dp);
+		return 1;
+	case Qclonus:
+		devdir(c, c->qid, sslnames[TYPE(c->qid)], 0, eve, 0555, dp);
+		return 1;
+	default:
+		ds = dstate[CONV(c->qid)];
+		if(ds != 0)
+			nm = ds->user;
+		else
+			nm = eve;
+		devdir(c, c->qid, sslnames[TYPE(c->qid)], 0, nm, 0660, dp);
+		return 1;
+	}
+	return -1;
+}
+
+static Chan*
+sslattach(char *spec)
+{
+	Chan *c;
+
+	c = devattach('D', spec);
+	c->qid.path = QID(0, Qtopdir);
+	c->qid.vers = 0;
+	c->qid.type = QTDIR;
+	return c;
+}
+
+static Walkqid*
+sslwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, nil, 0, sslgen);
+}
+
+static int
+sslstat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, nil, 0, sslgen);
+}
+
+static Chan*
+sslopen(Chan *c, int omode)
+{
+	Dstate *s, **pp;
+	int perm;
+	int ft;
+
+	perm = 0;
+	omode &= 3;
+	switch(omode) {
+	case OREAD:
+		perm = 4;
+		break;
+	case OWRITE:
+		perm = 2;
+		break;
+	case ORDWR:
+		perm = 6;
+		break;
+	}
+
+	ft = TYPE(c->qid);
+	switch(ft) {
+	default:
+		panic("sslopen");
+	case Qtopdir:
+	case Qprotodir:
+	case Qconvdir:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qclonus:
+		s = dsclone(c);
+		if(s == 0)
+			error(Enodev);
+		break;
+	case Qctl:
+	case Qdata:
+	case Qsecretin:
+	case Qsecretout:
+		if(waserror()) {
+			unlock(&dslock);
+			nexterror();
+		}
+		lock(&dslock);
+		pp = &dstate[CONV(c->qid)];
+		s = *pp;
+		if(s == 0)
+			dsnew(c, pp);
+		else {
+			if((perm & (s->perm>>6)) != perm
+			   && (strcmp(up->user, s->user) != 0
+			     || (perm & s->perm) != perm))
+				error(Eperm);
+
+			s->ref++;
+		}
+		unlock(&dslock);
+		poperror();
+		break;
+	case Qencalgs:
+	case Qhashalgs:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	}
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+static int
+sslwstat(Chan *c, uchar *db, int n)
+{
+	Dir *dir;
+	Dstate *s;
+	int m;
+
+	s = dstate[CONV(c->qid)];
+	if(s == 0)
+		error(Ebadusefd);
+	if(strcmp(s->user, up->user) != 0)
+		error(Eperm);
+
+	dir = smalloc(sizeof(Dir)+n);
+	m = convM2D(db, n, &dir[0], (char*)&dir[1]);
+	if(m == 0){
+		free(dir);
+		error(Eshortstat);
+	}
+
+	if(!emptystr(dir->uid))
+		kstrdup(&s->user, dir->uid);
+	if(dir->mode != ~0UL)
+		s->perm = dir->mode;
+
+	free(dir);
+	return m;
+}
+
+static void
+sslclose(Chan *c)
+{
+	Dstate *s;
+	int ft;
+
+	ft = TYPE(c->qid);
+	switch(ft) {
+	case Qctl:
+	case Qdata:
+	case Qsecretin:
+	case Qsecretout:
+		if((c->flag & COPEN) == 0)
+			break;
+
+		s = dstate[CONV(c->qid)];
+		if(s == 0)
+			break;
+
+		lock(&dslock);
+		if(--s->ref > 0) {
+			unlock(&dslock);
+			break;
+		}
+		dstate[CONV(c->qid)] = 0;
+		unlock(&dslock);
+
+		if(s->user != nil)
+			free(s->user);
+		sslhangup(s);
+		if(s->c)
+			cclose(s->c);
+		if(s->in.secret)
+			free(s->in.secret);
+		if(s->out.secret)
+			free(s->out.secret);
+		if(s->in.state)
+			free(s->in.state);
+		if(s->out.state)
+			free(s->out.state);
+		free(s);
+
+	}
+}
+
+/*
+ *  make sure we have at least 'n' bytes in list 'l'
+ */
+static void
+ensure(Dstate *s, Block **l, int n)
+{
+	int sofar, i;
+	Block *b, *bl;
+
+	sofar = 0;
+	for(b = *l; b; b = b->next){
+		sofar += BLEN(b);
+		if(sofar >= n)
+			return;
+		l = &b->next;
+	}
+
+	while(sofar < n){
+		bl = devtab[s->c->type]->bread(s->c, Maxdmsg, 0);
+		if(bl == 0)
+			nexterror();
+		*l = bl;
+		i = 0;
+		for(b = bl; b; b = b->next){
+			i += BLEN(b);
+			l = &b->next;
+		}
+		if(i == 0)
+			error(Ehungup);
+		sofar += i;
+	}
+}
+
+/*
+ *  copy 'n' bytes from 'l' into 'p' and free
+ *  the bytes in 'l'
+ */
+static void
+consume(Block **l, uchar *p, int n)
+{
+	Block *b;
+	int i;
+
+	for(; *l && n > 0; n -= i){
+		b = *l;
+		i = BLEN(b);
+		if(i > n)
+			i = n;
+		memmove(p, b->rp, i);
+		b->rp += i;
+		p += i;
+		if(BLEN(b) < 0)
+			panic("consume");
+		if(BLEN(b))
+			break;
+		*l = b->next;
+		freeb(b);
+	}
+}
+
+/*
+ *  give back n bytes
+static void
+regurgitate(Dstate *s, uchar *p, int n)
+{
+	Block *b;
+
+	if(n <= 0)
+		return;
+	b = s->unprocessed;
+	if(s->unprocessed == nil || b->rp - b->base < n) {
+		b = allocb(n);
+		memmove(b->wp, p, n);
+		b->wp += n;
+		b->next = s->unprocessed;
+		s->unprocessed = b;
+	} else {
+		b->rp -= n;
+		memmove(b->rp, p, n);
+	}
+}
+ */
+
+/*
+ *  remove at most n bytes from the queue, if discard is set
+ *  dump the remainder
+ */
+static Block*
+qtake(Block **l, int n, int discard)
+{
+	Block *nb, *b, *first;
+	int i;
+
+	first = *l;
+	for(b = first; b; b = b->next){
+		i = BLEN(b);
+		if(i == n){
+			if(discard){
+				freeblist(b->next);
+				*l = 0;
+			} else
+				*l = b->next;
+			b->next = 0;
+			return first;
+		} else if(i > n){
+			i -= n;
+			if(discard){
+				freeblist(b->next);
+				b->wp -= i;
+				*l = 0;
+			} else {
+				nb = allocb(i);
+				memmove(nb->wp, b->rp+n, i);
+				nb->wp += i;
+				b->wp -= i;
+				nb->next = b->next;
+				*l = nb;
+			}
+			b->next = 0;
+			if(BLEN(b) < 0)
+				panic("qtake");
+			return first;
+		} else
+			n -= i;
+		if(BLEN(b) < 0)
+			panic("qtake");
+	}
+	*l = 0;
+	return first;
+}
+
+/*
+ *  We can't let Eintr's lose data since the program
+ *  doing the read may be able to handle it.  The only
+ *  places Eintr is possible is during the read's in consume.
+ *  Therefore, we make sure we can always put back the bytes
+ *  consumed before the last ensure.
+ */
+static Block*
+sslbread(Chan *c, long n, ulong o)
+{
+	Dstate * volatile s;
+	Block *b;
+	uchar consumed[3], *p;
+	int toconsume;
+	int len, pad;
+
+	USED(o);
+	s = dstate[CONV(c->qid)];
+	if(s == 0)
+		panic("sslbread");
+	if(s->state == Sincomplete)
+		error(Ebadusefd);
+
+	qlock(&s->in.q);
+	if(waserror()){
+		qunlock(&s->in.q);
+		nexterror();
+	}
+
+	if(s->processed == 0){
+		/*
+		 * Read in the whole message.  Until we've got it all,
+		 * it stays on s->unprocessed, so that if we get Eintr,
+		 * we'll pick up where we left off.
+		 */
+		ensure(s, &s->unprocessed, 3);
+		s->unprocessed = pullupblock(s->unprocessed, 2);
+		p = s->unprocessed->rp;
+		if(p[0] & 0x80){
+			len = ((p[0] & 0x7f)<<8) | p[1];
+			ensure(s, &s->unprocessed, len);
+			pad = 0;
+			toconsume = 2;
+		} else {
+			s->unprocessed = pullupblock(s->unprocessed, 3);
+			len = ((p[0] & 0x3f)<<8) | p[1];
+			pad = p[2];
+			if(pad > len){
+				print("pad %d buf len %d\n", pad, len);
+				error("bad pad in ssl message");
+			}
+			toconsume = 3;
+		}
+		ensure(s, &s->unprocessed, toconsume+len);
+
+		/* skip header */
+		consume(&s->unprocessed, consumed, toconsume);
+
+		/* grab the next message and decode/decrypt it */
+		b = qtake(&s->unprocessed, len, 0);
+
+		if(blocklen(b) != len)
+			print("devssl: sslbread got wrong count %d != %d", blocklen(b), len);
+
+		if(waserror()){
+			qunlock(&s->in.ctlq);
+			if(b != nil)
+				freeb(b);
+			nexterror();
+		}
+		qlock(&s->in.ctlq);
+		switch(s->state){
+		case Sencrypting:
+			if(b == nil)
+				error("ssl message too short (encrypting)");
+			b = decryptb(s, b);
+			break;
+		case Sdigesting:
+			b = pullupblock(b, s->diglen);
+			if(b == nil)
+				error("ssl message too short (digesting)");
+			checkdigestb(s, b);
+			b->rp += s->diglen;
+			break;
+		case Sdigenc:
+			b = decryptb(s, b);
+			b = pullupblock(b, s->diglen);
+			if(b == nil)
+				error("ssl message too short (dig+enc)");
+			checkdigestb(s, b);
+			b->rp += s->diglen;
+			len -= s->diglen;
+			break;
+		}
+
+		/* remove pad */
+		if(pad)
+			s->processed = qtake(&b, len - pad, 1);
+		else
+			s->processed = b;
+		b = nil;
+		s->in.mid++;
+		qunlock(&s->in.ctlq);
+		poperror();
+	}
+
+	/* return at most what was asked for */
+	b = qtake(&s->processed, n, 0);
+
+	qunlock(&s->in.q);
+	poperror();
+
+	return b;
+}
+
+static long
+sslread(Chan *c, void *a, long n, vlong off)
+{
+	Block * volatile b;
+	Block *nb;
+	uchar *va;
+	int i;
+	char buf[128];
+	ulong offset = off;
+	int ft;
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, a, n, 0, 0, sslgen);
+
+	ft = TYPE(c->qid);
+	switch(ft) {
+	default:
+		error(Ebadusefd);
+	case Qctl:
+		ft = CONV(c->qid);
+		sprint(buf, "%d", ft);
+		return readstr(offset, a, n, buf);
+	case Qdata:
+		b = sslbread(c, n, offset);
+		break;
+	case Qencalgs:
+		return readstr(offset, a, n, encalgs);
+		break;
+	case Qhashalgs:
+		return readstr(offset, a, n, hashalgs);
+		break;
+	}
+
+	if(waserror()){
+		freeblist(b);
+		nexterror();
+	}
+
+	n = 0;
+	va = a;
+	for(nb = b; nb; nb = nb->next){
+		i = BLEN(nb);
+		memmove(va+n, nb->rp, i);
+		n += i;
+	}
+
+	freeblist(b);
+	poperror();
+
+	return n;
+}
+
+/*
+ *  this algorithm doesn't have to be great since we're just
+ *  trying to obscure the block fill
+ */
+static void
+randfill(uchar *buf, int len)
+{
+	while(len-- > 0)
+		*buf++ = nrand(256);
+}
+
+static long
+sslbwrite(Chan *c, Block *b, ulong o)
+{
+	Dstate * volatile s;
+	long rv;
+
+	USED(o);
+	s = dstate[CONV(c->qid)];
+	if(s == nil)
+		panic("sslbwrite");
+
+	if(s->state == Sincomplete){
+		freeb(b);
+		error(Ebadusefd);
+	}
+
+	/* lock so split writes won't interleave */
+	if(waserror()){
+		qunlock(&s->out.q);
+		nexterror();
+	}
+	qlock(&s->out.q);
+
+	rv = sslput(s, b);
+
+	poperror();
+	qunlock(&s->out.q);
+
+	return rv;
+}
+
+/*
+ *  use SSL record format, add in count, digest and/or encrypt.
+ *  the write is interruptable.  if it is interrupted, we'll
+ *  get out of sync with the far side.  not much we can do about
+ *  it since we don't know if any bytes have been written.
+ */
+static long
+sslput(Dstate *s, Block * volatile b)
+{
+	Block *nb;
+	int h, n, m, pad, rv;
+	uchar *p;
+	int offset;
+
+	if(waserror()){
+		if(b != nil)
+			free(b);
+		nexterror();
+	}
+
+	rv = 0;
+	while(b != nil){
+		m = n = BLEN(b);
+		h = s->diglen + 2;
+
+		/* trim to maximum block size */
+		pad = 0;
+		if(m > s->max){
+			m = s->max;
+		} else if(s->blocklen != 1){
+			pad = (m + s->diglen)%s->blocklen;
+			if(pad){
+				if(m > s->maxpad){
+					pad = 0;
+					m = s->maxpad;
+				} else {
+					pad = s->blocklen - pad;
+					h++;
+				}
+			}
+		}
+
+		rv += m;
+		if(m != n){
+			nb = allocb(m + h + pad);
+			memmove(nb->wp + h, b->rp, m);
+			nb->wp += m + h;
+			b->rp += m;
+		} else {
+			/* add header space */
+			nb = padblock(b, h);
+			b = 0;
+		}
+		m += s->diglen;
+
+		/* SSL style count */
+		if(pad){
+			nb = padblock(nb, -pad);
+			randfill(nb->wp, pad);
+			nb->wp += pad;
+			m += pad;
+
+			p = nb->rp;
+			p[0] = (m>>8);
+			p[1] = m;
+			p[2] = pad;
+			offset = 3;
+		} else {
+			p = nb->rp;
+			p[0] = (m>>8) | 0x80;
+			p[1] = m;
+			offset = 2;
+		}
+
+		switch(s->state){
+		case Sencrypting:
+			nb = encryptb(s, nb, offset);
+			break;
+		case Sdigesting:
+			nb = digestb(s, nb, offset);
+			break;
+		case Sdigenc:
+			nb = digestb(s, nb, offset);
+			nb = encryptb(s, nb, offset);
+			break;
+		}
+
+		s->out.mid++;
+
+		m = BLEN(nb);
+		devtab[s->c->type]->bwrite(s->c, nb, s->c->offset);
+		s->c->offset += m;
+	}
+
+	poperror();
+	return rv;
+}
+
+static void
+setsecret(OneWay *w, uchar *secret, int n)
+{
+	if(w->secret)
+		free(w->secret);
+
+	w->secret = smalloc(n);
+	memmove(w->secret, secret, n);
+	w->slen = n;
+}
+
+static void
+initDESkey(OneWay *w)
+{
+	if(w->state){
+		free(w->state);
+		w->state = 0;
+	}
+
+	w->state = smalloc(sizeof(DESstate));
+	if(w->slen >= 16)
+		setupDESstate(w->state, w->secret, w->secret+8);
+	else if(w->slen >= 8)
+		setupDESstate(w->state, w->secret, 0);
+	else
+		error("secret too short");
+}
+
+/*
+ *  40 bit DES is the same as 56 bit DES.  However,
+ *  16 bits of the key are masked to zero.
+ */
+static void
+initDESkey_40(OneWay *w)
+{
+	uchar key[8];
+
+	if(w->state){
+		free(w->state);
+		w->state = 0;
+	}
+
+	if(w->slen >= 8){
+		memmove(key, w->secret, 8);
+		key[0] &= 0x0f;
+		key[2] &= 0x0f;
+		key[4] &= 0x0f;
+		key[6] &= 0x0f;
+	}
+
+	w->state = malloc(sizeof(DESstate));
+	if(w->slen >= 16)
+		setupDESstate(w->state, key, w->secret+8);
+	else if(w->slen >= 8)
+		setupDESstate(w->state, key, 0);
+	else
+		error("secret too short");
+}
+
+static void
+initRC4key(OneWay *w)
+{
+	if(w->state){
+		free(w->state);
+		w->state = 0;
+	}
+
+	w->state = smalloc(sizeof(RC4state));
+	setupRC4state(w->state, w->secret, w->slen);
+}
+
+/*
+ *  40 bit RC4 is the same as n-bit RC4.  However,
+ *  we ignore all but the first 40 bits of the key.
+ */
+static void
+initRC4key_40(OneWay *w)
+{
+	if(w->state){
+		free(w->state);
+		w->state = 0;
+	}
+
+	if(w->slen > 5)
+		w->slen = 5;
+
+	w->state = malloc(sizeof(RC4state));
+	setupRC4state(w->state, w->secret, w->slen);
+}
+
+/*
+ *  128 bit RC4 is the same as n-bit RC4.  However,
+ *  we ignore all but the first 128 bits of the key.
+ */
+static void
+initRC4key_128(OneWay *w)
+{
+	if(w->state){
+		free(w->state);
+		w->state = 0;
+	}
+
+	if(w->slen > 16)
+		w->slen = 16;
+
+	w->state = malloc(sizeof(RC4state));
+	setupRC4state(w->state, w->secret, w->slen);
+}
+
+
+typedef struct Hashalg Hashalg;
+struct Hashalg
+{
+	char	*name;
+	int	diglen;
+	DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*);
+};
+
+Hashalg hashtab[] =
+{
+	{ "md4", MD4dlen, md4, },
+	{ "md5", MD5dlen, md5, },
+	{ "sha1", SHA1dlen, sha1, },
+	{ "sha", SHA1dlen, sha1, },
+	{ 0 }
+};
+
+static int
+parsehashalg(char *p, Dstate *s)
+{
+	Hashalg *ha;
+
+	for(ha = hashtab; ha->name; ha++){
+		if(strcmp(p, ha->name) == 0){
+			s->hf = ha->hf;
+			s->diglen = ha->diglen;
+			s->state &= ~Sclear;
+			s->state |= Sdigesting;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+typedef struct Encalg Encalg;
+struct Encalg
+{
+	char	*name;
+	int	blocklen;
+	int	alg;
+	void	(*keyinit)(OneWay*);
+};
+
+#ifdef NOSPOOKS
+Encalg encrypttab[] =
+{
+	{ "descbc", 8, DESCBC, initDESkey, },           /* DEPRECATED -- use des_56_cbc */
+	{ "desecb", 8, DESECB, initDESkey, },           /* DEPRECATED -- use des_56_ecb */
+	{ "des_56_cbc", 8, DESCBC, initDESkey, },
+	{ "des_56_ecb", 8, DESECB, initDESkey, },
+	{ "des_40_cbc", 8, DESCBC, initDESkey_40, },
+	{ "des_40_ecb", 8, DESECB, initDESkey_40, },
+	{ "rc4", 1, RC4, initRC4key_40, },              /* DEPRECATED -- use rc4_X      */
+	{ "rc4_256", 1, RC4, initRC4key, },
+	{ "rc4_128", 1, RC4, initRC4key_128, },
+	{ "rc4_40", 1, RC4, initRC4key_40, },
+	{ 0 }
+};
+#else
+Encalg encrypttab[] =
+{
+	{ "des_40_cbc", 8, DESCBC, initDESkey_40, },
+	{ "des_40_ecb", 8, DESECB, initDESkey_40, },
+	{ "rc4", 1, RC4, initRC4key_40, },              /* DEPRECATED -- use rc4_X      */
+	{ "rc4_40", 1, RC4, initRC4key_40, },
+	{ 0 }
+};
+#endif NOSPOOKS
+
+static int
+parseencryptalg(char *p, Dstate *s)
+{
+	Encalg *ea;
+
+	for(ea = encrypttab; ea->name; ea++){
+		if(strcmp(p, ea->name) == 0){
+			s->encryptalg = ea->alg;
+			s->blocklen = ea->blocklen;
+			(*ea->keyinit)(&s->in);
+			(*ea->keyinit)(&s->out);
+			s->state &= ~Sclear;
+			s->state |= Sencrypting;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+static long
+sslwrite(Chan *c, void *a, long n, vlong o)
+{
+	Dstate * volatile s;
+	Block * volatile b;
+	int m, t;
+	char *p, *np, *e, buf[128];
+	uchar *x;
+
+	USED(o);
+	s = dstate[CONV(c->qid)];
+	if(s == 0)
+		panic("sslwrite");
+
+	t = TYPE(c->qid);
+	if(t == Qdata){
+		if(s->state == Sincomplete)
+			error(Ebadusefd);
+
+		/* lock should a write gets split over multiple records */
+		if(waserror()){
+			qunlock(&s->out.q);
+			nexterror();
+		}
+		qlock(&s->out.q);
+
+		p = a;
+		e = p + n;
+		do {
+			m = e - p;
+			if(m > s->max)
+				m = s->max;
+
+			b = allocb(m);
+			if(waserror()){
+				freeb(b);
+				nexterror();
+			}
+			memmove(b->wp, p, m);
+			poperror();
+			b->wp += m;
+
+			sslput(s, b);
+
+			p += m;
+		} while(p < e);
+
+		poperror();
+		qunlock(&s->out.q);
+		return n;
+	}
+
+	/* mutex with operations using what we're about to change */
+	if(waserror()){
+		qunlock(&s->in.ctlq);
+		qunlock(&s->out.q);
+		nexterror();
+	}
+	qlock(&s->in.ctlq);
+	qlock(&s->out.q);
+
+	switch(t){
+	default:
+		panic("sslwrite");
+	case Qsecretin:
+		setsecret(&s->in, a, n);
+		goto out;
+	case Qsecretout:
+		setsecret(&s->out, a, n);
+		goto out;
+	case Qctl:
+		break;
+	}
+
+	if(n >= sizeof(buf))
+		error("arg too long");
+	strncpy(buf, a, n);
+	buf[n] = 0;
+	p = strchr(buf, '\n');
+	if(p)
+		*p = 0;
+	p = strchr(buf, ' ');
+	if(p)
+		*p++ = 0;
+
+	if(strcmp(buf, "fd") == 0){
+		s->c = buftochan(p);
+
+		/* default is clear (msg delimiters only) */
+		s->state = Sclear;
+		s->blocklen = 1;
+		s->diglen = 0;
+		s->maxpad = s->max = (1<<15) - s->diglen - 1;
+		s->in.mid = 0;
+		s->out.mid = 0;
+	} else if(strcmp(buf, "alg") == 0 && p != 0){
+		s->blocklen = 1;
+		s->diglen = 0;
+
+		if(s->c == 0)
+			error("must set fd before algorithm");
+
+		s->state = Sclear;
+		s->maxpad = s->max = (1<<15) - s->diglen - 1;
+		if(strcmp(p, "clear") == 0){
+			goto out;
+		}
+
+		if(s->in.secret && s->out.secret == 0)
+			setsecret(&s->out, s->in.secret, s->in.slen);
+		if(s->out.secret && s->in.secret == 0)
+			setsecret(&s->in, s->out.secret, s->out.slen);
+		if(s->in.secret == 0 || s->out.secret == 0)
+			error("algorithm but no secret");
+
+		s->hf = 0;
+		s->encryptalg = Noencryption;
+		s->blocklen = 1;
+
+		for(;;){
+			np = strchr(p, ' ');
+			if(np)
+				*np++ = 0;
+
+			if(parsehashalg(p, s) < 0)
+			if(parseencryptalg(p, s) < 0)
+				error("bad algorithm");
+
+			if(np == 0)
+				break;
+			p = np;
+		}
+
+		if(s->hf == 0 && s->encryptalg == Noencryption)
+			error("bad algorithm");
+
+		if(s->blocklen != 1){
+			s->max = (1<<15) - s->diglen - 1;
+			s->max -= s->max % s->blocklen;
+			s->maxpad = (1<<14) - s->diglen - 1;
+			s->maxpad -= s->maxpad % s->blocklen;
+		} else
+			s->maxpad = s->max = (1<<15) - s->diglen - 1;
+	} else if(strcmp(buf, "secretin") == 0 && p != 0) {
+		m = (strlen(p)*3)/2;
+		x = smalloc(m);
+		t = dec64(x, m, p, strlen(p));
+		setsecret(&s->in, x, t);
+		free(x);
+	} else if(strcmp(buf, "secretout") == 0 && p != 0) {
+		m = (strlen(p)*3)/2 + 1;
+		x = smalloc(m);
+		t = dec64(x, m, p, strlen(p));
+		setsecret(&s->out, x, t);
+		free(x);
+	} else
+		error(Ebadarg);
+
+out:
+	qunlock(&s->in.ctlq);
+	qunlock(&s->out.q);
+	poperror();
+	return n;
+}
+
+static void
+sslinit(void)
+{
+	struct Encalg *e;
+	struct Hashalg *h;
+	int n;
+	char *cp;
+
+	n = 1;
+	for(e = encrypttab; e->name != nil; e++)
+		n += strlen(e->name) + 1;
+	cp = encalgs = smalloc(n);
+	for(e = encrypttab;;){
+		strcpy(cp, e->name);
+		cp += strlen(e->name);
+		e++;
+		if(e->name == nil)
+			break;
+		*cp++ = ' ';
+	}
+	*cp = 0;
+
+	n = 1;
+	for(h = hashtab; h->name != nil; h++)
+		n += strlen(h->name) + 1;
+	cp = hashalgs = smalloc(n);
+	for(h = hashtab;;){
+		strcpy(cp, h->name);
+		cp += strlen(h->name);
+		h++;
+		if(h->name == nil)
+			break;
+		*cp++ = ' ';
+	}
+	*cp = 0;
+}
+
+Dev ssldevtab = {
+	'D',
+	"ssl",
+
+	devreset,
+	sslinit,
+	devshutdown,
+	sslattach,
+	sslwalk,
+	sslstat,
+	sslopen,
+	devcreate,
+	sslclose,
+	sslread,
+	sslbread,
+	sslwrite,
+	sslbwrite,
+	devremove,
+	sslwstat,
+};
+
+static Block*
+encryptb(Dstate *s, Block *b, int offset)
+{
+	uchar *p, *ep, *p2, *ip, *eip;
+	DESstate *ds;
+
+	switch(s->encryptalg){
+	case DESECB:
+		ds = s->out.state;
+		ep = b->rp + BLEN(b);
+		for(p = b->rp + offset; p < ep; p += 8)
+			block_cipher(ds->expanded, p, 0);
+		break;
+	case DESCBC:
+		ds = s->out.state;
+		ep = b->rp + BLEN(b);
+		for(p = b->rp + offset; p < ep; p += 8){
+			p2 = p;
+			ip = ds->ivec;
+			for(eip = ip+8; ip < eip; )
+				*p2++ ^= *ip++;
+			block_cipher(ds->expanded, p, 0);
+			memmove(ds->ivec, p, 8);
+		}
+		break;
+	case RC4:
+		rc4(s->out.state, b->rp + offset, BLEN(b) - offset);
+		break;
+	}
+	return b;
+}
+
+static Block*
+decryptb(Dstate *s, Block *bin)
+{
+	Block *b, **l;
+	uchar *p, *ep, *tp, *ip, *eip;
+	DESstate *ds;
+	uchar tmp[8];
+	int i;
+
+	l = &bin;
+	for(b = bin; b; b = b->next){
+		/* make sure we have a multiple of s->blocklen */
+		if(s->blocklen > 1){
+			i = BLEN(b);
+			if(i % s->blocklen){
+				*l = b = pullupblock(b, i + s->blocklen - (i%s->blocklen));
+				if(b == 0)
+					error("ssl encrypted message too short");
+			}
+		}
+		l = &b->next;
+
+		/* decrypt */
+		switch(s->encryptalg){
+		case DESECB:
+			ds = s->in.state;
+			ep = b->rp + BLEN(b);
+			for(p = b->rp; p < ep; p += 8)
+				block_cipher(ds->expanded, p, 1);
+			break;
+		case DESCBC:
+			ds = s->in.state;
+			ep = b->rp + BLEN(b);
+			for(p = b->rp; p < ep;){
+				memmove(tmp, p, 8);
+				block_cipher(ds->expanded, p, 1);
+				tp = tmp;
+				ip = ds->ivec;
+				for(eip = ip+8; ip < eip; ){
+					*p++ ^= *ip;
+					*ip++ = *tp++;
+				}
+			}
+			break;
+		case RC4:
+			rc4(s->in.state, b->rp, BLEN(b));
+			break;
+		}
+	}
+	return bin;
+}
+
+static Block*
+digestb(Dstate *s, Block *b, int offset)
+{
+	uchar *p;
+	DigestState ss;
+	uchar msgid[4];
+	ulong n, h;
+	OneWay *w;
+
+	w = &s->out;
+
+	memset(&ss, 0, sizeof(ss));
+	h = s->diglen + offset;
+	n = BLEN(b) - h;
+
+	/* hash secret + message */
+	(*s->hf)(w->secret, w->slen, 0, &ss);
+	(*s->hf)(b->rp + h, n, 0, &ss);
+
+	/* hash message id */
+	p = msgid;
+	n = w->mid;
+	*p++ = n>>24;
+	*p++ = n>>16;
+	*p++ = n>>8;
+	*p = n;
+	(*s->hf)(msgid, 4, b->rp + offset, &ss);
+
+	return b;
+}
+
+static void
+checkdigestb(Dstate *s, Block *bin)
+{
+	uchar *p;
+	DigestState ss;
+	uchar msgid[4];
+	int n, h;
+	OneWay *w;
+	uchar digest[128];
+	Block *b;
+
+	w = &s->in;
+
+	memset(&ss, 0, sizeof(ss));
+
+	/* hash secret */
+	(*s->hf)(w->secret, w->slen, 0, &ss);
+
+	/* hash message */
+	h = s->diglen;
+	for(b = bin; b; b = b->next){
+		n = BLEN(b) - h;
+		if(n < 0)
+			panic("checkdigestb");
+		(*s->hf)(b->rp + h, n, 0, &ss);
+		h = 0;
+	}
+
+	/* hash message id */
+	p = msgid;
+	n = w->mid;
+	*p++ = n>>24;
+	*p++ = n>>16;
+	*p++ = n>>8;
+	*p = n;
+	(*s->hf)(msgid, 4, digest, &ss);
+
+	if(memcmp(digest, bin->rp, s->diglen) != 0)
+		error("bad digest");
+}
+
+/* get channel associated with an fd */
+static Chan*
+buftochan(char *p)
+{
+	Chan *c;
+	int fd;
+
+	if(p == 0)
+		error(Ebadarg);
+	fd = strtoul(p, 0, 0);
+	if(fd < 0)
+		error(Ebadarg);
+	c = fdtochan(fd, -1, 0, 1);	/* error check and inc ref */
+	if(devtab[c->type] == &ssldevtab){
+		cclose(c);
+		error("cannot ssl encrypt devssl files");
+	}
+	return c;
+}
+
+/* hand up a digest connection */
+static void
+sslhangup(Dstate *s)
+{
+	Block *b;
+
+	qlock(&s->in.q);
+	for(b = s->processed; b; b = s->processed){
+		s->processed = b->next;
+		freeb(b);
+	}
+	if(s->unprocessed){
+		freeb(s->unprocessed);
+		s->unprocessed = 0;
+	}
+	s->state = Sincomplete;
+	qunlock(&s->in.q);
+}
+
+static Dstate*
+dsclone(Chan *ch)
+{
+	int i;
+	Dstate *ret;
+
+	if(waserror()) {
+		unlock(&dslock);
+		nexterror();
+	}
+	lock(&dslock);
+	ret = nil;
+	for(i=0; i<Maxdstate; i++){
+		if(dstate[i] == nil){
+			dsnew(ch, &dstate[i]);
+			ret = dstate[i];
+			break;
+		}
+	}
+	unlock(&dslock);
+	poperror();
+	return ret;
+}
+
+static void
+dsnew(Chan *ch, Dstate **pp)
+{
+	Dstate *s;
+	int t;
+
+	*pp = s = malloc(sizeof(*s));
+	if(!s)
+		error(Enomem);
+	if(pp - dstate >= dshiwat)
+		dshiwat++;
+	memset(s, 0, sizeof(*s));
+	s->state = Sincomplete;
+	s->ref = 1;
+	kstrdup(&s->user, up->user);
+	s->perm = 0660;
+	t = TYPE(ch->qid);
+	if(t == Qclonus)
+		t = Qctl;
+	ch->qid.path = QID(pp - dstate, t);
+	ch->qid.vers = 0;
+}
--- /dev/null
+++ b/kern/devtab.c
@@ -1,0 +1,27 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+extern Dev consdevtab;
+extern Dev rootdevtab;
+extern Dev pipedevtab;
+extern Dev ssldevtab;
+extern Dev mousedevtab;
+extern Dev drawdevtab;
+extern Dev ipdevtab;
+extern Dev fsdevtab;
+
+Dev *devtab[] = {
+	&rootdevtab,
+	&consdevtab,
+	&pipedevtab,
+	&ssldevtab,
+	&mousedevtab,
+	&drawdevtab,
+	&ipdevtab,
+	&fsdevtab,
+	0
+};
+
--- /dev/null
+++ b/kern/error.c
@@ -1,0 +1,50 @@
+char Enoerror[] = "no error";
+char Emount[] = "inconsistent mount";
+char Eunmount[] = "not mounted";
+char Eunion[] = "not in union";
+char Emountrpc[] = "mount rpc error";
+char Eshutdown[] = "device shut down";
+char Enocreate[] = "mounted directory forbids creation";
+char Enonexist[] = "file does not exist";
+char Eexist[] = "file already exists";
+char Ebadsharp[] = "unknown device in # filename";
+char Enotdir[] = "not a directory";
+char Eisdir[] = "file is a directory";
+char Ebadchar[] = "bad character in file name";
+char Efilename[] = "file name syntax";
+char Eperm[] = "permission denied";
+char Ebadusefd[] = "inappropriate use of fd";
+char Ebadarg[] = "bad arg in system call";
+char Einuse[] = "device or object already in use";
+char Eio[] = "i/o error";
+char Etoobig[] = "read or write too large";
+char Etoosmall[] = "read or write too small";
+char Enoport[] = "network port not available";
+char Ehungup[] = "i/o on hungup channel";
+char Ebadctl[] = "bad process or channel control request";
+char Enodev[] = "no free devices";
+char Eprocdied[] = "process exited";
+char Enochild[] = "no living children";
+char Eioload[] = "i/o error in demand load";
+char Enovmem[] = "virtual memory allocation failed";
+char Ebadfd[] = "fd out of range or not open";
+char Enofd[] = "no free file descriptors";
+char Eisstream[] = "seek on a stream";
+char Ebadexec[] = "exec header invalid";
+char Etimedout[] = "connection timed out";
+char Econrefused[] = "connection refused";
+char Econinuse[] = "connection in use";
+char Eintr[] = "interrupted";
+char Enomem[] = "kernel allocate failed";
+char Enoswap[] = "swap space full";
+char Esoverlap[] = "segments overlap";
+char Emouseset[] = "mouse type already set";
+char Eshort[] = "i/o count too small";
+char Egreg[] = "ken has left the building";
+char Ebadspec[] = "bad attach specifier";
+char Enoreg[] = "process has no saved registers";
+char Enoattach[] = "mount/attach disallowed";
+char Eshortstat[] = "stat buffer too small";
+char Ebadstat[] = "malformed stat buffer";
+char Enegoff[] = "negative i/o offset";
+char Ecmdargs[] = "wrong #args in control message";
--- /dev/null
+++ b/kern/error.h
@@ -1,0 +1,50 @@
+extern char Enoerror[];		/* no error */
+extern char Emount[];		/* inconsistent mount */
+extern char Eunmount[];		/* not mounted */
+extern char Eunion[];		/* not in union */
+extern char Emountrpc[];	/* mount rpc error */
+extern char Eshutdown[];	/* device shut down */
+extern char Enocreate[];	/* mounted directory forbids creation */
+extern char Enonexist[];	/* file does not exist */
+extern char Eexist[];		/* file already exists */
+extern char Ebadsharp[];	/* unknown device in # filename */
+extern char Enotdir[];		/* not a directory */
+extern char Eisdir[];		/* file is a directory */
+extern char Ebadchar[];		/* bad character in file name */
+extern char Efilename[];	/* file name syntax */
+extern char Eperm[];		/* permission denied */
+extern char Ebadusefd[];	/* inappropriate use of fd */
+extern char Ebadarg[];		/* bad arg in system call */
+extern char Einuse[];		/* device or object already in use */
+extern char Eio[];		/* i/o error */
+extern char Etoobig[];		/* read or write too large */
+extern char Etoosmall[];	/* read or write too small */
+extern char Enoport[];		/* network port not available */
+extern char Ehungup[];		/* i/o on hungup channel */
+extern char Ebadctl[];		/* bad process or channel control request */
+extern char Enodev[];		/* no free devices */
+extern char Eprocdied[];	/* process exited */
+extern char Enochild[];		/* no living children */
+extern char Eioload[];		/* i/o error in demand load */
+extern char Enovmem[];		/* virtual memory allocation failed */
+extern char Ebadfd[];		/* fd out of range or not open */
+extern char Enofd[];		/* no free file descriptors */
+extern char Eisstream[];	/* seek on a stream */
+extern char Ebadexec[];		/* exec header invalid */
+extern char Etimedout[];	/* connection timed out */
+extern char Econrefused[];	/* connection refused */
+extern char Econinuse[];	/* connection in use */
+extern char Eintr[];		/* interrupted */
+extern char Enomem[];		/* kernel allocate failed */
+extern char Enoswap[];		/* swap space full */
+extern char Esoverlap[];	/* segments overlap */
+extern char Emouseset[];	/* mouse type already set */
+extern char Eshort[];		/* i/o count too small */
+extern char Egreg[];		/* ken has left the building */
+extern char Ebadspec[];		/* bad attach specifier */
+extern char Enoreg[];		/* process has no saved registers */
+extern char Enoattach[];	/* mount/attach disallowed */
+extern char Eshortstat[];	/* stat buffer too small */
+extern char Ebadstat[];		/* malformed stat buffer */
+extern char Enegoff[];		/* negative i/o offset */
+extern char Ecmdargs[];		/* wrong #args in control message */
--- /dev/null
+++ b/kern/exportfs.c
@@ -1,0 +1,821 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+typedef	struct Fid	Fid;
+typedef	struct Export	Export;
+typedef	struct Exq	Exq;
+
+#define	nil	((void*)0)
+
+enum
+{
+	Nfidhash	= 1,
+	MAXRPC		= MAXMSG+MAXFDATA,
+	MAXDIRREAD	= (MAXFDATA/DIRLEN)*DIRLEN
+};
+
+struct Export
+{
+	Ref	r;
+	Exq*	work;
+	Lock	fidlock;
+	Fid*	fid[Nfidhash];
+	Chan*	root;
+	Chan*	io;
+	Pgrp*	pgrp;
+	int	npart;
+	char	part[MAXRPC];
+};
+
+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	nointr;
+	int	noresponse;	/* don't respond to this one */
+	Exq*	next;
+	int	shut;		/* has been noted for shutdown */
+	Export*	export;
+	void*	slave;
+	Fcall	rpc;
+	char	buf[MAXRPC];
+};
+
+struct
+{
+	Lock	l;
+	Qlock	qwait;
+	Rendez	rwait;
+	Exq	*head;		/* work waiting for a slave */
+	Exq	*tail;
+}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*	Exauth(Export*, Fcall*);
+static char*	Exattach(Export*, Fcall*);
+static char*	Exclunk(Export*, Fcall*);
+static char*	Excreate(Export*, Fcall*);
+static char*	Exopen(Export*, Fcall*);
+static char*	Exread(Export*, Fcall*);
+static char*	Exremove(Export*, Fcall*);
+static char*	Exstat(Export*, Fcall*);
+static char*	Exwalk(Export*, Fcall*);
+static char*	Exwrite(Export*, Fcall*);
+static char*	Exwstat(Export*, Fcall*);
+static char*	Exversion(Export*, Fcall*);
+
+static char	*(*fcalls[Tmax])(Export*, Fcall*);
+
+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 *c;
+	Export *fs;
+
+	if(waserror())
+		return -1;
+
+	c = fdtochan(fd, ORDWR, 1, 1);
+	poperror();
+	c->flag |= CMSG;
+
+	fs = mallocz(sizeof(Export));
+	fs->r.ref = 1;
+	fs->pgrp = up->pgrp;
+	refinc(&fs->pgrp->r);
+	refinc(&up->slash->r);
+	fs->root = up->slash;
+	refinc(&fs->root->r);
+	fs->root = domount(fs->root);
+	fs->io = c;
+
+	exportproc(fs);
+
+	return 0;
+}
+
+static void
+exportinit(void)
+{
+	lock(&exq.l);
+	if(fcalls[Tversion] != nil) {
+		unlock(&exq.l);
+		return;
+	}
+
+	fmtinstall('F', fcallfmt);
+	fmtinstall('D', dirfmt);
+	fmtinstall('M', dirmodefmt);
+	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);
+}
+
+void
+exportproc(Export *fs)
+{
+	Exq *q;
+	char *buf;
+	int n, cn, len;
+
+	exportinit();
+
+	for(;;){
+		q = mallocz(sizeof(Exq));
+		if(q == 0)
+			panic("no memory");
+
+		q->rpc.data = q->buf + MAXMSG;
+
+		buf = q->buf;
+		len = MAXRPC;
+		if(fs->npart) {
+			memmove(buf, fs->part, fs->npart);
+			buf += fs->npart;
+			len -= fs->npart;
+			goto chk;
+		}
+		for(;;) {
+			if(waserror())
+				goto bad;
+
+			n = (*devtab[fs->io->type].read)(fs->io, buf, len, 0);
+			poperror();
+
+			if(n <= 0)
+				goto bad;
+
+			buf += n;
+			len -= n;
+	chk:
+			n = buf - q->buf;
+
+			/* convM2S returns size of correctly decoded message */
+			cn = convM2S(q->buf, &q->rpc, n);
+			if(cn < 0){
+				iprint("bad message type in devmnt\n");
+				goto bad;
+			}
+			if(cn > 0) {
+				n -= cn;
+				if(n < 0){
+					iprint("negative size in devmnt");
+					goto bad;
+				}
+				fs->npart = n;
+				if(n != 0)
+					memmove(fs->part, q->buf+cn, n);
+				break;
+			}
+		}
+		if(exdebug)
+			iprint("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;
+		refinc(&fs->r);
+
+		lock(&exq.l);
+		if(exq.head == nil)
+			exq.head = q;
+		else
+			exq.tail->next = q;
+		q->next = nil;
+		exq.tail = q;
+		unlock(&exq.l);
+		if(exq.qwait.first == nil) {
+			n = thread("exportfs", exslave, nil);
+/* iprint("launch export (pid=%ux)\n", n); */
+		}
+		rendwakeup(&exq.rwait);
+	}
+bad:
+	free(q);
+	exshutdown(fs);
+	exfree(fs);
+}
+
+void
+exflush(Export *fs, int flushtag, int tag)
+{
+	Exq *q, **last;
+	int n;
+	Fcall fc;
+	char buf[MAXMSG];
+
+	/* 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.l);
+	for(q = fs->work; q != nil; q = q->next){
+		if(q->rpc.tag == tag && !q->noresponse){
+			lock(&q->lk);
+			q->noresponse = 1;
+			if(!q->nointr)
+				intr(q->slave);
+			unlock(&q->lk);
+			unlock(&fs->r.l);
+			goto Respond;
+			return;
+		}
+	}
+	unlock(&fs->r.l);
+
+if(exdebug) iprint("exflush: did not find rpc: %d\n", tag);
+
+Respond:
+	fc.type = Rflush;
+	fc.tag = flushtag;
+	n = convS2M(&fc, buf);
+if(exdebug) iprint("exflush -> %F\n", &fc);
+	if(!waserror()){
+		(*devtab[fs->io->type].write)(fs->io, buf, n, 0);
+		poperror();
+	}
+}
+
+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;
+	}
+	unlock(&exq.l);
+
+	lock(&fs->r.l);
+	q = fs->work;
+	while(q != nil){
+		if(q->shut){
+			q = q->next;
+			continue;
+		}
+		q->shut = 1;
+		unlock(&fs->r.l);
+	/*	postnote(q->slave, 1, "interrupted", NUser);	*/
+		iprint("postnote 2!\n");
+		lock(&fs->r.l);
+		q = fs->work;
+	}
+	unlock(&fs->r.l);
+}
+
+void
+exfree(Export *fs)
+{
+	Fid *f, *n;
+	int i;
+
+	if(refdec(&fs->r) != 0)
+		return;
+	closepgrp(fs->pgrp);
+	cclose(fs->root);
+	cclose(fs->io);
+	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);
+}
+
+int
+exwork(void *a)
+{
+	return exq.head != nil;
+}
+
+void
+exslave(void *a)
+{
+	Export *fs;
+	Exq *q, *t, **last;
+	char *err;
+	int n;
+/*
+	closepgrp(up->pgrp);
+	up->pgrp = nil;
+*/
+	for(;;){
+		qlock(&exq.qwait);
+		rendsleep(&exq.rwait, exwork, nil);
+
+		lock(&exq.l);
+		q = exq.head;
+		if(q == nil) {
+			unlock(&exq.l);
+			qunlock(&exq.qwait);
+			continue;
+		}
+		exq.head = q->next;
+		q->slave = curthread();
+		unlock(&exq.l);
+
+		qunlock(&exq.qwait);
+
+		q->noresponse = 0;
+		q->nointr = 0;
+		fs = q->export;
+		lock(&fs->r.l);
+		q->next = fs->work;
+		fs->work = q;
+		unlock(&fs->r.l);
+
+		up->pgrp = q->export->pgrp;
+
+		if(exdebug > 1)
+			iprint("exslave dispatch %d %F\n", getpid(), &q->rpc);
+
+		if(waserror()){
+			iprint("exslave err %r\n");
+			err = up->errstr;
+			goto Err;
+		}
+		if(q->rpc.type >= Tmax || !fcalls[q->rpc.type])
+			err = "bad fcall type";
+		else
+			err = (*fcalls[q->rpc.type])(fs, &q->rpc);
+
+		poperror();
+		Err:;
+		q->rpc.type++;
+		if(err){
+			q->rpc.type = Rerror;
+			strncpy(q->rpc.ename, err, ERRLEN);
+		}
+		n = convS2M(&q->rpc, q->buf);
+
+		if(exdebug)
+			iprint("exslve %d -> %F\n", getpid(), &q->rpc);
+
+		lock(&q->lk);
+		if(q->noresponse == 0){
+			q->nointr = 1;
+			clearintr();
+			if(!waserror()){
+				(*devtab[fs->io->type].write)(fs->io, q->buf, n, 0);
+				poperror();
+			}
+		}
+		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)
+			iprint("exslave %d written %d\n", getpid(), q->rpc.tag);
+
+		lock(&fs->r.l);
+		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.l);
+
+		exfree(q->export);
+		free(q);
+	}
+	iprint("exslave shut down");
+	threadexit();
+}
+
+Fid*
+Exmkfid(Export *fs, int fid)
+{
+	ulong h;
+	Fid *f, *nf;
+
+	nf = mallocz(sizeof(Fid));
+	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);
+}
+
+char*
+Exsession(Export *e, Fcall *rpc)
+{
+	memset(rpc->authid, 0, sizeof(rpc->authid));
+	memset(rpc->authdom, 0, sizeof(rpc->authdom));
+	memset(rpc->chal, 0, sizeof(rpc->chal));
+	return nil;
+}
+
+char*
+Exauth(Export *e, Fcall *f)
+{
+	return "authentication not required";
+}
+
+char*
+Exattach(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+
+	f = Exmkfid(fs, rpc->fid);
+	if(f == nil)
+		return Einuse;
+	if(waserror()){
+		f->attached = 0;
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	f->chan = clone(fs->root, nil);
+	poperror();
+	rpc->qid = f->chan->qid;
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Exclone(Export *fs, Fcall *rpc)
+{
+	Fid *f, *nf;
+
+	if(rpc->fid == rpc->newfid)
+		return Einuse;
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	nf = Exmkfid(fs, rpc->newfid);
+	if(nf == nil){
+		Exputfid(fs, f);
+		return Einuse;
+	}
+	if(waserror()){
+		Exputfid(fs, f);
+		Exputfid(fs, nf);
+		return up->errstr;
+	}
+	nf->chan = clone(f->chan, nil);
+	poperror();
+	Exputfid(fs, f);
+	Exputfid(fs, nf);
+	return nil;
+}
+
+char*
+Exclunk(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f != nil){
+		f->attached = 0;
+		Exputfid(fs, f);
+	}
+	return nil;
+}
+
+char*
+Exwalk(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	c = walk(f->chan, rpc->name, 1);
+	if(c == nil)
+		error(Enonexist);
+	poperror();
+
+	f->chan = c;
+	rpc->qid = c->qid;
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Exopen(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	c = f->chan;
+	c = (*devtab[c->type].open)(c, rpc->mode);
+	poperror();
+
+	f->chan = c;
+	f->offset = 0;
+	rpc->qid = f->chan->qid;
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Excreate(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	c = f->chan;
+	if(c->mnt && !(c->flag&CCREATE))
+		c = createdir(c);
+	(*devtab[c->type].create)(c, rpc->name, rpc->mode, rpc->perm);
+	poperror();
+
+	f->chan = c;
+	rpc->qid = f->chan->qid;
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Exread(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+	long off;
+	int dir, n, seek;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+
+	c = f->chan;
+	dir = c->qid.path & CHDIR;
+	if(dir){
+		rpc->count -= rpc->count%DIRLEN;
+		if(rpc->offset%DIRLEN || rpc->count==0){
+			Exputfid(fs, f);
+			return Ereaddir;
+		}
+		if(f->offset > rpc->offset){
+			Exputfid(fs, f);
+			return Eseekdir;
+		}
+	}
+
+	if(waserror()) {
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+
+	for(;;){
+		n = rpc->count;
+		seek = 0;
+		off = rpc->offset;
+		if(dir && f->offset != off){
+			off = f->offset;
+			n = rpc->offset - off;
+			if(n > MAXDIRREAD)
+				n = MAXDIRREAD;
+			seek = 1;
+		}
+		if(dir && c->mnt != nil)
+			n = unionread(c, rpc->data, n);
+		else{
+			c->offset = off;
+			n = (*devtab[c->type].read)(c, rpc->data, n, off);
+		}
+		if(n == 0 || !seek)
+			break;
+		f->offset = off + n;
+		c->offset += n;
+	}
+	rpc->count = n;
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Exwrite(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	c = f->chan;
+	if(c->qid.path & CHDIR)
+		error(Eisdir);
+	rpc->count = (*devtab[c->type].write)(c, rpc->data, rpc->count, rpc->offset);
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Exstat(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	c = f->chan;
+	(*devtab[c->type].stat)(c, rpc->stat);
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Exwstat(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	c = f->chan;
+	(*devtab[c->type].wstat)(c, rpc->stat);
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+char*
+Exremove(Export *fs, Fcall *rpc)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->errstr;
+	}
+	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;
+}
--- /dev/null
+++ b/kern/fns.h
@@ -1,0 +1,377 @@
+#define	ROUND(s, sz)	(((s)+((sz)-1))&~((sz)-1))
+
+void		accounttime(void);
+void		addclock0link(void (*)(void), int);
+int		addphysseg(Physseg*);
+void		addbootfile(char*, uchar*, ulong);
+Block*		adjustblock(Block*, int);
+void		alarmkproc(void*);
+Block*		allocb(int);
+int		anyhigher(void);
+int		anyready(void);
+Page*		auxpage(void);
+Block*		bl2mem(uchar*, Block*, int);
+int		blocklen(Block*);
+void		callwithureg(void(*)(Ureg*));
+char*		c2name(Chan*);
+int		cangetc(void*);
+int		canlock(Lock*);
+int		canpage(Proc*);
+int		canputc(void*);
+int		canqlock(QLock*);
+int		canrlock(RWlock*);
+void		chandevinit(void);
+void		chandevreset(void);
+void		chandevshutdown(void);
+void		chanfree(Chan*);
+void		chanrec(Mnt*);
+void		checkalarms(void);
+void		checkb(Block*, char*);
+void		cinit(void);
+Chan*		cclone(Chan*);
+void		cclose(Chan*);
+char*	clipread(void);
+int		clipwrite(char*);
+void		closeegrp(Egrp*);
+void		closefgrp(Fgrp*);
+void		closemount(Mount*);
+void		closepgrp(Pgrp*);
+void		closergrp(Rgrp*);
+long		clrfpintr(void);
+void		cmderror(Cmdbuf*, char*);
+int		cmount(Chan**, Chan*, int, char*);
+void		cnameclose(Cname*);
+void		confinit(void);
+void		confinit1(int);
+int		consactive(void);
+void		(*consdebug)(void);
+void		copen(Chan*);
+Block*		concatblock(Block*);
+Block*		copyblock(Block*, int);
+void		copypage(Page*, Page*);
+int		cread(Chan*, uchar*, int, vlong);
+void		cunmount(Chan*, Chan*);
+void		cupdate(Chan*, uchar*, int, vlong);
+void		cwrite(Chan*, uchar*, int, vlong);
+ulong		dbgpc(Proc*);
+int		decref(Ref*);
+int		decrypt(void*, void*, int);
+void		delay(int);
+Chan*		devattach(int, char*);
+Block*		devbread(Chan*, long, ulong);
+long		devbwrite(Chan*, Block*, ulong);
+Chan*		devclone(Chan*);
+int		devconfig(int, char *, DevConf *);
+void		devcreate(Chan*, char*, int, ulong);
+void		devdir(Chan*, Qid, char*, vlong, char*, long, Dir*);
+long		devdirread(Chan*, char*, long, Dirtab*, int, Devgen*);
+Devgen		devgen;
+void		devinit(void);
+int		devno(int, int);
+Chan*		devopen(Chan*, int, Dirtab*, int, Devgen*);
+void		devpermcheck(char*, ulong, int);
+void		devpower(int);
+void		devremove(Chan*);
+void		devreset(void);
+void		devshutdown(void);
+int		devstat(Chan*, uchar*, int, Dirtab*, int, Devgen*);
+Walkqid*	devwalk(Chan*, Chan*, char**, int, Dirtab*, int, Devgen*);
+int		devwstat(Chan*, uchar*, int);
+void		drawactive(int);
+void		drawcmap(void);
+void		dumpaproc(Proc*);
+void		dumpqueues(void);
+void		dumpregs(Ureg*);
+void		dumpstack(void);
+Fgrp*		dupfgrp(Fgrp*);
+void		duppage(Page*);
+void		dupswap(Page*);
+int		emptystr(char*);
+int		encrypt(void*, void*, int);
+void		envcpy(Egrp*, Egrp*);
+int		eqchan(Chan*, Chan*, int);
+int		eqqid(Qid, Qid);
+void		error(char*);
+long		execregs(ulong, ulong, ulong);
+void		exhausted(char*);
+void		exit(int);
+uvlong		fastticks(uvlong*);
+int		fault(ulong, int);
+void		fdclose(int, int);
+Chan*		fdtochan(int, int, int, int);
+int		fixfault(Segment*, ulong, int, int);
+void		flushmmu(void);
+void		forkchild(Proc*, Ureg*);
+void		forkret(void);
+void		free(void*);
+void		freeb(Block*);
+void		freeblist(Block*);
+int		freebroken(void);
+void		freepte(Segment*, Pte*);
+void		freesegs(int);
+void		freesession(Session*);
+ulong		getmalloctag(void*);
+ulong		getrealloctag(void*);
+void		gotolabel(Label*);
+char*		getconfenv(void);
+int		haswaitq(void*);
+long		hostdomainwrite(char*, int);
+long		hostownerwrite(char*, int);
+void		hzsched(void);
+void		iallocinit(void);
+Block*		iallocb(int);
+void		iallocsummary(void);
+long		ibrk(ulong, int);
+void		ilock(Lock*);
+void		iunlock(Lock*);
+int		incref(Ref*);
+void		initseg(void);
+int		iprint(char*, ...);
+void		isdir(Chan*);
+int		iseve(void);
+#define	islo()	(0)
+Segment*	isoverlap(Proc*, ulong, int);
+int		ispages(void*);
+int		isphysseg(char*);
+void		ixsummary(void);
+void		kbdclock(void);
+int		kbdcr2nl(Queue*, int);
+int		kbdputc(Queue*, int);
+void		kbdrepeat(int);
+long		keyread(char*, int, long);
+void		kickpager(void);
+void		killbig(void);
+int		kproc(char*, void(*)(void*), void*);
+void		kprocchild(Proc*, void (*)(void*), void*);
+void		(*kproftimer)(ulong);
+void		ksetenv(char*, char*, int);
+void		kstrcpy(char*, char*, int);
+void		kstrdup(char**, char*);
+long		latin1(Rune*, int);
+int		lock(Lock*);
+void		lockinit(void);
+void		logopen(Log*);
+void		logclose(Log*);
+char*		logctl(Log*, int, char**, Logflag*);
+void		logn(Log*, int, void*, int);
+long		logread(Log*, void*, ulong, long);
+void		log(Log*, int, char*, ...);
+Cmdtab*		lookupcmd(Cmdbuf*, Cmdtab*, int);
+void		machinit(void);
+void*		mallocz(ulong, int);
+#define		malloc kmalloc
+void*		malloc(ulong);
+void		mallocsummary(void);
+Block*		mem2bl(uchar*, int);
+void		mfreeseg(Segment*, ulong, int);
+void		microdelay(int);
+void		mkqid(Qid*, vlong, ulong, int);
+void		mmurelease(Proc*);
+void		mmuswitch(Proc*);
+Chan*		mntauth(Chan*, char*);
+void		mntdump(void);
+long		mntversion(Chan*, char*, int, int);
+void		mountfree(Mount*);
+ulong		ms2tk(ulong);
+ulong		msize(void*);
+ulong		ms2tk(ulong);
+uvlong		ms2fastticks(ulong);
+void		muxclose(Mnt*);
+Chan*		namec(char*, int, int, ulong);
+Chan*		newchan(void);
+int		newfd(Chan*);
+Mhead*		newmhead(Chan*);
+Mount*		newmount(Mhead*, Chan*, int, char*);
+Page*		newpage(int, Segment **, ulong);
+Pgrp*		newpgrp(void);
+Rgrp*		newrgrp(void);
+Proc*		newproc(void);
+char*		nextelem(char*, char*);
+void		nexterror(void);
+Cname*		newcname(char*);
+int		notify(Ureg*);
+int		nrand(int);
+int		okaddr(ulong, ulong, int);
+int		openmode(ulong);
+Block*		packblock(Block*);
+Block*		padblock(Block*, int);
+void		pagechainhead(Page*);
+void		pageinit(void);
+void		pagersummary(void);
+void		panic(char*, ...);
+Cmdbuf*		parsecmd(char *a, int n);
+ulong		perfticks(void);
+void		pexit(char*, int);
+int		preempted(void);
+void		printinit(void);
+int		procindex(ulong);
+void		pgrpcpy(Pgrp*, Pgrp*);
+void		pgrpnote(ulong, char*, long, int);
+Pgrp*		pgrptab(int);
+void		pio(Segment *, ulong, ulong, Page **);
+#define		poperror()		up->nerrlab--
+void		portclock(Ureg*);
+int		postnote(Proc*, int, char*, int);
+int		pprint(char*, ...);
+void		prflush(void);
+ulong		procalarm(ulong);
+int		proccounter(char *name);
+void		procctl(Proc*);
+void		procdump(void);
+int		procfdprint(Chan*, int, int, char*, int);
+void		procinit0(void);
+void		procflushseg(Segment*);
+void		procpriority(Proc*, int, int);
+Proc*		proctab(int);
+void		procwired(Proc*, int);
+Pte*		ptealloc(void);
+Pte*		ptecpy(Pte*);
+int		pullblock(Block**, int);
+Block*		pullupblock(Block*, int);
+Block*		pullupqueue(Queue*, int);
+void		putmhead(Mhead*);
+void		putmmu(ulong, ulong, Page*);
+void		putpage(Page*);
+void		putseg(Segment*);
+void		putstr(char*);
+void		putstrn(char*, int);
+void		putswap(Page*);
+ulong		pwait(Waitmsg*);
+Label*	pwaserror(void);
+void		qaddlist(Queue*, Block*);
+Block*		qbread(Queue*, int);
+long		qbwrite(Queue*, Block*);
+Queue*		qbypass(void (*)(void*, Block*), void*);
+int		qcanread(Queue*);
+void		qclose(Queue*);
+int		qconsume(Queue*, void*, int);
+Block*		qcopy(Queue*, int, ulong);
+int		qdiscard(Queue*, int);
+void		qflush(Queue*);
+void		qfree(Queue*);
+int		qfull(Queue*);
+Block*		qget(Queue*);
+void		qhangup(Queue*, char*);
+int		qisclosed(Queue*);
+void		qinit(void);
+int		qiwrite(Queue*, void*, int);
+int		qlen(Queue*);
+void		qlock(QLock*);
+Queue*		qopen(int, int, void (*)(void*), void*);
+int		qpass(Queue*, Block*);
+int		qpassnolim(Queue*, Block*);
+int		qproduce(Queue*, void*, int);
+void		qputback(Queue*, Block*);
+long		qread(Queue*, void*, int);
+Block*		qremove(Queue*);
+void		qreopen(Queue*);
+void		qsetlimit(Queue*, int);
+void		qunlock(QLock*);
+int		qwindow(Queue*);
+int		qwrite(Queue*, void*, int);
+void		qnoblock(Queue*, int);
+int		rand(void);
+void		randominit(void);
+ulong		randomread(void*, ulong);
+void		rdb(void);
+int		readnum(ulong, char*, ulong, ulong, int);
+int		readstr(ulong, char*, ulong, char*);
+void		ready(Proc*);
+void		rebootcmd(int, char**);
+void		reboot(void*, void*, ulong);
+void		relocateseg(Segment*, ulong);
+void		renameuser(char*, char*);
+void		resched(char*);
+void		resrcwait(char*);
+int		return0(void*);
+void		rlock(RWlock*);
+long		rtctime(void);
+void		runlock(RWlock*);
+Proc*		runproc(void);
+void		savefpregs(FPsave*);
+void		(*saveintrts)(void);
+void		sched(void);
+void		scheddump(void);
+void		schedinit(void);
+void		(*screenputs)(char*, int);
+long		seconds(void);
+ulong		segattach(Proc*, ulong, char *, ulong, ulong);
+void		segclock(ulong);
+void		segpage(Segment*, Page*);
+void		setkernur(Ureg*, Proc*);
+int		setlabel(Label*);
+void		setmalloctag(void*, ulong);
+void		setrealloctag(void*, ulong);
+void		setregisters(Ureg*, char*, char*, int);
+void		setswapchan(Chan*);
+char*		skipslash(char*);
+void		sleep(Rendez*, int(*)(void*), void*);
+void*		smalloc(ulong);
+int		splhi(void);
+int		spllo(void);
+void		splx(int);
+void		splxpc(int);
+char*		srvname(Chan*);
+int		swapcount(ulong);
+int		swapfull(void);
+void		swapinit(void);
+void		timeradd(Timer*);
+void		timerdel(Timer*);
+void		timersinit(void);
+void		timerintr(Ureg*, uvlong);
+void		timerset(uvlong);
+ulong		tk2ms(ulong);
+#define		TK2MS(x) ((x)*(1000/HZ))
+vlong		todget(vlong*);
+void		todfix(void);
+void		todsetfreq(vlong);
+void		todinit(void);
+void		todset(vlong, vlong, int);
+Block*		trimblock(Block*, int, int);
+void		tsleep(Rendez*, int (*)(void*), void*, int);
+int		uartctl(Uart*, char*);
+int		uartgetc(void);
+void		uartkick(void*);
+void		uartmouse(Uart*, int (*)(Queue*, int), int);
+void		uartputc(int);
+void		uartputs(char*, int);
+void		uartrecv(Uart*, char);
+Uart*		uartsetup(Uart*);
+int		uartstageoutput(Uart*);
+void		unbreak(Proc*);
+void		uncachepage(Page*);
+long		unionread(Chan*, void*, long);
+void		unlock(Lock*);
+Proc**	uploc(void);
+void		userinit(void);
+ulong		userpc(void);
+long		userwrite(char*, int);
+#define	validaddr(a, b, c)
+void		validname(char*, int);
+void		validstat(uchar*, int);
+void		vcacheinval(Page*, ulong);
+void*		vmemchr(void*, int, int);
+Proc*		wakeup(Rendez*);
+int		walk(Chan**, char**, int, int, int*);
+#define	waserror()	(setjmp(pwaserror()->buf))
+void		wlock(RWlock*);
+void		wunlock(RWlock*);
+void*		xalloc(ulong);
+void*		xallocz(ulong, int);
+void		xfree(void*);
+void		xhole(ulong, ulong);
+void		xinit(void);
+int		xmerge(void*, void*);
+void*		xspanalloc(ulong, int, ulong);
+void		xsummary(void);
+void		yield(void);
+Segment*	data2txt(Segment*);
+Segment*	dupseg(Segment**, int, int);
+Segment*	newseg(int, ulong, ulong);
+Segment*	seg(Proc*, ulong, int);
+void		hnputv(void*, vlong);
+void		hnputl(void*, ulong);
+void		hnputs(void*, ushort);
+vlong		nhgetv(void*);
+ulong		nhgetl(void*);
+ushort		nhgets(void*);
--- /dev/null
+++ b/kern/mkfile
@@ -1,0 +1,47 @@
+<$DSRC/mkfile-$CONF
+TARG=libkern.$L
+
+OFILES=\
+	allocb.$O\
+	cache.$O\
+	chan.$O\
+	data.$O\
+	dev.$O\
+	devcons.$O\
+	devdraw.$O\
+	devip.$O\
+	devmnt.$O\
+	devmouse.$O\
+	devpipe.$O\
+	devroot.$O\
+	devssl.$O\
+	devtab.$O\
+	error.$O\
+	parse.$O\
+	pgrp.$O\
+	procinit.$O\
+	rwlock.$O\
+	sleep.$O\
+	smalloc.$O\
+	stub.$O\
+	sysfile.$O\
+	sysproc.$O\
+	qio.$O\
+	qlock.$O\
+	term.$O\
+	todo.$O\
+	uart.$O\
+	waserror.$O\
+	$DEVIP.$O\
+	$OSHOOKS.$O\
+	$DEVFS.$O
+
+HFILE=\
+	dat.h\
+	devip.h\
+	error.h\
+	fns.h\
+	netif.h\
+	screen.h
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/kern/netif.h
@@ -1,0 +1,133 @@
+typedef struct Etherpkt	Etherpkt;
+typedef struct Netaddr	Netaddr;
+typedef struct Netfile	Netfile;
+typedef struct Netif	Netif;
+
+enum
+{
+	Nmaxaddr=	64,
+	Nmhash=		31,
+
+	Ncloneqid=	1,
+	Naddrqid,
+	N2ndqid,
+	N3rdqid,
+	Ndataqid,
+	Nctlqid,
+	Nstatqid,
+	Ntypeqid,
+	Nifstatqid,
+};
+
+/*
+ *  Macros to manage Qid's used for multiplexed devices
+ */
+#define NETTYPE(x)	(((ulong)x)&0x1f)
+#define NETID(x)	((((ulong)x))>>5)
+#define NETQID(i,t)	((((ulong)i)<<5)|(t))
+
+/*
+ *  one per multiplexed connection
+ */
+struct Netfile
+{
+	QLock lk;
+
+	int	inuse;
+	ulong	mode;
+	char	owner[KNAMELEN];
+
+	int	type;			/* multiplexor type */
+	int	prom;			/* promiscuous mode */
+	int	scan;			/* base station scanning interval */
+	int	bridge;			/* bridge mode */
+	int	headersonly;		/* headers only - no data */
+	uchar	maddr[8];		/* bitmask of multicast addresses requested */
+	int	nmaddr;			/* number of multicast addresses */
+
+	Queue	*in;			/* input buffer */
+};
+
+/*
+ *  a network address
+ */
+struct Netaddr
+{
+	Netaddr	*next;		/* allocation chain */
+	Netaddr	*hnext;
+	uchar	addr[Nmaxaddr];
+	int	ref;
+};
+
+/*
+ *  a network interface
+ */
+struct Netif
+{
+	QLock lk;
+
+	/* multiplexing */
+	char	name[KNAMELEN];		/* for top level directory */
+	int	nfile;			/* max number of Netfiles */
+	Netfile	**f;
+
+	/* about net */
+	int	limit;			/* flow control */
+	int	alen;			/* address length */
+	int	mbps;			/* megabits per sec */
+	uchar	addr[Nmaxaddr];
+	uchar	bcast[Nmaxaddr];
+	Netaddr	*maddr;			/* known multicast addresses */
+	int	nmaddr;			/* number of known multicast addresses */
+	Netaddr *mhash[Nmhash];		/* hash table of multicast addresses */
+	int	prom;			/* number of promiscuous opens */
+	int	scan;			/* number of base station scanners */
+	int	all;			/* number of -1 multiplexors */
+
+	/* statistics */
+	int	misses;
+	int	inpackets;
+	int	outpackets;
+	int	crcs;		/* input crc errors */
+	int	oerrs;		/* output errors */
+	int	frames;		/* framing errors */
+	int	overflows;	/* packet overflows */
+	int	buffs;		/* buffering errors */
+	int	soverflows;	/* software overflow */
+
+	/* routines for touching the hardware */
+	void	*arg;
+	void	(*promiscuous)(void*, int);
+	void	(*multicast)(void*, uchar*, int);
+	void	(*scanbs)(void*, uint);	/* scan for base stations */
+};
+
+void	netifinit(Netif*, char*, int, ulong);
+Walkqid*	netifwalk(Netif*, Chan*, Chan*, char **, int);
+Chan*	netifopen(Netif*, Chan*, int);
+void	netifclose(Netif*, Chan*);
+long	netifread(Netif*, Chan*, void*, long, ulong);
+Block*	netifbread(Netif*, Chan*, long, ulong);
+long	netifwrite(Netif*, Chan*, void*, long);
+int	netifwstat(Netif*, Chan*, uchar*, int);
+int	netifstat(Netif*, Chan*, uchar*, int);
+int	activemulti(Netif*, uchar*, int);
+
+/*
+ *  Ethernet specific
+ */
+enum
+{
+	Eaddrlen=	6,
+	ETHERMINTU =	60,		/* minimum transmit size */
+	ETHERMAXTU =	1514,		/* maximum transmit size */
+	ETHERHDRSIZE =	14,		/* size of an ethernet header */
+};
+
+struct Etherpkt
+{
+	uchar	d[Eaddrlen];
+	uchar	s[Eaddrlen];
+	uchar	type[2];
+	uchar	data[1500];
+};
--- /dev/null
+++ b/kern/os-windows.c
@@ -1,0 +1,184 @@
+#include <windows.h>
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Oproc Oproc;
+struct Oproc {
+	int tid;
+	HANDLE	*sema;
+};
+
+char	*argv0;
+_declspec(thread)       Proc *CT;
+
+Proc*
+_getproc(void)
+{
+	return CT;
+}
+
+void
+_setproc(Proc *p)
+{
+	CT = p;
+}
+
+void
+oserrstr(void)
+{
+	char *p;
+	char buf[ERRMAX];
+
+	if((p = strerror(errno)) != nil)
+		strecpy(up->errstr, up->errstr+ERRMAX, p);
+	else
+		snprint(up->errstr, ERRMAX, "unix error %d", errno);
+}
+
+void
+oserror(void)
+{
+	oserrstr();
+	nexterror();
+}
+
+void
+osinit(void)
+{
+	Oproc *t;
+	static Proc firstprocCTstore;
+
+	CT = &firstprocCTstore;
+	t = (Oproc*) CT->oproc;
+	assert(t != 0);
+
+	t->tid = GetCurrentThreadId();
+	t->sema = CreateSemaphore(0, 0, 1000, 0);
+	if(t->sema == 0) {
+		oserror();
+		fatal("could not create semaphore: %r");
+	}
+}
+
+void
+osnewproc(Proc *p)
+{
+	Oproc *op;
+
+	op = (Oproc*)p->oproc;
+	op->sema = CreateSemaphore(0, 0, 1000, 0);
+	if (op->sema == 0) {
+		oserror();
+		fatal("could not create semaphore: %r");
+	}
+}
+
+void
+osmsleep(int ms)
+{
+	Sleep((DWORD) ms);
+}
+
+void
+osyield(void)
+{
+	Sleep(0);
+}
+
+static DWORD WINAPI tramp(LPVOID vp);
+
+void
+osproc(Proc *p)
+{
+	int tid;
+
+	if(CreateThread(0, 0, tramp, p, 0, &tid) == 0) {
+		oserror();
+		fatal("osproc: %r");
+	}
+
+	Sleep(0);
+}
+
+static DWORD WINAPI
+tramp(LPVOID vp)
+{
+	Proc *p = (Proc *) vp;
+	Oproc *op = (Oproc*) p->oproc;
+
+	CT = p;
+	op->tid = GetCurrentThreadId();
+	op->sema = CreateSemaphore(0, 0, 1000, 0);
+	if(op->sema == 0) {
+		oserror();
+		fatal("could not create semaphore: %r");
+	}
+
+ 	(*p->fn)(p->arg);
+	ExitThread(0);
+	return 0;
+}
+
+void
+procsleep(void)
+{
+	Proc *p;
+	Oproc *op;
+
+	p = up;
+	op = (Oproc*)p->oproc;
+	WaitForSingleObject(op->sema, INFINITE);}
+
+void
+procwakeup(Proc *p)
+{
+	Oproc *op;
+
+	op = (Oproc*)p->oproc;
+	ReleaseSemaphore(op->sema, 1, 0);
+}
+
+void
+randominit(void)
+{
+	srand(seconds());
+}
+
+ulong
+randomread(void *v, ulong n)
+{
+	int m, i, *r;
+
+	m = (n / sizeof(int)) * sizeof(int);
+	for (i = 0, r = (int*)v; i < m; i += sizeof(int)) {
+		*r = rand();
+		r += sizeof(int);
+	}
+
+	return m;
+}
+
+long
+seconds(void)
+{
+	return time(0);
+}
+
+int
+ticks(void)
+{
+	return GetTickCount();
+}
+
+extern int	main(int, char*[]);
+static int	args(char *argv[], int n, char *p);
+
+int PASCAL
+WinMain(HANDLE hInst, HANDLE hPrev, LPSTR arg, int nshow)
+{
+	main(__argc, __argv);
+	ExitThread(0);
+	return 0;
+}
--- /dev/null
+++ b/kern/parse.c
@@ -1,0 +1,113 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+/*
+ * Generous estimate of number of fields, including terminal nil pointer
+ */
+static int
+ncmdfield(char *p, int n)
+{
+	int white, nwhite;
+	char *ep;
+	int nf;
+
+	if(p == nil)
+		return 1;
+
+	nf = 0;
+	ep = p+n;
+	white = 1;	/* first text will start field */
+	while(p < ep){
+		nwhite = (strchr(" \t\r\n", *p++ & 0xFF) != 0);	/* UTF is irrelevant */
+		if(white && !nwhite)	/* beginning of field */
+			nf++;
+		white = nwhite;
+	}
+	return nf+1;	/* +1 for nil */
+}
+
+/*
+ *  parse a command written to a device
+ */
+Cmdbuf*
+parsecmd(char *p, int n)
+{
+	Cmdbuf *volatile cb;
+	int nf;
+	char *sp;
+
+	nf = ncmdfield(p, n);
+
+	/* allocate Cmdbuf plus string pointers plus copy of string including \0 */
+	sp = smalloc(sizeof(*cb) + nf * sizeof(char*) + n + 1);
+	cb = (Cmdbuf*)sp;
+	cb->f = (char**)(&cb[1]);
+	cb->buf = (char*)(&cb->f[nf]);
+
+	if(up!=nil && waserror()){
+		free(cb);
+		nexterror();
+	}
+	memmove(cb->buf, p, n);
+	if(up != nil)
+		poperror();
+
+	/* dump new line and null terminate */
+	if(n > 0 && cb->buf[n-1] == '\n')
+		n--;
+	cb->buf[n] = '\0';
+
+	cb->nf = tokenize(cb->buf, cb->f, nf-1);
+	cb->f[cb->nf] = nil;
+
+	return cb;
+}
+
+/*
+ * Reconstruct original message, for error diagnostic
+ */
+void
+cmderror(Cmdbuf *cb, char *s)
+{
+	int i;
+	char *p, *e;
+
+	p = up->genbuf;
+	e = p+ERRMAX-10;
+	p = seprint(p, e, "%s \"", s);
+	for(i=0; i<cb->nf; i++){
+		if(i > 0)
+			p = seprint(p, e, " ");
+		p = seprint(p, e, "%q", cb->f[i]);
+	}
+	strcpy(p, "\"");
+	error(up->genbuf);
+}
+
+/*
+ * Look up entry in table
+ */
+Cmdtab*
+lookupcmd(Cmdbuf *cb, Cmdtab *ctab, int nctab)
+{
+	int i;
+	Cmdtab *ct;
+
+	if(cb->nf == 0)
+		error("empty control message");
+
+	for(ct = ctab, i=0; i<nctab; i++, ct++){
+		if(strcmp(ct->cmd, "*") !=0)	/* wildcard always matches */
+		if(strcmp(ct->cmd, cb->f[0]) != 0)
+			continue;
+		if(ct->narg != 0 && ct->narg != cb->nf)
+			cmderror(cb, Ecmdargs);
+		return ct;
+	}
+
+	cmderror(cb, "unknown control message");
+	return nil;
+}
--- /dev/null
+++ b/kern/pgrp.c
@@ -1,0 +1,272 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+static Ref pgrpid;
+static Ref mountid;
+
+#ifdef NOTDEF
+void
+pgrpnote(ulong noteid, char *a, long n, int flag)
+{
+	Proc *p, *ep;
+	char buf[ERRMAX];
+
+	if(n >= ERRMAX-1)
+		error(Etoobig);
+
+	memmove(buf, a, n);
+	buf[n] = 0;
+	p = proctab(0);
+	ep = p+conf.nproc;
+	for(; p < ep; p++) {
+		if(p->state == Dead)
+			continue;
+		if(up != p && p->noteid == noteid && p->kp == 0) {
+			qlock(&p->debug);
+			if(p->pid == 0 || p->noteid != noteid){
+				qunlock(&p->debug);
+				continue;
+			}
+			if(!waserror()) {
+				postnote(p, 0, buf, flag);
+				poperror();
+			}
+			qunlock(&p->debug);
+		}
+	}
+}
+#endif
+
+Pgrp*
+newpgrp(void)
+{
+	Pgrp *p;
+
+	p = smalloc(sizeof(Pgrp));
+	p->ref.ref = 1;
+	p->pgrpid = incref(&pgrpid);
+	return p;
+}
+
+Rgrp*
+newrgrp(void)
+{
+	Rgrp *r;
+
+	r = smalloc(sizeof(Rgrp));
+	r->ref.ref = 1;
+	return r;
+}
+
+void
+closergrp(Rgrp *r)
+{
+	if(decref(&r->ref) == 0)
+		free(r);
+}
+
+void
+closepgrp(Pgrp *p)
+{
+	Mhead **h, **e, *f, *next;
+
+	if(decref(&p->ref) != 0)
+		return;
+
+	qlock(&p->debug);
+	wlock(&p->ns);
+	p->pgrpid = -1;
+
+	e = &p->mnthash[MNTHASH];
+	for(h = p->mnthash; h < e; h++) {
+		for(f = *h; f; f = next) {
+			wlock(&f->lock);
+			cclose(f->from);
+			mountfree(f->mount);
+			f->mount = nil;
+			next = f->hash;
+			wunlock(&f->lock);
+			putmhead(f);
+		}
+	}
+	wunlock(&p->ns);
+	qunlock(&p->debug);
+	free(p);
+}
+
+void
+pgrpinsert(Mount **order, Mount *m)
+{
+	Mount *f;
+
+	m->order = 0;
+	if(*order == 0) {
+		*order = m;
+		return;
+	}
+	for(f = *order; f; f = f->order) {
+		if(m->mountid < f->mountid) {
+			m->order = f;
+			*order = m;
+			return;
+		}
+		order = &f->order;
+	}
+	*order = m;
+}
+
+/*
+ * pgrpcpy MUST preserve the mountid allocation order of the parent group
+ */
+void
+pgrpcpy(Pgrp *to, Pgrp *from)
+{
+	int i;
+	Mount *n, *m, **link, *order;
+	Mhead *f, **tom, **l, *mh;
+
+	wlock(&from->ns);
+	order = 0;
+	tom = to->mnthash;
+	for(i = 0; i < MNTHASH; i++) {
+		l = tom++;
+		for(f = from->mnthash[i]; f; f = f->hash) {
+			rlock(&f->lock);
+			mh = newmhead(f->from);
+			*l = mh;
+			l = &mh->hash;
+			link = &mh->mount;
+			for(m = f->mount; m; m = m->next) {
+				n = newmount(mh, m->to, m->mflag, m->spec);
+				m->copy = n;
+				pgrpinsert(&order, m);
+				*link = n;
+				link = &n->next;
+			}
+			runlock(&f->lock);
+		}
+	}
+	/*
+	 * Allocate mount ids in the same sequence as the parent group
+	 */
+	lock(&mountid.lk);
+	for(m = order; m; m = m->order)
+		m->copy->mountid = mountid.ref++;
+	unlock(&mountid.lk);
+	wunlock(&from->ns);
+}
+
+Fgrp*
+dupfgrp(Fgrp *f)
+{
+	Fgrp *new;
+	Chan *c;
+	int i;
+
+	new = smalloc(sizeof(Fgrp));
+	if(f == nil){
+		new->fd = smalloc(DELTAFD*sizeof(Chan*));
+		new->nfd = DELTAFD;
+		new->ref.ref = 1;
+		return new;
+	}
+
+	lock(&f->ref.lk);
+	/* Make new fd list shorter if possible, preserving quantization */
+	new->nfd = f->maxfd+1;
+	i = new->nfd%DELTAFD;
+	if(i != 0)
+		new->nfd += DELTAFD - i;
+	new->fd = malloc(new->nfd*sizeof(Chan*));
+	if(new->fd == 0){
+		unlock(&f->ref.lk);
+		error("no memory for fgrp");
+	}
+	new->ref.ref = 1;
+
+	new->maxfd = f->maxfd;
+	for(i = 0; i <= f->maxfd; i++) {
+		if(c = f->fd[i]){
+			incref(&c->ref);
+			new->fd[i] = c;
+		}
+	}
+	unlock(&f->ref.lk);
+
+	return new;
+}
+
+void
+closefgrp(Fgrp *f)
+{
+	int i;
+	Chan *c;
+
+	if(f == 0)
+		return;
+
+	if(decref(&f->ref) != 0)
+		return;
+
+	for(i = 0; i <= f->maxfd; i++)
+		if(c = f->fd[i])
+			cclose(c);
+
+	free(f->fd);
+	free(f);
+}
+
+Mount*
+newmount(Mhead *mh, Chan *to, int flag, char *spec)
+{
+	Mount *m;
+
+	m = smalloc(sizeof(Mount));
+	m->to = to;
+	m->head = mh;
+	incref(&to->ref);
+	m->mountid = incref(&mountid);
+	m->mflag = flag;
+	if(spec != 0)
+		kstrdup(&m->spec, spec);
+
+	return m;
+}
+
+void
+mountfree(Mount *m)
+{
+	Mount *f;
+
+	while(m) {
+		f = m->next;
+		cclose(m->to);
+		m->mountid = 0;
+		free(m->spec);
+		free(m);
+		m = f;
+	}
+}
+
+#ifdef NOTDEF
+void
+resrcwait(char *reason)
+{
+	char *p;
+
+	if(up == 0)
+		panic("resrcwait");
+
+	p = up->psstate;
+	if(reason) {
+		up->psstate = reason;
+		print("%s\n", reason);
+	}
+
+	tsleep(&up->sleep, return0, 0, 300);
+	up->psstate = p;
+}
+#endif
--- /dev/null
+++ b/kern/posix.c
@@ -1,0 +1,190 @@
+/*
+ * Posix generic OS implementation for drawterm.
+ */
+
+#include <pthread.h>
+#include <time.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Oproc Oproc;
+struct Oproc
+{
+	int p[2];
+};
+
+static pthread_key_t prdakey;
+
+Proc*
+_getproc(void)
+{
+	void *v;
+
+	if((v = pthread_getspecific(prdakey)) == nil)
+		panic("cannot getspecific");
+	return v;
+}
+
+void
+_setproc(Proc *p)
+{
+	if(pthread_setspecific(prdakey, p) != 0)
+		panic("cannot setspecific");
+}
+
+void
+osinit(void)
+{
+	if(pthread_key_create(&prdakey, 0))
+		panic("cannot pthread_key_create");
+}
+
+#undef pipe
+void
+osnewproc(Proc *p)
+{
+	Oproc *op;
+
+	op = (Oproc*)p->oproc;
+	if(pipe(op->p) < 0)
+		panic("cannot pipe");
+}
+
+void
+osmsleep(int ms)
+{
+	struct timeval tv;
+
+	tv.tv_sec = ms / 1000;
+	tv.tv_usec = (ms % 1000) * 1000; /* micro */
+	if(select(0, NULL, NULL, NULL, &tv) < 0)
+		panic("select");
+}
+
+void
+osyield(void)
+{
+	sched_yield();
+}
+
+void
+oserrstr(void)
+{
+	char *p;
+	char buf[ERRMAX];
+
+	if((p = strerror(errno)) != nil)
+		strecpy(up->errstr, up->errstr+ERRMAX, p);
+	else
+		snprint(up->errstr, ERRMAX, "unix error %d", errno);
+}
+
+void
+oserror(void)
+{
+	oserrstr();
+	nexterror();
+}
+
+static void* tramp(void*);
+
+void
+osproc(Proc *p)
+{
+	pthread_t pid;
+
+	if(pthread_create(&pid, nil, tramp, p)){
+		oserrstr();
+		panic("osproc: %r");
+	}
+	sched_yield();
+}
+
+static void*
+tramp(void *vp)
+{
+	Proc *p;
+
+	p = vp;
+	if(pthread_setspecific(prdakey, p))
+		panic("cannot setspecific");
+	(*p->fn)(p->arg);
+	/* BUG: leaks Proc */
+	pthread_setspecific(prdakey, 0);
+	pthread_exit(0);
+}
+
+void
+procsleep(void)
+{
+	int c;
+	Proc *p;
+	Oproc *op;
+
+	p = up;
+	op = (Oproc*)p->oproc;
+	while(read(op->p[0], &c, 1) != 1)
+		;
+}
+
+void
+procwakeup(Proc *p)
+{
+	char c;
+	Oproc *op;
+
+	op = (Oproc*)p->oproc;
+	c = 'a';
+	write(op->p[1], &c, 1);
+}
+
+int randfd;
+#undef open
+void
+randominit(void)
+{
+	if((randfd = open("/dev/urandom", OREAD)) < 0)
+	if((randfd = open("/dev/random", OREAD)) < 0)
+		panic("open /dev/random: %r");
+}
+
+#undef read
+ulong
+randomread(void *v, ulong n)
+{
+	int m;
+
+	if((m = read(randfd, v, n)) != n)
+		panic("short read from /dev/random: %d but %d", n, m);
+	return m;
+}
+
+#undef time
+long
+seconds(void)
+{
+	return time(0);
+}
+
+ulong
+ticks(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t, nil) < 0)
+		return 0;
+	if(sec0 == 0){
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000;
+}
+
--- /dev/null
+++ b/kern/procinit.c
@@ -1,0 +1,67 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Rgrp *thergrp;
+
+void
+procinit0(void)
+{
+	Proc *p;
+
+	p = newproc();
+	p->fgrp = dupfgrp(nil);
+	p->rgrp = newrgrp();
+	p->pgrp = newpgrp();
+	_setproc(p);
+
+	up->slash = namec("#/", Atodir, 0, 0);
+	cnameclose(up->slash->name);
+	up->slash->name = newcname("/");
+	up->dot = cclone(up->slash);
+}
+
+Ref pidref;
+
+Proc*
+newproc(void)
+{
+	Proc *p;
+
+	p = mallocz(sizeof(Proc), 1);
+	p->pid = incref(&pidref);
+	strcpy(p->user, eve);
+	p->syserrstr = p->errbuf0;
+	p->errstr = p->errbuf1;
+	strcpy(p->text, "drawterm");
+	osnewproc(p);
+	return p;
+}
+
+int
+kproc(char *name, void (*fn)(void*), void *arg)
+{
+	Proc *p;
+
+	p = newproc();
+	p->fn = fn;
+	p->arg = arg;
+	p->slash = cclone(up->slash);
+	p->dot = cclone(up->dot);
+	p->rgrp = up->rgrp;
+	if(p->rgrp)
+		incref(&p->rgrp->ref);
+	p->pgrp = up->pgrp;
+	if(up->pgrp)
+		incref(&up->pgrp->ref);
+	p->fgrp = up->fgrp;
+	if(p->fgrp)
+		incref(&p->fgrp->ref);
+	strecpy(p->text, sizeof p->text, name);
+
+	osproc(p);
+	return p->pid;
+}
+
--- /dev/null
+++ b/kern/qio.c
@@ -1,0 +1,1524 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+static ulong padblockcnt;
+static ulong concatblockcnt;
+static ulong pullupblockcnt;
+static ulong copyblockcnt;
+static ulong consumecnt;
+static ulong producecnt;
+static ulong qcopycnt;
+
+static int debugging;
+
+#define QDEBUG	if(0)
+
+/*
+ *  IO queues
+ */
+struct Queue
+{
+	Lock lk;
+
+	Block*	bfirst;		/* buffer */
+	Block*	blast;
+
+	int	len;		/* bytes allocated to queue */
+	int	dlen;		/* data bytes in queue */
+	int	limit;		/* max bytes in queue */
+	int	inilim;		/* initial limit */
+	int	state;
+	int	noblock;	/* true if writes return immediately when q full */
+	int	eof;		/* number of eofs read by user */
+
+	void	(*kick)(void*);	/* restart output */
+	void	(*bypass)(void*, Block*);	/* bypass queue altogether */
+	void*	arg;		/* argument to kick */
+
+	QLock	rlock;		/* mutex for reading processes */
+	Rendez	rr;		/* process waiting to read */
+	QLock	wlock;		/* mutex for writing processes */
+	Rendez	wr;		/* process waiting to write */
+
+	char	err[ERRMAX];
+};
+
+enum
+{
+	Maxatomic	= 64*1024,
+};
+
+uint	qiomaxatomic = Maxatomic;
+
+void
+ixsummary(void)
+{
+	debugging ^= 1;
+	iallocsummary();
+	print("pad %lud, concat %lud, pullup %lud, copy %lud\n",
+		padblockcnt, concatblockcnt, pullupblockcnt, copyblockcnt);
+	print("consume %lud, produce %lud, qcopy %lud\n",
+		consumecnt, producecnt, qcopycnt);
+}
+
+/*
+ *  free a list of blocks
+ */
+void
+freeblist(Block *b)
+{
+	Block *next;
+
+	for(; b != 0; b = next){
+		next = b->next;
+		b->next = 0;
+		freeb(b);
+	}
+}
+
+/*
+ *  pad a block to the front (or the back if size is negative)
+ */
+Block*
+padblock(Block *bp, int size)
+{
+	int n;
+	Block *nbp;
+
+	QDEBUG checkb(bp, "padblock 1");
+	if(size >= 0){
+		if(bp->rp - bp->base >= size){
+			bp->rp -= size;
+			return bp;
+		}
+
+		if(bp->next)
+			panic("padblock 0x%luX", getcallerpc(&bp));
+		n = BLEN(bp);
+		padblockcnt++;
+		nbp = allocb(size+n);
+		nbp->rp += size;
+		nbp->wp = nbp->rp;
+		memmove(nbp->wp, bp->rp, n);
+		nbp->wp += n;
+		freeb(bp);
+		nbp->rp -= size;
+	} else {
+		size = -size;
+
+		if(bp->next)
+			panic("padblock 0x%luX", getcallerpc(&bp));
+
+		if(bp->lim - bp->wp >= size)
+			return bp;
+
+		n = BLEN(bp);
+		padblockcnt++;
+		nbp = allocb(size+n);
+		memmove(nbp->wp, bp->rp, n);
+		nbp->wp += n;
+		freeb(bp);
+	}
+	QDEBUG checkb(nbp, "padblock 1");
+	return nbp;
+}
+
+/*
+ *  return count of bytes in a string of blocks
+ */
+int
+blocklen(Block *bp)
+{
+	int len;
+
+	len = 0;
+	while(bp) {
+		len += BLEN(bp);
+		bp = bp->next;
+	}
+	return len;
+}
+
+/*
+ * return count of space in blocks
+ */
+int
+blockalloclen(Block *bp)
+{
+	int len;
+
+	len = 0;
+	while(bp) {
+		len += BALLOC(bp);
+		bp = bp->next;
+	}
+	return len;
+}
+
+/*
+ *  copy the  string of blocks into
+ *  a single block and free the string
+ */
+Block*
+concatblock(Block *bp)
+{
+	int len;
+	Block *nb, *f;
+
+	if(bp->next == 0)
+		return bp;
+
+	nb = allocb(blocklen(bp));
+	for(f = bp; f; f = f->next) {
+		len = BLEN(f);
+		memmove(nb->wp, f->rp, len);
+		nb->wp += len;
+	}
+	concatblockcnt += BLEN(nb);
+	freeblist(bp);
+	QDEBUG checkb(nb, "concatblock 1");
+	return nb;
+}
+
+/*
+ *  make sure the first block has at least n bytes
+ */
+Block*
+pullupblock(Block *bp, int n)
+{
+	int i;
+	Block *nbp;
+
+	/*
+	 *  this should almost always be true, it's
+	 *  just to avoid every caller checking.
+	 */
+	if(BLEN(bp) >= n)
+		return bp;
+
+	/*
+	 *  if not enough room in the first block,
+	 *  add another to the front of the list.
+	 */
+	if(bp->lim - bp->rp < n){
+		nbp = allocb(n);
+		nbp->next = bp;
+		bp = nbp;
+	}
+
+	/*
+	 *  copy bytes from the trailing blocks into the first
+	 */
+	n -= BLEN(bp);
+	while(nbp = bp->next){
+		i = BLEN(nbp);
+		if(i > n) {
+			memmove(bp->wp, nbp->rp, n);
+			pullupblockcnt++;
+			bp->wp += n;
+			nbp->rp += n;
+			QDEBUG checkb(bp, "pullupblock 1");
+			return bp;
+		} else {
+			/* shouldn't happen but why crash if it does */
+			if(i < 0){
+				print("pullup negative length packet\n");
+				i = 0;
+			}
+			memmove(bp->wp, nbp->rp, i);
+			pullupblockcnt++;
+			bp->wp += i;
+			bp->next = nbp->next;
+			nbp->next = 0;
+			freeb(nbp);
+			n -= i;
+			if(n == 0){
+				QDEBUG checkb(bp, "pullupblock 2");
+				return bp;
+			}
+		}
+	}
+	freeb(bp);
+	return 0;
+}
+
+/*
+ *  make sure the first block has at least n bytes
+ */
+Block*
+pullupqueue(Queue *q, int n)
+{
+	Block *b;
+
+	if(BLEN(q->bfirst) >= n)
+		return q->bfirst;
+	q->bfirst = pullupblock(q->bfirst, n);
+	for(b = q->bfirst; b != nil && b->next != nil; b = b->next)
+		;
+	q->blast = b;
+	return q->bfirst;
+}
+
+/*
+ *  trim to len bytes starting at offset
+ */
+Block *
+trimblock(Block *bp, int offset, int len)
+{
+	ulong l;
+	Block *nb, *startb;
+
+	QDEBUG checkb(bp, "trimblock 1");
+	if(blocklen(bp) < offset+len) {
+		freeblist(bp);
+		return nil;
+	}
+
+	while((l = BLEN(bp)) < offset) {
+		offset -= l;
+		nb = bp->next;
+		bp->next = nil;
+		freeb(bp);
+		bp = nb;
+	}
+
+	startb = bp;
+	bp->rp += offset;
+
+	while((l = BLEN(bp)) < len) {
+		len -= l;
+		bp = bp->next;
+	}
+
+	bp->wp -= (BLEN(bp) - len);
+
+	if(bp->next) {
+		freeblist(bp->next);
+		bp->next = nil;
+	}
+
+	return startb;
+}
+
+/*
+ *  copy 'count' bytes into a new block
+ */
+Block*
+copyblock(Block *bp, int count)
+{
+	int l;
+	Block *nbp;
+
+	QDEBUG checkb(bp, "copyblock 0");
+	nbp = allocb(count);
+	for(; count > 0 && bp != 0; bp = bp->next){
+		l = BLEN(bp);
+		if(l > count)
+			l = count;
+		memmove(nbp->wp, bp->rp, l);
+		nbp->wp += l;
+		count -= l;
+	}
+	if(count > 0){
+		memset(nbp->wp, 0, count);
+		nbp->wp += count;
+	}
+	copyblockcnt++;
+	QDEBUG checkb(nbp, "copyblock 1");
+
+	return nbp;
+}
+
+Block*
+adjustblock(Block* bp, int len)
+{
+	int n;
+	Block *nbp;
+
+	if(len < 0){
+		freeb(bp);
+		return nil;
+	}
+
+	if(bp->rp+len > bp->lim){
+		nbp = copyblock(bp, len);
+		freeblist(bp);
+		QDEBUG checkb(nbp, "adjustblock 1");
+
+		return nbp;
+	}
+
+	n = BLEN(bp);
+	if(len > n)
+		memset(bp->wp, 0, len-n);
+	bp->wp = bp->rp+len;
+	QDEBUG checkb(bp, "adjustblock 2");
+
+	return bp;
+}
+
+
+/*
+ *  throw away up to count bytes from a
+ *  list of blocks.  Return count of bytes
+ *  thrown away.
+ */
+int
+pullblock(Block **bph, int count)
+{
+	Block *bp;
+	int n, bytes;
+
+	bytes = 0;
+	if(bph == nil)
+		return 0;
+
+	while(*bph != nil && count != 0) {
+		bp = *bph;
+		n = BLEN(bp);
+		if(count < n)
+			n = count;
+		bytes += n;
+		count -= n;
+		bp->rp += n;
+		QDEBUG checkb(bp, "pullblock ");
+		if(BLEN(bp) == 0) {
+			*bph = bp->next;
+			bp->next = nil;
+			freeb(bp);
+		}
+	}
+	return bytes;
+}
+
+/*
+ *  get next block from a queue, return null if nothing there
+ */
+Block*
+qget(Queue *q)
+{
+	int dowakeup;
+	Block *b;
+
+	/* sync with qwrite */
+	ilock(&q->lk);
+
+	b = q->bfirst;
+	if(b == nil){
+		q->state |= Qstarve;
+		iunlock(&q->lk);
+		return nil;
+	}
+	q->bfirst = b->next;
+	b->next = 0;
+	q->len -= BALLOC(b);
+	q->dlen -= BLEN(b);
+	QDEBUG checkb(b, "qget");
+
+	/* if writer flow controlled, restart */
+	if((q->state & Qflow) && q->len < q->limit/2){
+		q->state &= ~Qflow;
+		dowakeup = 1;
+	} else
+		dowakeup = 0;
+
+	iunlock(&q->lk);
+
+	if(dowakeup)
+		wakeup(&q->wr);
+
+	return b;
+}
+
+/*
+ *  throw away the next 'len' bytes in the queue
+ */
+int
+qdiscard(Queue *q, int len)
+{
+	Block *b;
+	int dowakeup, n, sofar;
+
+	ilock(&q->lk);
+	for(sofar = 0; sofar < len; sofar += n){
+		b = q->bfirst;
+		if(b == nil)
+			break;
+		QDEBUG checkb(b, "qdiscard");
+		n = BLEN(b);
+		if(n <= len - sofar){
+			q->bfirst = b->next;
+			b->next = 0;
+			q->len -= BALLOC(b);
+			q->dlen -= BLEN(b);
+			freeb(b);
+		} else {
+			n = len - sofar;
+			b->rp += n;
+			q->dlen -= n;
+		}
+	}
+
+	/*
+	 *  if writer flow controlled, restart
+	 *
+	 *  This used to be
+	 *	q->len < q->limit/2
+	 *  but it slows down tcp too much for certain write sizes.
+	 *  I really don't understand it completely.  It may be
+	 *  due to the queue draining so fast that the transmission
+	 *  stalls waiting for the app to produce more data.  - presotto
+	 */
+	if((q->state & Qflow) && q->len < q->limit){
+		q->state &= ~Qflow;
+		dowakeup = 1;
+	} else
+		dowakeup = 0;
+
+	iunlock(&q->lk);
+
+	if(dowakeup)
+		wakeup(&q->wr);
+
+	return sofar;
+}
+
+/*
+ *  Interrupt level copy out of a queue, return # bytes copied.
+ */
+int
+qconsume(Queue *q, void *vp, int len)
+{
+	Block *b;
+	int n, dowakeup;
+	uchar *p = vp;
+	Block *tofree = nil;
+
+	/* sync with qwrite */
+	ilock(&q->lk);
+
+	for(;;) {
+		b = q->bfirst;
+		if(b == 0){
+			q->state |= Qstarve;
+			iunlock(&q->lk);
+			return -1;
+		}
+		QDEBUG checkb(b, "qconsume 1");
+
+		n = BLEN(b);
+		if(n > 0)
+			break;
+		q->bfirst = b->next;
+		q->len -= BALLOC(b);
+
+		/* remember to free this */
+		b->next = tofree;
+		tofree = b;
+	};
+
+	if(n < len)
+		len = n;
+	memmove(p, b->rp, len);
+	consumecnt += n;
+	b->rp += len;
+	q->dlen -= len;
+
+	/* discard the block if we're done with it */
+	if((q->state & Qmsg) || len == n){
+		q->bfirst = b->next;
+		b->next = 0;
+		q->len -= BALLOC(b);
+		q->dlen -= BLEN(b);
+
+		/* remember to free this */
+		b->next = tofree;
+		tofree = b;
+	}
+
+	/* if writer flow controlled, restart */
+	if((q->state & Qflow) && q->len < q->limit/2){
+		q->state &= ~Qflow;
+		dowakeup = 1;
+	} else
+		dowakeup = 0;
+
+	iunlock(&q->lk);
+
+	if(dowakeup)
+		wakeup(&q->wr);
+
+	if(tofree != nil)
+		freeblist(tofree);
+
+	return len;
+}
+
+int
+qpass(Queue *q, Block *b)
+{
+	int dlen, len, dowakeup;
+
+	/* sync with qread */
+	dowakeup = 0;
+	ilock(&q->lk);
+	if(q->len >= q->limit){
+		freeblist(b);
+		iunlock(&q->lk);
+		return -1;
+	}
+	if(q->state & Qclosed){
+		freeblist(b);
+		iunlock(&q->lk);
+		return BALLOC(b);
+	}
+
+	/* add buffer to queue */
+	if(q->bfirst)
+		q->blast->next = b;
+	else
+		q->bfirst = b;
+	len = BALLOC(b);
+	dlen = BLEN(b);
+	QDEBUG checkb(b, "qpass");
+	while(b->next){
+		b = b->next;
+		QDEBUG checkb(b, "qpass");
+		len += BALLOC(b);
+		dlen += BLEN(b);
+	}
+	q->blast = b;
+	q->len += len;
+	q->dlen += dlen;
+
+	if(q->len >= q->limit/2)
+		q->state |= Qflow;
+
+	if(q->state & Qstarve){
+		q->state &= ~Qstarve;
+		dowakeup = 1;
+	}
+	iunlock(&q->lk);
+
+	if(dowakeup)
+		wakeup(&q->rr);
+
+	return len;
+}
+
+int
+qpassnolim(Queue *q, Block *b)
+{
+	int dlen, len, dowakeup;
+
+	/* sync with qread */
+	dowakeup = 0;
+	ilock(&q->lk);
+
+	if(q->state & Qclosed){
+		freeblist(b);
+		iunlock(&q->lk);
+		return BALLOC(b);
+	}
+
+	/* add buffer to queue */
+	if(q->bfirst)
+		q->blast->next = b;
+	else
+		q->bfirst = b;
+	len = BALLOC(b);
+	dlen = BLEN(b);
+	QDEBUG checkb(b, "qpass");
+	while(b->next){
+		b = b->next;
+		QDEBUG checkb(b, "qpass");
+		len += BALLOC(b);
+		dlen += BLEN(b);
+	}
+	q->blast = b;
+	q->len += len;
+	q->dlen += dlen;
+
+	if(q->len >= q->limit/2)
+		q->state |= Qflow;
+
+	if(q->state & Qstarve){
+		q->state &= ~Qstarve;
+		dowakeup = 1;
+	}
+	iunlock(&q->lk);
+
+	if(dowakeup)
+		wakeup(&q->rr);
+
+	return len;
+}
+
+/*
+ *  if the allocated space is way out of line with the used
+ *  space, reallocate to a smaller block
+ */
+Block*
+packblock(Block *bp)
+{
+	Block **l, *nbp;
+	int n;
+
+	for(l = &bp; *l; l = &(*l)->next){
+		nbp = *l;
+		n = BLEN(nbp);
+		if((n<<2) < BALLOC(nbp)){
+			*l = allocb(n);
+			memmove((*l)->wp, nbp->rp, n);
+			(*l)->wp += n;
+			(*l)->next = nbp->next;
+			freeb(nbp);
+		}
+	}
+
+	return bp;
+}
+
+int
+qproduce(Queue *q, void *vp, int len)
+{
+	Block *b;
+	int dowakeup;
+	uchar *p = vp;
+
+	/* sync with qread */
+	dowakeup = 0;
+	ilock(&q->lk);
+
+	/* no waiting receivers, room in buffer? */
+	if(q->len >= q->limit){
+		q->state |= Qflow;
+		iunlock(&q->lk);
+		return -1;
+	}
+
+	/* save in buffer */
+	b = iallocb(len);
+	if(b == 0){
+		iunlock(&q->lk);
+		return 0;
+	}
+	memmove(b->wp, p, len);
+	producecnt += len;
+	b->wp += len;
+	if(q->bfirst)
+		q->blast->next = b;
+	else
+		q->bfirst = b;
+	q->blast = b;
+	/* b->next = 0; done by iallocb() */
+	q->len += BALLOC(b);
+	q->dlen += BLEN(b);
+	QDEBUG checkb(b, "qproduce");
+
+	if(q->state & Qstarve){
+		q->state &= ~Qstarve;
+		dowakeup = 1;
+	}
+
+	if(q->len >= q->limit)
+		q->state |= Qflow;
+	iunlock(&q->lk);
+
+	if(dowakeup)
+		wakeup(&q->rr);
+
+	return len;
+}
+
+/*
+ *  copy from offset in the queue
+ */
+Block*
+qcopy(Queue *q, int len, ulong offset)
+{
+	int sofar;
+	int n;
+	Block *b, *nb;
+	uchar *p;
+
+	nb = allocb(len);
+
+	ilock(&q->lk);
+
+	/* go to offset */
+	b = q->bfirst;
+	for(sofar = 0; ; sofar += n){
+		if(b == nil){
+			iunlock(&q->lk);
+			return nb;
+		}
+		n = BLEN(b);
+		if(sofar + n > offset){
+			p = b->rp + offset - sofar;
+			n -= offset - sofar;
+			break;
+		}
+		QDEBUG checkb(b, "qcopy");
+		b = b->next;
+	}
+
+	/* copy bytes from there */
+	for(sofar = 0; sofar < len;){
+		if(n > len - sofar)
+			n = len - sofar;
+		memmove(nb->wp, p, n);
+		qcopycnt += n;
+		sofar += n;
+		nb->wp += n;
+		b = b->next;
+		if(b == nil)
+			break;
+		n = BLEN(b);
+		p = b->rp;
+	}
+	iunlock(&q->lk);
+
+	return nb;
+}
+
+/*
+ *  called by non-interrupt code
+ */
+Queue*
+qopen(int limit, int msg, void (*kick)(void*), void *arg)
+{
+	Queue *q;
+
+	q = malloc(sizeof(Queue));
+	if(q == 0)
+		return 0;
+
+	q->limit = q->inilim = limit;
+	q->kick = kick;
+	q->arg = arg;
+	q->state = msg;
+	
+	q->state |= Qstarve;
+	q->eof = 0;
+	q->noblock = 0;
+
+	return q;
+}
+
+/* open a queue to be bypassed */
+Queue*
+qbypass(void (*bypass)(void*, Block*), void *arg)
+{
+	Queue *q;
+
+	q = malloc(sizeof(Queue));
+	if(q == 0)
+		return 0;
+
+	q->limit = 0;
+	q->arg = arg;
+	q->bypass = bypass;
+	q->state = 0;
+
+	return q;
+}
+
+static int
+notempty(void *a)
+{
+	Queue *q = a;
+
+	return (q->state & Qclosed) || q->bfirst != 0;
+}
+
+/*
+ *  wait for the queue to be non-empty or closed.
+ *  called with q ilocked.
+ */
+static int
+qwait(Queue *q)
+{
+	/* wait for data */
+	for(;;){
+		if(q->bfirst != nil)
+			break;
+
+		if(q->state & Qclosed){
+			if(++q->eof > 3)
+				return -1;
+			if(*q->err && strcmp(q->err, Ehungup) != 0)
+				return -1;
+			return 0;
+		}
+
+		q->state |= Qstarve;	/* flag requesting producer to wake me */
+		iunlock(&q->lk);
+		sleep(&q->rr, notempty, q);
+		ilock(&q->lk);
+	}
+	return 1;
+}
+
+/*
+ * add a block list to a queue
+ */
+void
+qaddlist(Queue *q, Block *b)
+{
+	/* queue the block */
+	if(q->bfirst)
+		q->blast->next = b;
+	else
+		q->bfirst = b;
+	q->len += blockalloclen(b);
+	q->dlen += blocklen(b);
+	while(b->next)
+		b = b->next;
+	q->blast = b;
+}
+
+/*
+ *  called with q ilocked
+ */
+Block*
+qremove(Queue *q)
+{
+	Block *b;
+
+	b = q->bfirst;
+	if(b == nil)
+		return nil;
+	q->bfirst = b->next;
+	b->next = nil;
+	q->dlen -= BLEN(b);
+	q->len -= BALLOC(b);
+	QDEBUG checkb(b, "qremove");
+	return b;
+}
+
+/*
+ *  copy the contents of a string of blocks into
+ *  memory.  emptied blocks are freed.  return
+ *  pointer to first unconsumed block.
+ */
+Block*
+bl2mem(uchar *p, Block *b, int n)
+{
+	int i;
+	Block *next;
+
+	for(; b != nil; b = next){
+		i = BLEN(b);
+		if(i > n){
+			memmove(p, b->rp, n);
+			b->rp += n;
+			return b;
+		}
+		memmove(p, b->rp, i);
+		n -= i;
+		p += i;
+		b->rp += i;
+		next = b->next;
+		freeb(b);
+	}
+	return nil;
+}
+
+/*
+ *  copy the contents of memory into a string of blocks.
+ *  return nil on error.
+ */
+Block*
+mem2bl(uchar *p, int len)
+{
+	int n;
+	Block *b, *first, **l;
+
+	first = nil;
+	l = &first;
+	if(waserror()){
+		freeblist(first);
+		nexterror();
+	}
+	do {
+		n = len;
+		if(n > Maxatomic)
+			n = Maxatomic;
+
+		*l = b = allocb(n);
+	/*	setmalloctag(b, (up->text[0]<<24)|(up->text[1]<<16)|(up->text[2]<<8)|up->text[3]); */
+		memmove(b->wp, p, n);
+		b->wp += n;
+		p += n;
+		len -= n;
+		l = &b->next;
+	} while(len > 0);
+	poperror();
+
+	return first;
+}
+
+/*
+ *  put a block back to the front of the queue
+ *  called with q ilocked
+ */
+void
+qputback(Queue *q, Block *b)
+{
+	b->next = q->bfirst;
+	if(q->bfirst == nil)
+		q->blast = b;
+	q->bfirst = b;
+	q->len += BALLOC(b);
+	q->dlen += BLEN(b);
+}
+
+/*
+ *  flow control, get producer going again
+ *  called with q ilocked
+ */
+static void
+qwakeup_iunlock(Queue *q)
+{
+	int dowakeup = 0;
+
+	/* if writer flow controlled, restart */
+	if((q->state & Qflow) && q->len < q->limit/2){
+		q->state &= ~Qflow;
+		dowakeup = 1;
+	}
+
+	iunlock(&q->lk);
+
+	/* wakeup flow controlled writers */
+	if(dowakeup){
+		if(q->kick)
+			q->kick(q->arg);
+		wakeup(&q->wr);
+	}
+}
+
+/*
+ *  get next block from a queue (up to a limit)
+ */
+Block*
+qbread(Queue *q, int len)
+{
+	Block *b, *nb;
+	int n;
+
+	qlock(&q->rlock);
+	if(waserror()){
+		qunlock(&q->rlock);
+		nexterror();
+	}
+
+	ilock(&q->lk);
+	switch(qwait(q)){
+	case 0:
+		/* queue closed */
+		iunlock(&q->lk);
+		qunlock(&q->rlock);
+		poperror();
+		return nil;
+	case -1:
+		/* multiple reads on a closed queue */
+		iunlock(&q->lk);
+		error(q->err);
+	}
+
+	/* if we get here, there's at least one block in the queue */
+	b = qremove(q);
+	n = BLEN(b);
+
+	/* split block if it's too big and this is not a message queue */
+	nb = b;
+	if(n > len){
+		if((q->state&Qmsg) == 0){
+			n -= len;
+			b = allocb(n);
+			memmove(b->wp, nb->rp+len, n);
+			b->wp += n;
+			qputback(q, b);
+		}
+		nb->wp = nb->rp + len;
+	}
+
+	/* restart producer */
+	qwakeup_iunlock(q);
+
+	poperror();
+	qunlock(&q->rlock);
+	return nb;
+}
+
+/*
+ *  read a queue.  if no data is queued, post a Block
+ *  and wait on its Rendez.
+ */
+long
+qread(Queue *q, void *vp, int len)
+{
+	Block *b, *first, **l;
+	int m, n;
+
+	qlock(&q->rlock);
+	if(waserror()){
+		qunlock(&q->rlock);
+		nexterror();
+	}
+
+	ilock(&q->lk);
+again:
+	switch(qwait(q)){
+	case 0:
+		/* queue closed */
+		iunlock(&q->lk);
+		qunlock(&q->rlock);
+		poperror();
+		return 0;
+	case -1:
+		/* multiple reads on a closed queue */
+		iunlock(&q->lk);
+		error(q->err);
+	}
+
+	/* if we get here, there's at least one block in the queue */
+	if(q->state & Qcoalesce){
+		/* when coalescing, 0 length blocks just go away */
+		b = q->bfirst;
+		if(BLEN(b) <= 0){
+			freeb(qremove(q));
+			goto again;
+		}
+
+		/*  grab the first block plus as many
+		 *  following blocks as will completely
+		 *  fit in the read.
+		 */
+		n = 0;
+		l = &first;
+		m = BLEN(b);
+		for(;;) {
+			*l = qremove(q);
+			l = &b->next;
+			n += m;
+
+			b = q->bfirst;
+			if(b == nil)
+				break;
+			m = BLEN(b);
+			if(n+m > len)
+				break;
+		}
+	} else {
+		first = qremove(q);
+		n = BLEN(first);
+	}
+
+	/* copy to user space outside of the ilock */
+	iunlock(&q->lk);
+	b = bl2mem(vp, first, len);
+	ilock(&q->lk);
+
+	/* take care of any left over partial block */
+	if(b != nil){
+		n -= BLEN(b);
+		if(q->state & Qmsg)
+			freeb(b);
+		else
+			qputback(q, b);
+	}
+
+	/* restart producer */
+	qwakeup_iunlock(q);
+
+	poperror();
+	qunlock(&q->rlock);
+	return n;
+}
+
+static int
+qnotfull(void *a)
+{
+	Queue *q = a;
+
+	return q->len < q->limit || (q->state & Qclosed);
+}
+
+ulong noblockcnt;
+
+/*
+ *  add a block to a queue obeying flow control
+ */
+long
+qbwrite(Queue *q, Block *b)
+{
+	int n, dowakeup;
+	Proc *p;
+
+	n = BLEN(b);
+
+	if(q->bypass){
+		(*q->bypass)(q->arg, b);
+		return n;
+	}
+
+	dowakeup = 0;
+	qlock(&q->wlock);
+	if(waserror()){
+		if(b != nil)
+			freeb(b);
+		qunlock(&q->wlock);
+		nexterror();
+	}
+
+	ilock(&q->lk);
+
+	/* give up if the queue is closed */
+	if(q->state & Qclosed){
+		iunlock(&q->lk);
+		error(q->err);
+	}
+
+	/* if nonblocking, don't queue over the limit */
+	if(q->len >= q->limit){
+		if(q->noblock){
+			iunlock(&q->lk);
+			freeb(b);
+			noblockcnt += n;
+			qunlock(&q->wlock);
+			poperror();
+			return n;
+		}
+	}
+
+	/* queue the block */
+	if(q->bfirst)
+		q->blast->next = b;
+	else
+		q->bfirst = b;
+	q->blast = b;
+	b->next = 0;
+	q->len += BALLOC(b);
+	q->dlen += n;
+	QDEBUG checkb(b, "qbwrite");
+	b = nil;
+
+	/* make sure other end gets awakened */
+	if(q->state & Qstarve){
+		q->state &= ~Qstarve;
+		dowakeup = 1;
+	}
+	iunlock(&q->lk);
+
+	/*  get output going again */
+	if(q->kick && (dowakeup || (q->state&Qkick)))
+		q->kick(q->arg);
+
+	/* wakeup anyone consuming at the other end */
+	if(dowakeup){
+		p = wakeup(&q->rr);
+
+		/* if we just wokeup a higher priority process, let it run */
+	/*
+		if(p != nil && p->priority > up->priority)
+			sched();
+	 */
+	}
+
+	/*
+	 *  flow control, wait for queue to get below the limit
+	 *  before allowing the process to continue and queue
+	 *  more.  We do this here so that postnote can only
+	 *  interrupt us after the data has been queued.  This
+	 *  means that things like 9p flushes and ssl messages
+	 *  will not be disrupted by software interrupts.
+	 *
+	 *  Note - this is moderately dangerous since a process
+	 *  that keeps getting interrupted and rewriting will
+	 *  queue infinite crud.
+	 */
+	for(;;){
+		if(q->noblock || qnotfull(q))
+			break;
+
+		ilock(&q->lk);
+		q->state |= Qflow;
+		iunlock(&q->lk);
+		sleep(&q->wr, qnotfull, q);
+	}
+	USED(b);
+
+	qunlock(&q->wlock);
+	poperror();
+	return n;
+}
+
+/*
+ *  write to a queue.  only Maxatomic bytes at a time is atomic.
+ */
+int
+qwrite(Queue *q, void *vp, int len)
+{
+	int n, sofar;
+	Block *b;
+	uchar *p = vp;
+
+	QDEBUG if(!islo())
+		print("qwrite hi %lux\n", getcallerpc(&q));
+
+	sofar = 0;
+	do {
+		n = len-sofar;
+		if(n > Maxatomic)
+			n = Maxatomic;
+
+		b = allocb(n);
+	/*	setmalloctag(b, (up->text[0]<<24)|(up->text[1]<<16)|(up->text[2]<<8)|up->text[3]); */
+		if(waserror()){
+			freeb(b);
+			nexterror();
+		}
+		memmove(b->wp, p+sofar, n);
+		poperror();
+		b->wp += n;
+
+		qbwrite(q, b);
+
+		sofar += n;
+	} while(sofar < len && (q->state & Qmsg) == 0);
+
+	return len;
+}
+
+/*
+ *  used by print() to write to a queue.  Since we may be splhi or not in
+ *  a process, don't qlock.
+ */
+int
+qiwrite(Queue *q, void *vp, int len)
+{
+	int n, sofar, dowakeup;
+	Block *b;
+	uchar *p = vp;
+
+	dowakeup = 0;
+
+	sofar = 0;
+	do {
+		n = len-sofar;
+		if(n > Maxatomic)
+			n = Maxatomic;
+
+		b = iallocb(n);
+		if(b == nil)
+			break;
+		memmove(b->wp, p+sofar, n);
+		b->wp += n;
+
+		ilock(&q->lk);
+
+		QDEBUG checkb(b, "qiwrite");
+		if(q->bfirst)
+			q->blast->next = b;
+		else
+			q->bfirst = b;
+		q->blast = b;
+		q->len += BALLOC(b);
+		q->dlen += n;
+
+		if(q->state & Qstarve){
+			q->state &= ~Qstarve;
+			dowakeup = 1;
+		}
+
+		iunlock(&q->lk);
+
+		if(dowakeup){
+			if(q->kick)
+				q->kick(q->arg);
+			wakeup(&q->rr);
+		}
+
+		sofar += n;
+	} while(sofar < len && (q->state & Qmsg) == 0);
+
+	return sofar;
+}
+
+/*
+ *  be extremely careful when calling this,
+ *  as there is no reference accounting
+ */
+void
+qfree(Queue *q)
+{
+	qclose(q);
+	free(q);
+}
+
+/*
+ *  Mark a queue as closed.  No further IO is permitted.
+ *  All blocks are released.
+ */
+void
+qclose(Queue *q)
+{
+	Block *bfirst;
+
+	if(q == nil)
+		return;
+
+	/* mark it */
+	ilock(&q->lk);
+	q->state |= Qclosed;
+	q->state &= ~(Qflow|Qstarve);
+	strcpy(q->err, Ehungup);
+	bfirst = q->bfirst;
+	q->bfirst = 0;
+	q->len = 0;
+	q->dlen = 0;
+	q->noblock = 0;
+	iunlock(&q->lk);
+
+	/* free queued blocks */
+	freeblist(bfirst);
+
+	/* wake up readers/writers */
+	wakeup(&q->rr);
+	wakeup(&q->wr);
+}
+
+/*
+ *  Mark a queue as closed.  Wakeup any readers.  Don't remove queued
+ *  blocks.
+ */
+void
+qhangup(Queue *q, char *msg)
+{
+	/* mark it */
+	ilock(&q->lk);
+	q->state |= Qclosed;
+	if(msg == 0 || *msg == 0)
+		strcpy(q->err, Ehungup);
+	else
+		strncpy(q->err, msg, ERRMAX-1);
+	iunlock(&q->lk);
+
+	/* wake up readers/writers */
+	wakeup(&q->rr);
+	wakeup(&q->wr);
+}
+
+/*
+ *  return non-zero if the q is hungup
+ */
+int
+qisclosed(Queue *q)
+{
+	return q->state & Qclosed;
+}
+
+/*
+ *  mark a queue as no longer hung up
+ */
+void
+qreopen(Queue *q)
+{
+	ilock(&q->lk);
+	q->state &= ~Qclosed;
+	q->state |= Qstarve;
+	q->eof = 0;
+	q->limit = q->inilim;
+	iunlock(&q->lk);
+}
+
+/*
+ *  return bytes queued
+ */
+int
+qlen(Queue *q)
+{
+	return q->dlen;
+}
+
+/*
+ * return space remaining before flow control
+ */
+int
+qwindow(Queue *q)
+{
+	int l;
+
+	l = q->limit - q->len;
+	if(l < 0)
+		l = 0;
+	return l;
+}
+
+/*
+ *  return true if we can read without blocking
+ */
+int
+qcanread(Queue *q)
+{
+	return q->bfirst!=0;
+}
+
+/*
+ *  change queue limit
+ */
+void
+qsetlimit(Queue *q, int limit)
+{
+	q->limit = limit;
+}
+
+/*
+ *  set blocking/nonblocking
+ */
+void
+qnoblock(Queue *q, int onoff)
+{
+	q->noblock = onoff;
+}
+
+/*
+ *  flush the output queue
+ */
+void
+qflush(Queue *q)
+{
+	Block *bfirst;
+
+	/* mark it */
+	ilock(&q->lk);
+	bfirst = q->bfirst;
+	q->bfirst = 0;
+	q->len = 0;
+	q->dlen = 0;
+	iunlock(&q->lk);
+
+	/* free queued blocks */
+	freeblist(bfirst);
+
+	/* wake up readers/writers */
+	wakeup(&q->wr);
+}
+
+int
+qfull(Queue *q)
+{
+	return q->state & Qflow;
+}
+
+int
+qstate(Queue *q)
+{
+	return q->state;
+}
--- /dev/null
+++ b/kern/qlock.c
@@ -1,0 +1,94 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+
+static void
+queue(Proc **first, Proc **last)
+{
+	Proc *t;
+
+	t = *last;
+	if(t == 0)
+		*first = up;
+	else
+		t->qnext = up;
+	*last = up;
+	up->qnext = 0;
+}
+
+static Proc*
+dequeue(Proc **first, Proc **last)
+{
+	Proc *t;
+
+	t = *first;
+	if(t == 0)
+		return 0;
+	*first = t->qnext;
+	if(*first == 0)
+		*last = 0;
+	return t;
+}
+
+void
+qlock(QLock *q)
+{
+	lock(&q->lk);
+
+	if(q->hold == 0) {
+		q->hold = up;
+		unlock(&q->lk);
+		return;
+	}
+
+	/*
+	 * Can't assert this because of RWLock
+	assert(q->hold != up);
+	 */		
+
+	queue((Proc**)&q->first, (Proc**)&q->last);
+	unlock(&q->lk);
+	procsleep();
+}
+
+int
+canqlock(QLock *q)
+{
+	lock(&q->lk);
+	if(q->hold == 0) {
+		q->hold = up;
+		unlock(&q->lk);
+		return 1;
+	}
+	unlock(&q->lk);
+	return 0;
+}
+
+void
+qunlock(QLock *q)
+{
+	Proc *p;
+
+	lock(&q->lk);
+	/* 
+	 * Can't assert this because of RWlock
+	assert(q->hold == CT);
+	 */
+	p = dequeue((Proc**)&q->first, (Proc**)&q->last);
+	if(p) {
+		q->hold = p;
+		unlock(&q->lk);
+		procwakeup(p);
+	} else {
+		q->hold = 0;
+		unlock(&q->lk);
+	}
+}
+
+int
+holdqlock(QLock *q)
+{
+	return q->hold == up;
+}
+
--- /dev/null
+++ b/kern/rendez.c
@@ -1,0 +1,90 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+void
+sleep(Rendez *r, int (*f)(void*), void *arg)
+{
+	int s;
+
+	s = splhi();
+
+	lock(&r->lk);
+	lock(&up->rlock);
+	if(r->p){
+		print("double sleep %lud %lud\n", r->p->pid, up->pid);
+		dumpstack();
+	}
+
+	/*
+	 *  Wakeup only knows there may be something to do by testing
+	 *  r->p in order to get something to lock on.
+	 *  Flush that information out to memory in case the sleep is
+	 *  committed.
+	 */
+	r->p = up;
+
+	if((*f)(arg) || up->notepending){
+		/*
+		 *  if condition happened or a note is pending
+		 *  never mind
+		 */
+		r->p = nil;
+		unlock(&up->rlock);
+		unlock(&r->lk);
+	} else {
+		/*
+		 *  now we are committed to
+		 *  change state and call scheduler
+		 */
+		up->state = Wakeme;
+		up->r = r;
+
+		/* statistics */
+		/* m->cs++; */
+
+		unlock(&up->rlock);
+		unlock(&r->lk);
+
+		procsleep();
+	}
+
+	if(up->notepending) {
+		up->notepending = 0;
+		splx(s);
+		error(Eintr);
+	}
+
+	splx(s);
+}
+
+Proc*
+wakeup(Rendez *r)
+{
+	Proc *p;
+	int s;
+
+	s = splhi();
+
+	lock(&r->lk);
+	p = r->p;
+
+	if(p != nil){
+		lock(&p->rlock);
+		if(p->state != Wakeme || p->r != r)
+			panic("wakeup: state");
+		r->p = nil;
+		p->r = nil;
+		p->state = Running;
+		procwakeup(p);
+		unlock(&p->rlock);
+	}
+	unlock(&r->lk);
+
+	splx(s);
+
+	return p;
+}
+
--- /dev/null
+++ b/kern/rwlock.c
@@ -1,0 +1,39 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+rlock(RWlock *l)
+{
+	qlock(&l->x);		/* wait here for writers and exclusion */
+	lock(&l->lk);
+	l->readers++;
+	canqlock(&l->k);	/* block writers if we are the first reader */
+	unlock(&l->lk);
+	qunlock(&l->x);
+}
+
+void
+runlock(RWlock *l)
+{
+	lock(&l->lk);
+	if(--l->readers == 0)	/* last reader out allows writers */
+		qunlock(&l->k);
+	unlock(&l->lk);
+}
+
+void
+wlock(RWlock *l)
+{
+	qlock(&l->x);		/* wait here for writers and exclusion */
+	qlock(&l->k);		/* wait here for last reader */
+}
+
+void
+wunlock(RWlock *l)
+{
+	qunlock(&l->k);
+	qunlock(&l->x);
+}
--- /dev/null
+++ b/kern/screen.h
@@ -1,0 +1,59 @@
+typedef struct Mouseinfo Mouseinfo;
+typedef struct Mousestate Mousestate;
+typedef struct Cursorinfo Cursorinfo;
+typedef struct Screeninfo Screeninfo;
+
+#define Mousequeue 16		/* queue can only have Mousequeue-1 elements */
+#define Mousewindow 500		/* mouse event window in millisec */
+
+struct Mousestate {
+	int	buttons;
+	Point	xy;
+	ulong	msec;
+};
+
+struct Mouseinfo {
+	Lock	lk;
+	Mousestate queue[Mousequeue];
+	int	ri, wi;
+	int	lastb;
+	int	trans;
+	int	open;
+	Rendez	r;
+};
+
+struct Cursorinfo {
+	Lock	lk;
+	Point	offset;
+	uchar	clr[2*16];
+	uchar	set[2*16];
+};
+
+struct Screeninfo {
+	Lock		lk;
+	Memimage	*newsoft;
+	int		reshaped;
+	int		depth;
+	int		dibtype;
+};
+
+extern	Memimage *gscreen;
+extern	Mouseinfo mouse;
+extern	Cursorinfo cursor;
+extern	Screeninfo screen;
+
+void	screeninit(void);
+void	screenload(Rectangle, int, uchar *, Point, int);
+
+void	getcolor(ulong, ulong*, ulong*, ulong*);
+void	setcolor(ulong, ulong, ulong, ulong);
+
+void	refreshrect(Rectangle);
+
+void	cursorarrow(void);
+void	setcursor(void);
+void	mouseset(Point);
+void	drawflushr(Rectangle);
+void	flushmemscreen(Rectangle);
+uchar *attachscreen(Rectangle*, ulong*, int*, int*, int*, void**);
+
--- /dev/null
+++ b/kern/sleep.c
@@ -1,0 +1,90 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+void
+sleep(Rendez *r, int (*f)(void*), void *arg)
+{
+	int s;
+
+	s = splhi();
+
+	lock(&r->lk);
+	lock(&up->rlock);
+	if(r->p){
+		print("double sleep %lud %lud\n", r->p->pid, up->pid);
+		dumpstack();
+	}
+
+	/*
+	 *  Wakeup only knows there may be something to do by testing
+	 *  r->p in order to get something to lock on.
+	 *  Flush that information out to memory in case the sleep is
+	 *  committed.
+	 */
+	r->p = up;
+
+	if((*f)(arg) || up->notepending){
+		/*
+		 *  if condition happened or a note is pending
+		 *  never mind
+		 */
+		r->p = nil;
+		unlock(&up->rlock);
+		unlock(&r->lk);
+	} else {
+		/*
+		 *  now we are committed to
+		 *  change state and call scheduler
+		 */
+		up->state = Wakeme;
+		up->r = r;
+
+		/* statistics */
+		/* m->cs++; */
+
+		unlock(&up->rlock);
+		unlock(&r->lk);
+
+		procsleep();
+	}
+
+	if(up->notepending) {
+		up->notepending = 0;
+		splx(s);
+		error(Eintr);
+	}
+
+	splx(s);
+}
+
+Proc*
+wakeup(Rendez *r)
+{
+	Proc *p;
+	int s;
+
+	s = splhi();
+
+	lock(&r->lk);
+	p = r->p;
+
+	if(p != nil){
+		lock(&p->rlock);
+		if(p->state != Wakeme || p->r != r)
+			panic("wakeup: state");
+		r->p = nil;
+		p->r = nil;
+		p->state = Running;
+		procwakeup(p);
+		unlock(&p->rlock);
+	}
+	unlock(&r->lk);
+
+	splx(s);
+
+	return p;
+}
+
--- /dev/null
+++ b/kern/smalloc.c
@@ -1,0 +1,18 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void*
+smalloc(ulong n)
+{
+	return mallocz(n, 1);
+}
+
+void*
+malloc(ulong n)
+{
+	return mallocz(n, 1);
+}
+
--- /dev/null
+++ b/kern/stub.c
@@ -1,0 +1,170 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+mallocsummary(void)
+{
+}
+
+void
+pagersummary(void)
+{
+}
+
+int
+iseve(void)
+{
+	return 1;
+}
+
+void
+setswapchan(Chan *c)
+{
+	USED(c);
+}
+
+void
+splx(int x)
+{
+	USED(x);
+}
+
+int
+splhi(void)
+{
+	return 0;
+}
+
+int
+spllo(void)
+{
+	return 0;
+}
+
+void
+procdump(void)
+{
+}
+
+void
+scheddump(void)
+{
+}
+
+void
+killbig(void)
+{
+}
+
+void
+dumpstack(void)
+{
+}
+
+void
+xsummary(void)
+{
+}
+
+void
+rebootcmd(int argc, char **argv)
+{
+	USED(argc);
+	USED(argv);
+}
+
+void
+kickpager(void)
+{
+}
+
+int
+userwrite(char *a, int n)
+{
+	error(Eperm);
+	return 0;
+}
+
+vlong
+todget(vlong *p)
+{
+	if(p)
+		*p = 0;
+	return 0;
+}
+
+void
+todset(vlong a, vlong b, int c)
+{
+	USED(a);
+	USED(b);
+	USED(c);
+}
+
+void
+todsetfreq(vlong a)
+{
+	USED(a);
+}
+
+long
+hostdomainwrite(char *a, int n)
+{
+	USED(a);
+	USED(n);
+	error(Eperm);
+	return 0;
+}
+
+long
+hostownerwrite(char *a, int n)
+{
+	USED(a);
+	USED(n);
+	error(Eperm);
+	return 0;
+}
+
+void
+todinit(void)
+{
+}
+
+void
+rdb(void)
+{
+}
+
+void
+setmalloctag(void *v, ulong tag)
+{
+	USED(v);
+	USED(tag);
+}
+
+int
+postnote(Proc *p, int x, char *msg, int flag)
+{
+	USED(p);
+	USED(x);
+	USED(msg);
+	USED(flag);
+}
+
+void
+exhausted(char *s)
+{
+	panic("out of %s", s);
+}
+
+uvlong
+fastticks(uvlong *v)
+{
+	if(v)
+		*v = 1;
+	return 0;
+}
+
--- /dev/null
+++ b/kern/syscall.c
@@ -1,0 +1,837 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+Chan*
+fdtochan(int fd, int mode, int chkmnt, int iref)
+{
+	Fgrp *f;
+	Chan *c;
+
+	c = 0;
+	f = up->fgrp;
+
+	lock(&f->ref.lk);
+	if(fd<0 || NFD<=fd || (c = f->fd[fd])==0) {
+		unlock(&f->ref.lk);
+		error(Ebadfd);
+	}
+	if(iref)
+		refinc(&c->ref);
+	unlock(&f->ref.lk);
+
+	if(chkmnt && (c->flag&CMSG))
+		goto bad;
+	if(mode<0 || c->mode==ORDWR)
+		return c;
+	if((mode&OTRUNC) && c->mode==OREAD)
+		goto bad;
+	if((mode&~OTRUNC) != c->mode)
+		goto bad;
+	return c;
+bad:
+	if(iref)
+		cclose(c);
+	error(Ebadusefd);
+	return nil; /* shut up compiler */
+}
+
+static void
+fdclose(int fd, int flag)
+{
+	int i;
+	Chan *c;
+	Fgrp *f;
+
+	f = up->fgrp;
+
+	lock(&f->ref.lk);
+	c = f->fd[fd];
+	if(c == 0) {
+		unlock(&f->ref.lk);
+		return;
+	}
+	if(flag) {
+		if(c==0 || !(c->flag&flag)) {
+			unlock(&f->ref.lk);
+			return;
+		}
+	}
+	f->fd[fd] = 0;
+	if(fd == f->maxfd)
+		for(i=fd; --i>=0 && f->fd[i]==0; )
+			f->maxfd = i;
+
+	unlock(&f->ref.lk);
+	cclose(c);
+}
+
+static int
+newfd(Chan *c)
+{
+	int i;
+	Fgrp *f;
+
+	f = up->fgrp;
+	lock(&f->ref.lk);
+	for(i=0; i<NFD; i++)
+		if(f->fd[i] == 0){
+			if(i > f->maxfd)
+				f->maxfd = i;
+			f->fd[i] = c;
+			unlock(&f->ref.lk);
+			return i;
+		}
+	unlock(&f->ref.lk);
+	error("no file descriptors");
+	return 0;
+}
+
+int
+sysclose(int fd)
+{
+	if(waserror())
+		return -1;
+
+	fdtochan(fd, -1, 0, 0);
+	fdclose(fd, 0);
+	poperror();
+	return 0;
+}
+
+int
+syscreate(char *path, int mode, ulong perm)
+{
+	int fd;
+	Chan *c = 0;
+
+	if(waserror()) {
+		cclose(c);
+		return -1;
+	}
+
+	openmode(mode);			/* error check only */
+	c = namec(path, Acreate, mode, perm);
+	fd = newfd((Chan*)c);
+	poperror();
+	return fd;
+}
+
+int
+sysdup(int old, int new)
+{
+	Chan *oc;
+	Fgrp *f = up->fgrp;
+	Chan *c = 0;
+
+	if(waserror())
+		return -1;
+
+	c = fdtochan(old, -1, 0, 1);
+	if(new != -1) {
+		if(new < 0 || NFD <= new) {
+			cclose(c);
+			error(Ebadfd);
+		}
+		lock(&f->ref.lk);
+		if(new > f->maxfd)
+			f->maxfd = new;
+		oc = f->fd[new];
+		f->fd[new] = (Chan*)c;
+		unlock(&f->ref.lk);
+		if(oc != 0)
+			cclose(oc);
+	}
+	else {
+		if(waserror()) {
+			cclose(c);
+			nexterror();
+		}
+		new = newfd((Chan*)c);
+		poperror();
+	}
+	poperror();
+	return new;
+}
+
+int
+sysfstat(int fd, char *buf)
+{
+	Chan *c = 0;
+
+	if(waserror()) {
+		cclose(c);
+		return -1;
+	}
+	c = fdtochan(fd, -1, 0, 1);
+	devtab[c->type]->stat((Chan*)c, buf);
+	poperror();
+	cclose(c);
+	return 0;
+}
+
+int
+sysfwstat(int fd, char *buf)
+{
+	Chan *c = 0;
+
+	if(waserror()) {
+		cclose(c);
+		return -1;
+	}
+	nameok(buf);
+	c = fdtochan(fd, -1, 1, 1);
+	devtab[c->type]->wstat((Chan*)c, buf);
+	poperror();
+	cclose(c);
+	return 0;
+}
+
+int
+syschdir(char *dir)
+{
+	return 0;
+}
+
+long
+bindmount(Chan *c0, char *old, int flag, char *spec)
+{
+	int ret;
+	Chan *c1 = 0;
+
+	if(flag>MMASK || (flag&MORDER) == (MBEFORE|MAFTER))
+		error(Ebadarg);
+
+	c1 = namec(old, Amount, 0, 0);
+	if(waserror()){
+		cclose(c1);
+		nexterror();
+	}
+
+	ret = cmount(c0, c1, flag, spec);
+
+	poperror();
+	cclose(c1);
+	return ret;
+}
+
+int
+sysbind(char *new, char *old, int flags)
+{
+	long r;
+	Chan *c0 = 0;
+
+	if(waserror()) {
+		cclose(c0);
+		return -1;
+	}
+	c0 = namec(new, Aaccess, 0, 0);
+	r = bindmount(c0, old, flags, "");
+	poperror();
+	cclose(c0);
+	return 0;
+}
+
+int
+sysmount(int fd, char *old, int flags, char *spec)
+{
+	long r;
+	Chan *c0 = 0, *bc = 0;
+	struct {
+		Chan*	chan;
+		char*	spec;
+		int	flags;
+	} mntparam;
+
+	if(waserror()) {
+		cclose(bc);
+		cclose(c0);
+		return -1;
+	}
+	bc = fdtochan(fd, ORDWR, 0, 1);
+	mntparam.chan = (Chan*)bc;
+	mntparam.spec = spec;
+	mntparam.flags = flags;
+	c0 = (*devtab[devno('M', 0)].attach)(&mntparam);
+	cclose(bc);
+	r = bindmount(c0, old, flags, spec);
+	poperror();
+	cclose(c0);
+
+	return r;
+}
+
+int
+sysunmount(char *old, char *new)
+{
+	Chan *cmount = 0, *cmounted = 0;
+
+	if(waserror()) {
+		cclose(cmount);
+		cclose(cmounted);
+		return -1;
+	}
+
+	cmount = namec(new, Amount, OREAD, 0);
+	if(old != 0)
+		cmounted = namec(old, Aopen, OREAD, 0);
+
+	cunmount(cmount, cmounted);
+	poperror();	
+	cclose(cmount);
+	cclose(cmounted);
+	return 0;
+}
+
+int
+sysopen(char *path, int mode)
+{
+	int fd;
+	Chan *c = 0;
+
+	if(waserror()){
+		cclose(c);
+		return -1;
+	}
+	openmode(mode);				/* error check only */
+	c = namec(path, Aopen, mode, 0);
+	fd = newfd((Chan*)c);
+	poperror();
+	return fd;
+}
+
+long
+unionread(Chan *c, void *va, long n)
+{
+	long nr;
+	Chan *nc = 0;
+	Pgrp *pg = 0;
+
+	pg = up->pgrp;
+	rlock(&pg->ns);
+
+	for(;;) {
+		if(waserror()) {
+			runlock(&pg->ns);
+			nexterror();
+		}
+		nc = clone(c->mnt->to, 0);
+		poperror();
+
+		if(c->mountid != c->mnt->mountid) {
+			runlock(&pg->ns);
+			cclose(nc);
+			return 0;
+		}
+
+		/* Error causes component of union to be skipped */
+		if(waserror()) {	
+			cclose(nc);
+			goto next;
+		}
+
+		nc = (*devtab[nc->type].open)((Chan*)nc, OREAD);
+		nc->offset = c->offset;
+		nr = (*devtab[nc->type].read)((Chan*)nc, va, n, nc->offset);
+		/* devdirread e.g. changes it */
+		c->offset = nc->offset;	
+		poperror();
+
+		cclose(nc);
+		if(nr > 0) {
+			runlock(&pg->ns);
+			return nr;
+		}
+		/* Advance to next element */
+	next:
+		c->mnt = c->mnt->next;
+		if(c->mnt == 0)
+			break;
+		c->mountid = c->mnt->mountid;
+		c->offset = 0;
+	}
+	runlock(&pg->ns);
+	return 0;
+}
+
+long
+sysread(int fd, void *va, long n)
+{
+	int dir;
+	Lock *cl;
+	Chan *c = 0;
+
+	if(waserror()) {
+		cclose(c);
+		return -1;
+	}
+	c = fdtochan(fd, OREAD, 1, 1);
+
+	dir = c->qid.path&CHDIR;
+	if(dir) {
+		n -= n%DIRLEN;
+		if(c->offset%DIRLEN || n==0)
+			error(Etoosmall);
+	}
+
+	if(dir && c->mnt)
+		n = unionread((Chan*)c, va, n);
+	else
+		n = (*devtab[c->type].read)((Chan*)c, va, n, c->offset);
+
+	cl = (Lock*)&c->r.l;
+	lock(cl);
+	c->offset += n;
+	unlock(cl);
+
+	poperror();
+	cclose(c);
+
+	return n;
+}
+
+int
+sysremove(char *path)
+{
+	Chan *c = 0;
+
+	if(waserror()) {
+		if(c != 0)
+			c->type = 0;	/* see below */
+		cclose(c);
+		return -1;
+	}
+	c = namec(path, Aaccess, 0, 0);
+	(*devtab[c->type].remove)((Chan*)c);
+	/*
+	 * Remove clunks the fid, but we need to recover the Chan
+	 * so fake it up.  rootclose() is known to be a nop.
+	 */
+	c->type = 0;
+	poperror();
+	cclose(c);
+	return 0;
+}
+
+long
+sysseek(int fd, long off, int whence)
+{
+	Dir dir;
+	Chan *c;
+	char buf[DIRLEN];
+
+	if(waserror())
+		return -1;
+
+	c = fdtochan(fd, -1, 1, 0);
+	if(c->qid.path & CHDIR)
+		error(Eisdir);
+
+	switch(whence) {
+	case 0:
+		c->offset = off;
+		break;
+
+	case 1:
+		lock(&c->r.l);	/* lock for read/write update */
+		c->offset += off;
+		off = c->offset;
+		unlock(&c->r.l);
+		break;
+
+	case 2:
+		(*devtab[c->type].stat)(c, buf);
+		convM2D(buf, &dir);
+		c->offset = dir.length + off;
+		off = c->offset;
+		break;
+	}
+	poperror();
+	return off;
+}
+
+int
+sysstat(char *path, char *buf)
+{
+	Chan *c = 0;
+
+	if(waserror()){
+		cclose(c);
+		return -1;
+	}
+	c = namec(path, Aaccess, 0, 0);
+	(*devtab[c->type].stat)((Chan*)c, buf);
+	poperror();
+	cclose(c);
+	return 0;
+}
+
+long
+syswrite(int fd, void *va, long n)
+{
+	Lock *cl;
+	Chan *c = 0;
+
+	if(waserror()) {
+		cclose(c);
+		return -1;
+	}
+	c = fdtochan(fd, OWRITE, 1, 1);
+	if(c->qid.path & CHDIR)
+		error(Eisdir);
+
+	n = (*devtab[c->type].write)((Chan*)c, va, n, c->offset);
+
+	cl = (Lock*)&c->r.l;
+	lock(cl);
+	c->offset += n;
+	unlock(cl);
+
+	poperror();
+	cclose(c);
+
+	return n;
+}
+
+int
+syswstat(char *path, char *buf)
+{
+	Chan *c = 0;
+
+	if(waserror()) {
+		cclose(c);
+		return -1;
+	}
+
+	nameok(buf);
+	c = namec(path, Aaccess, 0, 0);
+	(*devtab[c->type].wstat)((Chan*)c, buf);
+	poperror();
+	cclose(c);
+	return 0;
+}
+
+int
+sysdirstat(char *name, Dir *dir)
+{
+	char buf[DIRLEN];
+
+	if(sysstat(name, buf) == -1)
+		return -1;
+	convM2D(buf, dir);
+	return 0;
+}
+
+int
+sysdirfstat(int fd, Dir *dir)
+{
+	char buf[DIRLEN];
+
+	if(sysfstat(fd, buf) == -1)
+		return -1;
+
+	convM2D(buf, dir);
+	return 0;
+}
+
+int
+sysdirwstat(char *name, Dir *dir)
+{
+	char buf[DIRLEN];
+
+	convD2M(dir, buf);
+	return syswstat(name, buf);
+}
+
+int
+sysdirfwstat(int fd, Dir *dir)
+{
+	char buf[DIRLEN];
+
+	convD2M(dir, buf);
+	return sysfwstat(fd, buf);
+}
+
+long
+sysdirread(int fd, Dir *dbuf, long count)
+{
+	int c, n, i, r;
+	char buf[DIRLEN*50];
+
+	n = 0;
+	count = (count/sizeof(Dir)) * DIRLEN;
+	while(n < count) {
+		c = count - n;
+		if(c > sizeof(buf))
+			c = sizeof(buf);
+		r = sysread(fd, buf, c);
+		if(r == 0)
+			break;
+		if(r < 0 || r % DIRLEN)
+			return -1;
+		for(i=0; i<r; i+=DIRLEN) {
+			convM2D(buf+i, dbuf);
+			dbuf++;
+		}
+		n += r;
+		if(r != c)
+			break;
+	}
+
+	return (n/DIRLEN) * sizeof(Dir);
+}
+
+static int
+call(char *clone, char *dest, int *cfdp, char *dir, char *local)
+{
+	int fd, cfd, n;
+	char *p, name[3*NAMELEN+5], data[3*NAMELEN+10];
+
+	cfd = sysopen(clone, ORDWR);
+	if(cfd < 0){
+		werrstr("%s: %r", clone);
+		return -1;
+	}
+
+	/* get directory name */
+	n = sysread(cfd, name, sizeof(name)-1);
+	if(n < 0) {
+		sysclose(cfd);
+		return -1;
+	}
+	name[n] = 0;
+	sprint(name, "%d", strtoul(name, 0, 0));
+	p = strrchr(clone, '/');
+	*p = 0;
+	if(dir)
+		snprint(dir, 2*NAMELEN, "%s/%s", clone, name);
+	snprint(data, sizeof(data), "%s/%s/data", clone, name);
+
+	/* connect */
+	/* set local side (port number, for example) if we need to */
+	if(local)
+		snprint(name, sizeof(name), "connect %s %s", dest, local);
+	else
+		snprint(name, sizeof(name), "connect %s", dest);
+	if(syswrite(cfd, name, strlen(name)) < 0){
+		werrstr("%s failed: %r", name);
+		sysclose(cfd);
+		return -1;
+	}
+
+	/* open data connection */
+	fd = sysopen(data, ORDWR);
+	if(fd < 0){
+		werrstr("can't open %s: %r", data);
+		sysclose(cfd);
+		return -1;
+	}
+	if(cfdp)
+		*cfdp = cfd;
+	else
+		sysclose(cfd);
+	return fd;
+}
+
+int
+sysdial(char *dest, char *local, char *dir, int *cfdp)
+{
+	int n, fd, rv;
+	char *p, net[128], clone[NAMELEN+12];
+
+	/* go for a standard form net!... */
+	p = strchr(dest, '!');
+	if(p == 0){
+		snprint(net, sizeof(net), "net!%s", dest);
+	} else {
+		strncpy(net, dest, sizeof(net)-1);
+		net[sizeof(net)-1] = 0;
+	}
+
+	/* call the connection server */
+	fd = sysopen("/net/cs", ORDWR);
+	if(fd < 0){
+		/* no connection server, don't translate */
+		p = strchr(net, '!');
+		*p++ = 0;
+		snprint(clone, sizeof(clone), "/net/%s/clone", net);
+		return call(clone, p, cfdp, dir, local);
+	}
+
+	/*
+	 *  send dest to connection to translate
+	 */
+	if(syswrite(fd, net, strlen(net)) < 0){
+		werrstr("%s: %r", net);
+		sysclose(fd);
+		return -1;
+	}
+
+	/*
+	 *  loop through each address from the connection server till
+	 *  we get one that works.
+	 */
+	rv = -1;
+	sysseek(fd, 0, 0);
+	while((n = sysread(fd, net, sizeof(net) - 1)) > 0){
+		net[n] = 0;
+		p = strchr(net, ' ');
+		if(p == 0)
+			continue;
+		*p++ = 0;
+		rv = call(net, p, cfdp, dir, local);
+		if(rv >= 0)
+			break;
+	}
+	sysclose(fd);
+	return rv;
+}
+
+static int
+identtrans(char *addr, char *naddr, int na, char *file, int nf)
+{
+	char *p;
+	char reply[4*NAMELEN];
+
+	/* parse the network */
+	strncpy(reply, addr, sizeof(reply));
+	reply[sizeof(reply)-1] = 0;
+	p = strchr(reply, '!');
+	if(p)
+		*p++ = 0;
+
+	sprint(file, "/net/%.*s/clone", na - sizeof("/net//clone"), reply);
+	strncpy(naddr, p, na);
+	naddr[na-1] = 0;
+	return 1;
+}
+
+static int
+nettrans(char *addr, char *naddr, int na, char *file, int nf)
+{
+	long n;
+	int fd;
+	char *cp;
+	char reply[4*NAMELEN];
+
+	/*
+	 *  ask the connection server
+	 */
+	fd = sysopen("/net/cs", ORDWR);
+	if(fd < 0)
+		return identtrans(addr, naddr, na, file, nf);
+	if(syswrite(fd, addr, strlen(addr)) < 0){
+		sysclose(fd);
+		return -1;
+	}
+	sysseek(fd, 0, 0);
+	n = sysread(fd, reply, sizeof(reply)-1);
+	sysclose(fd);
+	if(n <= 0)
+		return -1;
+	reply[n] = '\0';
+
+	/*
+	 *  parse the reply
+	 */
+	cp = strchr(reply, ' ');
+	if(cp == 0)
+		return -1;
+	*cp++ = 0;
+	strncpy(naddr, cp, na);
+	naddr[na-1] = 0;
+	strncpy(file, reply, nf);
+	file[nf-1] = 0;
+	return 0;
+}
+
+int
+sysannounce(char *addr, char *dir)
+{
+	char *cp;
+	int ctl, n, m;
+	char buf[3*NAMELEN];
+	char buf2[3*NAMELEN];
+	char netdir[2*NAMELEN];
+	char naddr[3*NAMELEN];
+
+	/*
+	 *  translate the address
+	 */
+	if(nettrans(addr, naddr, sizeof(naddr), netdir, sizeof(netdir)) < 0){
+		werrstr("can't translate address");
+		return -1;
+	}
+
+	/*
+	 * get a control channel
+	 */
+	ctl = sysopen(netdir, ORDWR);
+	if(ctl<0){
+		werrstr("can't open control channel");
+		return -1;
+	}
+	cp = strrchr(netdir, '/');
+	*cp = 0;
+
+	/*
+	 *  find out which line we have
+	 */
+	n = sprint(buf, "%.*s/", 2*NAMELEN+1, netdir);
+	m = sysread(ctl, &buf[n], sizeof(buf)-n-1);
+	if(m <= 0) {
+		sysclose(ctl);
+		werrstr("can't read control file");
+		return -1;
+	}
+	buf[n+m] = 0;
+
+	/*
+	 *  make the call
+	 */
+	n = sprint(buf2, "announce %.*s", 2*NAMELEN, naddr);
+	if(syswrite(ctl, buf2, n) != n) {
+