shithub: 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) {
+		sysclose(ctl);
+		werrstr("announcement fails");
+		return -1;
+	}
+
+	strcpy(dir, buf);
+
+	return ctl;
+}
+
+int
+syslisten(char *dir, char *newdir)
+{
+	char *cp;
+	int ctl, n, m;
+	char buf[3*NAMELEN];
+
+	/*
+	 *  open listen, wait for a call
+	 */
+	sprint(buf, "%.*s/listen", 2*NAMELEN+1, dir);
+	ctl = sysopen(buf, ORDWR);
+	if(ctl < 0)
+		return -1;
+
+	/*
+	 *  find out which line we have
+	 */
+	strcpy(buf, dir);
+	cp = strrchr(buf, '/');
+	*++cp = 0;
+	n = cp-buf;
+	m = sysread(ctl, cp, sizeof(buf) - n - 1);
+	if(n<=0){
+		sysclose(ctl);
+		return -1;
+	}
+	buf[n+m] = 0;
+
+	strcpy(newdir, buf);
+	return ctl;
+}
--- /dev/null
+++ b/kern/sysfile.c
@@ -1,0 +1,1242 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	"user.h"
+#undef open
+#undef mount
+#undef read
+#undef write
+#undef seek
+#undef stat
+#undef wstat
+#undef remove
+#undef close
+#undef fstat
+#undef fwstat
+
+/*
+ * The sys*() routines needn't poperror() as they return directly to syscall().
+ */
+
+static void
+unlockfgrp(Fgrp *f)
+{
+	int ex;
+
+	ex = f->exceed;
+	f->exceed = 0;
+	unlock(&f->ref.lk);
+	if(ex)
+		pprint("warning: process exceeds %d file descriptors\n", ex);
+}
+
+int
+growfd(Fgrp *f, int fd)	/* fd is always >= 0 */
+{
+	Chan **newfd, **oldfd;
+
+	if(fd < f->nfd)
+		return 0;
+	if(fd >= f->nfd+DELTAFD)
+		return -1;	/* out of range */
+	/*
+	 * Unbounded allocation is unwise; besides, there are only 16 bits
+	 * of fid in 9P
+	 */
+	if(f->nfd >= 5000){
+    Exhausted:
+		print("no free file descriptors\n");
+		return -1;
+	}
+	newfd = malloc((f->nfd+DELTAFD)*sizeof(Chan*));
+	if(newfd == 0)
+		goto Exhausted;
+	oldfd = f->fd;
+	memmove(newfd, oldfd, f->nfd*sizeof(Chan*));
+	f->fd = newfd;
+	free(oldfd);
+	f->nfd += DELTAFD;
+	if(fd > f->maxfd){
+		if(fd/100 > f->maxfd/100)
+			f->exceed = (fd/100)*100;
+		f->maxfd = fd;
+	}
+	return 1;
+}
+
+/*
+ *  this assumes that the fgrp is locked
+ */
+int
+findfreefd(Fgrp *f, int start)
+{
+	int fd;
+
+	for(fd=start; fd<f->nfd; fd++)
+		if(f->fd[fd] == 0)
+			break;
+	if(fd >= f->nfd && growfd(f, fd) < 0)
+		return -1;
+	return fd;
+}
+
+int
+newfd(Chan *c)
+{
+	int fd;
+	Fgrp *f;
+
+	f = up->fgrp;
+	lock(&f->ref.lk);
+	fd = findfreefd(f, 0);
+	if(fd < 0){
+		unlockfgrp(f);
+		return -1;
+	}
+	if(fd > f->maxfd)
+		f->maxfd = fd;
+	f->fd[fd] = c;
+	unlockfgrp(f);
+	return fd;
+}
+
+int
+newfd2(int fd[2], Chan *c[2])
+{
+	Fgrp *f;
+
+	f = up->fgrp;
+	lock(&f->ref.lk);
+	fd[0] = findfreefd(f, 0);
+	if(fd[0] < 0){
+		unlockfgrp(f);
+		return -1;
+	}
+	fd[1] = findfreefd(f, fd[0]+1);
+	if(fd[1] < 0){
+		unlockfgrp(f);
+		return -1;
+	}
+	if(fd[1] > f->maxfd)
+		f->maxfd = fd[1];
+	f->fd[fd[0]] = c[0];
+	f->fd[fd[1]] = c[1];
+	unlockfgrp(f);
+
+	return 0;
+}
+
+Chan*
+fdtochan(int fd, int mode, int chkmnt, int iref)
+{
+	Chan *c;
+	Fgrp *f;
+
+	c = 0;
+	f = up->fgrp;
+
+	lock(&f->ref.lk);
+	if(fd<0 || f->nfd<=fd || (c = f->fd[fd])==0) {
+		unlock(&f->ref.lk);
+		error(Ebadfd);
+	}
+	if(iref)
+		incref(&c->ref);
+	unlock(&f->ref.lk);
+
+	if(chkmnt && (c->flag&CMSG)) {
+		if(iref)
+			cclose(c);
+		error(Ebadusefd);
+	}
+
+	if(mode<0 || c->mode==ORDWR)
+		return c;
+
+	if((mode&OTRUNC) && c->mode==OREAD) {
+		if(iref)
+			cclose(c);
+		error(Ebadusefd);
+	}
+
+	if((mode&~OTRUNC) != c->mode) {
+		if(iref)
+			cclose(c);
+		error(Ebadusefd);
+	}
+
+	return c;
+}
+
+int
+openmode(ulong o)
+{
+	o &= ~(OTRUNC|OCEXEC|ORCLOSE);
+	if(o > OEXEC)
+		error(Ebadarg);
+	if(o == OEXEC)
+		return OREAD;
+	return o;
+}
+
+long
+_sysfd2path(int fd, char *buf, uint nbuf)
+{
+	Chan *c;
+
+	c = fdtochan(fd, -1, 0, 1);
+
+	if(c->name == nil)
+		snprint(buf, nbuf, "<null>");
+	else
+		snprint(buf, nbuf, "%s", c->name->s);
+	cclose(c);
+	return 0;
+}
+
+long
+_syspipe(int fd[2])
+{
+	Chan *c[2];
+	Dev *d;
+	static char *datastr[] = {"data", "data1"};
+
+	d = devtab[devno('|', 0)];
+	c[0] = namec("#|", Atodir, 0, 0);
+	c[1] = 0;
+	fd[0] = -1;
+	fd[1] = -1;
+
+	if(waserror()){
+		cclose(c[0]);
+		if(c[1])
+			cclose(c[1]);
+		nexterror();
+	}
+	c[1] = cclone(c[0]);
+	if(walk(&c[0], datastr+0, 1, 1, nil) < 0)
+		error(Egreg);
+	if(walk(&c[1], datastr+1, 1, 1, nil) < 0)
+		error(Egreg);
+	c[0] = d->open(c[0], ORDWR);
+	c[1] = d->open(c[1], ORDWR);
+	if(newfd2(fd, c) < 0)
+		error(Enofd);
+	poperror();
+
+	return 0;
+}
+
+long
+_sysdup(int fd0, int fd1)
+{
+	int fd;
+	Chan *c, *oc;
+	Fgrp *f = up->fgrp;
+
+	/*
+	 * Close after dup'ing, so date > #d/1 works
+	 */
+	c = fdtochan(fd0, -1, 0, 1);
+	fd = fd1;
+	if(fd != -1){
+		lock(&f->ref.lk);
+		if(fd<0 || growfd(f, fd)<0) {
+			unlockfgrp(f);
+			cclose(c);
+			error(Ebadfd);
+		}
+		if(fd > f->maxfd)
+			f->maxfd = fd;
+
+		oc = f->fd[fd];
+		f->fd[fd] = c;
+		unlockfgrp(f);
+		if(oc)
+			cclose(oc);
+	}else{
+		if(waserror()) {
+			cclose(c);
+			nexterror();
+		}
+		fd = newfd(c);
+		if(fd < 0)
+			error(Enofd);
+		poperror();
+	}
+
+	return fd;
+}
+
+long
+_sysopen(char *name, int mode)
+{
+	int fd;
+	Chan *c = 0;
+
+	openmode(mode);	/* error check only */
+	if(waserror()){
+		if(c)
+			cclose(c);
+		nexterror();
+	}
+	c = namec(name, Aopen, mode, 0);
+	fd = newfd(c);
+	if(fd < 0)
+		error(Enofd);
+	poperror();
+	return fd;
+}
+
+void
+fdclose(int fd, int flag)
+{
+	int i;
+	Chan *c;
+	Fgrp *f = up->fgrp;
+
+	lock(&f->ref.lk);
+	c = f->fd[fd];
+	if(c == 0){
+		/* can happen for users with shared fd tables */
+		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);
+}
+
+long
+_sysclose(int fd)
+{
+	fdtochan(fd, -1, 0, 0);
+	fdclose(fd, 0);
+
+	return 0;
+}
+
+long
+unionread(Chan *c, void *va, long n)
+{
+	int i;
+	long nr;
+	Mhead *m;
+	Mount *mount;
+
+	qlock(&c->umqlock);
+	m = c->umh;
+	rlock(&m->lock);
+	mount = m->mount;
+	/* bring mount in sync with c->uri and c->umc */
+	for(i = 0; mount != nil && i < c->uri; i++)
+		mount = mount->next;
+
+	nr = 0;
+	while(mount != nil) {
+		/* Error causes component of union to be skipped */
+		if(mount->to && !waserror()) {
+			if(c->umc == nil){
+				c->umc = cclone(mount->to);
+				c->umc = devtab[c->umc->type]->open(c->umc, OREAD);
+			}
+	
+			nr = devtab[c->umc->type]->read(c->umc, va, n, c->umc->offset);
+			c->umc->offset += nr;
+			poperror();
+		}
+		if(nr > 0)
+			break;
+
+		/* Advance to next element */
+		c->uri++;
+		if(c->umc) {
+			cclose(c->umc);
+			c->umc = nil;
+		}
+		mount = mount->next;
+	}
+	runlock(&m->lock);
+	qunlock(&c->umqlock);
+	return nr;
+}
+
+static long
+kread(int fd, void *buf, long n, vlong *offp)
+{
+	int dir;
+	Chan *c;
+	vlong off;
+
+	c = fdtochan(fd, OREAD, 1, 1);
+
+	if(waserror()) {
+		cclose(c);
+		nexterror();
+	}
+
+	dir = c->qid.type&QTDIR;
+	/*
+	 * The offset is passed through on directories, normally. sysseek complains but
+	 * pread is used by servers and e.g. exportfs that shouldn't need to worry about this issue.
+	 */
+
+	if(offp == nil)	/* use and maintain channel's offset */
+		off = c->offset;
+	else
+		off = *offp;
+
+	if(off < 0)
+		error(Enegoff);
+
+	if(dir && c->umh)
+		n = unionread(c, buf, n);
+	else
+		n = devtab[c->type]->read(c, buf, n, off);
+
+	if(offp == nil){
+		lock(&c->ref.lk);
+		c->offset += n;
+		unlock(&c->ref.lk);
+	}
+
+	poperror();
+	cclose(c);
+
+	return n;
+}
+
+long
+_sys_read(int fd, void *buf, long n)
+{
+	return kread(fd, buf, n, nil);
+}
+
+long
+_syspread(int fd, void *buf, long n, vlong off)
+{
+	if(off == ((uvlong) ~0))
+		return kread(fd, buf, n, nil);
+	return kread(fd, buf, n, &off);
+}
+
+static long
+kwrite(int fd, void *buf, long nn, vlong *offp)
+{
+	Chan *c;
+	long m, n;
+	vlong off;
+
+	n = 0;
+	c = fdtochan(fd, OWRITE, 1, 1);
+	if(waserror()) {
+		if(offp == nil){
+			lock(&c->ref.lk);
+			c->offset -= n;
+			unlock(&c->ref.lk);
+		}
+		cclose(c);
+		nexterror();
+	}
+
+	if(c->qid.type & QTDIR)
+		error(Eisdir);
+
+	n = nn;
+
+	if(offp == nil){	/* use and maintain channel's offset */
+		lock(&c->ref.lk);
+		off = c->offset;
+		c->offset += n;
+		unlock(&c->ref.lk);
+	}else
+		off = *offp;
+
+	if(off < 0)
+		error(Enegoff);
+
+	m = devtab[c->type]->write(c, buf, n, off);
+
+	if(offp == nil && m < n){
+		lock(&c->ref.lk);
+		c->offset -= n - m;
+		unlock(&c->ref.lk);
+	}
+
+	poperror();
+	cclose(c);
+
+	return m;
+}
+
+long
+sys_write(int fd, void *buf, long n)
+{
+	return kwrite(fd, buf, n, nil);
+}
+
+long
+_syspwrite(int fd, void *buf, long n, vlong off)
+{
+	if(off == ((uvlong) ~0))
+		return kwrite(fd, buf, n, nil);
+	return kwrite(fd, buf, n, &off);
+}
+
+static vlong
+_sysseek(int fd, vlong off, int whence)
+{
+	Chan *c;
+	uchar buf[sizeof(Dir)+100];
+	Dir dir;
+	int n;
+
+	c = fdtochan(fd, -1, 1, 1);
+	if(waserror()){
+		cclose(c);
+		nexterror();
+	}
+	if(devtab[c->type]->dc == '|')
+		error(Eisstream);
+
+	switch(whence){
+	case 0:
+		if((c->qid.type & QTDIR) && off != 0)
+			error(Eisdir);
+		if(off < 0)
+			error(Enegoff);
+		c->offset = off;
+		break;
+
+	case 1:
+		if(c->qid.type & QTDIR)
+			error(Eisdir);
+		lock(&c->ref.lk);	/* lock for read/write update */
+		off = off + c->offset;
+		if(off < 0)
+			error(Enegoff);
+		c->offset = off;
+		unlock(&c->ref.lk);
+		break;
+
+	case 2:
+		if(c->qid.type & QTDIR)
+			error(Eisdir);
+		n = devtab[c->type]->stat(c, buf, sizeof buf);
+		if(convM2D(buf, n, &dir, nil) == 0)
+			error("internal error: stat error in seek");
+		off = dir.length + off;
+		if(off < 0)
+			error(Enegoff);
+		c->offset = off;
+		break;
+
+	default:
+		error(Ebadarg);
+	}
+	c->uri = 0;
+	c->dri = 0;
+	cclose(c);
+	poperror();
+	return off;
+}
+
+void
+validstat(uchar *s, int n)
+{
+	int m;
+	char buf[64];
+
+	if(statcheck(s, n) < 0)
+		error(Ebadstat);
+	/* verify that name entry is acceptable */
+	s += STATFIXLEN - 4*BIT16SZ;	/* location of first string */
+	/*
+	 * s now points at count for first string.
+	 * if it's too long, let the server decide; this is
+	 * only for his protection anyway. otherwise
+	 * we'd have to allocate and waserror.
+	 */
+	m = GBIT16(s);
+	s += BIT16SZ;
+	if(m+1 > sizeof buf)
+		return;
+	memmove(buf, s, m);
+	buf[m] = '\0';
+	/* name could be '/' */
+	if(strcmp(buf, "/") != 0)
+		validname(buf, 0);
+}
+
+long
+_sysfstat(int fd, void *buf, long n)
+{
+	Chan *c;
+	uint l;
+
+	l = n;
+	validaddr(buf, l, 1);
+	c = fdtochan(fd, -1, 0, 1);
+	if(waserror()) {
+		cclose(c);
+		nexterror();
+	}
+	l = devtab[c->type]->stat(c, buf, l);
+	poperror();
+	cclose(c);
+	return l;
+}
+
+long
+_sysstat(char *name, void *buf, long n)
+{
+	Chan *c;
+	uint l;
+
+	l = n;
+	validaddr(buf, l, 1);
+	validaddr(name, 1, 0);
+	c = namec(name, Aaccess, 0, 0);
+	if(waserror()){
+		cclose(c);
+		nexterror();
+	}
+	l = devtab[c->type]->stat(c, buf, l);
+	poperror();
+	cclose(c);
+	return l;
+}
+
+long
+_syschdir(char *name)
+{
+	Chan *c;
+
+	validaddr(name, 1, 0);
+
+	c = namec(name, Atodir, 0, 0);
+	cclose(up->dot);
+	up->dot = c;
+	return 0;
+}
+
+long
+bindmount(int ismount, int fd, int afd, char* arg0, char* arg1, ulong flag, char* spec)
+{
+	int ret;
+	Chan *c0, *c1, *ac, *bc;
+	struct{
+		Chan	*chan;
+		Chan	*authchan;
+		char	*spec;
+		int	flags;
+	}bogus;
+
+	if((flag&~MMASK) || (flag&MORDER)==(MBEFORE|MAFTER))
+		error(Ebadarg);
+
+	bogus.flags = flag & MCACHE;
+
+	if(ismount){
+		if(up->pgrp->noattach)
+			error(Enoattach);
+
+		ac = nil;
+		bc = fdtochan(fd, ORDWR, 0, 1);
+		if(waserror()) {
+			if(ac)
+				cclose(ac);
+			cclose(bc);
+			nexterror();
+		}
+
+		if(afd >= 0)
+			ac = fdtochan(afd, ORDWR, 0, 1);
+
+		bogus.chan = bc;
+		bogus.authchan = ac;
+
+		validaddr((ulong)spec, 1, 0);
+		bogus.spec = spec;
+		if(waserror())
+			error(Ebadspec);
+		validname(spec, 1);
+		poperror();
+
+		ret = devno('M', 0);
+		c0 = devtab[ret]->attach((char*)&bogus);
+
+		poperror();
+		if(ac)
+			cclose(ac);
+		cclose(bc);
+	}else{
+		bogus.spec = 0;
+		validaddr((ulong)arg0, 1, 0);
+		c0 = namec(arg0, Abind, 0, 0);
+	}
+
+	if(waserror()){
+		cclose(c0);
+		nexterror();
+	}
+
+	validaddr((ulong)arg1, 1, 0);
+	c1 = namec(arg1, Amount, 0, 0);
+	if(waserror()){
+		cclose(c1);
+		nexterror();
+	}
+
+	ret = cmount(&c0, c1, flag, bogus.spec);
+
+	poperror();
+	cclose(c1);
+	poperror();
+	cclose(c0);
+	if(ismount)
+		fdclose(fd, 0);
+
+	return ret;
+}
+
+long
+_sysbind(char *old, char *new, int flag)
+{
+	return bindmount(0, -1, -1, old, new, flag, nil);
+}
+
+long
+_sysmount(int fd, int afd, char *new, int flag, char *spec)
+{
+	return bindmount(1, fd, afd, nil, new, flag, spec);
+}
+
+long
+_sysunmount(char *old, char *new)
+{
+	Chan *cmount, *cmounted;
+
+	cmounted = 0;
+
+	cmount = namec(new, Amount, 0, 0);
+
+	if(old) {
+		if(waserror()) {
+			cclose(cmount);
+			nexterror();
+		}
+		validaddr(old, 1, 0);
+		/*
+		 * This has to be namec(..., Aopen, ...) because
+		 * if arg[0] is something like /srv/cs or /fd/0,
+		 * opening it is the only way to get at the real
+		 * Chan underneath.
+		 */
+		cmounted = namec(old, Aopen, OREAD, 0);
+		poperror();
+	}
+
+	if(waserror()) {
+		cclose(cmount);
+		if(cmounted)
+			cclose(cmounted);
+		nexterror();
+	}
+
+	cunmount(cmount, cmounted);
+	cclose(cmount);
+	if(cmounted)
+		cclose(cmounted);
+	poperror();
+	return 0;
+}
+
+long
+_syscreate(char *name, int mode, ulong perm)
+{
+	int fd;
+	Chan *c = 0;
+
+	openmode(mode&~OEXCL);	/* error check only; OEXCL okay here */
+	if(waserror()) {
+		if(c)
+			cclose(c);
+		nexterror();
+	}
+	validaddr(name, 1, 0);
+	c = namec(name, Acreate, mode, perm);
+	fd = newfd(c);
+	if(fd < 0)
+		error(Enofd);
+	poperror();
+	return fd;
+}
+
+long
+_sysremove(char *name)
+{
+	Chan *c;
+
+	c = namec(name, Aremove, 0, 0);
+	if(waserror()){
+		c->type = 0;	/* see below */
+		cclose(c);
+		nexterror();
+	}
+	devtab[c->type]->remove(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
+_syswstat(char *name, void *buf, long n)
+{
+	Chan *c;
+	uint l;
+
+	l = n;
+	validstat(buf, l);
+	validaddr(name, 1, 0);
+	c = namec(name, Aaccess, 0, 0);
+	if(waserror()){
+		cclose(c);
+		nexterror();
+	}
+	l = devtab[c->type]->wstat(c, buf, l);
+	poperror();
+	cclose(c);
+	return l;
+}
+
+long
+_sysfwstat(int fd, void *buf, long n)
+{
+	Chan *c;
+	uint l;
+
+	l = n;
+	validaddr(buf, l, 0);
+	validstat(buf, l);
+	c = fdtochan(fd, -1, 1, 1);
+	if(waserror()) {
+		cclose(c);
+		nexterror();
+	}
+	l = devtab[c->type]->wstat(c, buf, l);
+	poperror();
+	cclose(c);
+	return l;
+}
+
+
+static void
+starterror(void)
+{
+	assert(up->nerrlab == 0);
+}
+
+static void
+_syserror(void)
+{
+	char *p;
+
+	p = up->syserrstr;
+	up->syserrstr = up->errstr;
+	up->errstr = p;
+}
+
+static void
+enderror(void)
+{
+	assert(up->nerrlab == 1);
+	poperror();
+}
+
+int
+sysbind(char *old, char *new, int flag)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysbind(old, new, flag);
+	enderror();
+	return n;
+}
+
+int
+syschdir(char *path)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syschdir(path);
+	enderror();
+	return n;
+}
+
+int
+sysclose(int fd)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysclose(fd);
+	enderror();
+	return n;
+}
+
+int
+syscreate(char *name, int mode, ulong perm)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syscreate(name, mode, perm);
+	enderror();
+	return n;
+}
+
+int
+sysdup(int fd0, int fd1)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysdup(fd0, fd1);
+	enderror();
+	return n;
+}
+
+int
+sysfstat(int fd, uchar *buf, int n)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysfstat(fd, buf, n);
+	enderror();
+	return n;
+}
+
+int
+sysfwstat(int fd, uchar *buf, int n)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysfwstat(fd, buf, n);
+	enderror();
+	return n;
+}
+
+int
+sysmount(int fd, int afd, char *new, int flag, char *spec)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysmount(fd, afd, new, flag, spec);
+	enderror();
+	return n;
+}
+
+int
+sysunmount(char *old, char *new)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysunmount(old, new);
+	enderror();
+	return n;
+}
+
+int
+sysopen(char *name, int mode)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysopen(name, mode);
+	enderror();
+	return n;
+}
+
+int
+syspipe(int *fd)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syspipe(fd);
+	enderror();
+	return n;
+}
+
+long
+syspread(int fd, void *buf, long n, vlong off)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syspread(fd, buf, n, off);
+	enderror();
+	return n;
+}
+
+long
+syspwrite(int fd, void *buf, long n, vlong off)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syspwrite(fd, buf, n, off);
+	enderror();
+	return n;
+}
+
+long
+sysread(int fd, void *buf, long n)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syspread(fd, buf, n, (uvlong) ~0);
+	enderror();
+	return n;
+}
+
+int
+sysremove(char *path)
+{
+	int n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysremove(path);
+	enderror();
+	return n;
+}
+
+vlong
+sysseek(int fd, vlong off, int whence)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	off = _sysseek(fd, off, whence);
+	enderror();
+	return off;
+}
+
+int
+sysstat(char *name, uchar *buf, int n)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysstat(name, buf, n);
+	enderror();
+	return n;
+}
+
+long
+syswrite(int fd, void *buf, long n)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syspwrite(fd, buf, n, (uvlong) ~0);
+	enderror();
+	return n;
+}
+
+int
+syswstat(char *name, uchar *buf, int n)
+{
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _syswstat(name, buf, n);
+	enderror();
+	return n;
+}
+
+void
+werrstr(char *f, ...)
+{
+	char buf[ERRMAX];
+	va_list arg;
+
+	va_start(arg, f);
+	vsnprint(buf, sizeof buf, f, arg);
+	va_end(arg);
+
+	if(up->nerrlab)
+		strecpy(up->errstr, up->errstr+ERRMAX, buf);
+	else
+		strecpy(up->syserrstr, up->syserrstr+ERRMAX, buf);
+}
+
+int
+errfmt(Fmt *fmt)
+{
+	if(up->nerrlab)
+		return fmtstrcpy(fmt, up->errstr);
+	else
+		return fmtstrcpy(fmt, up->syserrstr);
+}
+
+int
+errstr(char *buf, uint n)
+{
+	char tmp[ERRMAX];
+	char *p;
+
+	p = up->nerrlab ? up->errstr : up->syserrstr;
+	memmove(tmp, p, ERRMAX);
+	utfecpy(p, p+ERRMAX, buf);
+	utfecpy(buf, buf+n, tmp);
+	return strlen(buf);
+}
+
+int
+rerrstr(char *buf, uint n)
+{
+	char *p;
+
+	p = up->nerrlab ? up->errstr : up->syserrstr;
+	utfecpy(buf, buf+n, p);
+	return strlen(buf);
+}
+
+ulong
+_sysrendezvous(ulong arg0, ulong arg1)
+{
+	ulong tag, val;
+	Proc *p, **l;
+
+	tag = arg0;
+	l = &REND(up->rgrp, tag);
+	up->rendval = ~0UL;
+
+	lock(&up->rgrp->ref.lk);
+	for(p = *l; p; p = p->rendhash) {
+		if(p->rendtag == tag) {
+			*l = p->rendhash;
+			val = p->rendval;
+			p->rendval = arg1;
+
+			while(p->mach != 0)
+				;
+			procwakeup(p);
+			unlock(&up->rgrp->ref.lk);
+			return val;
+		}
+		l = &p->rendhash;
+	}
+
+	/* Going to sleep here */
+	up->rendtag = tag;
+	up->rendval = arg1;
+	up->rendhash = *l;
+	*l = up;
+	up->state = Rendezvous;
+	unlock(&up->rgrp->ref.lk);
+
+	procsleep();
+
+	return up->rendval;
+}
+
+ulong
+sysrendezvous(ulong tag, ulong val)
+{
+	ulong n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return -1;
+	}
+	n = _sysrendezvous(tag, val);
+	enderror();
+	return n;
+}
--- /dev/null
+++ b/kern/sysproc.c
@@ -1,0 +1,32 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+long
+sysexits(ulong *arg)
+{
+	char *status;
+	char *inval = "invalid exit string";
+	char buf[ERRMAX];
+
+	status = (char*)arg[0];
+	if(status){
+		if(waserror())
+			status = inval;
+		else{
+			validaddr((ulong)status, 1, 0);
+			if(vmemchr(status, 0, ERRMAX) == 0){
+				memmove(buf, status, ERRMAX);
+				buf[ERRMAX-1] = 0;
+				status = buf;
+			}
+		}
+		poperror();
+
+	}
+	pexit(status, 1);
+	return 0;		/* not reached */
+}
+
--- /dev/null
+++ b/kern/term.c
@@ -1,0 +1,204 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	<draw.h>
+#include	<memdraw.h>
+#include	"screen.h"
+
+#define	MINX	8
+#define	Backgnd		0xFF	/* white */
+
+		Memsubfont	*memdefont;
+		
+struct{
+	Point	pos;
+	int	bwid;
+}out;
+
+Lock	screenlock;
+
+Memimage *conscol;
+Memimage *back;
+extern Memimage *gscreen;
+
+static Rectangle flushr;
+static Rectangle window;
+static Point curpos;
+static int h, w;
+static void	termscreenputs(char*, int);
+
+Point ZP;
+
+static void
+screenflush(void)
+{
+	drawflushr(flushr);
+	flushr = Rect(10000, 10000, -10000, -10000);
+}
+
+static void
+addflush(Rectangle r)
+{
+	if(flushr.min.x >= flushr.max.x)
+		flushr = r;
+	else
+		combinerect(&flushr, r);
+}
+
+static void
+screenwin(void)
+{
+	Point p, q;
+	char *greet;
+	Memimage *grey;
+
+	back = memwhite;
+	conscol = memblack;
+	memfillcolor(gscreen, 0x444488FF);
+	
+	w = memdefont->info[' '].width;
+	h = memdefont->height;
+
+	window.min = addpt(gscreen->r.min, Pt(20,20));
+	window.max.x = window.min.x + Dx(gscreen->r)*3/4-40;
+	window.max.y = window.min.y + Dy(gscreen->r)*3/4-100;
+
+	memimagedraw(gscreen, window, memblack, ZP, memopaque, ZP, S);
+	window = insetrect(window, 4);
+	memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S);
+
+	/* a lot of work to get a grey color */
+	grey = allocmemimage(Rect(0,0,1,1), CMAP8);
+	grey->flags |= Frepl;
+	grey->clipr = gscreen->r;
+	memfillcolor(grey, 0xAAAAAAFF);
+	memimagedraw(gscreen, Rect(window.min.x, window.min.y,
+			window.max.x, window.min.y+h+5+6), grey, ZP, nil, ZP, S);
+	freememimage(grey);
+	window = insetrect(window, 5);
+
+	greet = " Plan 9 Console ";
+	p = addpt(window.min, Pt(10, 0));
+	q = memsubfontwidth(memdefont, greet);
+	memimagestring(gscreen, p, conscol, ZP, memdefont, greet);
+	window.min.y += h+6;
+	curpos = window.min;
+	window.max.y = window.min.y+((window.max.y-window.min.y)/h)*h;
+	flushmemscreen(gscreen->r);
+}
+
+void
+terminit(void)
+{
+	memdefont = getmemdefont();
+	out.pos.x = MINX;
+	out.pos.y = 0;
+	out.bwid = memdefont->info[' '].width;
+	screenwin();
+	screenputs = termscreenputs;
+}
+
+static void
+scroll(void)
+{
+	int o;
+	Point p;
+	Rectangle r;
+
+	o = 8*h;
+	r = Rpt(window.min, Pt(window.max.x, window.max.y-o));
+	p = Pt(window.min.x, window.min.y+o);
+	memimagedraw(gscreen, r, gscreen, p, nil, p, S);
+	r = Rpt(Pt(window.min.x, window.max.y-o), window.max);
+	memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
+	flushmemscreen(gscreen->r);
+	
+	curpos.y -= o;
+}
+
+static void
+screenputc(char *buf)
+{
+	Point p;
+	int w, pos;
+	Rectangle r;
+	static int *xp;
+	static int xbuf[256];
+
+	if(xp < xbuf || xp >= &xbuf[sizeof(xbuf)])
+		xp = xbuf;
+
+	switch(buf[0]) {
+	case '\n':
+		if(curpos.y+h >= window.max.y)
+			scroll();
+		curpos.y += h;
+		screenputc("\r");
+		break;
+	case '\r':
+		xp = xbuf;
+		curpos.x = window.min.x;
+		break;
+	case '\t':
+		p = memsubfontwidth(memdefont, " ");
+		w = p.x;
+		*xp++ = curpos.x;
+		pos = (curpos.x-window.min.x)/w;
+		pos = 8-(pos%8);
+		r = Rect(curpos.x, curpos.y, curpos.x+pos*w, curpos.y + h);
+		memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+		addflush(r);
+		curpos.x += pos*w;
+		break;
+	case '\b':
+		if(xp <= xbuf)
+			break;
+		xp--;
+		r = Rect(*xp, curpos.y, curpos.x, curpos.y + h);
+		memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+		addflush(r);
+		curpos.x = *xp;
+		break;
+	default:
+		p = memsubfontwidth(memdefont, buf);
+		w = p.x;
+
+		if(curpos.x >= window.max.x-w)
+			screenputc("\n");
+
+		*xp++ = curpos.x;
+		r = Rect(curpos.x, curpos.y, curpos.x+w, curpos.y + h);
+		memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+		memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf);
+		addflush(r);
+		curpos.x += w;
+	}
+}
+
+static void
+termscreenputs(char *s, int n)
+{
+	int i;
+	Rune r;
+	char buf[4];
+
+	lock(&screenlock);
+	while(n > 0){
+		i = chartorune(&r, s);
+		if(i == 0){
+			s++;
+			--n;
+			continue;
+		}
+		memmove(buf, s, i);
+		buf[i] = 0;
+		n -= i;
+		s += i;
+		screenputc(buf);
+	}
+	screenflush();
+	unlock(&screenlock);
+}
--- /dev/null
+++ b/kern/uart.c
@@ -1,0 +1,14 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#undef write
+void
+uartputs(char *s, int n)
+{
+	write(1, s, n); 
+}
+
+
--- /dev/null
+++ b/kern/waserror.c
@@ -1,0 +1,27 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Label*
+pwaserror(void)
+{
+	if(up->nerrlab == NERR)
+		panic("error stack overflow");
+	return &up->errlab[up->nerrlab++];
+}
+
+void
+nexterror(void)
+{
+	longjmp(up->errlab[--up->nerrlab].buf, 1);
+}
+
+void
+error(char *e)
+{
+	kstrcpy(up->errstr, e, ERRMAX);
+	setjmp(up->errlab[NERR-1].buf);
+	nexterror();
+}
--- /dev/null
+++ b/kern/winduhz.h
@@ -1,0 +1,18 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <fcntl.h>
+#include <io.h>
+#include <setjmp.h>
+#include <direct.h>
+#include <process.h>
+#include <time.h>
+#include <assert.h>
+#include <stdarg.h>
+
+/* disable various silly warnings */
+#pragma warning( disable : 4245 4305 4244 4102 4761 4090 4028 4024)
+
+typedef __int64		p9_vlong;
+typedef	unsigned __int64 p9_uvlong;
--- /dev/null
+++ b/kern/x
@@ -1,0 +1,601 @@
+#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;
+	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);
+
+static char*
+lastelem(Chan *c)
+{
+	char *s, *t;
+
+	s = c2name(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;
+
+	c->aux = uif;
+	c->dev = devno++;
+
+	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;
+
+	c->qid = fsqid(path, &stbuf);
+
+	return 1;
+}
+
+static Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	int i;
+	Walkqid *wq;
+
+	if(nc != nil)
+		panic("fswalk: nc != nil");
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	nc = devclone(c);
+	fsclone(c, nc);
+	wq->clone = nc;
+	for(i=0; i<nname; i++){
+		if(fswalk1(nc, name[i]) == 0)
+			break;
+		wq->qid[i] = nc->qid;
+	}
+	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.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;
+
+	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;
+
+	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));
+}
+
+void
+fswstat(Chan *c, uchar *buf, int n)
+{
+	Dir d;
+	struct stat stbuf;
+	char old[MAXPATH], new[MAXPATH], dir[MAXPATH];
+	char strs[MAXPATH*3];
+	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->parent, 0, dir);
+		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;
+	}
+*/
+}
+
+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, c2name(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;
+	int n;
+
+	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) {
+			fprint(2, "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;
+	}
+	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/latin1.c
@@ -1,0 +1,177 @@
+#include "u.h"
+#include "libc.h"
+
+/*
+ * The code makes two assumptions: strlen(ld) is 1 or 2; latintab[i].ld can be a
+ * prefix of latintab[j].ld only when j<i.
+ */
+struct cvlist
+{
+	char	*ld;		/* must be seen before using this conversion */
+	char	*si;		/* options for last input characters */
+	Rune	so[50];		/* the corresponding Rune for each si entry */
+} latintab[] = {
+	" ", " i",	{ 0x2423, 0x0131 },
+	"w", "kqrbnp", { 0x2654, 0x2655, 0x2656, 0x2657, 0x2658, 0x2659, },
+	"x", "O", { 0x2297, },
+	"f", "a", { 0x2200, },
+	"=", "V:=O<>", { 0x21D2, 0x2255, 0x2261, 0x229C, 0x22DC, 0x22DD, },
+	"V", "=", { 0x21D0, },
+	"7", "8", { 0x215E, },
+	"5", "68", { 0x215A, 0x215D, },
+	"4", "5", { 0x2158, },
+	"R", "R", { 0x211D, },
+	"Q", "Q", { 0x211A, },
+	"P", "P", { 0x2119, },
+	"C", "CAU", { 0x2102, 0x22C2, 0x22C3, },
+	"e", "nmsl", { 0x2013, 0x2014, 0x2205, 0x22EF, },
+	"b", "u0123456789+-=()kqrbnp", { 0x2022, 0x2080, 0x2081, 0x2082, 0x2083, 0x2084, 0x2085, 0x2086, 0x2087, 0x2088, 0x2089, 0x208A, 0x208B, 0x208C, 0x208D, 0x208E, 0x265A, 0x265B, 0x265C, 0x265D, 0x265E, 0x265F, },
+	"@e", "h", { 0x44D, },
+	"@\'", "\'", { 0x44A, },
+	"@s", "hc", { 0x448, 0x449, },
+	"@c", "h", { 0x447, },
+	"@t", "s", { 0x446, },
+	"@k", "h", { 0x445, },
+	"@z", "h", { 0x436, },
+	"@y", "euao", { 0x435, 0x44E, 0x44F, 0x451, },
+	"@E", "Hh", { 0x42D, 0x42D, },
+	"@S", "HhCc", { 0x428, 0x428, 0x429, 0x429, },
+	"@C", "Hh", { 0x427, 0x427, },
+	"@T", "Ss", { 0x426, 0x426, },
+	"@K", "Hh", { 0x425, 0x425, },
+	"@Z", "Hh", { 0x416, 0x416, },
+	"@@", "EZKSTYezksty\'", { 0x415, 0x417, 0x41A, 0x421, 0x422, 0x42B, 0x435, 0x437, 0x43A, 0x441, 0x442, 0x44B, 0x44C, },
+	"@Y", "OoEeUuAa", { 0x401, 0x401, 0x415, 0x415, 0x42E, 0x42E, 0x42F, 0x42F, },
+	"@", "ABVGDIJLMNOPRUFXabvgdijlmnoprufx", { 0x410, 0x411, 0x412, 0x413, 0x414, 0x418, 0x419, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F, 0x420, 0x423, 0x424, 0x425, 0x430, 0x431, 0x432, 0x433, 0x434, 0x438, 0x439, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F, 0x440, 0x443, 0x444, 0x445, },
+	"*", "ABGDEZYHIKLMNCOPRSTUFXQWabgdezyhiklmncoprstufxqw*", { 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39A, 0x39B, 0x39C, 0x39D, 0x39E, 0x39F, 0x3A0, 0x3A1, 0x3A3, 0x3A4, 0x3A5, 0x3A6, 0x3A7, 0x3A8, 0x3A9, 0x3B1, 0x3B2, 0x3B3, 0x3B4, 0x3B5, 0x3B6, 0x3B7, 0x3B8, 0x3B9, 0x3BA, 0x3BB, 0x3BC, 0x3BD, 0x3BE, 0x3BF, 0x3C0, 0x3C1, 0x3C3, 0x3C4, 0x3C5, 0x3C6, 0x3C7, 0x3C8, 0x3C9, 0x2217, },
+	"G", "-", { 0x1E4, },
+	"N", "JjN", { 0x1CA, 0x1CB, 0x2115, },
+	"2", "-35", { 0x1BB, 0x2154, 0x2156, },
+	"z", "-", { 0x1B6, },
+	"Z", "-Z", { 0x1B5, 0x2124, },
+	"Y", "R", { 0x1A6, },
+	"h", "v-", { 0x195, 0x210F, },
+	"$*", "hfk", { 0x3D1, 0x3D5, 0x3F0, },
+	"$", "fVavgHILlpRBeEFMo", { 0x192, 0x1B2, 0x251, 0x28B, 0x210A, 0x210B, 0x2110, 0x2112, 0x2113, 0x2118, 0x211B, 0x212C, 0x212F, 0x2130, 0x2131, 0x2133, 0x2134, },
+	"t", "-smefu", { 0x167, 0x3C2, 0x2122, 0x2203, 0x2234, 0x22A2, },
+	"T", "-u", { 0x166, 0x22A8, },
+	"L", "-Jj&|", { 0x141, 0x1C7, 0x1C8, 0x22C0, 0x22C1, },
+	"i", "j-fsbp", { 0x133, 0x268, 0x221E, 0x222B, 0x2286, 0x2287, },
+	"I", "J-", { 0x132, 0x197, },
+	"H", "-H", { 0x126, 0x210D, },
+	"v\"", "Uu", { 0x1D9, 0x1DA, },
+	"v", "CcDdEeLlNnRrSsTtZzAaIiOoUuGgKkj", { 0x10C, 0x10D, 0x10E, 0x10F, 0x11A, 0x11B, 0x13D, 0x13E, 0x147, 0x148, 0x158, 0x159, 0x160, 0x161, 0x164, 0x165, 0x17D, 0x17E, 0x1CD, 0x1CE, 0x1CF, 0x1D0, 0x1D1, 0x1D2, 0x1D3, 0x1D4, 0x1E6, 0x1E7, 0x1E8, 0x1E9, 0x1F0, },
+	"u", "AEeGgIiOoUu-a", { 0x102, 0x114, 0x115, 0x11E, 0x11F, 0x12C, 0x12D, 0x14E, 0x14F, 0x16C, 0x16D, 0x289, 0x2191, },
+	":", "-=()", { 0xF7, 0x2254, 0x2639, 0x263A, },
+	"a", "ebn", { 0xE6, 0x2194, 0x2220, },
+	"/", "Oo", { 0xD8, 0xF8, },
+	"Dv", "Zz", { 0x1C4, 0x1C5, },
+	"D", "-e", { 0xD0, 0x2206, },
+	"A", "E", { 0xC6, },
+	"o", "AaeUuiO", { 0xC5, 0xE5, 0x153, 0x16E, 0x16F, 0x1A3, 0x229A, },
+	"~!", "=", { 0x2246, },
+	"~", "ANOanoIiUu-=~", { 0xC3, 0xD1, 0xD5, 0xE3, 0xF1, 0xF5, 0x128, 0x129, 0x168, 0x169, 0x2243, 0x2245, 0x2248, },
+	"^", "AEIOUaeiouCcGgHhJjSsWwYy", { 0xC2, 0xCA, 0xCE, 0xD4, 0xDB, 0xE2, 0xEA, 0xEE, 0xF4, 0xFB, 0x108, 0x109, 0x11C, 0x11D, 0x124, 0x125, 0x134, 0x135, 0x15C, 0x15D, 0x174, 0x175, 0x176, 0x177, },
+	"`\"", "Uu", { 0x1DB, 0x1DC, },
+	"`", "AEIOUaeiou", { 0xC0, 0xC8, 0xCC, 0xD2, 0xD9, 0xE0, 0xE8, 0xEC, 0xF2, 0xF9, },
+	"?", "?!", { 0xBF, 0x203D, },
+	"3", "458", { 0xBE, 0x2157, 0x215C, },
+	"1", "423568", { 0xBC, 0xBD, 0x2153, 0x2155, 0x2159, 0x215B, },
+	">!", "=~", { 0x2269, 0x22E7, },
+	">", ">=~<", { 0xBB, 0x2265, 0x2273, 0x2277, },
+	",", ",CcAaEeGgIiKkLlNnRrSsTtUuOo", { 0xB8, 0xC7, 0xE7, 0x104, 0x105, 0x118, 0x119, 0x122, 0x123, 0x12E, 0x12F, 0x136, 0x137, 0x13B, 0x13C, 0x145, 0x146, 0x156, 0x157, 0x15E, 0x15F, 0x162, 0x163, 0x172, 0x173, 0x1EA, 0x1EB, },
+	".", ".CcEeGgILlZzO", { 0xB7, 0x10A, 0x10B, 0x116, 0x117, 0x120, 0x121, 0x130, 0x13F, 0x140, 0x17B, 0x17C, 0x2299, },
+	"p", "gOdrt", { 0xB6, 0x2117, 0x2202, 0x220F, 0x221D, },
+	"m", "iuo", { 0xB5, 0xD7, 0x2208, },
+	"\'\"", "Uu", { 0x1D7, 0x1D8, },
+	"\'", "\'AEIOUYaeiouyCcgLlNnRrSsZz", { 0xB4, 0xC1, 0xC9, 0xCD, 0xD3, 0xDA, 0xDD, 0xE1, 0xE9, 0xED, 0xF3, 0xFA, 0xFD, 0x106, 0x107, 0x123, 0x139, 0x13A, 0x143, 0x144, 0x154, 0x155, 0x15A, 0x15B, 0x179, 0x17A, },
+	"+", "-O", { 0xB1, 0x2295, },
+	"dv", "z", { 0x1C6, },
+	"d", "e-zgda", { 0xB0, 0xF0, 0x2A3, 0x2020, 0x2021, 0x2193, },
+	"_,", "Oo", { 0x1EC, 0x1ED, },
+	"_.", "Aa", { 0x1E0, 0x1E1, },
+	"_\"", "UuAa", { 0x1D5, 0x1D6, 0x1DE, 0x1DF, },
+	"_", "_AaEeIiOoUu", { 0xAF, 0x100, 0x101, 0x112, 0x113, 0x12A, 0x12B, 0x14C, 0x14D, 0x16A, 0x16B, },
+	"r", "O\'\"", { 0xAE, 0x2019, 0x201D, },
+	"-*", "l", { 0x19B, },
+	"-", "-Dd:HLlTtbIZz2Ggiuh>+~O", { 0xAD, 0xD0, 0xF0, 0xF7, 0x126, 0x141, 0x142, 0x166, 0x167, 0x180, 0x197, 0x1B5, 0x1B6, 0x1BB, 0x1E4, 0x1E5, 0x268, 0x289, 0x210F, 0x2192, 0x2213, 0x2242, 0x2296, },
+	"n", "oj", { 0xAC, 0x1CC, },
+	"<!", "=~", { 0x2268, 0x22E6, },
+	"<", "<-=~>", { 0xAB, 0x2190, 0x2264, 0x2272, 0x2276, },
+	"s", "a231os0456789+-=()nturbp", { 0xAA, 0xB2, 0xB3, 0xB9, 0xBA, 0xDF, 0x2070, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, 0x207A, 0x207B, 0x207C, 0x207D, 0x207E, 0x207F, 0x220D, 0x2211, 0x221A, 0x2282, 0x2283, },
+	"O", "crEIp+-x/.o*=", { 0xA9, 0xAE, 0x152, 0x1A2, 0x2117, 0x2295, 0x2296, 0x2297, 0x2298, 0x2299, 0x229A, 0x229B, 0x229C, },
+	"\"*", "IUiu", { 0x3AA, 0x3AB, 0x3CA, 0x3CB, },
+	"\"", "\"AEIOUaeiouyY", { 0xA8, 0xC4, 0xCB, 0xCF, 0xD6, 0xDC, 0xE4, 0xEB, 0xEF, 0xF6, 0xFC, 0xFF, 0x178, },
+	"S", "S", { 0xA7, },
+	"|", "|Pp", { 0xA6, 0xDE, 0xFE, },
+	"y", "$", { 0xA5, },
+	"g", "$-r", { 0xA4, 0x1E5, 0x2207, },
+	"l", "$-j\'\"&|z", { 0xA3, 0x142, 0x1C9, 0x2018, 0x201C, 0x2227, 0x2228, 0x22C4, },
+	"c", "$Oaug", { 0xA2, 0xA9, 0x2229, 0x222A, 0x2245, },
+	"!~", "-=~", { 0x2244, 0x2247, 0x2249, },
+	"!", "!?m=<>bp", { 0xA1, 0x203D, 0x2209, 0x2260, 0x226E, 0x226F, 0x2284, 0x2285, },
+	0, 0, { 0, }
+};
+
+/*
+ * Given 5 characters k[0]..k[4], find the rune or return -1 for failure.
+ */
+long
+unicode(Rune *k)
+{
+	long i, c;
+
+	k++;	/* skip 'X' */
+	c = 0;
+	for(i=0; i<4; i++,k++){
+		c <<= 4;
+		if('0'<=*k && *k<='9')
+			c += *k-'0';
+		else if('a'<=*k && *k<='f')
+			c += 10 + *k-'a';
+		else if('A'<=*k && *k<='F')
+			c += 10 + *k-'A';
+		else
+			return -1;
+	}
+	return c;
+}
+
+/*
+ * Given n characters k[0]..k[n-1], find the corresponding rune or return -1 for
+ * failure, or something < -1 if n is too small.  In the latter case, the result
+ * is minus the required n.
+ */
+long
+latin1(Rune *k, int n)
+{
+	struct cvlist *l;
+	int c;
+	char* p;
+
+	if(k[0] == 'X'){
+		if(n>=5)
+			return unicode(k);
+		else
+			return -5;
+	}
+	
+	for(l=latintab; l->ld!=0; l++)
+		if(k[0] == l->ld[0]){
+			if(n == 1)
+				return -2;
+			if(l->ld[1] == 0)
+				c = k[1];
+			else if(l->ld[1] != k[1])
+				continue;
+			else if(n == 2)
+				return -3;
+			else
+				c = k[2];
+			for(p=l->si; *p!=0; p++)
+				if(*p == c)
+					return l->so[p - l->si];
+			return -1;
+		}
+	return -1;
+}
--- /dev/null
+++ b/libauthsrv/Makefile
@@ -1,0 +1,28 @@
+LIB=libauthsrv.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	_asgetticket.$O\
+	_asrdresp.$O\
+	convA2M.$O\
+	convM2A.$O\
+	convM2PR.$O\
+	convM2T.$O\
+	convM2TR.$O\
+	convPR2M.$O\
+	convT2M.$O\
+	convTR2M.$O\
+	nvcsum.$O\
+	opasstokey.$O\
+	passtokey.$O\
+	readnvram.$O\
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libauthsrv/authdial.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include <ndb.h>
+
+int
+authdial(char *netroot, char *dom)
+{
+	char server[Ndbvlen];
+	Ndbtuple *nt;
+
+	
+	if(dom != nil){
+		/* look up an auth server in an authentication domain */
+		nt = csgetval(netroot, "authdom", dom, "auth", server);
+
+		/* if that didn't work, just try the IP domain */
+		if(nt == nil)
+			nt = csgetval(netroot, "dom", dom, "auth", server);
+		if(nt == nil){
+			werrstr("no auth server found for %s", dom);
+			return -1;
+		}
+		ndbfree(nt);
+		return dial(netmkaddr(server, netroot, "ticket"), 0, 0, 0);
+	} else {
+		/* look for one relative to my machine */
+		return dial(netmkaddr("$auth", netroot, "ticket"), 0, 0, 0);
+	}
+}
--- /dev/null
+++ b/libauthsrv/convA2M.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		*p++ = f->x
+#define	SHORT(x)	p[0] = f->x; p[1] = f->x>>8; p += 2
+#define	VLONG(q)	p[0] = (q); p[1] = (q)>>8; p[2] = (q)>>16; p[3] = (q)>>24; p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(p, f->x, n); p += n
+
+int
+convA2M(Authenticator *f, char *ap, char *key)
+{
+	int n;
+	uchar *p;
+
+	p = (uchar*)ap;
+	CHAR(num);
+	STRING(chal, CHALLEN);
+	LONG(id);
+	n = p - (uchar*)ap;
+	if(key)
+		encrypt(key, ap, n);
+	return n;
+}
--- /dev/null
+++ b/libauthsrv/convM2A.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		f->x = *p++
+#define	SHORT(x)	f->x = (p[0] | (p[1]<<8)); p += 2
+#define	VLONG(q)	q = (p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24)); p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(f->x, p, n); p += n
+
+void
+convM2A(char *ap, Authenticator *f, char *key)
+{
+	uchar *p;
+
+	if(key)
+		decrypt(key, ap, AUTHENTLEN);
+	p = (uchar*)ap;
+	CHAR(num);
+	STRING(chal, CHALLEN);
+	LONG(id);
+	USED(p);
+}
--- /dev/null
+++ b/libauthsrv/convM2PR.c
@@ -1,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		f->x = *p++
+#define	SHORT(x)	f->x = (p[0] | (p[1]<<8)); p += 2
+#define	VLONG(q)	q = (p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24)); p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(f->x, p, n); p += n
+
+void
+convM2PR(char *ap, Passwordreq *f, char *key)
+{
+	uchar *p;
+
+	p = (uchar*)ap;
+	if(key)
+		decrypt(key, ap, PASSREQLEN);
+	CHAR(num);
+	STRING(old, ANAMELEN);
+	f->old[ANAMELEN-1] = 0;
+	STRING(new, ANAMELEN);
+	f->new[ANAMELEN-1] = 0;
+	CHAR(changesecret);
+	STRING(secret, SECRETLEN);
+	f->secret[SECRETLEN-1] = 0;
+	USED(p);
+}
--- /dev/null
+++ b/libauthsrv/convM2T.c
@@ -1,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		f->x = *p++
+#define	SHORT(x)	f->x = (p[0] | (p[1]<<8)); p += 2
+#define	VLONG(q)	q = (p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24)); p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(f->x, p, n); p += n
+
+void
+convM2T(char *ap, Ticket *f, char *key)
+{
+	uchar *p;
+
+	if(key)
+		decrypt(key, ap, TICKETLEN);
+	p = (uchar*)ap;
+	CHAR(num);
+	STRING(chal, CHALLEN);
+	STRING(cuid, ANAMELEN);
+	f->cuid[ANAMELEN-1] = 0;
+	STRING(suid, ANAMELEN);
+	f->suid[ANAMELEN-1] = 0;
+	STRING(key, DESKEYLEN);
+	USED(p);
+}
+
--- /dev/null
+++ b/libauthsrv/convM2TR.c
@@ -1,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		f->x = *p++
+#define	SHORT(x)	f->x = (p[0] | (p[1]<<8)); p += 2
+#define	VLONG(q)	q = (p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24)); p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(f->x, p, n); p += n
+
+void
+convM2TR(char *ap, Ticketreq *f)
+{
+	uchar *p;
+
+	p = (uchar*)ap;
+	CHAR(type);
+	STRING(authid, ANAMELEN);
+	f->authid[ANAMELEN-1] = 0;
+	STRING(authdom, DOMLEN);
+	f->authdom[DOMLEN-1] = 0;
+	STRING(chal, CHALLEN);
+	STRING(hostid, ANAMELEN);
+	f->hostid[ANAMELEN-1] = 0;
+	STRING(uid, ANAMELEN);
+	f->uid[ANAMELEN-1] = 0;
+	USED(p);
+}
--- /dev/null
+++ b/libauthsrv/convPR2M.c
@@ -1,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		*p++ = f->x
+#define	SHORT(x)	p[0] = f->x; p[1] = f->x>>8; p += 2
+#define	VLONG(q)	p[0] = (q); p[1] = (q)>>8; p[2] = (q)>>16; p[3] = (q)>>24; p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(p, f->x, n); p += n
+
+int
+convPR2M(Passwordreq *f, char *ap, char *key)
+{
+	int n;
+	uchar *p;
+
+	p = (uchar*)ap;
+	CHAR(num);
+	STRING(old, ANAMELEN);
+	STRING(new, ANAMELEN);
+	CHAR(changesecret);
+	STRING(secret, SECRETLEN);
+	n = p - (uchar*)ap;
+	if(key)
+		encrypt(key, ap, n);
+	return n;
+}
+
--- /dev/null
+++ b/libauthsrv/convT2M.c
@@ -1,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		*p++ = f->x
+#define	SHORT(x)	p[0] = f->x; p[1] = f->x>>8; p += 2
+#define	VLONG(q)	p[0] = (q); p[1] = (q)>>8; p[2] = (q)>>16; p[3] = (q)>>24; p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(p, f->x, n); p += n
+
+int
+convT2M(Ticket *f, char *ap, char *key)
+{
+	int n;
+	uchar *p;
+
+	p = (uchar*)ap;
+	CHAR(num);
+	STRING(chal, CHALLEN);
+	STRING(cuid, ANAMELEN);
+	STRING(suid, ANAMELEN);
+	STRING(key, DESKEYLEN);
+	n = p - (uchar*)ap;
+	if(key)
+		encrypt(key, ap, n);
+	return n;
+}
--- /dev/null
+++ b/libauthsrv/convTR2M.c
@@ -1,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+#define	CHAR(x)		*p++ = f->x
+#define	SHORT(x)	p[0] = f->x; p[1] = f->x>>8; p += 2
+#define	VLONG(q)	p[0] = (q); p[1] = (q)>>8; p[2] = (q)>>16; p[3] = (q)>>24; p += 4
+#define	LONG(x)		VLONG(f->x)
+#define	STRING(x,n)	memmove(p, f->x, n); p += n
+
+int
+convTR2M(Ticketreq *f, char *ap)
+{
+	int n;
+	uchar *p;
+
+	p = (uchar*)ap;
+	CHAR(type);
+	STRING(authid, 28);	/* BUG */
+	STRING(authdom, DOMLEN);
+	STRING(chal, CHALLEN);
+	STRING(hostid, 28);	/* BUG */
+	STRING(uid, 28);	/* BUG */
+	n = p - (uchar*)ap;
+	return n;
+}
+
--- /dev/null
+++ b/libauthsrv/mkfile
@@ -1,0 +1,25 @@
+<$DSRC/mkfile-$CONF
+TARG=libauthsrv.$L
+
+OFILES=\
+	_asgetticket.$O\
+	_asrdresp.$O\
+	convA2M.$O\
+	convM2A.$O\
+	convM2PR.$O\
+	convM2T.$O\
+	convM2TR.$O\
+	convPR2M.$O\
+	convT2M.$O\
+	convTR2M.$O\
+	nvcsum.$O\
+	opasstokey.$O\
+	passtokey.$O\
+	readnvram.$O\
+
+HFILE=\
+
+
+TARGOBJ=${OFILES:%=$TARG(%)}
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/libauthsrv/nvcsum.c
@@ -1,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+uchar
+nvcsum(void *vmem, int n)
+{
+	uchar *mem, sum;
+	int i;
+
+	sum = 9;
+	mem = vmem;
+	for(i = 0; i < n; i++)
+		sum += mem[i];
+	return sum;
+}
--- /dev/null
+++ b/libauthsrv/opasstokey.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+opasstokey(char *key, char *p)
+{
+	uchar t[10];
+	int c, n;
+
+	n = strlen(p);
+	memset(t, ' ', sizeof t);
+	if(n < 5)
+		return 0;
+	if(n > 10)
+		n = 10;
+	strncpy((char*)t, p, n);
+	if(n >= 9){
+		c = p[8] & 0xf;
+		if(n == 10)
+			c += p[9] << 4;
+		for(n = 0; n < 8; n++)
+			if(c & (1 << n))
+				t[n] -= ' ';
+	}
+	for(n = 0; n < 7; n++)
+		key[n] = (t[n] >> n) + (t[n+1] << (8 - (n+1)));
+	return 1;
+}
--- /dev/null
+++ b/libauthsrv/passtokey.c
@@ -1,0 +1,33 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+passtokey(char *key, char *p)
+{
+	uchar buf[ANAMELEN], *t;
+	int i, n;
+
+	n = strlen(p);
+	if(n >= ANAMELEN)
+		n = ANAMELEN-1;
+	memset(buf, ' ', 8);
+	t = buf;
+	strncpy((char*)t, p, n);
+	t[n] = 0;
+	memset(key, 0, DESKEYLEN);
+	for(;;){
+		for(i = 0; i < DESKEYLEN; i++)
+			key[i] = (t[i] >> i) + (t[i+1] << (8 - (i+1)));
+		if(n <= 8)
+			return 1;
+		n -= 8;
+		t += 8;
+		if(n < 8){
+			t -= 8 - n;
+			n = 8;
+		}
+		encrypt(key, t, 8);
+	}
+	return 1;	/* not reached */
+}
--- /dev/null
+++ b/libauthsrv/readnvram.c
@@ -1,0 +1,368 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+static long	finddosfile(int, char*);
+
+static int
+check(void *x, int len, uchar sum, char *msg)
+{
+	if(nvcsum(x, len) == sum)
+		return 0;
+	memset(x, 0, len);
+	fprint(2, "%s\n", msg);
+	return 1;
+}
+
+/*
+ *  get key info out of nvram.  since there isn't room in the PC's nvram use
+ *  a disk partition there.
+ */
+static struct {
+	char *cputype;
+	char *file;
+	int off;
+	int len;
+} nvtab[] = {
+	"sparc", "#r/nvram", 1024+850, sizeof(Nvrsafe),
+	"pc", "#S/sdC0/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sdC0/9fat", -1, sizeof(Nvrsafe),
+	"pc", "#S/sd00/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sd00/9fat", -1, sizeof(Nvrsafe),
+	"pc", "#S/sd01/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sd01/9fat", -1, sizeof(Nvrsafe),
+	"pc", "#f/fd0disk", -1, 512,	/* 512: #f requires whole sector reads */
+	"pc", "#f/fd1disk", -1, 512,
+	"mips", "#r/nvram", 1024+900, sizeof(Nvrsafe),
+	"power", "#F/flash/flash0", 0x300000, sizeof(Nvrsafe),
+	"power", "#r/nvram", 4352, sizeof(Nvrsafe),	/* OK for MTX-604e */
+	"debug", "/tmp/nvram", 0, sizeof(Nvrsafe),
+};
+
+static char*
+readcons(char *prompt, char *def, int raw, char *buf, int nbuf)
+{
+	int fdin, fdout, ctl, n, m;
+	char line[10];
+
+	fdin = open("/dev/cons", OREAD);
+	if(fdin < 0)
+		fdin = 0;
+	fdout = open("/dev/cons", OWRITE);
+	if(fdout < 0)
+		fdout = 1;
+	if(def != nil)
+		fprint(fdout, "%s[%s]: ", prompt, def);
+	else
+		fprint(fdout, "%s: ", prompt);
+	if(raw){
+		ctl = open("/dev/consctl", OWRITE);
+		if(ctl >= 0)
+			write(ctl, "rawon", 5);
+	} else
+		ctl = -1;
+
+	m = 0;
+	for(;;){
+		n = read(fdin, line, 1);
+		if(n == 0){
+			close(ctl);
+			werrstr("readcons: EOF");
+			return nil;
+		}
+		if(n < 0){
+			close(ctl);
+			werrstr("can't read cons");
+			return nil;
+		}
+		if(line[0] == 0x7f)
+			exits(0);
+		if(n == 0 || line[0] == '\n' || line[0] == '\r'){
+			if(raw){
+				write(ctl, "rawoff", 6);
+				write(fdout, "\n", 1);
+				close(ctl);
+			}
+			buf[m] = '\0';
+			if(buf[0]=='\0' && def)
+				strcpy(buf, def);
+			return buf;
+		}
+		if(line[0] == '\b'){
+			if(m > 0)
+				m--;
+		}else if(line[0] == 0x15){	/* ^U: line kill */
+			m = 0;
+			if(def != nil)
+				fprint(fdout, "%s[%s]: ", prompt, def);
+			else
+				fprint(fdout, "%s: ", prompt);
+		}else{
+			if(m >= nbuf-1){
+				fprint(fdout, "line too long\n");
+				m = 0;
+				if(def != nil)
+					fprint(fdout, "%s[%s]: ", prompt, def);
+				else
+					fprint(fdout, "%s: ", prompt);
+			}else
+				buf[m++] = line[0];
+		}
+	}
+	return buf;	/* how does this happen */
+}
+
+
+/*
+ *  get key info out of nvram.  since there isn't room in the PC's nvram use
+ *  a disk partition there.
+ */
+int
+readnvram(Nvrsafe *safep, int flag)
+{
+	char buf[1024], in[128], *cputype, *nvrfile, *nvrlen, *nvroff, *v[2];
+	int fd, err, i, safeoff, safelen;
+	Nvrsafe *safe;
+
+	err = 0;
+	memset(safep, 0, sizeof(*safep));
+
+	nvrfile = getenv("nvram");
+	cputype = getenv("cputype");
+	if(cputype == nil)
+		cputype = "mips";
+	if(strcmp(cputype, "386")==0 || strcmp(cputype, "alpha")==0)
+		cputype = "pc";
+
+	safe = (Nvrsafe*)buf;
+
+	fd = -1;
+	safeoff = -1;
+	safelen = -1;
+	if(nvrfile != nil){
+		/* accept device and device!file */
+		i = gettokens(nvrfile, v, nelem(v), "!");
+		fd = open(v[0], ORDWR);
+		safelen = sizeof(Nvrsafe);
+		if(strstr(v[0], "/9fat") == nil)
+			safeoff = 0;
+		nvrlen = getenv("nvrlen");
+		if(nvrlen != nil)
+			safelen = atoi(nvrlen);
+		nvroff = getenv("nvroff");
+		if(nvroff != nil){
+			if(strcmp(nvroff, "dos") == 0)
+				safeoff = -1;
+			else
+				safeoff = atoi(nvroff);
+		}
+		if(safeoff < 0 && fd >= 0){
+			safelen = 512;
+			safeoff = finddosfile(fd, i == 2 ? v[1] : "plan9.nvr");
+			if(safeoff < 0){
+				close(fd);
+				fd = -1;
+			}
+		}
+		free(nvrfile);
+		if(nvrlen != nil)
+			free(nvrlen);
+		if(nvroff != nil)
+			free(nvroff);
+	}else{
+		for(i=0; i<nelem(nvtab); i++){
+			if(strcmp(cputype, nvtab[i].cputype) != 0)
+				continue;
+			if((fd = open(nvtab[i].file, ORDWR)) < 0)
+				continue;
+			safeoff = nvtab[i].off;
+			safelen = nvtab[i].len;
+			if(safeoff == -1){
+				safeoff = finddosfile(fd, "plan9.nvr");
+				if(safeoff < 0){
+					close(fd);
+					fd = -1;
+					continue;
+				}
+			}
+			break;
+		}
+	}
+
+	if(fd < 0
+	|| seek(fd, safeoff, 0) < 0
+	|| read(fd, buf, safelen) != safelen){
+		err = 1;
+		if(flag&(NVwrite|NVwriteonerr))
+			fprint(2, "can't read nvram: %r\n");
+		memset(safep, 0, sizeof(*safep));
+		safe = safep;
+	}else{
+		*safep = *safe;
+		safe = safep;
+
+		err |= check(safe->machkey, DESKEYLEN, safe->machsum, "bad nvram key");
+//		err |= check(safe->config, CONFIGLEN, safe->configsum, "bad secstore key");
+		err |= check(safe->authid, ANAMELEN, safe->authidsum, "bad authentication id");
+		err |= check(safe->authdom, DOMLEN, safe->authdomsum, "bad authentication domain");
+	}
+
+	if((flag&NVwrite) || (err && (flag&NVwriteonerr))){
+		readcons("authid", nil, 0, safe->authid, sizeof(safe->authid));
+		readcons("authdom", nil, 0, safe->authdom, sizeof(safe->authdom));
+		readcons("secstore key", nil, 1, safe->config, sizeof(safe->config));
+		for(;;){
+			if(readcons("password", nil, 1, in, sizeof in) == nil)
+				goto Out;
+			if(passtokey(safe->machkey, in))
+				break;
+		}
+		safe->machsum = nvcsum(safe->machkey, DESKEYLEN);
+		safe->configsum = nvcsum(safe->config, CONFIGLEN);
+		safe->authidsum = nvcsum(safe->authid, sizeof(safe->authid));
+		safe->authdomsum = nvcsum(safe->authdom, sizeof(safe->authdom));
+		*(Nvrsafe*)buf = *safe;
+		if(seek(fd, safeoff, 0) < 0
+		|| write(fd, buf, safelen) != safelen){
+			fprint(2, "can't write key to nvram: %r\n");
+			err = 1;
+		}else
+			err = 0;
+	}
+Out:
+	close(fd);
+	return err ? -1 : 0;
+}
+
+typedef struct Dosboot	Dosboot;
+struct Dosboot{
+	uchar	magic[3];	/* really an xx86 JMP instruction */
+	uchar	version[8];
+	uchar	sectsize[2];
+	uchar	clustsize;
+	uchar	nresrv[2];
+	uchar	nfats;
+	uchar	rootsize[2];
+	uchar	volsize[2];
+	uchar	mediadesc;
+	uchar	fatsize[2];
+	uchar	trksize[2];
+	uchar	nheads[2];
+	uchar	nhidden[4];
+	uchar	bigvolsize[4];
+	uchar	driveno;
+	uchar	reserved0;
+	uchar	bootsig;
+	uchar	volid[4];
+	uchar	label[11];
+	uchar	type[8];
+};
+#define	GETSHORT(p) (((p)[1]<<8) | (p)[0])
+#define	GETLONG(p) ((GETSHORT((p)+2) << 16) | GETSHORT((p)))
+
+typedef struct Dosdir	Dosdir;
+struct Dosdir
+{
+	char	name[8];
+	char	ext[3];
+	uchar	attr;
+	uchar	reserved[10];
+	uchar	time[2];
+	uchar	date[2];
+	uchar	start[2];
+	uchar	length[4];
+};
+
+static char*
+dosparse(char *from, char *to, int len)
+{
+	char c;
+
+	memset(to, ' ', len);
+	if(from == 0)
+		return 0;
+	while(len-- > 0){
+		c = *from++;
+		if(c == '.')
+			return from;
+		if(c == 0)
+			break;
+		if(c >= 'a' && c <= 'z')
+			*to++ = c + 'A' - 'a';
+		else
+			*to++ = c;
+	}
+	return 0;
+}
+
+/*
+ *  return offset of first file block
+ *
+ *  This is a very simplistic dos file system.  It only
+ *  works on floppies, only looks in the root, and only
+ *  returns a pointer to the first block of a file.
+ *
+ *  This exists for cpu servers that have no hard disk
+ *  or nvram to store the key on.
+ *
+ *  Please don't make this any smarter: it stays resident
+ *  and I'ld prefer not to waste the space on something that
+ *  runs only at boottime -- presotto.
+ */
+static long
+finddosfile(int fd, char *file)
+{
+	uchar secbuf[512];
+	char name[8];
+	char ext[3];
+	Dosboot	*b;
+	Dosdir *root, *dp;
+	int nroot, sectsize, rootoff, rootsects, n;
+
+	/* dos'ize file name */
+	file = dosparse(file, name, 8);
+	dosparse(file, ext, 3);
+
+	/* read boot block, check for sanity */
+	b = (Dosboot*)secbuf;
+	if(read(fd, secbuf, sizeof(secbuf)) != sizeof(secbuf))
+		return -1;
+	if(b->magic[0] != 0xEB || b->magic[1] != 0x3C || b->magic[2] != 0x90)
+		return -1;
+	sectsize = GETSHORT(b->sectsize);
+	if(sectsize != 512)
+		return -1;
+	rootoff = (GETSHORT(b->nresrv) + b->nfats*GETSHORT(b->fatsize)) * sectsize;
+	if(seek(fd, rootoff, 0) < 0)
+		return -1;
+	nroot = GETSHORT(b->rootsize);
+	rootsects = (nroot*sizeof(Dosdir)+sectsize-1)/sectsize;
+	if(rootsects <= 0 || rootsects > 64)
+		return -1;
+
+	/* 
+	 *  read root. it is contiguous to make stuff like
+	 *  this easier
+	 */
+	root = malloc(rootsects*sectsize);
+	if(read(fd, root, rootsects*sectsize) != rootsects*sectsize)
+		return -1;
+	n = -1;
+	for(dp = root; dp < &root[nroot]; dp++)
+		if(memcmp(name, dp->name, 8) == 0 && memcmp(ext, dp->ext, 3) == 0){
+			n = GETSHORT(dp->start);
+			break;
+		}
+	free(root);
+
+	if(n < 0)
+		return -1;
+
+	/*
+	 *  dp->start is in cluster units, not sectors.  The first
+	 *  cluster is cluster 2 which starts immediately after the
+	 *  root directory
+	 */
+	return rootoff + rootsects*sectsize + (n-2)*sectsize*b->clustsize;
+}
+
--- /dev/null
+++ b/libc/Makefile
@@ -1,0 +1,89 @@
+LIB=libc.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	charstod.$O\
+	cleanname.$O\
+	convD2M.$O\
+	convM2D.$O\
+	convM2S.$O\
+	convS2M.$O\
+	crypt.$O\
+	dial.$O\
+	dirfstat.$O\
+	dirfwstat.$O\
+	dirmodefmt.$O\
+	dirstat.$O\
+	dirwstat.$O\
+	dofmt.$O\
+	dorfmt.$O\
+	fcallfmt.$O\
+	fltfmt.$O\
+	fmt.$O\
+	fmtfd.$O\
+	fmtlock.$O\
+	fmtprint.$O\
+	fmtquote.$O\
+	fmtrune.$O\
+	fmtstr.$O\
+	fmtvprint.$O\
+	fprint.$O\
+	frand.$O\
+	getfields.$O\
+	getpid.$O\
+	lnrand.$O\
+	lock.$O\
+	lrand.$O\
+	mallocz.$O\
+	nan64.$O\
+	netmkaddr.$O\
+	nrand.$O\
+	nsec.$O\
+	pow10.$O\
+	pushssl.$O\
+	read9pmsg.$O\
+	readn.$O\
+	rune.$O\
+	runefmtstr.$O\
+	runeseprint.$O\
+	runesmprint.$O\
+	runesnprint.$O\
+	runesprint.$O\
+	runetype.$O\
+	runevseprint.$O\
+	runevsmprint.$O\
+	runevsnprint.$O\
+	seprint.$O\
+	smprint.$O\
+	snprint.$O\
+	sprint.$O\
+	strecpy.$O\
+	strtod.$O\
+	strtoll.$O\
+	sysfatal.$O\
+	time.$O\
+	tokenize.$O\
+	truerand.$O\
+	u16.$O\
+	u32.$O\
+	u64.$O\
+	utfecpy.$O\
+	utflen.$O\
+	utfnlen.$O\
+	utfrrune.$O\
+	utfrune.$O\
+	utfutf.$O\
+	vfprint.$O\
+	vseprint.$O\
+	vsmprint.$O\
+	vsnprint.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libc/charstod.c
@@ -1,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * Reads a floating-point number by interpreting successive characters
+ * returned by (*f)(vp).  The last call it makes to f terminates the
+ * scan, so is not a character in the number.  It may therefore be
+ * necessary to back up the input stream up one byte after calling charstod.
+ */
+
+#define ADVANCE *s++ = c; if(s>=e) return NaN(); c = (*f)(vp)
+
+double
+charstod(int(*f)(void*), void *vp)
+{
+	char str[400], *s, *e, *start;
+	int c;
+
+	s = str;
+	e = str + sizeof str - 1;
+	c = (*f)(vp);
+	while(c == ' ' || c == '\t')
+		c = (*f)(vp);
+	if(c == '-' || c == '+'){
+		ADVANCE;
+	}
+	start = s;
+	while(c >= '0' && c <= '9'){
+		ADVANCE;
+	}
+	if(c == '.'){
+		ADVANCE;
+		while(c >= '0' && c <= '9'){
+			ADVANCE;
+		}
+	}
+	if(s > start && (c == 'e' || c == 'E')){
+		ADVANCE;
+		if(c == '-' || c == '+'){
+			ADVANCE;
+		}
+		while(c >= '0' && c <= '9'){
+			ADVANCE;
+		}
+	}else if(s == start && (c == 'i' || c == 'I')){
+		ADVANCE;
+		if(c != 'n' && c != 'N')
+			return NaN();
+		ADVANCE;
+		if(c != 'f' && c != 'F')
+			return NaN();
+		ADVANCE;
+		if(c != 'i' && c != 'I')
+			return NaN();
+		ADVANCE;
+		if(c != 'n' && c != 'N')
+			return NaN();
+		ADVANCE;
+		if(c != 'i' && c != 'I')
+			return NaN();
+		ADVANCE;
+		if(c != 't' && c != 'T')
+			return NaN();
+		ADVANCE;
+		if(c != 'y' && c != 'Y')
+			return NaN();
+		ADVANCE;  /* so caller can back up uniformly */
+		USED(c);
+	}else if(s == str && (c == 'n' || c == 'N')){
+		ADVANCE;
+		if(c != 'a' && c != 'A')
+			return NaN();
+		ADVANCE;
+		if(c != 'n' && c != 'N')
+			return NaN();
+		ADVANCE;  /* so caller can back up uniformly */
+		USED(c);
+	}
+	*s = 0;
+	return strtod(str, &s);
+}
--- /dev/null
+++ b/libc/cleanname.c
@@ -1,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * In place, rewrite name to compress multiple /, eliminate ., and process ..
+ */
+#define SEP(x)	((x)=='/' || (x) == 0)
+char*
+cleanname(char *name)
+{
+	char *p, *q, *dotdot;
+	int rooted;
+
+	rooted = name[0] == '/';
+
+	/*
+	 * invariants:
+	 *	p points at beginning of path element we're considering.
+	 *	q points just past the last path element we wrote (no slash).
+	 *	dotdot points just past the point where .. cannot backtrack
+	 *		any further (no slash).
+	 */
+	p = q = dotdot = name+rooted;
+	while(*p) {
+		if(p[0] == '/')	/* null element */
+			p++;
+		else if(p[0] == '.' && SEP(p[1]))
+			p += 1;	/* don't count the separator in case it is nul */
+		else if(p[0] == '.' && p[1] == '.' && SEP(p[2])) {
+			p += 2;
+			if(q > dotdot) {	/* can backtrack */
+				while(--q > dotdot && *q != '/')
+					;
+			} else if(!rooted) {	/* /.. is / but ./../ is .. */
+				if(q != name)
+					*q++ = '/';
+				*q++ = '.';
+				*q++ = '.';
+				dotdot = q;
+			}
+		} else {	/* real path element */
+			if(q != name+rooted)
+				*q++ = '/';
+			while((*q = *p) != '/' && *q != 0)
+				p++, q++;
+		}
+	}
+	if(q == name)	/* empty string is really ``.'' */
+		*q++ = '.';
+	*q = '\0';
+	return name;
+}
--- /dev/null
+++ b/libc/convD2M.c
@@ -1,0 +1,95 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+uint
+sizeD2M(Dir *d)
+{
+	char *sv[4];
+	int i, ns;
+
+	sv[0] = d->name;
+	sv[1] = d->uid;
+	sv[2] = d->gid;
+	sv[3] = d->muid;
+
+	ns = 0;
+	for(i = 0; i < 4; i++)
+		if(sv[i])
+			ns += strlen(sv[i]);
+
+	return STATFIXLEN + ns;
+}
+
+uint
+convD2M(Dir *d, uchar *buf, uint nbuf)
+{
+	uchar *p, *ebuf;
+	char *sv[4];
+	int i, ns, nsv[4], ss;
+
+	if(nbuf < BIT16SZ)
+		return 0;
+
+	p = buf;
+	ebuf = buf + nbuf;
+
+	sv[0] = d->name;
+	sv[1] = d->uid;
+	sv[2] = d->gid;
+	sv[3] = d->muid;
+
+	ns = 0;
+	for(i = 0; i < 4; i++){
+		if(sv[i])
+			nsv[i] = strlen(sv[i]);
+		else
+			nsv[i] = 0;
+		ns += nsv[i];
+	}
+
+	ss = STATFIXLEN + ns;
+
+	/* set size befor erroring, so user can know how much is needed */
+	/* note that length excludes count field itself */
+	PBIT16(p, ss-BIT16SZ);
+	p += BIT16SZ;
+
+	if(ss > nbuf)
+		return BIT16SZ;
+
+	PBIT16(p, d->type);
+	p += BIT16SZ;
+	PBIT32(p, d->dev);
+	p += BIT32SZ;
+	PBIT8(p, d->qid.type);
+	p += BIT8SZ;
+	PBIT32(p, d->qid.vers);
+	p += BIT32SZ;
+	PBIT64(p, d->qid.path);
+	p += BIT64SZ;
+	PBIT32(p, d->mode);
+	p += BIT32SZ;
+	PBIT32(p, d->atime);
+	p += BIT32SZ;
+	PBIT32(p, d->mtime);
+	p += BIT32SZ;
+	PBIT64(p, d->length);
+	p += BIT64SZ;
+
+	for(i = 0; i < 4; i++){
+		ns = nsv[i];
+		if(p + ns + BIT16SZ > ebuf)
+			return 0;
+		PBIT16(p, ns);
+		p += BIT16SZ;
+		if(ns)
+			memmove(p, sv[i], ns);
+		p += ns;
+	}
+
+	if(ss != p - buf)
+		return 0;
+
+	return p - buf;
+}
--- /dev/null
+++ b/libc/convM2D.c
@@ -1,0 +1,94 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+int
+statcheck(uchar *buf, uint nbuf)
+{
+	uchar *ebuf;
+	int i;
+
+	ebuf = buf + nbuf;
+
+	if(nbuf < STATFIXLEN || nbuf != BIT16SZ + GBIT16(buf))
+		return -1;
+
+	buf += STATFIXLEN - 4 * BIT16SZ;
+
+	for(i = 0; i < 4; i++){
+		if(buf + BIT16SZ > ebuf)
+			return -1;
+		buf += BIT16SZ + GBIT16(buf);
+	}
+
+	if(buf != ebuf)
+		return -1;
+
+	return 0;
+}
+
+static char nullstring[] = "";
+
+uint
+convM2D(uchar *buf, uint nbuf, Dir *d, char *strs)
+{
+	uchar *p, *ebuf;
+	char *sv[4];
+	int i, ns;
+
+	if(nbuf < STATFIXLEN)
+		return 0; 
+
+	p = buf;
+	ebuf = buf + nbuf;
+
+	p += BIT16SZ;	/* ignore size */
+	d->type = GBIT16(p);
+	p += BIT16SZ;
+	d->dev = GBIT32(p);
+	p += BIT32SZ;
+	d->qid.type = GBIT8(p);
+	p += BIT8SZ;
+	d->qid.vers = GBIT32(p);
+	p += BIT32SZ;
+	d->qid.path = GBIT64(p);
+	p += BIT64SZ;
+	d->mode = GBIT32(p);
+	p += BIT32SZ;
+	d->atime = GBIT32(p);
+	p += BIT32SZ;
+	d->mtime = GBIT32(p);
+	p += BIT32SZ;
+	d->length = GBIT64(p);
+	p += BIT64SZ;
+
+	for(i = 0; i < 4; i++){
+		if(p + BIT16SZ > ebuf)
+			return 0;
+		ns = GBIT16(p);
+		p += BIT16SZ;
+		if(p + ns > ebuf)
+			return 0;
+		if(strs){
+			sv[i] = strs;
+			memmove(strs, p, ns);
+			strs += ns;
+			*strs++ = '\0';
+		}
+		p += ns;
+	}
+
+	if(strs){
+		d->name = sv[0];
+		d->uid = sv[1];
+		d->gid = sv[2];
+		d->muid = sv[3];
+	}else{
+		d->name = nullstring;
+		d->uid = nullstring;
+		d->gid = nullstring;
+		d->muid = nullstring;
+	}
+	
+	return p - buf;
+}
--- /dev/null
+++ b/libc/convM2S.c
@@ -1,0 +1,315 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+static
+uchar*
+gstring(uchar *p, uchar *ep, char **s)
+{
+	uint n;
+
+	if(p+BIT16SZ > ep)
+		return nil;
+	n = GBIT16(p);
+	p += BIT16SZ - 1;
+	if(p+n+1 > ep)
+		return nil;
+	/* move it down, on top of count, to make room for '\0' */
+	memmove(p, p + 1, n);
+	p[n] = '\0';
+	*s = (char*)p;
+	p += n+1;
+	return p;
+}
+
+static
+uchar*
+gqid(uchar *p, uchar *ep, Qid *q)
+{
+	if(p+QIDSZ > ep)
+		return nil;
+	q->type = GBIT8(p);
+	p += BIT8SZ;
+	q->vers = GBIT32(p);
+	p += BIT32SZ;
+	q->path = GBIT64(p);
+	p += BIT64SZ;
+	return p;
+}
+
+/*
+ * no syntactic checks.
+ * three causes for error:
+ *  1. message size field is incorrect
+ *  2. input buffer too short for its own data (counts too long, etc.)
+ *  3. too many names or qids
+ * gqid() and gstring() return nil if they would reach beyond buffer.
+ * main switch statement checks range and also can fall through
+ * to test at end of routine.
+ */
+uint
+convM2S(uchar *ap, uint nap, Fcall *f)
+{
+	uchar *p, *ep;
+	uint i, size;
+
+	p = ap;
+	ep = p + nap;
+
+	if(p+BIT32SZ+BIT8SZ+BIT16SZ > ep)
+		return 0;
+	size = GBIT32(p);
+	p += BIT32SZ;
+
+	if(size < BIT32SZ+BIT8SZ+BIT16SZ)
+		return 0;
+
+	f->type = GBIT8(p);
+	p += BIT8SZ;
+	f->tag = GBIT16(p);
+	p += BIT16SZ;
+
+	switch(f->type)
+	{
+	default:
+		return 0;
+
+	case Tversion:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->msize = GBIT32(p);
+		p += BIT32SZ;
+		p = gstring(p, ep, &f->version);
+		break;
+
+	case Tflush:
+		if(p+BIT16SZ > ep)
+			return 0;
+		f->oldtag = GBIT16(p);
+		p += BIT16SZ;
+		break;
+
+	case Tauth:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->afid = GBIT32(p);
+		p += BIT32SZ;
+		p = gstring(p, ep, &f->uname);
+		if(p == nil)
+			break;
+		p = gstring(p, ep, &f->aname);
+		if(p == nil)
+			break;
+		break;
+
+	case Tattach:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->afid = GBIT32(p);
+		p += BIT32SZ;
+		p = gstring(p, ep, &f->uname);
+		if(p == nil)
+			break;
+		p = gstring(p, ep, &f->aname);
+		if(p == nil)
+			break;
+		break;
+
+	case Twalk:
+		if(p+BIT32SZ+BIT32SZ+BIT16SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		f->newfid = GBIT32(p);
+		p += BIT32SZ;
+		f->nwname = GBIT16(p);
+		p += BIT16SZ;
+		if(f->nwname > MAXWELEM)
+			return 0;
+		for(i=0; i<f->nwname; i++){
+			p = gstring(p, ep, &f->wname[i]);
+			if(p == nil)
+				break;
+		}
+		break;
+
+	case Topen:
+		if(p+BIT32SZ+BIT8SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		f->mode = GBIT8(p);
+		p += BIT8SZ;
+		break;
+
+	case Tcreate:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		p = gstring(p, ep, &f->name);
+		if(p == nil)
+			break;
+		if(p+BIT32SZ+BIT8SZ > ep)
+			return 0;
+		f->perm = GBIT32(p);
+		p += BIT32SZ;
+		f->mode = GBIT8(p);
+		p += BIT8SZ;
+		break;
+
+	case Tread:
+		if(p+BIT32SZ+BIT64SZ+BIT32SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		f->offset = GBIT64(p);
+		p += BIT64SZ;
+		f->count = GBIT32(p);
+		p += BIT32SZ;
+		break;
+
+	case Twrite:
+		if(p+BIT32SZ+BIT64SZ+BIT32SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		f->offset = GBIT64(p);
+		p += BIT64SZ;
+		f->count = GBIT32(p);
+		p += BIT32SZ;
+		if(p+f->count > ep)
+			return 0;
+		f->data = (char*)p;
+		p += f->count;
+		break;
+
+	case Tclunk:
+	case Tremove:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		break;
+
+	case Tstat:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		break;
+
+	case Twstat:
+		if(p+BIT32SZ+BIT16SZ > ep)
+			return 0;
+		f->fid = GBIT32(p);
+		p += BIT32SZ;
+		f->nstat = GBIT16(p);
+		p += BIT16SZ;
+		if(p+f->nstat > ep)
+			return 0;
+		f->stat = p;
+		p += f->nstat;
+		break;
+
+/*
+ */
+	case Rversion:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->msize = GBIT32(p);
+		p += BIT32SZ;
+		p = gstring(p, ep, &f->version);
+		break;
+
+	case Rerror:
+		p = gstring(p, ep, &f->ename);
+		break;
+
+	case Rflush:
+		break;
+
+	case Rauth:
+		p = gqid(p, ep, &f->aqid);
+		if(p == nil)
+			break;
+		break;
+
+	case Rattach:
+		p = gqid(p, ep, &f->qid);
+		if(p == nil)
+			break;
+		break;
+
+	case Rwalk:
+		if(p+BIT16SZ > ep)
+			return 0;
+		f->nwqid = GBIT16(p);
+		p += BIT16SZ;
+		if(f->nwqid > MAXWELEM)
+			return 0;
+		for(i=0; i<f->nwqid; i++){
+			p = gqid(p, ep, &f->wqid[i]);
+			if(p == nil)
+				break;
+		}
+		break;
+
+	case Ropen:
+	case Rcreate:
+		p = gqid(p, ep, &f->qid);
+		if(p == nil)
+			break;
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->iounit = GBIT32(p);
+		p += BIT32SZ;
+		break;
+
+	case Rread:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->count = GBIT32(p);
+		p += BIT32SZ;
+		if(p+f->count > ep)
+			return 0;
+		f->data = (char*)p;
+		p += f->count;
+		break;
+
+	case Rwrite:
+		if(p+BIT32SZ > ep)
+			return 0;
+		f->count = GBIT32(p);
+		p += BIT32SZ;
+		break;
+
+	case Rclunk:
+	case Rremove:
+		break;
+
+	case Rstat:
+		if(p+BIT16SZ > ep)
+			return 0;
+		f->nstat = GBIT16(p);
+		p += BIT16SZ;
+		if(p+f->nstat > ep)
+			return 0;
+		f->stat = p;
+		p += f->nstat;
+		break;
+
+	case Rwstat:
+		break;
+	}
+
+	if(p==nil || p>ep)
+		return 0;
+	if(ap+size == p)
+		return size;
+	return 0;
+}
--- /dev/null
+++ b/libc/convS2M.c
@@ -1,0 +1,386 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+static
+uchar*
+pstring(uchar *p, char *s)
+{
+	uint n;
+
+	if(s == nil){
+		PBIT16(p, 0);
+		p += BIT16SZ;
+		return p;
+	}
+
+	n = strlen(s);
+	PBIT16(p, n);
+	p += BIT16SZ;
+	memmove(p, s, n);
+	p += n;
+	return p;
+}
+
+static
+uchar*
+pqid(uchar *p, Qid *q)
+{
+	PBIT8(p, q->type);
+	p += BIT8SZ;
+	PBIT32(p, q->vers);
+	p += BIT32SZ;
+	PBIT64(p, q->path);
+	p += BIT64SZ;
+	return p;
+}
+
+static
+uint
+stringsz(char *s)
+{
+	if(s == nil)
+		return BIT16SZ;
+
+	return BIT16SZ+strlen(s);
+}
+
+uint
+sizeS2M(Fcall *f)
+{
+	uint n;
+	int i;
+
+	n = 0;
+	n += BIT32SZ;	/* size */
+	n += BIT8SZ;	/* type */
+	n += BIT16SZ;	/* tag */
+
+	switch(f->type)
+	{
+	default:
+		return 0;
+
+	case Tversion:
+		n += BIT32SZ;
+		n += stringsz(f->version);
+		break;
+
+	case Tflush:
+		n += BIT16SZ;
+		break;
+
+	case Tauth:
+		n += BIT32SZ;
+		n += stringsz(f->uname);
+		n += stringsz(f->aname);
+		break;
+
+	case Tattach:
+		n += BIT32SZ;
+		n += BIT32SZ;
+		n += stringsz(f->uname);
+		n += stringsz(f->aname);
+		break;
+
+	case Twalk:
+		n += BIT32SZ;
+		n += BIT32SZ;
+		n += BIT16SZ;
+		for(i=0; i<f->nwname; i++)
+			n += stringsz(f->wname[i]);
+		break;
+
+	case Topen:
+		n += BIT32SZ;
+		n += BIT8SZ;
+		break;
+
+	case Tcreate:
+		n += BIT32SZ;
+		n += stringsz(f->name);
+		n += BIT32SZ;
+		n += BIT8SZ;
+		break;
+
+	case Tread:
+		n += BIT32SZ;
+		n += BIT64SZ;
+		n += BIT32SZ;
+		break;
+
+	case Twrite:
+		n += BIT32SZ;
+		n += BIT64SZ;
+		n += BIT32SZ;
+		n += f->count;
+		break;
+
+	case Tclunk:
+	case Tremove:
+		n += BIT32SZ;
+		break;
+
+	case Tstat:
+		n += BIT32SZ;
+		break;
+
+	case Twstat:
+		n += BIT32SZ;
+		n += BIT16SZ;
+		n += f->nstat;
+		break;
+/*
+ */
+
+	case Rversion:
+		n += BIT32SZ;
+		n += stringsz(f->version);
+		break;
+
+	case Rerror:
+		n += stringsz(f->ename);
+		break;
+
+	case Rflush:
+		break;
+
+	case Rauth:
+		n += QIDSZ;
+		break;
+
+	case Rattach:
+		n += QIDSZ;
+		break;
+
+	case Rwalk:
+		n += BIT16SZ;
+		n += f->nwqid*QIDSZ;
+		break;
+
+	case Ropen:
+	case Rcreate:
+		n += QIDSZ;
+		n += BIT32SZ;
+		break;
+
+	case Rread:
+		n += BIT32SZ;
+		n += f->count;
+		break;
+
+	case Rwrite:
+		n += BIT32SZ;
+		break;
+
+	case Rclunk:
+		break;
+
+	case Rremove:
+		break;
+
+	case Rstat:
+		n += BIT16SZ;
+		n += f->nstat;
+		break;
+
+	case Rwstat:
+		break;
+	}
+	return n;
+}
+
+uint
+convS2M(Fcall *f, uchar *ap, uint nap)
+{
+	uchar *p;
+	uint i, size;
+
+	size = sizeS2M(f);
+	if(size == 0)
+		return 0;
+	if(size > nap)
+		return 0;
+
+	p = (uchar*)ap;
+
+	PBIT32(p, size);
+	p += BIT32SZ;
+	PBIT8(p, f->type);
+	p += BIT8SZ;
+	PBIT16(p, f->tag);
+	p += BIT16SZ;
+
+	switch(f->type)
+	{
+	default:
+		return 0;
+
+	case Tversion:
+		PBIT32(p, f->msize);
+		p += BIT32SZ;
+		p = pstring(p, f->version);
+		break;
+
+	case Tflush:
+		PBIT16(p, f->oldtag);
+		p += BIT16SZ;
+		break;
+
+	case Tauth:
+		PBIT32(p, f->afid);
+		p += BIT32SZ;
+		p  = pstring(p, f->uname);
+		p  = pstring(p, f->aname);
+		break;
+
+	case Tattach:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		PBIT32(p, f->afid);
+		p += BIT32SZ;
+		p  = pstring(p, f->uname);
+		p  = pstring(p, f->aname);
+		break;
+
+	case Twalk:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		PBIT32(p, f->newfid);
+		p += BIT32SZ;
+		PBIT16(p, f->nwname);
+		p += BIT16SZ;
+		if(f->nwname > MAXWELEM)
+			return 0;
+		for(i=0; i<f->nwname; i++)
+			p = pstring(p, f->wname[i]);
+		break;
+
+	case Topen:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		PBIT8(p, f->mode);
+		p += BIT8SZ;
+		break;
+
+	case Tcreate:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		p = pstring(p, f->name);
+		PBIT32(p, f->perm);
+		p += BIT32SZ;
+		PBIT8(p, f->mode);
+		p += BIT8SZ;
+		break;
+
+	case Tread:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		PBIT64(p, f->offset);
+		p += BIT64SZ;
+		PBIT32(p, f->count);
+		p += BIT32SZ;
+		break;
+
+	case Twrite:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		PBIT64(p, f->offset);
+		p += BIT64SZ;
+		PBIT32(p, f->count);
+		p += BIT32SZ;
+		memmove(p, f->data, f->count);
+		p += f->count;
+		break;
+
+	case Tclunk:
+	case Tremove:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		break;
+
+	case Tstat:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		break;
+
+	case Twstat:
+		PBIT32(p, f->fid);
+		p += BIT32SZ;
+		PBIT16(p, f->nstat);
+		p += BIT16SZ;
+		memmove(p, f->stat, f->nstat);
+		p += f->nstat;
+		break;
+/*
+ */
+
+	case Rversion:
+		PBIT32(p, f->msize);
+		p += BIT32SZ;
+		p = pstring(p, f->version);
+		break;
+
+	case Rerror:
+		p = pstring(p, f->ename);
+		break;
+
+	case Rflush:
+		break;
+
+	case Rauth:
+		p = pqid(p, &f->aqid);
+		break;
+
+	case Rattach:
+		p = pqid(p, &f->qid);
+		break;
+
+	case Rwalk:
+		PBIT16(p, f->nwqid);
+		p += BIT16SZ;
+		if(f->nwqid > MAXWELEM)
+			return 0;
+		for(i=0; i<f->nwqid; i++)
+			p = pqid(p, &f->wqid[i]);
+		break;
+
+	case Ropen:
+	case Rcreate:
+		p = pqid(p, &f->qid);
+		PBIT32(p, f->iounit);
+		p += BIT32SZ;
+		break;
+
+	case Rread:
+		PBIT32(p, f->count);
+		p += BIT32SZ;
+		memmove(p, f->data, f->count);
+		p += f->count;
+		break;
+
+	case Rwrite:
+		PBIT32(p, f->count);
+		p += BIT32SZ;
+		break;
+
+	case Rclunk:
+		break;
+
+	case Rremove:
+		break;
+
+	case Rstat:
+		PBIT16(p, f->nstat);
+		p += BIT16SZ;
+		memmove(p, f->stat, f->nstat);
+		p += f->nstat;
+		break;
+
+	case Rwstat:
+		break;
+	}
+	if(size != p-ap)
+		return 0;
+	return size;
+}
--- /dev/null
+++ b/libc/crypt.c
@@ -1,0 +1,68 @@
+/*
+ *	Data Encryption Standard
+ *	D.P.Mitchell  83/06/08.
+ *
+ *	block_cipher(key, block, decrypting)
+ *
+ *	these routines use the non-standard 7 byte format
+ *	for DES keys.
+ */
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <libsec.h>
+
+/*
+ * destructively encrypt the buffer, which
+ * must be at least 8 characters long.
+ */
+int
+encrypt(void *key, void *vbuf, int n)
+{
+	ulong ekey[32];
+	uchar *buf;
+	int i, r;
+
+	if(n < 8)
+		return 0;
+	key_setup(key, ekey);
+	buf = vbuf;
+	n--;
+	r = n % 7;
+	n /= 7;
+	for(i = 0; i < n; i++){
+		block_cipher(ekey, buf, 0);
+		buf += 7;
+	}
+	if(r)
+		block_cipher(ekey, buf - 7 + r, 0);
+	return 1;
+}
+
+/*
+ * destructively decrypt the buffer, which
+ * must be at least 8 characters long.
+ */
+int
+decrypt(void *key, void *vbuf, int n)
+{
+	ulong ekey[128];
+	uchar *buf;
+	int i, r;
+
+	if(n < 8)
+		return 0;
+	key_setup(key, ekey);
+	buf = vbuf;
+	n--;
+	r = n % 7;
+	n /= 7;
+	buf += n * 7;
+	if(r)
+		block_cipher(ekey, buf - 7 + r, 1);
+	for(i = 0; i < n; i++){
+		buf -= 7;
+		block_cipher(ekey, buf, 1);
+	}
+	return 1;
+}
--- /dev/null
+++ b/libc/dial.c
@@ -1,0 +1,209 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct DS DS;
+
+static int	call(char*, char*, DS*);
+static int	csdial(DS*);
+static void	_dial_string_parse(char*, DS*);
+
+enum
+{
+	Maxstring	= 128,
+	Maxpath		= 256,
+};
+
+struct DS {
+	/* dist string */
+	char	buf[Maxstring];
+	char	*netdir;
+	char	*proto;
+	char	*rem;
+
+	/* other args */
+	char	*local;
+	char	*dir;
+	int	*cfdp;
+};
+
+
+/*
+ *  the dialstring is of the form '[/net/]proto!dest'
+ */
+int
+dial(char *dest, char *local, char *dir, int *cfdp)
+{
+	DS ds;
+	int rv;
+	char err[ERRMAX], alterr[ERRMAX];
+
+	ds.local = local;
+	ds.dir = dir;
+	ds.cfdp = cfdp;
+
+	_dial_string_parse(dest, &ds);
+	if(ds.netdir)
+		return csdial(&ds);
+
+	ds.netdir = "/net";
+	rv = csdial(&ds);
+	if(rv >= 0)
+		return rv;
+	err[0] = '\0';
+	errstr(err, sizeof err);
+	if(strstr(err, "refused") != 0){
+		werrstr("%s", err);
+		return rv;
+	}
+	ds.netdir = "/net.alt";
+	rv = csdial(&ds);
+	if(rv >= 0)
+		return rv;
+
+	alterr[0] = 0;
+	errstr(alterr, sizeof alterr);
+	if(strstr(alterr, "translate") || strstr(alterr, "does not exist"))
+		werrstr("%s", err);
+	else
+		werrstr("%s", alterr);
+	return rv;
+}
+
+static int
+csdial(DS *ds)
+{
+	int n, fd, rv;
+	char *p, buf[Maxstring], clone[Maxpath], err[ERRMAX], besterr[ERRMAX];
+
+	/*
+	 *  open connection server
+	 */
+	snprint(buf, sizeof(buf), "%s/cs", ds->netdir);
+	fd = open(buf, ORDWR);
+	if(fd < 0){
+		/* no connection server, don't translate */
+		snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto);
+		return call(clone, ds->rem, ds);
+	}
+
+	/*
+	 *  ask connection server to translate
+	 */
+	snprint(buf, sizeof(buf), "%s!%s", ds->proto, ds->rem);
+	if(write(fd, buf, strlen(buf)) < 0){
+		close(fd);
+		return -1;
+	}
+
+	/*
+	 *  loop through each address from the connection server till
+	 *  we get one that works.
+	 */
+	*besterr = 0;
+	rv = -1;
+	seek(fd, 0, 0);
+	strcpy(err, "cs gave empty translation list");
+	while((n = read(fd, buf, sizeof(buf) - 1)) > 0){
+		buf[n] = 0;
+		p = strchr(buf, ' ');
+		if(p == 0)
+			continue;
+		*p++ = 0;
+		rv = call(buf, p, ds);
+		if(rv >= 0)
+			break;
+		err[0] = '\0';
+		errstr(err, sizeof err);
+		if(strstr(err, "does not exist") == 0)
+			strcpy(besterr, err);
+	}
+	close(fd);
+
+	if(rv < 0 && *besterr)
+		werrstr("%s", besterr);
+	else
+		werrstr("%s", err);
+	return rv;
+}
+
+static int
+call(char *clone, char *dest, DS *ds)
+{
+	int fd, cfd, n;
+	char name[Maxpath], data[Maxpath], *p;
+
+	cfd = open(clone, ORDWR);
+	if(cfd < 0)
+		return -1;
+
+	/* get directory name */
+	n = read(cfd, name, sizeof(name)-1);
+	if(n < 0){
+		close(cfd);
+		return -1;
+	}
+	name[n] = 0;
+	for(p = name; *p == ' '; p++)
+		;
+	snprint(name, sizeof(name), "%ld", strtoul(p, 0, 0));
+	p = strrchr(clone, '/');
+	*p = 0;
+	if(ds->dir)
+		snprint(ds->dir, NETPATHLEN, "%s/%s", clone, name);
+	snprint(data, sizeof(data), "%s/%s/data", clone, name);
+
+	/* connect */
+	if(ds->local)
+		snprint(name, sizeof(name), "connect %s %s", dest, ds->local);
+	else
+		snprint(name, sizeof(name), "connect %s", dest);
+	if(write(cfd, name, strlen(name)) < 0){
+		close(cfd);
+		return -1;
+	}
+
+	/* open data connection */
+	fd = open(data, ORDWR);
+	if(fd < 0){
+print("open %s: %r\n", data);
+		close(cfd);
+		return -1;
+	}
+	if(ds->cfdp)
+		*ds->cfdp = cfd;
+	else
+		close(cfd);
+	return fd;
+}
+
+/*
+ *  parse a dial string
+ */
+static void
+_dial_string_parse(char *str, DS *ds)
+{
+	char *p, *p2;
+
+	strncpy(ds->buf, str, Maxstring);
+	ds->buf[Maxstring-1] = 0;
+
+	p = strchr(ds->buf, '!');
+	if(p == 0) {
+		ds->netdir = 0;
+		ds->proto = "net";
+		ds->rem = ds->buf;
+	} else {
+		if(*ds->buf != '/' && *ds->buf != '#'){
+			ds->netdir = 0;
+			ds->proto = ds->buf;
+		} else {
+			for(p2 = p; *p2 != '/'; p2--)
+				;
+			*p2++ = 0;
+			ds->netdir = ds->buf;
+			ds->proto = p2;
+		}
+		*p = 0;
+		ds->rem = p + 1;
+	}
+}
--- /dev/null
+++ b/libc/dirfstat.c
@@ -1,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+enum
+{
+	DIRSIZE	= STATFIXLEN + 16 * 4		/* enough for encoded stat buf + some reasonable strings */
+};
+
+Dir*
+dirfstat(int fd)
+{
+	Dir *d;
+	uchar *buf;
+	int n, nd, i;
+
+	nd = DIRSIZE;
+	for(i=0; i<2; i++){	/* should work by the second try */
+		d = malloc(sizeof(Dir) + BIT16SZ + nd);
+		if(d == nil)
+			return nil;
+		buf = (uchar*)&d[1];
+		n = fstat(fd, buf, BIT16SZ+nd);
+		if(n < BIT16SZ){
+			free(d);
+			return nil;
+		}
+		nd = GBIT16(buf);	/* upper bound on size of Dir + strings */
+		if(nd <= n){
+			convM2D(buf, n, d, (char*)&d[1]);
+			return d;
+		}
+		/* else sizeof(Dir)+BIT16SZ+nd is plenty */
+		free(d);
+	}
+	return nil;
+}
--- /dev/null
+++ b/libc/dirfwstat.c
@@ -1,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+int
+dirfwstat(int fd, Dir *d)
+{
+	uchar *buf;
+	int r;
+
+	r = sizeD2M(d);
+	buf = malloc(r);
+	if(buf == nil)
+		return -1;
+	convD2M(d, buf, r);
+	r = fwstat(fd, buf, r);
+	free(buf);
+	return r;
+}
--- /dev/null
+++ b/libc/dirmodefmt.c
@@ -1,0 +1,48 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+static char *modes[] =
+{
+	"---",
+	"--x",
+	"-w-",
+	"-wx",
+	"r--",
+	"r-x",
+	"rw-",
+	"rwx",
+};
+
+static void
+rwx(long m, char *s)
+{
+	strncpy(s, modes[m], 3);
+}
+
+int
+dirmodefmt(Fmt *f)
+{
+	static char buf[16];
+	ulong m;
+
+	m = va_arg(f->args, ulong);
+
+	if(m & DMDIR)
+		buf[0]='d';
+	else if(m & DMAPPEND)
+		buf[0]='a';
+	else if(m & DMAUTH)
+		buf[0]='A';
+	else
+		buf[0]='-';
+	if(m & DMEXCL)
+		buf[1]='l';
+	else
+		buf[1]='-';
+	rwx((m>>6)&7, buf+2);
+	rwx((m>>3)&7, buf+5);
+	rwx((m>>0)&7, buf+8);
+	buf[11] = 0;
+	return fmtstrcpy(f, buf);
+}
--- /dev/null
+++ b/libc/dirstat.c
@@ -1,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+enum
+{
+	DIRSIZE	= STATFIXLEN + 16 * 4		/* enough for encoded stat buf + some reasonable strings */
+};
+
+Dir*
+dirstat(char *name)
+{
+	Dir *d;
+	uchar *buf;
+	int n, nd, i;
+
+	nd = DIRSIZE;
+	for(i=0; i<2; i++){	/* should work by the second try */
+		d = malloc(sizeof(Dir) + BIT16SZ + nd);
+		if(d == nil)
+			return nil;
+		buf = (uchar*)&d[1];
+		n = stat(name, buf, BIT16SZ+nd);
+		if(n < BIT16SZ){
+			free(d);
+			return nil;
+		}
+		nd = GBIT16((uchar*)buf);	/* upper bound on size of Dir + strings */
+		if(nd <= n){
+			convM2D(buf, n, d, (char*)&d[1]);
+			return d;
+		}
+		/* else sizeof(Dir)+BIT16SZ+nd is plenty */
+		free(d);
+	}
+	return nil;
+}
--- /dev/null
+++ b/libc/dirwstat.c
@@ -1,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+int
+dirwstat(char *name, Dir *d)
+{
+	uchar *buf;
+	int r;
+
+	r = sizeD2M(d);
+	buf = malloc(r);
+	if(buf == nil)
+		return -1;
+	convD2M(d, buf, r);
+	r = wstat(name, buf, r);
+	free(buf);
+	return r;
+}
--- /dev/null
+++ b/libc/dofmt.c
@@ -1,0 +1,520 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+/* format the output into f->to and return the number of characters fmted  */
+int
+dofmt(Fmt *f, char *fmt)
+{
+	Rune rune, *rt, *rs;
+	int r;
+	char *t, *s;
+	int n, nfmt;
+
+	nfmt = f->nfmt;
+	for(;;){
+		if(f->runes){
+			rt = f->to;
+			rs = f->stop;
+			while((r = *(uchar*)fmt) && r != '%'){
+				if(r < Runeself)
+					fmt++;
+				else{
+					fmt += chartorune(&rune, fmt);
+					r = rune;
+				}
+				FMTRCHAR(f, rt, rs, r);
+			}
+			fmt++;
+			f->nfmt += rt - (Rune *)f->to;
+			f->to = rt;
+			if(!r)
+				return f->nfmt - nfmt;
+			f->stop = rs;
+		}else{
+			t = f->to;
+			s = f->stop;
+			while((r = *(uchar*)fmt) && r != '%'){
+				if(r < Runeself){
+					FMTCHAR(f, t, s, r);
+					fmt++;
+				}else{
+					n = chartorune(&rune, fmt);
+					if(t + n > s){
+						t = _fmtflush(f, t, n);
+						if(t != nil)
+							s = f->stop;
+						else
+							return -1;
+					}
+					while(n--)
+						*t++ = *fmt++;
+				}
+			}
+			fmt++;
+			f->nfmt += t - (char *)f->to;
+			f->to = t;
+			if(!r)
+				return f->nfmt - nfmt;
+			f->stop = s;
+		}
+
+		fmt = _fmtdispatch(f, fmt, 0);
+		if(fmt == nil)
+			return -1;
+	}
+	return 0;	/* not reached */
+}
+
+void *
+_fmtflush(Fmt *f, void *t, int len)
+{
+	if(f->runes)
+		f->nfmt += (Rune*)t - (Rune*)f->to;
+	else
+		f->nfmt += (char*)t - (char *)f->to;
+	f->to = t;
+	if(f->flush == 0 || (*f->flush)(f) == 0 || (char*)f->to + len > (char*)f->stop){
+		f->stop = f->to;
+		return nil;
+	}
+	return f->to;
+}
+
+/*
+ * put a formatted block of memory sz bytes long of n runes into the output buffer,
+ * left/right justified in a field of at least f->width charactes
+ */
+int
+_fmtpad(Fmt *f, int n)
+{
+	char *t, *s;
+	int i;
+
+	t = f->to;
+	s = f->stop;
+	for(i = 0; i < n; i++)
+		FMTCHAR(f, t, s, ' ');
+	f->nfmt += t - (char *)f->to;
+	f->to = t;
+	return 0;
+}
+
+int
+_rfmtpad(Fmt *f, int n)
+{
+	Rune *t, *s;
+	int i;
+
+	t = f->to;
+	s = f->stop;
+	for(i = 0; i < n; i++)
+		FMTRCHAR(f, t, s, ' ');
+	f->nfmt += t - (Rune *)f->to;
+	f->to = t;
+	return 0;
+}
+
+int
+_fmtcpy(Fmt *f, void *vm, int n, int sz)
+{
+	Rune *rt, *rs, r;
+	char *t, *s, *m, *me;
+	ulong fl;
+	int nc, w;
+
+	m = vm;
+	me = m + sz;
+	w = f->width;
+	fl = f->flags;
+	if((fl & FmtPrec) && n > f->prec)
+		n = f->prec;
+	if(f->runes){
+		if(!(fl & FmtLeft) && _rfmtpad(f, w - n) < 0)
+			return -1;
+		rt = f->to;
+		rs = f->stop;
+		for(nc = n; nc > 0; nc--){
+			r = *(uchar*)m;
+			if(r < Runeself)
+				m++;
+			else if((me - m) >= UTFmax || fullrune(m, me-m))
+				m += chartorune(&r, m);
+			else
+				break;
+			FMTRCHAR(f, rt, rs, r);
+		}
+		f->nfmt += rt - (Rune *)f->to;
+		f->to = rt;
+		if(m < me)
+			return -1;
+		if(fl & FmtLeft && _rfmtpad(f, w - n) < 0)
+			return -1;
+	}else{
+		if(!(fl & FmtLeft) && _fmtpad(f, w - n) < 0)
+			return -1;
+		t = f->to;
+		s = f->stop;
+		for(nc = n; nc > 0; nc--){
+			r = *(uchar*)m;
+			if(r < Runeself)
+				m++;
+			else if((me - m) >= UTFmax || fullrune(m, me-m))
+				m += chartorune(&r, m);
+			else
+				break;
+			FMTRUNE(f, t, s, r);
+		}
+		f->nfmt += t - (char *)f->to;
+		f->to = t;
+		if(fl & FmtLeft && _fmtpad(f, w - n) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+int
+_fmtrcpy(Fmt *f, void *vm, int n)
+{
+	Rune r, *m, *me, *rt, *rs;
+	char *t, *s;
+	ulong fl;
+	int w;
+
+	m = vm;
+	w = f->width;
+	fl = f->flags;
+	if((fl & FmtPrec) && n > f->prec)
+		n = f->prec;
+	if(f->runes){
+		if(!(fl & FmtLeft) && _rfmtpad(f, w - n) < 0)
+			return -1;
+		rt = f->to;
+		rs = f->stop;
+		for(me = m + n; m < me; m++)
+			FMTRCHAR(f, rt, rs, *m);
+		f->nfmt += rt - (Rune *)f->to;
+		f->to = rt;
+		if(fl & FmtLeft && _rfmtpad(f, w - n) < 0)
+			return -1;
+	}else{
+		if(!(fl & FmtLeft) && _fmtpad(f, w - n) < 0)
+			return -1;
+		t = f->to;
+		s = f->stop;
+		for(me = m + n; m < me; m++){
+			r = *m;
+			FMTRUNE(f, t, s, r);
+		}
+		f->nfmt += t - (char *)f->to;
+		f->to = t;
+		if(fl & FmtLeft && _fmtpad(f, w - n) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+/* fmt out one character */
+int
+_charfmt(Fmt *f)
+{
+	char x[1];
+
+	x[0] = va_arg(f->args, int);
+	f->prec = 1;
+	return _fmtcpy(f, x, 1, 1);
+}
+
+/* fmt out one rune */
+int
+_runefmt(Fmt *f)
+{
+	Rune x[1];
+
+	x[0] = va_arg(f->args, int);
+	return _fmtrcpy(f, x, 1);
+}
+
+/* public helper routine: fmt out a null terminated string already in hand */
+int
+fmtstrcpy(Fmt *f, char *s)
+{
+	int p, i;
+	if(!s)
+		return _fmtcpy(f, "<nil>", 5, 5);
+	/* if precision is specified, make sure we don't wander off the end */
+	if(f->flags & FmtPrec){
+		p = f->prec;
+		for(i = 0; i < p; i++)
+			if(s[i] == 0)
+				break;
+		return _fmtcpy(f, s, utfnlen(s, i), i);	/* BUG?: won't print a partial rune at end */
+	}
+
+	return _fmtcpy(f, s, utflen(s), strlen(s));
+}
+
+/* fmt out a null terminated utf string */
+int
+_strfmt(Fmt *f)
+{
+	char *s;
+
+	s = va_arg(f->args, char *);
+	return fmtstrcpy(f, s);
+}
+
+/* public helper routine: fmt out a null terminated rune string already in hand */
+int
+fmtrunestrcpy(Fmt *f, Rune *s)
+{
+	Rune *e;
+	int n, p;
+
+	if(!s)
+		return _fmtcpy(f, "<nil>", 5, 5);
+	/* if precision is specified, make sure we don't wander off the end */
+	if(f->flags & FmtPrec){
+		p = f->prec;
+		for(n = 0; n < p; n++)
+			if(s[n] == 0)
+				break;
+	}else{
+		for(e = s; *e; e++)
+			;
+		n = e - s;
+	}
+	return _fmtrcpy(f, s, n);
+}
+
+/* fmt out a null terminated rune string */
+int
+_runesfmt(Fmt *f)
+{
+	Rune *s;
+
+	s = va_arg(f->args, Rune *);
+	return fmtrunestrcpy(f, s);
+}
+
+/* fmt a % */
+int
+_percentfmt(Fmt *f)
+{
+	Rune x[1];
+
+	x[0] = f->r;
+	f->prec = 1;
+	return _fmtrcpy(f, x, 1);
+}
+
+/* fmt an integer */
+int
+_ifmt(Fmt *f)
+{
+	char buf[70], *p, *conv;
+	uvlong vu;
+	ulong u;
+	int neg, base, i, n, fl, w, isv;
+
+	neg = 0;
+	fl = f->flags;
+	isv = 0;
+	vu = 0;
+	u = 0;
+	if(f->r == 'p'){
+		u = (ulong)va_arg(f->args, void*);
+		f->r = 'x';
+		fl |= FmtUnsigned;
+	}else if(fl & FmtVLong){
+		isv = 1;
+		if(fl & FmtUnsigned)
+			vu = va_arg(f->args, uvlong);
+		else
+			vu = va_arg(f->args, vlong);
+	}else if(fl & FmtLong){
+		if(fl & FmtUnsigned)
+			u = va_arg(f->args, ulong);
+		else
+			u = va_arg(f->args, long);
+	}else if(fl & FmtByte){
+		if(fl & FmtUnsigned)
+			u = (uchar)va_arg(f->args, int);
+		else
+			u = (char)va_arg(f->args, int);
+	}else if(fl & FmtShort){
+		if(fl & FmtUnsigned)
+			u = (ushort)va_arg(f->args, int);
+		else
+			u = (short)va_arg(f->args, int);
+	}else{
+		if(fl & FmtUnsigned)
+			u = va_arg(f->args, uint);
+		else
+			u = va_arg(f->args, int);
+	}
+	conv = "0123456789abcdef";
+	switch(f->r){
+	case 'd':
+		base = 10;
+		break;
+	case 'x':
+		base = 16;
+		break;
+	case 'X':
+		base = 16;
+		conv = "0123456789ABCDEF";
+		break;
+	case 'b':
+		base = 2;
+		break;
+	case 'o':
+		base = 8;
+		break;
+	default:
+		return -1;
+	}
+	if(!(fl & FmtUnsigned)){
+		if(isv && (vlong)vu < 0){
+			vu = -(vlong)vu;
+			neg = 1;
+		}else if(!isv && (long)u < 0){
+			u = -(long)u;
+			neg = 1;
+		}
+	}
+	p = buf + sizeof buf - 1;
+	n = 0;
+	if(isv){
+		while(vu){
+			i = vu % base;
+			vu /= base;
+			if((fl & FmtComma) && n % 4 == 3){
+				*p-- = ',';
+				n++;
+			}
+			*p-- = conv[i];
+			n++;
+		}
+	}else{
+		while(u){
+			i = u % base;
+			u /= base;
+			if((fl & FmtComma) && n % 4 == 3){
+				*p-- = ',';
+				n++;
+			}
+			*p-- = conv[i];
+			n++;
+		}
+	}
+	if(n == 0){
+		*p-- = '0';
+		n = 1;
+	}
+	for(w = f->prec; n < w && p > buf+3; n++)
+		*p-- = '0';
+	if(neg || (fl & (FmtSign|FmtSpace)))
+		n++;
+	if(fl & FmtSharp){
+		if(base == 16)
+			n += 2;
+		else if(base == 8){
+			if(p[1] == '0')
+				fl &= ~FmtSharp;
+			else
+				n++;
+		}
+	}
+	if((fl & FmtZero) && !(fl & FmtLeft)){
+		for(w = f->width; n < w && p > buf+3; n++)
+			*p-- = '0';
+		f->width = 0;
+	}
+	if(fl & FmtSharp){
+		if(base == 16)
+			*p-- = f->r;
+		if(base == 16 || base == 8)
+			*p-- = '0';
+	}
+	if(neg)
+		*p-- = '-';
+	else if(fl & FmtSign)
+		*p-- = '+';
+	else if(fl & FmtSpace)
+		*p-- = ' ';
+	f->flags &= ~FmtPrec;
+	return _fmtcpy(f, p + 1, n, n);
+}
+
+int
+_countfmt(Fmt *f)
+{
+	void *p;
+	ulong fl;
+
+	fl = f->flags;
+	p = va_arg(f->args, void*);
+	if(fl & FmtVLong){
+		*(vlong*)p = f->nfmt;
+	}else if(fl & FmtLong){
+		*(long*)p = f->nfmt;
+	}else if(fl & FmtByte){
+		*(char*)p = f->nfmt;
+	}else if(fl & FmtShort){
+		*(short*)p = f->nfmt;
+	}else{
+		*(int*)p = f->nfmt;
+	}
+	return 0;
+}
+
+int
+_flagfmt(Fmt *f)
+{
+	switch(f->r){
+	case ',':
+		f->flags |= FmtComma;
+		break;
+	case '-':
+		f->flags |= FmtLeft;
+		break;
+	case '+':
+		f->flags |= FmtSign;
+		break;
+	case '#':
+		f->flags |= FmtSharp;
+		break;
+	case ' ':
+		f->flags |= FmtSpace;
+		break;
+	case 'u':
+		f->flags |= FmtUnsigned;
+		break;
+	case 'h':
+		if(f->flags & FmtShort)
+			f->flags |= FmtByte;
+		f->flags |= FmtShort;
+		break;
+	case 'l':
+		if(f->flags & FmtLong)
+			f->flags |= FmtVLong;
+		f->flags |= FmtLong;
+		break;
+	}
+	return 1;
+}
+
+/* default error format */
+int
+_badfmt(Fmt *f)
+{
+	char x[3];
+
+	x[0] = '%';
+	x[1] = f->r;
+	x[2] = '%';
+	f->prec = 3;
+	_fmtcpy(f, x, 3, 3);
+	return 0;
+}
--- /dev/null
+++ b/libc/dorfmt.c
@@ -1,0 +1,46 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+/* format the output into f->to and return the number of characters fmted  */
+
+int
+dorfmt(Fmt *f, Rune *fmt)
+{
+	Rune *rt, *rs;
+	int r;
+	char *t, *s;
+	int nfmt;
+
+	nfmt = f->nfmt;
+	for(;;){
+		if(f->runes){
+			rt = f->to;
+			rs = f->stop;
+			while((r = *fmt++) && r != '%'){
+				FMTRCHAR(f, rt, rs, r);
+			}
+			f->nfmt += rt - (Rune *)f->to;
+			f->to = rt;
+			if(!r)
+				return f->nfmt - nfmt;
+			f->stop = rs;
+		}else{
+			t = f->to;
+			s = f->stop;
+			while((r = *fmt++) && r != '%'){
+				FMTRUNE(f, t, f->stop, r);
+			}
+			f->nfmt += t - (char *)f->to;
+			f->to = t;
+			if(!r)
+				return f->nfmt - nfmt;
+			f->stop = s;
+		}
+
+		fmt = _fmtdispatch(f, fmt, 1);
+		if(fmt == nil)
+			return -1;
+	}
+	return 0;		/* not reached */
+}
--- /dev/null
+++ b/libc/errfmt.c
@@ -1,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+int
+errfmt(Fmt *f)
+{
+	char buf[ERRMAX];
+
+	rerrstr(buf, sizeof buf);
+	return _fmtcpy(f, buf, utflen(buf), strlen(buf));
+}
--- /dev/null
+++ b/libc/fcallfmt.c
@@ -1,0 +1,234 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+static uint dumpsome(char*, char*, char*, long);
+static void fdirconv(char*, char*, Dir*);
+static char *qidtype(char*, uchar);
+
+#define	QIDFMT	"(%.16llux %lud %s)"
+
+int
+fcallfmt(Fmt *fmt)
+{
+	Fcall *f;
+	int fid, type, tag, i;
+	char buf[512], tmp[200];
+	char *p, *e;
+	Dir *d;
+	Qid *q;
+
+	e = buf+sizeof(buf);
+	f = va_arg(fmt->args, Fcall*);
+	type = f->type;
+	fid = f->fid;
+	tag = f->tag;
+	switch(type){
+	case Tversion:	/* 100 */
+		seprint(buf, e, "Tversion tag %ud msize %ud version '%s'", tag, f->msize, f->version);
+		break;
+	case Rversion:
+		seprint(buf, e, "Rversion tag %ud msize %ud version '%s'", tag, f->msize, f->version);
+		break;
+	case Tauth:	/* 102 */
+		seprint(buf, e, "Tauth tag %ud afid %d uname %s aname %s", tag,
+			f->afid, f->uname, f->aname);
+		break;
+	case Rauth:
+		seprint(buf, e, "Rauth tag %ud qid " QIDFMT, tag,
+			f->aqid.path, f->aqid.vers, qidtype(tmp, f->aqid.type));
+		break;
+	case Tattach:	/* 104 */
+		seprint(buf, e, "Tattach tag %ud fid %d afid %d uname %s aname %s", tag,
+			fid, f->afid, f->uname, f->aname);
+		break;
+	case Rattach:
+		seprint(buf, e, "Rattach tag %ud qid " QIDFMT, tag,
+			f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type));
+		break;
+	case Rerror:	/* 107; 106 (Terror) illegal */
+		seprint(buf, e, "Rerror tag %ud ename %s", tag, f->ename);
+		break;
+	case Tflush:	/* 108 */
+		seprint(buf, e, "Tflush tag %ud oldtag %ud", tag, f->oldtag);
+		break;
+	case Rflush:
+		seprint(buf, e, "Rflush tag %ud", tag);
+		break;
+	case Twalk:	/* 110 */
+		p = seprint(buf, e, "Twalk tag %ud fid %d newfid %d nwname %d ", tag, fid, f->newfid, f->nwname);
+		if(f->nwname <= MAXWELEM)
+			for(i=0; i<f->nwname; i++)
+				p = seprint(p, e, "%d:%s ", i, f->wname[i]);
+		break;
+	case Rwalk:
+		p = seprint(buf, e, "Rwalk tag %ud nwqid %ud ", tag, f->nwqid);
+		if(f->nwqid <= MAXWELEM)
+			for(i=0; i<f->nwqid; i++){
+				q = &f->wqid[i];
+				p = seprint(p, e, "%d:" QIDFMT " ", i,
+					q->path, q->vers, qidtype(tmp, q->type));
+			}
+		break;
+	case Topen:	/* 112 */
+		seprint(buf, e, "Topen tag %ud fid %ud mode %d", tag, fid, f->mode);
+		break;
+	case Ropen:
+		seprint(buf, e, "Ropen tag %ud qid " QIDFMT " iounit %ud ", tag,
+			f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type), f->iounit);
+		break;
+	case Tcreate:	/* 114 */
+		seprint(buf, e, "Tcreate tag %ud fid %ud name %s perm %M mode %d", tag, fid, f->name, (ulong)f->perm, f->mode);
+		break;
+	case Rcreate:
+		seprint(buf, e, "Rcreate tag %ud qid " QIDFMT " iounit %ud ", tag,
+			f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type), f->iounit);
+		break;
+	case Tread:	/* 116 */
+		seprint(buf, e, "Tread tag %ud fid %d offset %lld count %ud",
+			tag, fid, f->offset, f->count);
+		break;
+	case Rread:
+		p = seprint(buf, e, "Rread tag %ud count %ud ", tag, f->count);
+			dumpsome(p, e, f->data, f->count);
+		break;
+	case Twrite:	/* 118 */
+		p = seprint(buf, e, "Twrite tag %ud fid %d offset %lld count %ud ",
+			tag, fid, f->offset, f->count);
+		dumpsome(p, e, f->data, f->count);
+		break;
+	case Rwrite:
+		seprint(buf, e, "Rwrite tag %ud count %ud", tag, f->count);
+		break;
+	case Tclunk:	/* 120 */
+		seprint(buf, e, "Tclunk tag %ud fid %ud", tag, fid);
+		break;
+	case Rclunk:
+		seprint(buf, e, "Rclunk tag %ud", tag);
+		break;
+	case Tremove:	/* 122 */
+		seprint(buf, e, "Tremove tag %ud fid %ud", tag, fid);
+		break;
+	case Rremove:
+		seprint(buf, e, "Rremove tag %ud", tag);
+		break;
+	case Tstat:	/* 124 */
+		seprint(buf, e, "Tstat tag %ud fid %ud", tag, fid);
+		break;
+	case Rstat:
+		p = seprint(buf, e, "Rstat tag %ud ", tag);
+		if(f->nstat > sizeof tmp)
+			seprint(p, e, " stat(%d bytes)", f->nstat);
+		else{
+			d = (Dir*)tmp;
+			convM2D(f->stat, f->nstat, d, (char*)(d+1));
+			seprint(p, e, " stat ");
+			fdirconv(p+6, e, d);
+		}
+		break;
+	case Twstat:	/* 126 */
+		p = seprint(buf, e, "Twstat tag %ud fid %ud", tag, fid);
+		if(f->nstat > sizeof tmp)
+			seprint(p, e, " stat(%d bytes)", f->nstat);
+		else{
+			d = (Dir*)tmp;
+			convM2D(f->stat, f->nstat, d, (char*)(d+1));
+			seprint(p, e, " stat ");
+			fdirconv(p+6, e, d);
+		}
+		break;
+	case Rwstat:
+		seprint(buf, e, "Rwstat tag %ud", tag);
+		break;
+	default:
+		seprint(buf, e,  "unknown type %d", type);
+	}
+	return fmtstrcpy(fmt, buf);
+}
+
+static char*
+qidtype(char *s, uchar t)
+{
+	char *p;
+
+	p = s;
+	if(t & QTDIR)
+		*p++ = 'd';
+	if(t & QTAPPEND)
+		*p++ = 'a';
+	if(t & QTEXCL)
+		*p++ = 'l';
+	if(t & QTAUTH)
+		*p++ = 'A';
+	*p = '\0';
+	return s;
+}
+
+int
+dirfmt(Fmt *fmt)
+{
+	char buf[160];
+
+	fdirconv(buf, buf+sizeof buf, va_arg(fmt->args, Dir*));
+	return fmtstrcpy(fmt, buf);
+}
+
+static void
+fdirconv(char *buf, char *e, Dir *d)
+{
+	char tmp[16];
+
+	seprint(buf, e, "'%s' '%s' '%s' '%s' "
+		"q " QIDFMT " m %#luo "
+		"at %ld mt %ld l %lld "
+		"t %d d %d",
+			d->name, d->uid, d->gid, d->muid,
+			d->qid.path, d->qid.vers, qidtype(tmp, d->qid.type), d->mode,
+			d->atime, d->mtime, d->length,
+			d->type, d->dev);
+}
+
+/*
+ * dump out count (or DUMPL, if count is bigger) bytes from
+ * buf to ans, as a string if they are all printable,
+ * else as a series of hex bytes
+ */
+#define DUMPL 64
+
+static uint
+dumpsome(char *ans, char *e, char *buf, long count)
+{
+	int i, printable;
+	char *p;
+
+	if(buf == nil){
+		seprint(ans, e, "<no data>");
+		return strlen(ans);
+	}
+	printable = 1;
+	if(count > DUMPL)
+		count = DUMPL;
+	for(i=0; i<count && printable; i++)
+		if((buf[i]<32 && buf[i] !='\n' && buf[i] !='\t') || buf[i]>127)
+			printable = 0;
+	p = ans;
+	*p++ = '\'';
+	if(printable){
+		if(count > e-p-2)
+			count = e-p-2;
+		memmove(p, buf, count);
+		p += count;
+	}else{
+		if(2*count > e-p-2)
+			count = (e-p-2)/2;
+		for(i=0; i<count; i++){
+			if(i>0 && i%4==0)
+				*p++ = ' ';
+			sprint(p, "%2.2ux", buf[i]);
+			p += 2;
+		}
+	}
+	*p++ = '\'';
+	*p = 0;
+	return p - ans;
+}
--- /dev/null
+++ b/libc/fltfmt.c
@@ -1,0 +1,312 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include "fmtdef.h"
+
+enum
+{
+	FDIGIT	= 30,
+	FDEFLT	= 6,
+	NSIGNIF	= 17,
+};
+
+static int
+xadd(char *a, int n, int v)
+{
+	char *b;
+	int c;
+
+	if(n < 0 || n >= NSIGNIF)
+		return 0;
+	for(b = a+n; b >= a; b--) {
+		c = *b + v;
+		if(c <= '9') {
+			*b = c;
+			return 0;
+		}
+		*b = '0';
+		v = 1;
+	}
+	*a = '1';	// overflow adding
+	return 1;
+}
+
+static int
+xsub(char *a, int n, int v)
+{
+	char *b;
+	int c;
+
+	for(b = a+n; b >= a; b--) {
+		c = *b - v;
+		if(c >= '0') {
+			*b = c;
+			return 0;
+		}
+		*b = '9';
+		v = 1;
+	}
+	*a = '9';	// underflow subtracting
+	return 1;
+}
+
+static void
+xdtoa(Fmt *fmt, char *s2, double f)
+{
+	char s1[NSIGNIF+10];
+	double g, h;
+	int e, d, i, n;
+	int c1, c2, c3, c4, ucase, sign, chr, prec;
+
+	prec = FDEFLT;
+	if(fmt->flags & FmtPrec)
+		prec = fmt->prec;
+	if(prec > FDIGIT)
+		prec = FDIGIT;
+	if(__isNaN(f)) {
+		strcpy(s2, "NaN");
+		return;
+	}
+	if(__isInf(f, 1)) {
+		strcpy(s2, "+Inf");
+		return;
+	}
+	if(__isInf(f, -1)) {
+		strcpy(s2, "-Inf");
+		return;
+	}
+	sign = 0;
+	if(f < 0) {
+		f = -f;
+		sign++;
+	}
+	ucase = 0;
+	chr = fmt->r;
+	if(isupper(chr)) {
+		ucase = 1;
+		chr = tolower(chr);
+	}
+
+	e = 0;
+	g = f;
+	if(g != 0) {
+		frexp(f, &e);
+		e = e * .301029995664;
+		if(e >= -150 && e <= +150) {
+			d = 0;
+			h = f;
+		} else {
+			d = e/2;
+			h = f * pow10(-d);
+		}
+		g = h * pow10(d-e);
+		while(g < 1) {
+			e--;
+			g = h * pow10(d-e);
+		}
+		while(g >= 10) {
+			e++;
+			g = h * pow10(d-e);
+		}
+	}
+
+	/*
+	 * convert NSIGNIF digits and convert
+	 * back to get accuracy.
+	 */
+	for(i=0; i<NSIGNIF; i++) {
+		d = g;
+		s1[i] = d + '0';
+		g = (g - d) * 10;
+	}
+	s1[i] = 0;
+
+	/*
+	 * try decimal rounding to eliminate 9s
+	 */
+	c2 = prec + 1;
+	if(chr == 'f')
+		c2 += e;
+	if(c2 >= NSIGNIF-2) {
+		strcpy(s2, s1);
+		d = e;
+		s1[NSIGNIF-2] = '0';
+		s1[NSIGNIF-1] = '0';
+		sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
+		g = strtod(s1, nil);
+		if(g == f)
+			goto found;
+		if(xadd(s1, NSIGNIF-3, 1)) {
+			e++;
+			sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
+		}
+		g = strtod(s1, nil);
+		if(g == f)
+			goto found;
+		strcpy(s1, s2);
+		e = d;
+	}
+
+	/*
+	 * convert back so s1 gets exact answer
+	 */
+	for(;;) {
+		sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
+		g = strtod(s1, nil);
+		if(f > g) {
+			if(xadd(s1, NSIGNIF-1, 1))
+				e--;
+			continue;
+		}
+		if(f < g) {
+			if(xsub(s1, NSIGNIF-1, 1))
+				e++;
+			continue;
+		}
+		break;
+	}
+
+found:
+	/*
+	 * sign
+	 */
+	d = 0;
+	i = 0;
+	if(sign)
+		s2[d++] = '-';
+	else if(fmt->flags & FmtSign)
+		s2[d++] = '+';
+	else if(fmt->flags & FmtSpace)
+		s2[d++] = ' ';
+
+	/*
+	 * copy into final place
+	 * c1 digits of leading '0'
+	 * c2 digits from conversion
+	 * c3 digits of trailing '0'
+	 * c4 digits after '.'
+	 */
+	c1 = 0;
+	c2 = prec + 1;
+	c3 = 0;
+	c4 = prec;
+	switch(chr) {
+	default:
+		if(xadd(s1, c2, 5))
+			e++;
+		break;
+	case 'g':
+		/*
+		 * decide on 'e' of 'f' style convers
+		 */
+		if(xadd(s1, c2, 5))
+			e++;
+		if(e >= -5 && e <= prec) {
+			c1 = -e - 1;
+			c4 = prec - e;
+			chr = 'h';	// flag for 'f' style
+		}
+		break;
+	case 'f':
+		if(xadd(s1, c2+e, 5))
+			e++;
+		c1 = -e;
+		if(c1 > prec)
+			c1 = c2;
+		c2 += e;
+		break;
+	}
+
+	/*
+	 * clean up c1 c2 and c3
+	 */
+	if(c1 < 0)
+		c1 = 0;
+	if(c2 < 0)
+		c2 = 0;
+	if(c2 > NSIGNIF) {
+		c3 = c2-NSIGNIF;
+		c2 = NSIGNIF;
+	}
+
+	/*
+	 * copy digits
+	 */
+	while(c1 > 0) {
+		if(c1+c2+c3 == c4)
+			s2[d++] = '.';
+		s2[d++] = '0';
+		c1--;
+	}
+	while(c2 > 0) {
+		if(c2+c3 == c4)
+			s2[d++] = '.';
+		s2[d++] = s1[i++];
+		c2--;
+	}
+	while(c3 > 0) {
+		if(c3 == c4)
+			s2[d++] = '.';
+		s2[d++] = '0';
+		c3--;
+	}
+
+	/*
+	 * strip trailing '0' on g conv
+	 */
+	if(fmt->flags & FmtSharp) {
+		if(0 == c4)
+			s2[d++] = '.';
+	} else
+	if(chr == 'g' || chr == 'h') {
+		for(n=d-1; n>=0; n--)
+			if(s2[n] != '0')
+				break;
+		for(i=n; i>=0; i--)
+			if(s2[i] == '.') {
+				d = n;
+				if(i != n)
+					d++;
+				break;
+			}
+	}
+	if(chr == 'e' || chr == 'g') {
+		if(ucase)
+			s2[d++] = 'E';
+		else
+			s2[d++] = 'e';
+		c1 = e;
+		if(c1 < 0) {
+			s2[d++] = '-';
+			c1 = -c1;
+		} else
+			s2[d++] = '+';
+		if(c1 >= 100) {
+			s2[d++] = c1/100 + '0';
+			c1 = c1%100;
+		}
+		s2[d++] = c1/10 + '0';
+		s2[d++] = c1%10 + '0';
+	}
+	s2[d] = 0;
+}
+
+int
+_floatfmt(Fmt *fmt, double f)
+{
+	char s[FDIGIT+10];
+
+	xdtoa(fmt, s, f);
+	fmt->flags &= FmtWidth|FmtLeft;
+	_fmtcpy(fmt, s, strlen(s), strlen(s));
+	return 0;
+}
+
+int
+_efgfmt(Fmt *f)
+{
+	double d;
+
+	d = va_arg(f->args, double);
+	return _floatfmt(f, d);
+}
--- /dev/null
+++ b/libc/fmt.c
@@ -1,0 +1,193 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+enum
+{
+	Maxfmt = 64
+};
+
+typedef struct Convfmt Convfmt;
+struct Convfmt
+{
+	int	c;
+	volatile	Fmts	fmt;	/* for spin lock in fmtfmt; avoids race due to write order */
+};
+
+struct
+{
+	/* lock by calling _fmtlock, _fmtunlock */
+	int	nfmt;
+	Convfmt	fmt[Maxfmt];
+} fmtalloc;
+
+static Convfmt knownfmt[] = {
+	' ',	_flagfmt,
+	'#',	_flagfmt,
+	'%',	_percentfmt,
+	'+',	_flagfmt,
+	',',	_flagfmt,
+	'-',	_flagfmt,
+	'C',	_runefmt,
+	'E',	_efgfmt,
+	'G',	_efgfmt,
+	'S',	_runesfmt,
+	'X',	_ifmt,
+	'b',	_ifmt,
+	'c',	_charfmt,
+	'd',	_ifmt,
+	'e',	_efgfmt,
+	'f',	_efgfmt,
+	'g',	_efgfmt,
+	'h',	_flagfmt,
+	'l',	_flagfmt,
+	'n',	_countfmt,
+	'o',	_ifmt,
+	'p',	_ifmt,
+/*	'r',	errfmt, */
+	's',	_strfmt,
+	'u',	_flagfmt,
+	'x',	_ifmt,
+	0,	nil,
+};
+
+int	(*doquote)(int);
+
+/*
+ * _fmtlock() must be set
+ */
+static int
+_fmtinstall(int c, Fmts f)
+{
+	Convfmt *p, *ep;
+
+	if(c<=0 || c>=65536)
+		return -1;
+	if(!f)
+		f = _badfmt;
+
+	ep = &fmtalloc.fmt[fmtalloc.nfmt];
+	for(p=fmtalloc.fmt; p<ep; p++)
+		if(p->c == c)
+			break;
+
+	if(p == &fmtalloc.fmt[Maxfmt])
+		return -1;
+
+	p->fmt = f;
+	if(p == ep){	/* installing a new format character */
+		fmtalloc.nfmt++;
+		p->c = c;
+	}
+
+	return 0;
+}
+
+int
+fmtinstall(int c, Fmts f)
+{
+	int ret;
+
+	_fmtlock();
+	ret = _fmtinstall(c, f);
+	_fmtunlock();
+	return ret;
+}
+
+static Fmts
+fmtfmt(int c)
+{
+	Convfmt *p, *ep;
+
+	ep = &fmtalloc.fmt[fmtalloc.nfmt];
+	for(p=fmtalloc.fmt; p<ep; p++)
+		if(p->c == c){
+			while(p->fmt == nil)	/* loop until value is updated */
+				;
+			return p->fmt;
+		}
+
+	/* is this a predefined format char? */
+	_fmtlock();
+	for(p=knownfmt; p->c; p++)
+		if(p->c == c){
+			_fmtinstall(p->c, p->fmt);
+			_fmtunlock();
+			return p->fmt;
+		}
+	_fmtunlock();
+
+	return _badfmt;
+}
+
+void*
+_fmtdispatch(Fmt *f, void *fmt, int isrunes)
+{
+	Rune rune, r;
+	int i, n;
+
+	f->flags = 0;
+	f->width = f->prec = 0;
+
+	for(;;){
+		if(isrunes){
+			r = *(Rune*)fmt;
+			fmt = (Rune*)fmt + 1;
+		}else{
+			fmt = (char*)fmt + chartorune(&rune, fmt);
+			r = rune;
+		}
+		f->r = r;
+		switch(r){
+		case '\0':
+			return nil;
+		case '.':
+			f->flags |= FmtWidth|FmtPrec;
+			continue;
+		case '0':
+			if(!(f->flags & FmtWidth)){
+				f->flags |= FmtZero;
+				continue;
+			}
+			/* fall through */
+		case '1': case '2': case '3': case '4':
+		case '5': case '6': case '7': case '8': case '9':
+			i = 0;
+			while(r >= '0' && r <= '9'){
+				i = i * 10 + r - '0';
+				if(isrunes){
+					r = *(Rune*)fmt;
+					fmt = (Rune*)fmt + 1;
+				}else{
+					r = *(char*)fmt;
+					fmt = (char*)fmt + 1;
+				}
+			}
+			if(isrunes)
+				fmt = (Rune*)fmt - 1;
+			else
+				fmt = (char*)fmt - 1;
+		numflag:
+			if(f->flags & FmtWidth){
+				f->flags |= FmtPrec;
+				f->prec = i;
+			}else{
+				f->flags |= FmtWidth;
+				f->width = i;
+			}
+			continue;
+		case '*':
+			i = va_arg(f->args, int);
+			if(i < 0){
+				i = -i;
+				f->flags |= FmtLeft;
+			}
+			goto numflag;
+		}
+		n = (*fmtfmt(r))(f);
+		if(n < 0)
+			return nil;
+		if(n == 0)
+			return fmt;
+	}
+}
--- /dev/null
+++ b/libc/fmt.h
@@ -1,0 +1,99 @@
+
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ *              Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+
+#ifndef _FMTH_
+#define _FMTH_ 1
+
+#include <stdarg.h>
+
+#ifndef _UTFH_
+#include <utf.h>
+#endif
+
+typedef struct Fmt	Fmt;
+struct Fmt{
+	unsigned char	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;
+	unsigned long	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,
+	FmtLDouble	= FmtByte << 1,
+
+	FmtFlag		= FmtLDouble << 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	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	char*	fmtstrflush(Fmt*);
+
+extern	int	quotestrfmt(Fmt *f);
+extern	void	quotefmtinstall(void);
+extern	int	(*fmtdoquote)(int);
+
+
+extern	int	fmtinstall(int, int (*)(Fmt*));
+extern	int	dofmt(Fmt*, char*);
+extern	int	fmtprint(Fmt*, char*, ...);
+extern	int	fmtvprint(Fmt*, char*, va_list);
+extern	int	fmtrune(Fmt*, int);
+extern	int	fmtstrcpy(Fmt*, char*);
+
+extern	double	fmtstrtod(const char *, char **);
+extern	double	fmtcharstod(int(*)(void*), void*);
+
+#endif
--- /dev/null
+++ b/libc/fmtdef.h
@@ -1,0 +1,85 @@
+/*
+ * dofmt -- format to a buffer
+ * the number of characters formatted is returned,
+ * or -1 if there was an error.
+ * if the buffer is ever filled, flush is called.
+ * it should reset the buffer and return whether formatting should continue.
+ */
+
+typedef int (*Fmts)(Fmt*);
+
+typedef struct Quoteinfo Quoteinfo;
+struct Quoteinfo
+{
+	int	quoted;		/* if set, string must be quoted */
+	int	nrunesin;	/* number of input runes that can be accepted */
+	int	nbytesin;	/* number of input bytes that can be accepted */
+	int	nrunesout;	/* number of runes that will be generated */
+	int	nbytesout;	/* number of bytes that will be generated */
+};
+
+void	*_fmtflush(Fmt*, void*, int);
+void	*_fmtdispatch(Fmt*, void*, int);
+int	_floatfmt(Fmt*, double);
+int	_fmtpad(Fmt*, int);
+int	_rfmtpad(Fmt*, int);
+int	_fmtFdFlush(Fmt*);
+
+int	_efgfmt(Fmt*);
+int	_charfmt(Fmt*);
+int	_countfmt(Fmt*);
+int	_flagfmt(Fmt*);
+int	_percentfmt(Fmt*);
+int	_ifmt(Fmt*);
+int	_runefmt(Fmt*);
+int	_runesfmt(Fmt*);
+int	_strfmt(Fmt*);
+int	_badfmt(Fmt*);
+int	_fmtcpy(Fmt*, void*, int, int);
+int	_fmtrcpy(Fmt*, void*, int n);
+
+void	_fmtlock(void);
+void	_fmtunlock(void);
+
+#define FMTCHAR(f, t, s, c)\
+	do{\
+	if(t + 1 > (char*)s){\
+		t = _fmtflush(f, t, 1);\
+		if(t != nil)\
+			s = f->stop;\
+		else\
+			return -1;\
+	}\
+	*t++ = c;\
+	}while(0)
+
+#define FMTRCHAR(f, t, s, c)\
+	do{\
+	if(t + 1 > (Rune*)s){\
+		t = _fmtflush(f, t, sizeof(Rune));\
+		if(t != nil)\
+			s = f->stop;\
+		else\
+			return -1;\
+	}\
+	*t++ = c;\
+	}while(0)
+
+#define FMTRUNE(f, t, s, r)\
+	do{\
+	Rune _rune;\
+	int _runelen;\
+	if(t + UTFmax > (char*)s && t + (_runelen = runelen(r)) > (char*)s){\
+		t = _fmtflush(f, t, _runelen);\
+		if(t != nil)\
+			s = f->stop;\
+		else\
+			return -1;\
+	}\
+	if(r < Runeself)\
+		*t++ = r;\
+	else{\
+		_rune = r;\
+		t += runetochar(t, &_rune);\
+	}\
+	}while(0)
--- /dev/null
+++ b/libc/fmtfd.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+/*
+ * public routine for final flush of a formatting buffer
+ * to a file descriptor; returns total char count.
+ */
+int
+fmtfdflush(Fmt *f)
+{
+	if(_fmtFdFlush(f) <= 0)
+		return -1;
+	return f->nfmt;
+}
+
+/*
+ * initialize an output buffer for buffered printing
+ */
+int
+fmtfdinit(Fmt *f, int fd, char *buf, int size)
+{
+	f->runes = 0;
+	f->start = buf;
+	f->to = buf;
+	f->stop = buf + size;
+	f->flush = _fmtFdFlush;
+	f->farg = (void*)fd;
+	f->nfmt = 0;
+	return 0;
+}
--- /dev/null
+++ b/libc/fmtlock.c
@@ -1,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+
+static Lock fmtl;
+
+void
+_fmtlock(void)
+{
+	lock(&fmtl);
+}
+
+void
+_fmtunlock(void)
+{
+	unlock(&fmtl);
+}
--- /dev/null
+++ b/libc/fmtprint.c
@@ -1,0 +1,32 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+
+/*
+ * format a string into the output buffer
+ * designed for formats which themselves call fmt,
+ * but ignore any width flags
+ */
+int
+fmtprint(Fmt *f, char *fmt, ...)
+{
+	va_list va;
+	int n;
+
+	f->flags = 0;
+	f->width = 0;
+	f->prec = 0;
+	va = f->args;
+	va_start(f->args, fmt);
+	n = dofmt(f, fmt);
+	va_end(f->args);
+	f->flags = 0;
+	f->width = 0;
+	f->prec = 0;
+	f->args = va;
+	if(n >= 0)
+		return 0;
+	return n;
+}
+
--- /dev/null
+++ b/libc/fmtquote.c
@@ -1,0 +1,247 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+/*
+ * How many bytes of output UTF will be produced by quoting (if necessary) this string?
+ * How many runes? How much of the input will be consumed?
+ * The parameter q is filled in by _quotesetup.
+ * The string may be UTF or Runes (s or r).
+ * Return count does not include NUL.
+ * Terminate the scan at the first of:
+ *	NUL in input
+ *	count exceeded in input
+ *	count exceeded on output
+ * *ninp is set to number of input bytes accepted.
+ * nin may be <0 initially, to avoid checking input by count.
+ */
+void
+_quotesetup(char *s, Rune *r, int nin, int nout, Quoteinfo *q, int sharp, int runesout)
+{
+	int w;
+	Rune c;
+
+	q->quoted = 0;
+	q->nbytesout = 0;
+	q->nrunesout = 0;
+	q->nbytesin = 0;
+	q->nrunesin = 0;
+	if(sharp || nin==0 || (s && *s=='\0') || (r && *r=='\0')){
+		if(nout < 2)
+			return;
+		q->quoted = 1;
+		q->nbytesout = 2;
+		q->nrunesout = 2;
+	}
+	for(; nin!=0; nin-=w){
+		if(s)
+			w = chartorune(&c, s);
+		else{
+			c = *r;
+			w = runelen(c);
+		}
+
+		if(c == '\0')
+			break;
+		if(runesout){
+			if(q->nrunesout+1 > nout)
+				break;
+		}else{
+			if(q->nbytesout+w > nout)
+				break;
+		}
+
+		if((c <= L' ') || (c == L'\'') || (doquote!=nil && doquote(c))){
+			if(!q->quoted){
+				if(runesout){
+					if(1+q->nrunesout+1+1 > nout)	/* no room for quotes */
+						break;
+				}else{
+					if(1+q->nbytesout+w+1 > nout)	/* no room for quotes */
+						break;
+				}
+				q->nrunesout += 2;	/* include quotes */
+				q->nbytesout += 2;	/* include quotes */
+				q->quoted = 1;
+			}
+			if(c == '\'')	{
+				if(runesout){
+					if(1+q->nrunesout+1 > nout)	/* no room for quotes */
+						break;
+				}else{
+					if(1+q->nbytesout+w > nout)	/* no room for quotes */
+						break;
+				}
+				q->nbytesout++;
+				q->nrunesout++;	/* quotes reproduce as two characters */
+			}
+		}
+
+		/* advance input */
+		if(s)
+			s += w;
+		else
+			r++;
+		q->nbytesin += w;
+		q->nrunesin++;
+
+		/* advance output */
+		q->nbytesout += w;
+		q->nrunesout++;
+	}
+}
+
+static int
+qstrfmt(char *sin, Rune *rin, Quoteinfo *q, Fmt *f)
+{
+	Rune r, *rm, *rme;
+	char *t, *s, *m, *me;
+	Rune *rt, *rs;
+	ulong fl;
+	int nc, w;
+
+	m = sin;
+	me = m + q->nbytesin;
+	rm = rin;
+	rme = rm + q->nrunesin;
+
+	w = f->width;
+	fl = f->flags;
+	if(f->runes){
+		if(!(fl & FmtLeft) && _rfmtpad(f, w - q->nrunesout) < 0)
+			return -1;
+	}else{
+		if(!(fl & FmtLeft) && _fmtpad(f, w - q->nbytesout) < 0)
+			return -1;
+	}
+	t = f->to;
+	s = f->stop;
+	rt = f->to;
+	rs = f->stop;
+	if(f->runes)
+		FMTRCHAR(f, rt, rs, '\'');
+	else
+		FMTRUNE(f, t, s, '\'');
+	for(nc = q->nrunesin; nc > 0; nc--){
+		if(sin){
+			r = *(uchar*)m;
+			if(r < Runeself)
+				m++;
+			else if((me - m) >= UTFmax || fullrune(m, me-m))
+				m += chartorune(&r, m);
+			else
+				break;
+		}else{
+			if(rm >= rme)
+				break;
+			r = *(uchar*)rm++;
+		}
+		if(f->runes){
+			FMTRCHAR(f, rt, rs, r);
+			if(r == '\'')
+				FMTRCHAR(f, rt, rs, r);
+		}else{
+			FMTRUNE(f, t, s, r);
+			if(r == '\'')
+				FMTRUNE(f, t, s, r);
+		}
+	}
+
+	if(f->runes){
+		FMTRCHAR(f, rt, rs, '\'');
+		USED(rs);
+		f->nfmt += rt - (Rune *)f->to;
+		f->to = rt;
+		if(fl & FmtLeft && _rfmtpad(f, w - q->nrunesout) < 0)
+			return -1;
+	}else{
+		FMTRUNE(f, t, s, '\'');
+		USED(s);
+		f->nfmt += t - (char *)f->to;
+		f->to = t;
+		if(fl & FmtLeft && _fmtpad(f, w - q->nbytesout) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+int
+_quotestrfmt(int runesin, Fmt *f)
+{
+	int outlen;
+	Rune *r;
+	char *s;
+	Quoteinfo q;
+
+	f->flags &= ~FmtPrec;	/* ignored for %q %Q, so disable for %s %S in easy case */
+	if(runesin){
+		r = va_arg(f->args, Rune *);
+		s = nil;
+	}else{
+		s = va_arg(f->args, char *);
+		r = nil;
+	}
+	if(!s && !r)
+		return _fmtcpy(f, "<nil>", 5, 5);
+
+	if(f->flush)
+		outlen = 0x7FFFFFFF;	/* if we can flush, no output limit */
+	else if(f->runes)
+		outlen = (Rune*)f->stop - (Rune*)f->to;
+	else
+		outlen = (char*)f->stop - (char*)f->to;
+
+	_quotesetup(s, r, -1, outlen, &q, f->flags&FmtSharp, f->runes);
+//print("bytes in %d bytes out %d runes in %d runesout %d\n", q.nbytesin, q.nbytesout, q.nrunesin, q.nrunesout);
+
+	if(runesin){
+		if(!q.quoted)
+			return _fmtrcpy(f, r, q.nrunesin);
+		return qstrfmt(nil, r, &q, f);
+	}
+
+	if(!q.quoted)
+		return _fmtcpy(f, s, q.nrunesin, q.nbytesin);
+	return qstrfmt(s, nil, &q, f);
+}
+
+int
+quotestrfmt(Fmt *f)
+{
+	return _quotestrfmt(0, f);
+}
+
+int
+quoterunestrfmt(Fmt *f)
+{
+	return _quotestrfmt(1, f);
+}
+
+void
+quotefmtinstall(void)
+{
+	fmtinstall('q', quotestrfmt);
+	fmtinstall('Q', quoterunestrfmt);
+}
+
+int
+_needsquotes(char *s, int *quotelenp)
+{
+	Quoteinfo q;
+
+	_quotesetup(s, nil, -1, 0x7FFFFFFF, &q, 0, 0);
+	*quotelenp = q.nbytesout;
+
+	return q.quoted;
+}
+
+int
+_runeneedsquotes(Rune *r, int *quotelenp)
+{
+	Quoteinfo q;
+
+	_quotesetup(nil, r, -1, 0x7FFFFFFF, &q, 0, 0);
+	*quotelenp = q.nrunesout;
+
+	return q.quoted;
+}
--- /dev/null
+++ b/libc/fmtrune.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+int
+fmtrune(Fmt *f, int r)
+{
+	Rune *rt;
+	char *t;
+	int n;
+
+	if(f->runes){
+		rt = f->to;
+		FMTRCHAR(f, rt, f->stop, r);
+		f->to = rt;
+		n = 1;
+	}else{
+		t = f->to;
+		FMTRUNE(f, t, f->stop, r);
+		n = t - (char*)f->to;
+		f->to = t;
+	}
+	f->nfmt += n;
+	return 0;
+}
--- /dev/null
+++ b/libc/fmtstr.c
@@ -1,0 +1,11 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+fmtstrflush(Fmt *f)
+{
+	if(f->start == nil)
+		return nil;
+	*(char*)f->to = '\0';
+	return f->start;
+}
--- /dev/null
+++ b/libc/fmtvprint.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+
+/*
+ * format a string into the output buffer
+ * designed for formats which themselves call fmt,
+ * but ignore any width flags
+ */
+int
+fmtvprint(Fmt *f, char *fmt, va_list args)
+{
+	va_list va;
+	int n;
+
+	f->flags = 0;
+	f->width = 0;
+	f->prec = 0;
+	va = f->args;
+	f->args = args;
+	n = dofmt(f, fmt);
+	f->flags = 0;
+	f->width = 0;
+	f->prec = 0;
+	f->args = va;
+	if(n >= 0)
+		return 0;
+	return n;
+}
+
--- /dev/null
+++ b/libc/fprint.c
@@ -1,0 +1,14 @@
+#include	<u.h>
+#include	<libc.h>
+
+int
+fprint(int fd, char *fmt, ...)
+{
+	int n;
+	va_list args;
+
+	va_start(args, fmt);
+	n = vfprint(fd, fmt, args);
+	va_end(args);
+	return n;
+}
--- /dev/null
+++ b/libc/frand.c
@@ -1,0 +1,17 @@
+#include	<u.h>
+#include	<libc.h>
+
+#define	MASK	0x7fffffffL
+#define	NORM	(1.0/(1.0+MASK))
+
+double
+frand(void)
+{
+	double x;
+
+	do {
+		x = lrand() * NORM;
+		x = (x + lrand()) * NORM;
+	} while(x >= 1);
+	return x;
+}
--- /dev/null
+++ b/libc/getfields.c
@@ -1,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+
+int
+getfields(char *str, char **args, int max, int mflag, char *set)
+{
+	Rune r;
+	int nr, intok, narg;
+
+	if(max <= 0)
+		return 0;
+
+	narg = 0;
+	args[narg] = str;
+	if(!mflag)
+		narg++;
+	intok = 0;
+	for(;; str += nr) {
+		nr = chartorune(&r, str);
+		if(r == 0)
+			break;
+		if(utfrune(set, r)) {
+			if(narg >= max)
+				break;
+			*str = 0;
+			intok = 0;
+			args[narg] = str + nr;
+			if(!mflag)
+				narg++;
+		} else {
+			if(!intok && mflag)
+				narg++;
+			intok = 1;
+		}
+	}
+	return narg;
+}
--- /dev/null
+++ b/libc/getpid.c
@@ -1,0 +1,17 @@
+#include	<u.h>
+#include	<libc.h>
+
+int
+getpid(void)
+{
+	char b[20];
+	int f;
+
+	memset(b, 0, sizeof(b));
+	f = open("#c/pid", 0);
+	if(f >= 0) {
+		read(f, b, sizeof(b));
+		close(f);
+	}
+	return atol(b);
+}
--- /dev/null
+++ b/libc/lnrand.c
@@ -1,0 +1,18 @@
+#include	<u.h>
+#include	<libc.h>
+
+#define	MASK	0x7fffffffL
+
+long
+lnrand(long n)
+{
+	long slop, v;
+
+	if(n < 0)
+		return n;
+	slop = MASK % n;
+	do
+		v = lrand();
+	while(v <= slop);
+	return v % n;
+}
--- /dev/null
+++ b/libc/lock.c
@@ -1,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+
+int
+canlock(Lock *lk)
+{
+	return !tas(&lk->key);
+}
+
+void
+lock(Lock *lk)
+{
+	int i;
+
+	/* easy case */
+	if(canlock(lk))
+		return;
+
+	/* for multi processor machines */
+	for(i=0; i<100; i++)
+		if(canlock(lk))
+			return;
+
+	for(i=0; i<100; i++) {
+		osyield(0);
+		if(canlock(lk))
+			return;
+	}
+
+	/* looking bad - make sure it is not a priority problem */
+	for(i=0; i<12; i++) {
+		osmsleep(1<<i);
+		if(canlock(lk))
+			return;
+	}
+
+	/* we are in trouble */
+	for(;;) {
+		if(canlock(lk))
+			return;
+		iprint("lock loop %ld: val=%d &lock=%ux pc=%ux\n", getpid(), lk->key, lk, getcallerpc(&lk));
+		osmsleep(1000);
+	}
+}
+
+void
+unlock(Lock *lk)
+{
+	assert(lk->key);
+	lk->key = 0;
+}
+
+void
+ilock(Lock *lk)
+{
+	lock(lk);
+}
+
+void
+iunlock(Lock *lk)
+{
+	unlock(lk);
+}
+
--- /dev/null
+++ b/libc/lrand.c
@@ -1,0 +1,83 @@
+#include	<u.h>
+#include	<libc.h>
+
+/*
+ *	algorithm by
+ *	D. P. Mitchell & J. A. Reeds
+ */
+
+#define	LEN	607
+#define	TAP	273
+#define	MASK	0x7fffffffL
+#define	A	48271
+#define	M	2147483647
+#define	Q	44488
+#define	R	3399
+#define	NORM	(1.0/(1.0+MASK))
+
+static	ulong	rng_vec[LEN];
+static	ulong*	rng_tap = rng_vec;
+static	ulong*	rng_feed = 0;
+static	Lock	lk;
+
+static void
+isrand(long seed)
+{
+	long lo, hi, x;
+	int i;
+
+	rng_tap = rng_vec;
+	rng_feed = rng_vec+LEN-TAP;
+	seed = seed%M;
+	if(seed < 0)
+		seed += M;
+	if(seed == 0)
+		seed = 89482311;
+	x = seed;
+	/*
+	 *	Initialize by x[n+1] = 48271 * x[n] mod (2**31 - 1)
+	 */
+	for(i = -20; i < LEN; i++) {
+		hi = x / Q;
+		lo = x % Q;
+		x = A*lo - R*hi;
+		if(x < 0)
+			x += M;
+		if(i >= 0)
+			rng_vec[i] = x;
+	}
+}
+
+void
+srand(long seed)
+{
+	lock(&lk);
+	isrand(seed);
+	unlock(&lk);
+}
+
+long
+lrand(void)
+{
+	ulong x;
+
+	lock(&lk);
+
+	rng_tap--;
+	if(rng_tap < rng_vec) {
+		if(rng_feed == 0) {
+			isrand(1);
+			rng_tap--;
+		}
+		rng_tap += LEN;
+	}
+	rng_feed--;
+	if(rng_feed < rng_vec)
+		rng_feed += LEN;
+	x = (*rng_feed + *rng_tap) & MASK;
+	*rng_feed = x;
+
+	unlock(&lk);
+
+	return x;
+}
--- /dev/null
+++ b/libc/mallocz.c
@@ -1,0 +1,13 @@
+#include <u.h>
+#include <libc.h>
+
+void*
+mallocz(ulong n, int clr)
+{
+	void *v;
+
+	v = malloc(n);
+	if(v && clr)
+		memset(v, 0, n);
+	return v;
+}
--- /dev/null
+++ b/libc/mkfile
@@ -1,0 +1,90 @@
+<$DSRC/mkfile-$CONF
+TARG=libc.$L
+
+OFILES=\
+	charstod.$O\
+	cleanname.$O\
+	convD2M.$O\
+	convM2D.$O\
+	convM2S.$O\
+	convS2M.$O\
+	crypt.$O\
+	dial.$O\
+	dirfstat.$O\
+	dirfwstat.$O\
+	dirmodefmt.$O\
+	dirstat.$O\
+	dirwstat.$O\
+	dofmt.$O\
+	dorfmt.$O\
+	fcallfmt.$O\
+	fltfmt.$O\
+	fmt.$O\
+	fmtfd.$O\
+	fmtlock.$O\
+	fmtprint.$O\
+	fmtquote.$O\
+	fmtrune.$O\
+	fmtstr.$O\
+	fmtvprint.$O\
+	fprint.$O\
+	frand.$O\
+	getfields.$O\
+	getpid.$O\
+	lnrand.$O\
+	lock.$O\
+	lrand.$O\
+	mallocz.$O\
+	nan64.$O\
+	netmkaddr.$O\
+	nrand.$O\
+	nsec.$O\
+	pow10.$O\
+	pushssl.$O\
+	read9pmsg.$O\
+	readn.$O\
+	rune.$O\
+	runefmtstr.$O\
+	runeseprint.$O\
+	runesmprint.$O\
+	runesnprint.$O\
+	runesprint.$O\
+	runetype.$O\
+	runevseprint.$O\
+	runevsmprint.$O\
+	runevsnprint.$O\
+	seprint.$O\
+	smprint.$O\
+	snprint.$O\
+	sprint.$O\
+	strecpy.$O\
+	strtod.$O\
+	strtoll.$O\
+	sysfatal.$O\
+	time.$O\
+	tokenize.$O\
+	truerand.$O\
+	u16.$O\
+	u32.$O\
+	u64.$O\
+	utfecpy.$O\
+	utflen.$O\
+	utfnlen.$O\
+	utfrrune.$O\
+	utfrune.$O\
+	utfutf.$O\
+	vfprint.$O\
+	vseprint.$O\
+	vsmprint.$O\
+	vsnprint.$O
+
+HFILES=../include/libc.h\
+	fmt.h\
+	fmtdef.h\
+	nan.h\
+	strtod.h\
+	utf.h\
+	utfdef.h
+
+<$DSRC/mklib-$CONF
+
--- /dev/null
+++ b/libc/nan.h
@@ -1,0 +1,4 @@
+extern double __NaN(void);
+extern double __Inf(int);
+extern int __isNaN(double);
+extern int __isInf(double, int);
--- /dev/null
+++ b/libc/nan64.c
@@ -1,0 +1,55 @@
+/*
+ * 64-bit IEEE not-a-number routines.
+ * This is big/little-endian portable assuming that 
+ * the 64-bit doubles and 64-bit integers have the
+ * same byte ordering.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include "nan.h"
+
+// typedef unsigned long long uvlong;
+// typedef unsigned long ulong;
+
+static uvlong uvnan    = 0x7FF0000000000001;
+static uvlong uvinf    = 0x7FF0000000000000;
+static uvlong uvneginf = 0xFFF0000000000000;
+
+double
+__NaN(void)
+{
+	return *(double*)&uvnan;
+}
+
+int
+__isNaN(double d)
+{
+	uvlong x = *(uvlong*)&d;
+	return (ulong)(x>>32)==0x7FF00000 && !__isInf(d, 0);
+}
+
+double
+__Inf(int sign)
+{
+	if(sign < 0)
+		return *(double*)&uvinf;
+	else
+		return *(double*)&uvneginf;
+}
+
+int
+__isInf(double d, int sign)
+{
+	uvlong x;
+
+	x = *(uvlong*)&d;
+	if(sign == 0)
+		return x==uvinf || x==uvneginf;
+	else if(sign > 0)
+		return x==uvinf;
+	else
+		return x==uvneginf;
+}
+
+
--- /dev/null
+++ b/libc/netmkaddr.c
@@ -1,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+/*
+ *  make an address, add the defaults
+ */
+char *
+netmkaddr(char *linear, char *defnet, char *defsrv)
+{
+	static char addr[256];
+	char *cp;
+
+	/*
+	 *  dump network name
+	 */
+	cp = strchr(linear, '!');
+	if(cp == 0){
+		if(defnet==0){
+			if(defsrv)
+				snprint(addr, sizeof(addr), "net!%s!%s",
+					linear, defsrv);
+			else
+				snprint(addr, sizeof(addr), "net!%s", linear);
+		}
+		else {
+			if(defsrv)
+				snprint(addr, sizeof(addr), "%s!%s!%s", defnet,
+					linear, defsrv);
+			else
+				snprint(addr, sizeof(addr), "%s!%s", defnet,
+					linear);
+		}
+		return addr;
+	}
+
+	/*
+	 *  if there is already a service, use it
+	 */
+	cp = strchr(cp+1, '!');
+	if(cp)
+		return linear;
+
+	/*
+	 *  add default service
+	 */
+	if(defsrv == 0)
+		return linear;
+	snprint(addr, sizeof(addr), "%s!%s", linear, defsrv);
+
+	return addr;
+}
--- /dev/null
+++ b/libc/nrand.c
@@ -1,0 +1,18 @@
+#include	<u.h>
+#include	<libc.h>
+
+#define	MASK	0x7fffffffL
+
+int
+nrand(int n)
+{
+	long slop, v;
+
+	if(n < 0)
+		return n;
+	slop = MASK % n;
+	do
+		v = lrand();
+	while(v <= slop);
+	return v % n;
+}
--- /dev/null
+++ b/libc/nsec.c
@@ -1,0 +1,66 @@
+#include <u.h>
+#include <libc.h>
+
+
+static uvlong order = (uvlong) 0x0001020304050607;
+
+static void
+be2vlong(vlong *to, uchar *f)
+{
+	uchar *t, *o;
+	int i;
+
+	t = (uchar*)to;
+	o = (uchar*)&order;
+	for(i = 0; i < 8; i++)
+		t[o[i]] = f[i];
+}
+
+/*
+ *  After a fork with fd's copied, both fd's are pointing to
+ *  the same Chan structure.  Since the offset is kept in the Chan
+ *  structure, the seek's and read's in the two processes can
+ *  compete at moving the offset around.  Hence the retry loop.
+ *
+ *  Since the bintime version doesn't need a seek, it doesn't
+ *  have the loop.
+ */
+vlong
+nsec(void)
+{
+	char b[12+1];
+	static int f = -1;
+	static int usebintime;
+	int retries;
+	vlong t;
+
+	if(f < 0){
+		usebintime = 1;
+		f = open("/dev/bintime", OREAD|OCEXEC);
+		if(f < 0){
+			usebintime = 0;
+			f = open("/dev/nsec", OREAD|OCEXEC);
+			if(f < 0)
+				return 0;
+		}
+	}
+
+	if(usebintime){
+		if(read(f, b, sizeof(uvlong)) < 0)
+			goto error;
+		be2vlong(&t, (uchar*)b);
+		return t;
+	} else {
+		for(retries = 0; retries < 100; retries++){
+			if(seek(f, 0, 0) >= 0 && read(f, b, sizeof(b)-1) >= 0){
+				b[sizeof(b)-1] = 0;
+				return strtoll(b, 0, 0);
+			}
+		}
+	}
+
+error:
+	close(f);
+	f = -1;
+	return 0;
+}
--- /dev/null
+++ b/libc/pow10.c
@@ -1,0 +1,49 @@
+#include	<u.h>
+#include	<libc.h>
+
+/*
+ * this table might overflow 127-bit exponent representations.
+ * in that case, truncate it after 1.0e38.
+ * it is important to get all one can from this
+ * routine since it is used in atof to scale numbers.
+ * the presumption is that C converts fp numbers better
+ * than multipication of lower powers of 10.
+ */
+static
+double	tab[] =
+{
+	1.0e0,  1.0e1,  1.0e2,  1.0e3,  1.0e4,  1.0e5,  1.0e6,  1.0e7,  1.0e8,  1.0e9, 
+	1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19, 
+	1.0e20, 1.0e21, 1.0e22, 1.0e23, 1.0e24, 1.0e25, 1.0e26, 1.0e27, 1.0e28, 1.0e29, 
+	1.0e30, 1.0e31, 1.0e32, 1.0e33, 1.0e34, 1.0e35, 1.0e36, 1.0e37, 1.0e38, 1.0e39, 
+	1.0e40, 1.0e41, 1.0e42, 1.0e43, 1.0e44, 1.0e45, 1.0e46, 1.0e47, 1.0e48, 1.0e49, 
+	1.0e50, 1.0e51, 1.0e52, 1.0e53, 1.0e54, 1.0e55, 1.0e56, 1.0e57, 1.0e58, 1.0e59, 
+	1.0e60, 1.0e61, 1.0e62, 1.0e63, 1.0e64, 1.0e65, 1.0e66, 1.0e67, 1.0e68, 1.0e69, 
+	1.0e70, 1.0e71, 1.0e72, 1.0e73, 1.0e74, 1.0e75, 1.0e76, 1.0e77, 1.0e78, 1.0e79, 
+	1.0e80, 1.0e81, 1.0e82, 1.0e83, 1.0e84, 1.0e85, 1.0e86, 1.0e87, 1.0e88, 1.0e89, 
+	1.0e90, 1.0e91, 1.0e92, 1.0e93, 1.0e94, 1.0e95, 1.0e96, 1.0e97, 1.0e98, 1.0e99, 
+	1.0e100,1.0e101,1.0e102,1.0e103,1.0e104,1.0e105,1.0e106,1.0e107,1.0e108,1.0e109,
+	1.0e110,1.0e111,1.0e112,1.0e113,1.0e114,1.0e115,1.0e116,1.0e117,1.0e118,1.0e119,
+	1.0e120,1.0e121,1.0e122,1.0e123,1.0e124,1.0e125,1.0e126,1.0e127,1.0e128,1.0e129,
+	1.0e130,1.0e131,1.0e132,1.0e133,1.0e134,1.0e135,1.0e136,1.0e137,1.0e138,1.0e139,
+	1.0e140,1.0e141,1.0e142,1.0e143,1.0e144,1.0e145,1.0e146,1.0e147,1.0e148,1.0e149,
+	1.0e150,1.0e151,1.0e152,1.0e153,1.0e154,1.0e155,1.0e156,1.0e157,1.0e158,1.0e159,
+};
+
+double
+pow10(int n)
+{
+	int m;
+
+	if(n < 0) {
+		n = -n;
+		if(n < sizeof(tab)/sizeof(tab[0]))
+			return 1/tab[n];
+		m = n/2;
+		return 1/(pow10(m) * pow10(n-m));
+	}
+	if(n < sizeof(tab)/sizeof(tab[0]))
+		return tab[n];
+	m = n/2;
+	return pow10(m) * pow10(n-m);
+}
--- /dev/null
+++ b/libc/print.c
@@ -1,0 +1,14 @@
+#include	<u.h>
+#include	<libc.h>
+
+int
+print(char *fmt, ...)
+{
+	int n;
+	va_list args;
+
+	va_start(args, fmt);
+	n = vfprint(1, fmt, args);
+	va_end(args);
+	return n;
+}
--- /dev/null
+++ b/libc/pushssl.c
@@ -1,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * Since the SSL device uses decimal file descriptors to name channels,
+ * it is impossible for a user-level file server to stand in for the kernel device.
+ * Thus we hard-code #D rather than use /net/ssl.
+ */
+
+int
+pushssl(int fd, char *alg, char *secin, char *secout, int *cfd)
+{
+	char buf[8];
+	char dname[64];
+	int n, data, ctl;
+
+	ctl = open("#D/ssl/clone", ORDWR);
+	if(ctl < 0)
+		return -1;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0)
+		goto error;
+	buf[n] = 0;
+	sprint(dname, "#D/ssl/%s/data", buf);
+	data = open(dname, ORDWR);
+	if(data < 0)
+		goto error;
+	if(fprint(ctl, "fd %d", fd) < 0 ||
+	   fprint(ctl, "secretin %s", secin) < 0 ||
+	   fprint(ctl, "secretout %s", secout) < 0 ||
+	   fprint(ctl, "alg %s", alg) < 0){
+		close(data);
+		goto error;
+	}
+	close(fd);
+	if(cfd != 0)
+		*cfd = ctl;
+	else
+		close(ctl);
+	return data;
+error:
+	close(ctl);
+	return -1;
+}
--- /dev/null
+++ b/libc/rand.c
@@ -1,0 +1,8 @@
+#include	<u.h>
+#include	<libc.h>
+
+int
+rand(void)
+{
+	return lrand() & 0x7fff;
+}
--- /dev/null
+++ b/libc/read9pmsg.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+int
+read9pmsg(int fd, void *abuf, uint n)
+{
+	int m, len;
+	uchar *buf;
+
+	buf = abuf;
+
+	/* read count */
+	m = readn(fd, buf, BIT32SZ);
+	if(m != BIT32SZ){
+		if(m < 0)
+			return -1;
+		return 0;
+	}
+
+	len = GBIT32(buf);
+	if(len <= BIT32SZ || len > n){
+		werrstr("bad length in 9P2000 message header");
+		return -1;
+	}
+	len -= BIT32SZ;
+	m = readn(fd, buf+BIT32SZ, len);
+	if(m < len)
+		return 0;
+	return BIT32SZ+m;
+}
--- /dev/null
+++ b/libc/readn.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+
+long
+readn(int f, void *av, long n)
+{
+	char *a;
+	long m, t;
+
+	a = av;
+	t = 0;
+	while(t < n){
+		m = read(f, a+t, n-t);
+		if(m <= 0){
+			if(t == 0)
+				return m;
+			break;
+		}
+		t += m;
+	}
+	return t;
+}
--- /dev/null
+++ b/libc/rune.c
@@ -1,0 +1,162 @@
+#include	<u.h>
+#include	<libc.h>
+
+enum
+{
+	Bit1	= 7,
+	Bitx	= 6,
+	Bit2	= 5,
+	Bit3	= 4,
+	Bit4	= 3,
+
+	T1	= ((1<<(Bit1+1))-1) ^ 0xFF,	/* 0000 0000 */
+	Tx	= ((1<<(Bitx+1))-1) ^ 0xFF,	/* 1000 0000 */
+	T2	= ((1<<(Bit2+1))-1) ^ 0xFF,	/* 1100 0000 */
+	T3	= ((1<<(Bit3+1))-1) ^ 0xFF,	/* 1110 0000 */
+	T4	= ((1<<(Bit4+1))-1) ^ 0xFF,	/* 1111 0000 */
+
+	Rune1	= (1<<(Bit1+0*Bitx))-1,		/* 0000 0000 0111 1111 */
+	Rune2	= (1<<(Bit2+1*Bitx))-1,		/* 0000 0111 1111 1111 */
+	Rune3	= (1<<(Bit3+2*Bitx))-1,		/* 1111 1111 1111 1111 */
+
+	Maskx	= (1<<Bitx)-1,			/* 0011 1111 */
+	Testx	= Maskx ^ 0xFF,			/* 1100 0000 */
+
+	Bad	= Runeerror,
+};
+
+int
+chartorune(Rune *rune, char *str)
+{
+	int c, c1, c2;
+	long l;
+
+	/*
+	 * one character sequence
+	 *	00000-0007F => T1
+	 */
+	c = *(uchar*)str;
+	if(c < Tx) {
+		*rune = c;
+		return 1;
+	}
+
+	/*
+	 * two character sequence
+	 *	0080-07FF => T2 Tx
+	 */
+	c1 = *(uchar*)(str+1) ^ Tx;
+	if(c1 & Testx)
+		goto bad;
+	if(c < T3) {
+		if(c < T2)
+			goto bad;
+		l = ((c << Bitx) | c1) & Rune2;
+		if(l <= Rune1)
+			goto bad;
+		*rune = l;
+		return 2;
+	}
+
+	/*
+	 * three character sequence
+	 *	0800-FFFF => T3 Tx Tx
+	 */
+	c2 = *(uchar*)(str+2) ^ Tx;
+	if(c2 & Testx)
+		goto bad;
+	if(c < T4) {
+		l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3;
+		if(l <= Rune2)
+			goto bad;
+		*rune = l;
+		return 3;
+	}
+
+	/*
+	 * bad decoding
+	 */
+bad:
+	*rune = Bad;
+	return 1;
+}
+
+int
+runetochar(char *str, Rune *rune)
+{
+	long c;
+
+	/*
+	 * one character sequence
+	 *	00000-0007F => 00-7F
+	 */
+	c = *rune;
+	if(c <= Rune1) {
+		str[0] = c;
+		return 1;
+	}
+
+	/*
+	 * two character sequence
+	 *	0080-07FF => T2 Tx
+	 */
+	if(c <= Rune2) {
+		str[0] = T2 | (c >> 1*Bitx);
+		str[1] = Tx | (c & Maskx);
+		return 2;
+	}
+
+	/*
+	 * three character sequence
+	 *	0800-FFFF => T3 Tx Tx
+	 */
+	str[0] = T3 |  (c >> 2*Bitx);
+	str[1] = Tx | ((c >> 1*Bitx) & Maskx);
+	str[2] = Tx |  (c & Maskx);
+	return 3;
+}
+
+int
+runelen(long c)
+{
+	Rune rune;
+	char str[10];
+
+	rune = c;
+	return runetochar(str, &rune);
+}
+
+int
+runenlen(Rune *r, int nrune)
+{
+	int nb, c;
+
+	nb = 0;
+	while(nrune--) {
+		c = *r++;
+		if(c <= Rune1)
+			nb++;
+		else
+		if(c <= Rune2)
+			nb += 2;
+		else
+			nb += 3;
+	}
+	return nb;
+}
+
+int
+fullrune(char *str, int n)
+{
+	int c;
+
+	if(n > 0) {
+		c = *(uchar*)str;
+		if(c < Tx)
+			return 1;
+		if(n > 1)
+			if(c < T3 || n > 2)
+				return 1;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libc/runefmtstr.c
@@ -1,0 +1,11 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runefmtstrflush(Fmt *f)
+{
+	if(f->start == nil)
+		return nil;
+	*(Rune*)f->to = '\0';
+	return f->start;
+}
--- /dev/null
+++ b/libc/runeseprint.c
@@ -1,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runeseprint(Rune *buf, Rune *e, char *fmt, ...)
+{
+	Rune *p;
+	va_list args;
+
+	va_start(args, fmt);
+	p = runevseprint(buf, e, fmt, args);
+	va_end(args);
+	return p;
+}
--- /dev/null
+++ b/libc/runesmprint.c
@@ -1,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runesmprint(char *fmt, ...)
+{
+	va_list args;
+	Rune *p;
+
+	va_start(args, fmt);
+	p = runevsmprint(fmt, args);
+	va_end(args);
+	return p;
+}
--- /dev/null
+++ b/libc/runesnprint.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+
+int
+runesnprint(Rune *buf, int len, char *fmt, ...)
+{
+	int n;
+	va_list args;
+
+	va_start(args, fmt);
+	n = runevsnprint(buf, len, fmt, args);
+	va_end(args);
+	return n;
+}
+
--- /dev/null
+++ b/libc/runesprint.c
@@ -1,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+
+int
+runesprint(Rune *buf, char *fmt, ...)
+{
+	int n;
+	va_list args;
+
+	va_start(args, fmt);
+	n = runevsnprint(buf, 256, fmt, args);
+	va_end(args);
+	return n;
+}
--- /dev/null
+++ b/libc/runestrcat.c
@@ -1,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrcat(Rune *s1, Rune *s2)
+{
+
+	runestrcpy(runestrchr(s1, 0), s2);
+	return s1;
+}
--- /dev/null
+++ b/libc/runestrchr.c
@@ -1,0 +1,20 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrchr(Rune *s, Rune c)
+{
+	Rune c0 = c;
+	Rune c1;
+
+	if(c == 0) {
+		while(*s++)
+			;
+		return s-1;
+	}
+
+	while(c1 = *s++)
+		if(c1 == c0)
+			return s-1;
+	return 0;
+}
--- /dev/null
+++ b/libc/runestrcmp.c
@@ -1,0 +1,20 @@
+#include <u.h>
+#include <libc.h>
+
+int
+runestrcmp(Rune *s1, Rune *s2)
+{
+	Rune c1, c2;
+
+	for(;;) {
+		c1 = *s1++;
+		c2 = *s2++;
+		if(c1 != c2) {
+			if(c1 > c2)
+				return 1;
+			return -1;
+		}
+		if(c1 == 0)
+			return 0;
+	}
+}
--- /dev/null
+++ b/libc/runestrcpy.c
@@ -1,0 +1,13 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrcpy(Rune *s1, Rune *s2)
+{
+	Rune *os1;
+
+	os1 = s1;
+	while(*s1++ = *s2++)
+		;
+	return os1;
+}
--- /dev/null
+++ b/libc/runestrdup.c
@@ -1,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrdup(Rune *s) 
+{  
+	Rune *ns;
+
+	ns = malloc(sizeof(Rune)*(runestrlen(s) + 1));
+	if(ns == 0)
+		return 0;
+
+	return runestrcpy(ns, s);
+}
--- /dev/null
+++ b/libc/runestrecpy.c
@@ -1,0 +1,17 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrecpy(Rune *s1, Rune *es1, Rune *s2)
+{
+	if(s1 >= es1)
+		return s1;
+
+	while(*s1++ = *s2++){
+		if(s1 == es1){
+			*--s1 = '\0';
+			break;
+		}
+	}
+	return s1;
+}
--- /dev/null
+++ b/libc/runestrlen.c
@@ -1,0 +1,9 @@
+#include <u.h>
+#include <libc.h>
+
+long
+runestrlen(Rune *s)
+{
+
+	return runestrchr(s, 0) - s;
+}
--- /dev/null
+++ b/libc/runestrncat.c
@@ -1,0 +1,17 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrncat(Rune *s1, Rune *s2, long n)
+{
+	Rune *os1;
+
+	os1 = s1;
+	s1 = runestrchr(s1, 0);
+	while(*s1++ = *s2++)
+		if(--n < 0) {
+			s1[-1] = 0;
+			break;
+		}
+	return os1;
+}
--- /dev/null
+++ b/libc/runestrncmp.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+
+int
+runestrncmp(Rune *s1, Rune *s2, long n)
+{
+	Rune c1, c2;
+
+	while(n > 0) {
+		c1 = *s1++;
+		c2 = *s2++;
+		n--;
+		if(c1 != c2) {
+			if(c1 > c2)
+				return 1;
+			return -1;
+		}
+		if(c1 == 0)
+			break;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libc/runestrncpy.c
@@ -1,0 +1,18 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrncpy(Rune *s1, Rune *s2, long n)
+{
+	int i;
+	Rune *os1;
+
+	os1 = s1;
+	for(i = 0; i < n; i++)
+		if((*s1++ = *s2++) == 0) {
+			while(++i < n)
+				*s1++ = 0;
+			return os1;
+		}
+	return os1;
+}
--- /dev/null
+++ b/libc/runestrrchr.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrrchr(Rune *s, Rune c)
+{
+	Rune *r;
+
+	if(c == 0)
+		return runestrchr(s, 0);
+	r = 0;
+	while(s = runestrchr(s, c))
+		r = s++;
+	return r;
+}
--- /dev/null
+++ b/libc/runestrstr.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * Return pointer to first occurrence of s2 in s1,
+ * 0 if none
+ */
+Rune*
+runestrstr(Rune *s1, Rune *s2)
+{
+	Rune *p, *pa, *pb;
+	int c0, c;
+
+	c0 = *s2;
+	if(c0 == 0)
+		return s1;
+	s2++;
+	for(p=runestrchr(s1, c0); p; p=runestrchr(p+1, c0)) {
+		pa = p;
+		for(pb=s2;; pb++) {
+			c = *pb;
+			if(c == 0)
+				return p;
+			if(c != *++pa)
+				break;
+		}
+	}
+	return 0;
+}
--- /dev/null
+++ b/libc/runetype.c
@@ -1,0 +1,1138 @@
+#include	<u.h>
+#include	<libc.h>
+
+/*
+ * alpha ranges -
+ *	only covers ranges not in lower||upper
+ */
+static
+Rune	_alpha2[] =
+{
+	0x00d8,	0x00f6,	/* Ø - ö */
+	0x00f8,	0x01f5,	/* ø - ǵ */
+	0x0250,	0x02a8,	/* ɐ - ʨ */
+	0x038e,	0x03a1,	/* Ύ - Ρ */
+	0x03a3,	0x03ce,	/* Σ - ώ */
+	0x03d0,	0x03d6,	/* ϐ - ϖ */
+	0x03e2,	0x03f3,	/* Ϣ - ϳ */
+	0x0490,	0x04c4,	/* Ґ - ӄ */
+	0x0561,	0x0587,	/* ա - և */
+	0x05d0,	0x05ea,	/* א - ת */
+	0x05f0,	0x05f2,	/* װ - ײ */
+	0x0621,	0x063a,	/* ء - غ */
+	0x0640,	0x064a,	/* ـ - ي */
+	0x0671,	0x06b7,	/* ٱ - ڷ */
+	0x06ba,	0x06be,	/* ں - ھ */
+	0x06c0,	0x06ce,	/* ۀ - ێ */
+	0x06d0,	0x06d3,	/* ې - ۓ */
+	0x0905,	0x0939,	/* अ - ह */
+	0x0958,	0x0961,	/* क़ - ॡ */
+	0x0985,	0x098c,	/* অ - ঌ */
+	0x098f,	0x0990,	/* এ - ঐ */
+	0x0993,	0x09a8,	/* ও - ন */
+	0x09aa,	0x09b0,	/* প - র */
+	0x09b6,	0x09b9,	/* শ - হ */
+	0x09dc,	0x09dd,	/* ড় - ঢ় */
+	0x09df,	0x09e1,	/* য় - ৡ */
+	0x09f0,	0x09f1,	/* ৰ - ৱ */
+	0x0a05,	0x0a0a,	/* ਅ - ਊ */
+	0x0a0f,	0x0a10,	/* ਏ - ਐ */
+	0x0a13,	0x0a28,	/* ਓ - ਨ */
+	0x0a2a,	0x0a30,	/* ਪ - ਰ */
+	0x0a32,	0x0a33,	/* ਲ - ਲ਼ */
+	0x0a35,	0x0a36,	/* ਵ - ਸ਼ */
+	0x0a38,	0x0a39,	/* ਸ - ਹ */
+	0x0a59,	0x0a5c,	/* ਖ਼ - ੜ */
+	0x0a85,	0x0a8b,	/* અ - ઋ */
+	0x0a8f,	0x0a91,	/* એ - ઑ */
+	0x0a93,	0x0aa8,	/* ઓ - ન */
+	0x0aaa,	0x0ab0,	/* પ - ર */
+	0x0ab2,	0x0ab3,	/* લ - ળ */
+	0x0ab5,	0x0ab9,	/* વ - હ */
+	0x0b05,	0x0b0c,	/* ଅ - ଌ */
+	0x0b0f,	0x0b10,	/* ଏ - ଐ */
+	0x0b13,	0x0b28,	/* ଓ - ନ */
+	0x0b2a,	0x0b30,	/* ପ - ର */
+	0x0b32,	0x0b33,	/* ଲ - ଳ */
+	0x0b36,	0x0b39,	/* ଶ - ହ */
+	0x0b5c,	0x0b5d,	/* ଡ଼ - ଢ଼ */
+	0x0b5f,	0x0b61,	/* ୟ - ୡ */
+	0x0b85,	0x0b8a,	/* அ - ஊ */
+	0x0b8e,	0x0b90,	/* எ - ஐ */
+	0x0b92,	0x0b95,	/* ஒ - க */
+	0x0b99,	0x0b9a,	/* ங - ச */
+	0x0b9e,	0x0b9f,	/* ஞ - ட */
+	0x0ba3,	0x0ba4,	/* ண - த */
+	0x0ba8,	0x0baa,	/* ந - ப */
+	0x0bae,	0x0bb5,	/* ம - வ */
+	0x0bb7,	0x0bb9,	/* ஷ - ஹ */
+	0x0c05,	0x0c0c,	/* అ - ఌ */
+	0x0c0e,	0x0c10,	/* ఎ - ఐ */
+	0x0c12,	0x0c28,	/* ఒ - న */
+	0x0c2a,	0x0c33,	/* ప - ళ */
+	0x0c35,	0x0c39,	/* వ - హ */
+	0x0c60,	0x0c61,	/* ౠ - ౡ */
+	0x0c85,	0x0c8c,	/* ಅ - ಌ */
+	0x0c8e,	0x0c90,	/* ಎ - ಐ */
+	0x0c92,	0x0ca8,	/* ಒ - ನ */
+	0x0caa,	0x0cb3,	/* ಪ - ಳ */
+	0x0cb5,	0x0cb9,	/* ವ - ಹ */
+	0x0ce0,	0x0ce1,	/* ೠ - ೡ */
+	0x0d05,	0x0d0c,	/* അ - ഌ */
+	0x0d0e,	0x0d10,	/* എ - ഐ */
+	0x0d12,	0x0d28,	/* ഒ - ന */
+	0x0d2a,	0x0d39,	/* പ - ഹ */
+	0x0d60,	0x0d61,	/* ൠ - ൡ */
+	0x0e01,	0x0e30,	/* ก - ะ */
+	0x0e32,	0x0e33,	/* า - ำ */
+	0x0e40,	0x0e46,	/* เ - ๆ */
+	0x0e5a,	0x0e5b,	/* ๚ - ๛ */
+	0x0e81,	0x0e82,	/* ກ - ຂ */
+	0x0e87,	0x0e88,	/* ງ - ຈ */
+	0x0e94,	0x0e97,	/* ດ - ທ */
+	0x0e99,	0x0e9f,	/* ນ - ຟ */
+	0x0ea1,	0x0ea3,	/* ມ - ຣ */
+	0x0eaa,	0x0eab,	/* ສ - ຫ */
+	0x0ead,	0x0eae,	/* ອ - ຮ */
+	0x0eb2,	0x0eb3,	/* າ - ຳ */
+	0x0ec0,	0x0ec4,	/* ເ - ໄ */
+	0x0edc,	0x0edd,	/* ໜ - ໝ */
+	0x0f18,	0x0f19,	/* ༘ - ༙ */
+	0x0f40,	0x0f47,	/* ཀ - ཇ */
+	0x0f49,	0x0f69,	/* ཉ - ཀྵ */
+	0x10d0,	0x10f6,	/* ა - ჶ */
+	0x1100,	0x1159,	/* ᄀ - ᅙ */
+	0x115f,	0x11a2,	/* ᅟ - ᆢ */
+	0x11a8,	0x11f9,	/* ᆨ - ᇹ */
+	0x1e00,	0x1e9b,	/* Ḁ - ẛ */
+	0x1f50,	0x1f57,	/* ὐ - ὗ */
+	0x1f80,	0x1fb4,	/* ᾀ - ᾴ */
+	0x1fb6,	0x1fbc,	/* ᾶ - ᾼ */
+	0x1fc2,	0x1fc4,	/* ῂ - ῄ */
+	0x1fc6,	0x1fcc,	/* ῆ - ῌ */
+	0x1fd0,	0x1fd3,	/* ῐ - ΐ */
+	0x1fd6,	0x1fdb,	/* ῖ - Ί */
+	0x1fe0,	0x1fec,	/* ῠ - Ῥ */
+	0x1ff2,	0x1ff4,	/* ῲ - ῴ */
+	0x1ff6,	0x1ffc,	/* ῶ - ῼ */
+	0x210a,	0x2113,	/* ℊ - ℓ */
+	0x2115,	0x211d,	/* ℕ - ℝ */
+	0x2120,	0x2122,	/* ℠ - ™ */
+	0x212a,	0x2131,	/* K - ℱ */
+	0x2133,	0x2138,	/* ℳ - ℸ */
+	0x3041,	0x3094,	/* ぁ - ゔ */
+	0x30a1,	0x30fa,	/* ァ - ヺ */
+	0x3105,	0x312c,	/* ㄅ - ㄬ */
+	0x3131,	0x318e,	/* ㄱ - ㆎ */
+	0x3192,	0x319f,	/* ㆒ - ㆟ */
+	0x3260,	0x327b,	/* ㉠ - ㉻ */
+	0x328a,	0x32b0,	/* ㊊ - ㊰ */
+	0x32d0,	0x32fe,	/* ㋐ - ㋾ */
+	0x3300,	0x3357,	/* ㌀ - ㍗ */
+	0x3371,	0x3376,	/* ㍱ - ㍶ */
+	0x337b,	0x3394,	/* ㍻ - ㎔ */
+	0x3399,	0x339e,	/* ㎙ - ㎞ */
+	0x33a9,	0x33ad,	/* ㎩ - ㎭ */
+	0x33b0,	0x33c1,	/* ㎰ - ㏁ */
+	0x33c3,	0x33c5,	/* ㏃ - ㏅ */
+	0x33c7,	0x33d7,	/* ㏇ - ㏗ */
+	0x33d9,	0x33dd,	/* ㏙ - ㏝ */
+	0x4e00,	0x9fff,	/* 一 - 鿿 */
+	0xac00,	0xd7a3,	/* 가 - 힣 */
+	0xf900,	0xfb06,	/* 豈 - st */
+	0xfb13,	0xfb17,	/* ﬓ - ﬗ */
+	0xfb1f,	0xfb28,	/* ײַ - ﬨ */
+	0xfb2a,	0xfb36,	/* שׁ - זּ */
+	0xfb38,	0xfb3c,	/* טּ - לּ */
+	0xfb40,	0xfb41,	/* נּ - סּ */
+	0xfb43,	0xfb44,	/* ףּ - פּ */
+	0xfb46,	0xfbb1,	/* צּ - ﮱ */
+	0xfbd3,	0xfd3d,	/* ﯓ - ﴽ */
+	0xfd50,	0xfd8f,	/* ﵐ - ﶏ */
+	0xfd92,	0xfdc7,	/* ﶒ - ﷇ */
+	0xfdf0,	0xfdf9,	/* ﷰ - ﷹ */
+	0xfe70,	0xfe72,	/* ﹰ - ﹲ */
+	0xfe76,	0xfefc,	/* ﹶ - ﻼ */
+	0xff66,	0xff6f,	/* ヲ - ッ */
+	0xff71,	0xff9d,	/* ア - ン */
+	0xffa0,	0xffbe,	/* ᅠ - ᄒ */
+	0xffc2,	0xffc7,	/* ᅡ - ᅦ */
+	0xffca,	0xffcf,	/* ᅧ - ᅬ */
+	0xffd2,	0xffd7,	/* ᅭ - ᅲ */
+	0xffda,	0xffdc,	/* ᅳ - ᅵ */
+};
+
+/*
+ * alpha singlets -
+ *	only covers ranges not in lower||upper
+ */
+static
+Rune	_alpha1[] =
+{
+	0x00aa,	/* ª */
+	0x00b5,	/* µ */
+	0x00ba,	/* º */
+	0x03da,	/* Ϛ */
+	0x03dc,	/* Ϝ */
+	0x03de,	/* Ϟ */
+	0x03e0,	/* Ϡ */
+	0x06d5,	/* ە */
+	0x09b2,	/* ল */
+	0x0a5e,	/* ਫ਼ */
+	0x0a8d,	/* ઍ */
+	0x0ae0,	/* ૠ */
+	0x0b9c,	/* ஜ */
+	0x0cde,	/* ೞ */
+	0x0e4f,	/* ๏ */
+	0x0e84,	/* ຄ */
+	0x0e8a,	/* ຊ */
+	0x0e8d,	/* ຍ */
+	0x0ea5,	/* ລ */
+	0x0ea7,	/* ວ */
+	0x0eb0,	/* ະ */
+	0x0ebd,	/* ຽ */
+	0x1fbe,	/* ι */
+	0x207f,	/* ⁿ */
+	0x20a8,	/* ₨ */
+	0x2102,	/* ℂ */
+	0x2107,	/* ℇ */
+	0x2124,	/* ℤ */
+	0x2126,	/* Ω */
+	0x2128,	/* ℨ */
+	0xfb3e,	/* מּ */
+	0xfe74,	/* ﹴ */
+};
+
+/*
+ * space ranges
+ */
+static
+Rune	_space2[] =
+{
+	0x0009,	0x000a,	/* tab and newline */
+	0x0020,	0x0020,	/* space */
+	0x00a0,	0x00a0,	/*   */
+	0x2000,	0x200b,	/*   - ​ */
+	0x2028,	0x2029,	/* 
 - 
 */
+	0x3000,	0x3000,	/*   */
+	0xfeff,	0xfeff,	/*  */
+};
+
+/*
+ * lower case ranges
+ *	3rd col is conversion excess 500
+ */
+static
+Rune	_toupper2[] =
+{
+	0x0061,	0x007a, 468,	/* a-z A-Z */
+	0x00e0,	0x00f6, 468,	/* à-ö À-Ö */
+	0x00f8,	0x00fe, 468,	/* ø-þ Ø-Þ */
+	0x0256,	0x0257, 295,	/* ɖ-ɗ Ɖ-Ɗ */
+	0x0258,	0x0259, 298,	/* ɘ-ə Ǝ-Ə */
+	0x028a,	0x028b, 283,	/* ʊ-ʋ Ʊ-Ʋ */
+	0x03ad,	0x03af, 463,	/* έ-ί Έ-Ί */
+	0x03b1,	0x03c1, 468,	/* α-ρ Α-Ρ */
+	0x03c3,	0x03cb, 468,	/* σ-ϋ Σ-Ϋ */
+	0x03cd,	0x03ce, 437,	/* ύ-ώ Ύ-Ώ */
+	0x0430,	0x044f, 468,	/* а-я А-Я */
+	0x0451,	0x045c, 420,	/* ё-ќ Ё-Ќ */
+	0x045e,	0x045f, 420,	/* ў-џ Ў-Џ */
+	0x0561,	0x0586, 452,	/* ա-ֆ Ա-Ֆ */
+	0x1f00,	0x1f07, 508,	/* ἀ-ἇ Ἀ-Ἇ */
+	0x1f10,	0x1f15, 508,	/* ἐ-ἕ Ἐ-Ἕ */
+	0x1f20,	0x1f27, 508,	/* ἠ-ἧ Ἠ-Ἧ */
+	0x1f30,	0x1f37, 508,	/* ἰ-ἷ Ἰ-Ἷ */
+	0x1f40,	0x1f45, 508,	/* ὀ-ὅ Ὀ-Ὅ */
+	0x1f60,	0x1f67, 508,	/* ὠ-ὧ Ὠ-Ὧ */
+	0x1f70,	0x1f71, 574,	/* ὰ-ά Ὰ-Ά */
+	0x1f72,	0x1f75, 586,	/* ὲ-ή Ὲ-Ή */
+	0x1f76,	0x1f77, 600,	/* ὶ-ί Ὶ-Ί */
+	0x1f78,	0x1f79, 628,	/* ὸ-ό Ὸ-Ό */
+	0x1f7a,	0x1f7b, 612,	/* ὺ-ύ Ὺ-Ύ */
+	0x1f7c,	0x1f7d, 626,	/* ὼ-ώ Ὼ-Ώ */
+	0x1f80,	0x1f87, 508,	/* ᾀ-ᾇ ᾈ-ᾏ */
+	0x1f90,	0x1f97, 508,	/* ᾐ-ᾗ ᾘ-ᾟ */
+	0x1fa0,	0x1fa7, 508,	/* ᾠ-ᾧ ᾨ-ᾯ */
+	0x1fb0,	0x1fb1, 508,	/* ᾰ-ᾱ Ᾰ-Ᾱ */
+	0x1fd0,	0x1fd1, 508,	/* ῐ-ῑ Ῐ-Ῑ */
+	0x1fe0,	0x1fe1, 508,	/* ῠ-ῡ Ῠ-Ῡ */
+	0x2170,	0x217f, 484,	/* ⅰ-ⅿ Ⅰ-Ⅿ */
+	0x24d0,	0x24e9, 474,	/* ⓐ-ⓩ Ⓐ-Ⓩ */
+	0xff41,	0xff5a, 468,	/* a-z A-Z */
+};
+
+/*
+ * lower case singlets
+ *	2nd col is conversion excess 500
+ */
+static
+Rune	_toupper1[] =
+{
+	0x00ff, 621,	/* ÿ Ÿ */
+	0x0101, 499,	/* ā Ā */
+	0x0103, 499,	/* ă Ă */
+	0x0105, 499,	/* ą Ą */
+	0x0107, 499,	/* ć Ć */
+	0x0109, 499,	/* ĉ Ĉ */
+	0x010b, 499,	/* ċ Ċ */
+	0x010d, 499,	/* č Č */
+	0x010f, 499,	/* ď Ď */
+	0x0111, 499,	/* đ Đ */
+	0x0113, 499,	/* ē Ē */
+	0x0115, 499,	/* ĕ Ĕ */
+	0x0117, 499,	/* ė Ė */
+	0x0119, 499,	/* ę Ę */
+	0x011b, 499,	/* ě Ě */
+	0x011d, 499,	/* ĝ Ĝ */
+	0x011f, 499,	/* ğ Ğ */
+	0x0121, 499,	/* ġ Ġ */
+	0x0123, 499,	/* ģ Ģ */
+	0x0125, 499,	/* ĥ Ĥ */
+	0x0127, 499,	/* ħ Ħ */
+	0x0129, 499,	/* ĩ Ĩ */
+	0x012b, 499,	/* ī Ī */
+	0x012d, 499,	/* ĭ Ĭ */
+	0x012f, 499,	/* į Į */
+	0x0131, 268,	/* ı I */
+	0x0133, 499,	/* ij IJ */
+	0x0135, 499,	/* ĵ Ĵ */
+	0x0137, 499,	/* ķ Ķ */
+	0x013a, 499,	/* ĺ Ĺ */
+	0x013c, 499,	/* ļ Ļ */
+	0x013e, 499,	/* ľ Ľ */
+	0x0140, 499,	/* ŀ Ŀ */
+	0x0142, 499,	/* ł Ł */
+	0x0144, 499,	/* ń Ń */
+	0x0146, 499,	/* ņ Ņ */
+	0x0148, 499,	/* ň Ň */
+	0x014b, 499,	/* ŋ Ŋ */
+	0x014d, 499,	/* ō Ō */
+	0x014f, 499,	/* ŏ Ŏ */
+	0x0151, 499,	/* ő Ő */
+	0x0153, 499,	/* œ Œ */
+	0x0155, 499,	/* ŕ Ŕ */
+	0x0157, 499,	/* ŗ Ŗ */
+	0x0159, 499,	/* ř Ř */
+	0x015b, 499,	/* ś Ś */
+	0x015d, 499,	/* ŝ Ŝ */
+	0x015f, 499,	/* ş Ş */
+	0x0161, 499,	/* š Š */
+	0x0163, 499,	/* ţ Ţ */
+	0x0165, 499,	/* ť Ť */
+	0x0167, 499,	/* ŧ Ŧ */
+	0x0169, 499,	/* ũ Ũ */
+	0x016b, 499,	/* ū Ū */
+	0x016d, 499,	/* ŭ Ŭ */
+	0x016f, 499,	/* ů Ů */
+	0x0171, 499,	/* ű Ű */
+	0x0173, 499,	/* ų Ų */
+	0x0175, 499,	/* ŵ Ŵ */
+	0x0177, 499,	/* ŷ Ŷ */
+	0x017a, 499,	/* ź Ź */
+	0x017c, 499,	/* ż Ż */
+	0x017e, 499,	/* ž Ž */
+	0x017f, 200,	/* ſ S */
+	0x0183, 499,	/* ƃ Ƃ */
+	0x0185, 499,	/* ƅ Ƅ */
+	0x0188, 499,	/* ƈ Ƈ */
+	0x018c, 499,	/* ƌ Ƌ */
+	0x0192, 499,	/* ƒ Ƒ */
+	0x0199, 499,	/* ƙ Ƙ */
+	0x01a1, 499,	/* ơ Ơ */
+	0x01a3, 499,	/* ƣ Ƣ */
+	0x01a5, 499,	/* ƥ Ƥ */
+	0x01a8, 499,	/* ƨ Ƨ */
+	0x01ad, 499,	/* ƭ Ƭ */
+	0x01b0, 499,	/* ư Ư */
+	0x01b4, 499,	/* ƴ Ƴ */
+	0x01b6, 499,	/* ƶ Ƶ */
+	0x01b9, 499,	/* ƹ Ƹ */
+	0x01bd, 499,	/* ƽ Ƽ */
+	0x01c5, 499,	/* Dž DŽ */
+	0x01c6, 498,	/* dž DŽ */
+	0x01c8, 499,	/* Lj LJ */
+	0x01c9, 498,	/* lj LJ */
+	0x01cb, 499,	/* Nj NJ */
+	0x01cc, 498,	/* nj NJ */
+	0x01ce, 499,	/* ǎ Ǎ */
+	0x01d0, 499,	/* ǐ Ǐ */
+	0x01d2, 499,	/* ǒ Ǒ */
+	0x01d4, 499,	/* ǔ Ǔ */
+	0x01d6, 499,	/* ǖ Ǖ */
+	0x01d8, 499,	/* ǘ Ǘ */
+	0x01da, 499,	/* ǚ Ǚ */
+	0x01dc, 499,	/* ǜ Ǜ */
+	0x01df, 499,	/* ǟ Ǟ */
+	0x01e1, 499,	/* ǡ Ǡ */
+	0x01e3, 499,	/* ǣ Ǣ */
+	0x01e5, 499,	/* ǥ Ǥ */
+	0x01e7, 499,	/* ǧ Ǧ */
+	0x01e9, 499,	/* ǩ Ǩ */
+	0x01eb, 499,	/* ǫ Ǫ */
+	0x01ed, 499,	/* ǭ Ǭ */
+	0x01ef, 499,	/* ǯ Ǯ */
+	0x01f2, 499,	/* Dz DZ */
+	0x01f3, 498,	/* dz DZ */
+	0x01f5, 499,	/* ǵ Ǵ */
+	0x01fb, 499,	/* ǻ Ǻ */
+	0x01fd, 499,	/* ǽ Ǽ */
+	0x01ff, 499,	/* ǿ Ǿ */
+	0x0201, 499,	/* ȁ Ȁ */
+	0x0203, 499,	/* ȃ Ȃ */
+	0x0205, 499,	/* ȅ Ȅ */
+	0x0207, 499,	/* ȇ Ȇ */
+	0x0209, 499,	/* ȉ Ȉ */
+	0x020b, 499,	/* ȋ Ȋ */
+	0x020d, 499,	/* ȍ Ȍ */
+	0x020f, 499,	/* ȏ Ȏ */
+	0x0211, 499,	/* ȑ Ȑ */
+	0x0213, 499,	/* ȓ Ȓ */
+	0x0215, 499,	/* ȕ Ȕ */
+	0x0217, 499,	/* ȗ Ȗ */
+	0x0253, 290,	/* ɓ Ɓ */
+	0x0254, 294,	/* ɔ Ɔ */
+	0x025b, 297,	/* ɛ Ɛ */
+	0x0260, 295,	/* ɠ Ɠ */
+	0x0263, 293,	/* ɣ Ɣ */
+	0x0268, 291,	/* ɨ Ɨ */
+	0x0269, 289,	/* ɩ Ɩ */
+	0x026f, 289,	/* ɯ Ɯ */
+	0x0272, 287,	/* ɲ Ɲ */
+	0x0283, 282,	/* ʃ Ʃ */
+	0x0288, 282,	/* ʈ Ʈ */
+	0x0292, 281,	/* ʒ Ʒ */
+	0x03ac, 462,	/* ά Ά */
+	0x03cc, 436,	/* ό Ό */
+	0x03d0, 438,	/* ϐ Β */
+	0x03d1, 443,	/* ϑ Θ */
+	0x03d5, 453,	/* ϕ Φ */
+	0x03d6, 446,	/* ϖ Π */
+	0x03e3, 499,	/* ϣ Ϣ */
+	0x03e5, 499,	/* ϥ Ϥ */
+	0x03e7, 499,	/* ϧ Ϧ */
+	0x03e9, 499,	/* ϩ Ϩ */
+	0x03eb, 499,	/* ϫ Ϫ */
+	0x03ed, 499,	/* ϭ Ϭ */
+	0x03ef, 499,	/* ϯ Ϯ */
+	0x03f0, 414,	/* ϰ Κ */
+	0x03f1, 420,	/* ϱ Ρ */
+	0x0461, 499,	/* ѡ Ѡ */
+	0x0463, 499,	/* ѣ Ѣ */
+	0x0465, 499,	/* ѥ Ѥ */
+	0x0467, 499,	/* ѧ Ѧ */
+	0x0469, 499,	/* ѩ Ѩ */
+	0x046b, 499,	/* ѫ Ѫ */
+	0x046d, 499,	/* ѭ Ѭ */
+	0x046f, 499,	/* ѯ Ѯ */
+	0x0471, 499,	/* ѱ Ѱ */
+	0x0473, 499,	/* ѳ Ѳ */
+	0x0475, 499,	/* ѵ Ѵ */
+	0x0477, 499,	/* ѷ Ѷ */
+	0x0479, 499,	/* ѹ Ѹ */
+	0x047b, 499,	/* ѻ Ѻ */
+	0x047d, 499,	/* ѽ Ѽ */
+	0x047f, 499,	/* ѿ Ѿ */
+	0x0481, 499,	/* ҁ Ҁ */
+	0x0491, 499,	/* ґ Ґ */
+	0x0493, 499,	/* ғ Ғ */
+	0x0495, 499,	/* ҕ Ҕ */
+	0x0497, 499,	/* җ Җ */
+	0x0499, 499,	/* ҙ Ҙ */
+	0x049b, 499,	/* қ Қ */
+	0x049d, 499,	/* ҝ Ҝ */
+	0x049f, 499,	/* ҟ Ҟ */
+	0x04a1, 499,	/* ҡ Ҡ */
+	0x04a3, 499,	/* ң Ң */
+	0x04a5, 499,	/* ҥ Ҥ */
+	0x04a7, 499,	/* ҧ Ҧ */
+	0x04a9, 499,	/* ҩ Ҩ */
+	0x04ab, 499,	/* ҫ Ҫ */
+	0x04ad, 499,	/* ҭ Ҭ */
+	0x04af, 499,	/* ү Ү */
+	0x04b1, 499,	/* ұ Ұ */
+	0x04b3, 499,	/* ҳ Ҳ */
+	0x04b5, 499,	/* ҵ Ҵ */
+	0x04b7, 499,	/* ҷ Ҷ */
+	0x04b9, 499,	/* ҹ Ҹ */
+	0x04bb, 499,	/* һ Һ */
+	0x04bd, 499,	/* ҽ Ҽ */
+	0x04bf, 499,	/* ҿ Ҿ */
+	0x04c2, 499,	/* ӂ Ӂ */
+	0x04c4, 499,	/* ӄ Ӄ */
+	0x04c8, 499,	/* ӈ Ӈ */
+	0x04cc, 499,	/* ӌ Ӌ */
+	0x04d1, 499,	/* ӑ Ӑ */
+	0x04d3, 499,	/* ӓ Ӓ */
+	0x04d5, 499,	/* ӕ Ӕ */
+	0x04d7, 499,	/* ӗ Ӗ */
+	0x04d9, 499,	/* ә Ә */
+	0x04db, 499,	/* ӛ Ӛ */
+	0x04dd, 499,	/* ӝ Ӝ */
+	0x04df, 499,	/* ӟ Ӟ */
+	0x04e1, 499,	/* ӡ Ӡ */
+	0x04e3, 499,	/* ӣ Ӣ */
+	0x04e5, 499,	/* ӥ Ӥ */
+	0x04e7, 499,	/* ӧ Ӧ */
+	0x04e9, 499,	/* ө Ө */
+	0x04eb, 499,	/* ӫ Ӫ */
+	0x04ef, 499,	/* ӯ Ӯ */
+	0x04f1, 499,	/* ӱ Ӱ */
+	0x04f3, 499,	/* ӳ Ӳ */
+	0x04f5, 499,	/* ӵ Ӵ */
+	0x04f9, 499,	/* ӹ Ӹ */
+	0x1e01, 499,	/* ḁ Ḁ */
+	0x1e03, 499,	/* ḃ Ḃ */
+	0x1e05, 499,	/* ḅ Ḅ */
+	0x1e07, 499,	/* ḇ Ḇ */
+	0x1e09, 499,	/* ḉ Ḉ */
+	0x1e0b, 499,	/* ḋ Ḋ */
+	0x1e0d, 499,	/* ḍ Ḍ */
+	0x1e0f, 499,	/* ḏ Ḏ */
+	0x1e11, 499,	/* ḑ Ḑ */
+	0x1e13, 499,	/* ḓ Ḓ */
+	0x1e15, 499,	/* ḕ Ḕ */
+	0x1e17, 499,	/* ḗ Ḗ */
+	0x1e19, 499,	/* ḙ Ḙ */
+	0x1e1b, 499,	/* ḛ Ḛ */
+	0x1e1d, 499,	/* ḝ Ḝ */
+	0x1e1f, 499,	/* ḟ Ḟ */
+	0x1e21, 499,	/* ḡ Ḡ */
+	0x1e23, 499,	/* ḣ Ḣ */
+	0x1e25, 499,	/* ḥ Ḥ */
+	0x1e27, 499,	/* ḧ Ḧ */
+	0x1e29, 499,	/* ḩ Ḩ */
+	0x1e2b, 499,	/* ḫ Ḫ */
+	0x1e2d, 499,	/* ḭ Ḭ */
+	0x1e2f, 499,	/* ḯ Ḯ */
+	0x1e31, 499,	/* ḱ Ḱ */
+	0x1e33, 499,	/* ḳ Ḳ */
+	0x1e35, 499,	/* ḵ Ḵ */
+	0x1e37, 499,	/* ḷ Ḷ */
+	0x1e39, 499,	/* ḹ Ḹ */
+	0x1e3b, 499,	/* ḻ Ḻ */
+	0x1e3d, 499,	/* ḽ Ḽ */
+	0x1e3f, 499,	/* ḿ Ḿ */
+	0x1e41, 499,	/* ṁ Ṁ */
+	0x1e43, 499,	/* ṃ Ṃ */
+	0x1e45, 499,	/* ṅ Ṅ */
+	0x1e47, 499,	/* ṇ Ṇ */
+	0x1e49, 499,	/* ṉ Ṉ */
+	0x1e4b, 499,	/* ṋ Ṋ */
+	0x1e4d, 499,	/* ṍ Ṍ */
+	0x1e4f, 499,	/* ṏ Ṏ */
+	0x1e51, 499,	/* ṑ Ṑ */
+	0x1e53, 499,	/* ṓ Ṓ */
+	0x1e55, 499,	/* ṕ Ṕ */
+	0x1e57, 499,	/* ṗ Ṗ */
+	0x1e59, 499,	/* ṙ Ṙ */
+	0x1e5b, 499,	/* ṛ Ṛ */
+	0x1e5d, 499,	/* ṝ Ṝ */
+	0x1e5f, 499,	/* ṟ Ṟ */
+	0x1e61, 499,	/* ṡ Ṡ */
+	0x1e63, 499,	/* ṣ Ṣ */
+	0x1e65, 499,	/* ṥ Ṥ */
+	0x1e67, 499,	/* ṧ Ṧ */
+	0x1e69, 499,	/* ṩ Ṩ */
+	0x1e6b, 499,	/* ṫ Ṫ */
+	0x1e6d, 499,	/* ṭ Ṭ */
+	0x1e6f, 499,	/* ṯ Ṯ */
+	0x1e71, 499,	/* ṱ Ṱ */
+	0x1e73, 499,	/* ṳ Ṳ */
+	0x1e75, 499,	/* ṵ Ṵ */
+	0x1e77, 499,	/* ṷ Ṷ */
+	0x1e79, 499,	/* ṹ Ṹ */
+	0x1e7b, 499,	/* ṻ Ṻ */
+	0x1e7d, 499,	/* ṽ Ṽ */
+	0x1e7f, 499,	/* ṿ Ṿ */
+	0x1e81, 499,	/* ẁ Ẁ */
+	0x1e83, 499,	/* ẃ Ẃ */
+	0x1e85, 499,	/* ẅ Ẅ */
+	0x1e87, 499,	/* ẇ Ẇ */
+	0x1e89, 499,	/* ẉ Ẉ */
+	0x1e8b, 499,	/* ẋ Ẋ */
+	0x1e8d, 499,	/* ẍ Ẍ */
+	0x1e8f, 499,	/* ẏ Ẏ */
+	0x1e91, 499,	/* ẑ Ẑ */
+	0x1e93, 499,	/* ẓ Ẓ */
+	0x1e95, 499,	/* ẕ Ẕ */
+	0x1ea1, 499,	/* ạ Ạ */
+	0x1ea3, 499,	/* ả Ả */
+	0x1ea5, 499,	/* ấ Ấ */
+	0x1ea7, 499,	/* ầ Ầ */
+	0x1ea9, 499,	/* ẩ Ẩ */
+	0x1eab, 499,	/* ẫ Ẫ */
+	0x1ead, 499,	/* ậ Ậ */
+	0x1eaf, 499,	/* ắ Ắ */
+	0x1eb1, 499,	/* ằ Ằ */
+	0x1eb3, 499,	/* ẳ Ẳ */
+	0x1eb5, 499,	/* ẵ Ẵ */
+	0x1eb7, 499,	/* ặ Ặ */
+	0x1eb9, 499,	/* ẹ Ẹ */
+	0x1ebb, 499,	/* ẻ Ẻ */
+	0x1ebd, 499,	/* ẽ Ẽ */
+	0x1ebf, 499,	/* ế Ế */
+	0x1ec1, 499,	/* ề Ề */
+	0x1ec3, 499,	/* ể Ể */
+	0x1ec5, 499,	/* ễ Ễ */
+	0x1ec7, 499,	/* ệ Ệ */
+	0x1ec9, 499,	/* ỉ Ỉ */
+	0x1ecb, 499,	/* ị Ị */
+	0x1ecd, 499,	/* ọ Ọ */
+	0x1ecf, 499,	/* ỏ Ỏ */
+	0x1ed1, 499,	/* ố Ố */
+	0x1ed3, 499,	/* ồ Ồ */
+	0x1ed5, 499,	/* ổ Ổ */
+	0x1ed7, 499,	/* ỗ Ỗ */
+	0x1ed9, 499,	/* ộ Ộ */
+	0x1edb, 499,	/* ớ Ớ */
+	0x1edd, 499,	/* ờ Ờ */
+	0x1edf, 499,	/* ở Ở */
+	0x1ee1, 499,	/* ỡ Ỡ */
+	0x1ee3, 499,	/* ợ Ợ */
+	0x1ee5, 499,	/* ụ Ụ */
+	0x1ee7, 499,	/* ủ Ủ */
+	0x1ee9, 499,	/* ứ Ứ */
+	0x1eeb, 499,	/* ừ Ừ */
+	0x1eed, 499,	/* ử Ử */
+	0x1eef, 499,	/* ữ Ữ */
+	0x1ef1, 499,	/* ự Ự */
+	0x1ef3, 499,	/* ỳ Ỳ */
+	0x1ef5, 499,	/* ỵ Ỵ */
+	0x1ef7, 499,	/* ỷ Ỷ */
+	0x1ef9, 499,	/* ỹ Ỹ */
+	0x1f51, 508,	/* ὑ Ὑ */
+	0x1f53, 508,	/* ὓ Ὓ */
+	0x1f55, 508,	/* ὕ Ὕ */
+	0x1f57, 508,	/* ὗ Ὗ */
+	0x1fb3, 509,	/* ᾳ ᾼ */
+	0x1fc3, 509,	/* ῃ ῌ */
+	0x1fe5, 507,	/* ῥ Ῥ */
+	0x1ff3, 509,	/* ῳ ῼ */
+};
+
+/*
+ * upper case ranges
+ *	3rd col is conversion excess 500
+ */
+static
+Rune	_tolower2[] =
+{
+	0x0041,	0x005a, 532,	/* A-Z a-z */
+	0x00c0,	0x00d6, 532,	/* À-Ö à-ö */
+	0x00d8,	0x00de, 532,	/* Ø-Þ ø-þ */
+	0x0189,	0x018a, 705,	/* Ɖ-Ɗ ɖ-ɗ */
+	0x018e,	0x018f, 702,	/* Ǝ-Ə ɘ-ə */
+	0x01b1,	0x01b2, 717,	/* Ʊ-Ʋ ʊ-ʋ */
+	0x0388,	0x038a, 537,	/* Έ-Ί έ-ί */
+	0x038e,	0x038f, 563,	/* Ύ-Ώ ύ-ώ */
+	0x0391,	0x03a1, 532,	/* Α-Ρ α-ρ */
+	0x03a3,	0x03ab, 532,	/* Σ-Ϋ σ-ϋ */
+	0x0401,	0x040c, 580,	/* Ё-Ќ ё-ќ */
+	0x040e,	0x040f, 580,	/* Ў-Џ ў-џ */
+	0x0410,	0x042f, 532,	/* А-Я а-я */
+	0x0531,	0x0556, 548,	/* Ա-Ֆ ա-ֆ */
+	0x10a0,	0x10c5, 548,	/* Ⴀ-Ⴥ ა-ჵ */
+	0x1f08,	0x1f0f, 492,	/* Ἀ-Ἇ ἀ-ἇ */
+	0x1f18,	0x1f1d, 492,	/* Ἐ-Ἕ ἐ-ἕ */
+	0x1f28,	0x1f2f, 492,	/* Ἠ-Ἧ ἠ-ἧ */
+	0x1f38,	0x1f3f, 492,	/* Ἰ-Ἷ ἰ-ἷ */
+	0x1f48,	0x1f4d, 492,	/* Ὀ-Ὅ ὀ-ὅ */
+	0x1f68,	0x1f6f, 492,	/* Ὠ-Ὧ ὠ-ὧ */
+	0x1f88,	0x1f8f, 492,	/* ᾈ-ᾏ ᾀ-ᾇ */
+	0x1f98,	0x1f9f, 492,	/* ᾘ-ᾟ ᾐ-ᾗ */
+	0x1fa8,	0x1faf, 492,	/* ᾨ-ᾯ ᾠ-ᾧ */
+	0x1fb8,	0x1fb9, 492,	/* Ᾰ-Ᾱ ᾰ-ᾱ */
+	0x1fba,	0x1fbb, 426,	/* Ὰ-Ά ὰ-ά */
+	0x1fc8,	0x1fcb, 414,	/* Ὲ-Ή ὲ-ή */
+	0x1fd8,	0x1fd9, 492,	/* Ῐ-Ῑ ῐ-ῑ */
+	0x1fda,	0x1fdb, 400,	/* Ὶ-Ί ὶ-ί */
+	0x1fe8,	0x1fe9, 492,	/* Ῠ-Ῡ ῠ-ῡ */
+	0x1fea,	0x1feb, 388,	/* Ὺ-Ύ ὺ-ύ */
+	0x1ff8,	0x1ff9, 372,	/* Ὸ-Ό ὸ-ό */
+	0x1ffa,	0x1ffb, 374,	/* Ὼ-Ώ ὼ-ώ */
+	0x2160,	0x216f, 516,	/* Ⅰ-Ⅿ ⅰ-ⅿ */
+	0x24b6,	0x24cf, 526,	/* Ⓐ-Ⓩ ⓐ-ⓩ */
+	0xff21,	0xff3a, 532,	/* A-Z a-z */
+};
+
+/*
+ * upper case singlets
+ *	2nd col is conversion excess 500
+ */
+static
+Rune	_tolower1[] =
+{
+	0x0100, 501,	/* Ā ā */
+	0x0102, 501,	/* Ă ă */
+	0x0104, 501,	/* Ą ą */
+	0x0106, 501,	/* Ć ć */
+	0x0108, 501,	/* Ĉ ĉ */
+	0x010a, 501,	/* Ċ ċ */
+	0x010c, 501,	/* Č č */
+	0x010e, 501,	/* Ď ď */
+	0x0110, 501,	/* Đ đ */
+	0x0112, 501,	/* Ē ē */
+	0x0114, 501,	/* Ĕ ĕ */
+	0x0116, 501,	/* Ė ė */
+	0x0118, 501,	/* Ę ę */
+	0x011a, 501,	/* Ě ě */
+	0x011c, 501,	/* Ĝ ĝ */
+	0x011e, 501,	/* Ğ ğ */
+	0x0120, 501,	/* Ġ ġ */
+	0x0122, 501,	/* Ģ ģ */
+	0x0124, 501,	/* Ĥ ĥ */
+	0x0126, 501,	/* Ħ ħ */
+	0x0128, 501,	/* Ĩ ĩ */
+	0x012a, 501,	/* Ī ī */
+	0x012c, 501,	/* Ĭ ĭ */
+	0x012e, 501,	/* Į į */
+	0x0130, 301,	/* İ i */
+	0x0132, 501,	/* IJ ij */
+	0x0134, 501,	/* Ĵ ĵ */
+	0x0136, 501,	/* Ķ ķ */
+	0x0139, 501,	/* Ĺ ĺ */
+	0x013b, 501,	/* Ļ ļ */
+	0x013d, 501,	/* Ľ ľ */
+	0x013f, 501,	/* Ŀ ŀ */
+	0x0141, 501,	/* Ł ł */
+	0x0143, 501,	/* Ń ń */
+	0x0145, 501,	/* Ņ ņ */
+	0x0147, 501,	/* Ň ň */
+	0x014a, 501,	/* Ŋ ŋ */
+	0x014c, 501,	/* Ō ō */
+	0x014e, 501,	/* Ŏ ŏ */
+	0x0150, 501,	/* Ő ő */
+	0x0152, 501,	/* Œ œ */
+	0x0154, 501,	/* Ŕ ŕ */
+	0x0156, 501,	/* Ŗ ŗ */
+	0x0158, 501,	/* Ř ř */
+	0x015a, 501,	/* Ś ś */
+	0x015c, 501,	/* Ŝ ŝ */
+	0x015e, 501,	/* Ş ş */
+	0x0160, 501,	/* Š š */
+	0x0162, 501,	/* Ţ ţ */
+	0x0164, 501,	/* Ť ť */
+	0x0166, 501,	/* Ŧ ŧ */
+	0x0168, 501,	/* Ũ ũ */
+	0x016a, 501,	/* Ū ū */
+	0x016c, 501,	/* Ŭ ŭ */
+	0x016e, 501,	/* Ů ů */
+	0x0170, 501,	/* Ű ű */
+	0x0172, 501,	/* Ų ų */
+	0x0174, 501,	/* Ŵ ŵ */
+	0x0176, 501,	/* Ŷ ŷ */
+	0x0178, 379,	/* Ÿ ÿ */
+	0x0179, 501,	/* Ź ź */
+	0x017b, 501,	/* Ż ż */
+	0x017d, 501,	/* Ž ž */
+	0x0181, 710,	/* Ɓ ɓ */
+	0x0182, 501,	/* Ƃ ƃ */
+	0x0184, 501,	/* Ƅ ƅ */
+	0x0186, 706,	/* Ɔ ɔ */
+	0x0187, 501,	/* Ƈ ƈ */
+	0x018b, 501,	/* Ƌ ƌ */
+	0x0190, 703,	/* Ɛ ɛ */
+	0x0191, 501,	/* Ƒ ƒ */
+	0x0193, 705,	/* Ɠ ɠ */
+	0x0194, 707,	/* Ɣ ɣ */
+	0x0196, 711,	/* Ɩ ɩ */
+	0x0197, 709,	/* Ɨ ɨ */
+	0x0198, 501,	/* Ƙ ƙ */
+	0x019c, 711,	/* Ɯ ɯ */
+	0x019d, 713,	/* Ɲ ɲ */
+	0x01a0, 501,	/* Ơ ơ */
+	0x01a2, 501,	/* Ƣ ƣ */
+	0x01a4, 501,	/* Ƥ ƥ */
+	0x01a7, 501,	/* Ƨ ƨ */
+	0x01a9, 718,	/* Ʃ ʃ */
+	0x01ac, 501,	/* Ƭ ƭ */
+	0x01ae, 718,	/* Ʈ ʈ */
+	0x01af, 501,	/* Ư ư */
+	0x01b3, 501,	/* Ƴ ƴ */
+	0x01b5, 501,	/* Ƶ ƶ */
+	0x01b7, 719,	/* Ʒ ʒ */
+	0x01b8, 501,	/* Ƹ ƹ */
+	0x01bc, 501,	/* Ƽ ƽ */
+	0x01c4, 502,	/* DŽ dž */
+	0x01c5, 501,	/* Dž dž */
+	0x01c7, 502,	/* LJ lj */
+	0x01c8, 501,	/* Lj lj */
+	0x01ca, 502,	/* NJ nj */
+	0x01cb, 501,	/* Nj nj */
+	0x01cd, 501,	/* Ǎ ǎ */
+	0x01cf, 501,	/* Ǐ ǐ */
+	0x01d1, 501,	/* Ǒ ǒ */
+	0x01d3, 501,	/* Ǔ ǔ */
+	0x01d5, 501,	/* Ǖ ǖ */
+	0x01d7, 501,	/* Ǘ ǘ */
+	0x01d9, 501,	/* Ǚ ǚ */
+	0x01db, 501,	/* Ǜ ǜ */
+	0x01de, 501,	/* Ǟ ǟ */
+	0x01e0, 501,	/* Ǡ ǡ */
+	0x01e2, 501,	/* Ǣ ǣ */
+	0x01e4, 501,	/* Ǥ ǥ */
+	0x01e6, 501,	/* Ǧ ǧ */
+	0x01e8, 501,	/* Ǩ ǩ */
+	0x01ea, 501,	/* Ǫ ǫ */
+	0x01ec, 501,	/* Ǭ ǭ */
+	0x01ee, 501,	/* Ǯ ǯ */
+	0x01f1, 502,	/* DZ dz */
+	0x01f2, 501,	/* Dz dz */
+	0x01f4, 501,	/* Ǵ ǵ */
+	0x01fa, 501,	/* Ǻ ǻ */
+	0x01fc, 501,	/* Ǽ ǽ */
+	0x01fe, 501,	/* Ǿ ǿ */
+	0x0200, 501,	/* Ȁ ȁ */
+	0x0202, 501,	/* Ȃ ȃ */
+	0x0204, 501,	/* Ȅ ȅ */
+	0x0206, 501,	/* Ȇ ȇ */
+	0x0208, 501,	/* Ȉ ȉ */
+	0x020a, 501,	/* Ȋ ȋ */
+	0x020c, 501,	/* Ȍ ȍ */
+	0x020e, 501,	/* Ȏ ȏ */
+	0x0210, 501,	/* Ȑ ȑ */
+	0x0212, 501,	/* Ȓ ȓ */
+	0x0214, 501,	/* Ȕ ȕ */
+	0x0216, 501,	/* Ȗ ȗ */
+	0x0386, 538,	/* Ά ά */
+	0x038c, 564,	/* Ό ό */
+	0x03e2, 501,	/* Ϣ ϣ */
+	0x03e4, 501,	/* Ϥ ϥ */
+	0x03e6, 501,	/* Ϧ ϧ */
+	0x03e8, 501,	/* Ϩ ϩ */
+	0x03ea, 501,	/* Ϫ ϫ */
+	0x03ec, 501,	/* Ϭ ϭ */
+	0x03ee, 501,	/* Ϯ ϯ */
+	0x0460, 501,	/* Ѡ ѡ */
+	0x0462, 501,	/* Ѣ ѣ */
+	0x0464, 501,	/* Ѥ ѥ */
+	0x0466, 501,	/* Ѧ ѧ */
+	0x0468, 501,	/* Ѩ ѩ */
+	0x046a, 501,	/* Ѫ ѫ */
+	0x046c, 501,	/* Ѭ ѭ */
+	0x046e, 501,	/* Ѯ ѯ */
+	0x0470, 501,	/* Ѱ ѱ */
+	0x0472, 501,	/* Ѳ ѳ */
+	0x0474, 501,	/* Ѵ ѵ */
+	0x0476, 501,	/* Ѷ ѷ */
+	0x0478, 501,	/* Ѹ ѹ */
+	0x047a, 501,	/* Ѻ ѻ */
+	0x047c, 501,	/* Ѽ ѽ */
+	0x047e, 501,	/* Ѿ ѿ */
+	0x0480, 501,	/* Ҁ ҁ */
+	0x0490, 501,	/* Ґ ґ */
+	0x0492, 501,	/* Ғ ғ */
+	0x0494, 501,	/* Ҕ ҕ */
+	0x0496, 501,	/* Җ җ */
+	0x0498, 501,	/* Ҙ ҙ */
+	0x049a, 501,	/* Қ қ */
+	0x049c, 501,	/* Ҝ ҝ */
+	0x049e, 501,	/* Ҟ ҟ */
+	0x04a0, 501,	/* Ҡ ҡ */
+	0x04a2, 501,	/* Ң ң */
+	0x04a4, 501,	/* Ҥ ҥ */
+	0x04a6, 501,	/* Ҧ ҧ */
+	0x04a8, 501,	/* Ҩ ҩ */
+	0x04aa, 501,	/* Ҫ ҫ */
+	0x04ac, 501,	/* Ҭ ҭ */
+	0x04ae, 501,	/* Ү ү */
+	0x04b0, 501,	/* Ұ ұ */
+	0x04b2, 501,	/* Ҳ ҳ */
+	0x04b4, 501,	/* Ҵ ҵ */
+	0x04b6, 501,	/* Ҷ ҷ */
+	0x04b8, 501,	/* Ҹ ҹ */
+	0x04ba, 501,	/* Һ һ */
+	0x04bc, 501,	/* Ҽ ҽ */
+	0x04be, 501,	/* Ҿ ҿ */
+	0x04c1, 501,	/* Ӂ ӂ */
+	0x04c3, 501,	/* Ӄ ӄ */
+	0x04c7, 501,	/* Ӈ ӈ */
+	0x04cb, 501,	/* Ӌ ӌ */
+	0x04d0, 501,	/* Ӑ ӑ */
+	0x04d2, 501,	/* Ӓ ӓ */
+	0x04d4, 501,	/* Ӕ ӕ */
+	0x04d6, 501,	/* Ӗ ӗ */
+	0x04d8, 501,	/* Ә ә */
+	0x04da, 501,	/* Ӛ ӛ */
+	0x04dc, 501,	/* Ӝ ӝ */
+	0x04de, 501,	/* Ӟ ӟ */
+	0x04e0, 501,	/* Ӡ ӡ */
+	0x04e2, 501,	/* Ӣ ӣ */
+	0x04e4, 501,	/* Ӥ ӥ */
+	0x04e6, 501,	/* Ӧ ӧ */
+	0x04e8, 501,	/* Ө ө */
+	0x04ea, 501,	/* Ӫ ӫ */
+	0x04ee, 501,	/* Ӯ ӯ */
+	0x04f0, 501,	/* Ӱ ӱ */
+	0x04f2, 501,	/* Ӳ ӳ */
+	0x04f4, 501,	/* Ӵ ӵ */
+	0x04f8, 501,	/* Ӹ ӹ */
+	0x1e00, 501,	/* Ḁ ḁ */
+	0x1e02, 501,	/* Ḃ ḃ */
+	0x1e04, 501,	/* Ḅ ḅ */
+	0x1e06, 501,	/* Ḇ ḇ */
+	0x1e08, 501,	/* Ḉ ḉ */
+	0x1e0a, 501,	/* Ḋ ḋ */
+	0x1e0c, 501,	/* Ḍ ḍ */
+	0x1e0e, 501,	/* Ḏ ḏ */
+	0x1e10, 501,	/* Ḑ ḑ */
+	0x1e12, 501,	/* Ḓ ḓ */
+	0x1e14, 501,	/* Ḕ ḕ */
+	0x1e16, 501,	/* Ḗ ḗ */
+	0x1e18, 501,	/* Ḙ ḙ */
+	0x1e1a, 501,	/* Ḛ ḛ */
+	0x1e1c, 501,	/* Ḝ ḝ */
+	0x1e1e, 501,	/* Ḟ ḟ */
+	0x1e20, 501,	/* Ḡ ḡ */
+	0x1e22, 501,	/* Ḣ ḣ */
+	0x1e24, 501,	/* Ḥ ḥ */
+	0x1e26, 501,	/* Ḧ ḧ */
+	0x1e28, 501,	/* Ḩ ḩ */
+	0x1e2a, 501,	/* Ḫ ḫ */
+	0x1e2c, 501,	/* Ḭ ḭ */
+	0x1e2e, 501,	/* Ḯ ḯ */
+	0x1e30, 501,	/* Ḱ ḱ */
+	0x1e32, 501,	/* Ḳ ḳ */
+	0x1e34, 501,	/* Ḵ ḵ */
+	0x1e36, 501,	/* Ḷ ḷ */
+	0x1e38, 501,	/* Ḹ ḹ */
+	0x1e3a, 501,	/* Ḻ ḻ */
+	0x1e3c, 501,	/* Ḽ ḽ */
+	0x1e3e, 501,	/* Ḿ ḿ */
+	0x1e40, 501,	/* Ṁ ṁ */
+	0x1e42, 501,	/* Ṃ ṃ */
+	0x1e44, 501,	/* Ṅ ṅ */
+	0x1e46, 501,	/* Ṇ ṇ */
+	0x1e48, 501,	/* Ṉ ṉ */
+	0x1e4a, 501,	/* Ṋ ṋ */
+	0x1e4c, 501,	/* Ṍ ṍ */
+	0x1e4e, 501,	/* Ṏ ṏ */
+	0x1e50, 501,	/* Ṑ ṑ */
+	0x1e52, 501,	/* Ṓ ṓ */
+	0x1e54, 501,	/* Ṕ ṕ */
+	0x1e56, 501,	/* Ṗ ṗ */
+	0x1e58, 501,	/* Ṙ ṙ */
+	0x1e5a, 501,	/* Ṛ ṛ */
+	0x1e5c, 501,	/* Ṝ ṝ */
+	0x1e5e, 501,	/* Ṟ ṟ */
+	0x1e60, 501,	/* Ṡ ṡ */
+	0x1e62, 501,	/* Ṣ ṣ */
+	0x1e64, 501,	/* Ṥ ṥ */
+	0x1e66, 501,	/* Ṧ ṧ */
+	0x1e68, 501,	/* Ṩ ṩ */
+	0x1e6a, 501,	/* Ṫ ṫ */
+	0x1e6c, 501,	/* Ṭ ṭ */
+	0x1e6e, 501,	/* Ṯ ṯ */
+	0x1e70, 501,	/* Ṱ ṱ */
+	0x1e72, 501,	/* Ṳ ṳ */
+	0x1e74, 501,	/* Ṵ ṵ */
+	0x1e76, 501,	/* Ṷ ṷ */
+	0x1e78, 501,	/* Ṹ ṹ */
+	0x1e7a, 501,	/* Ṻ ṻ */
+	0x1e7c, 501,	/* Ṽ ṽ */
+	0x1e7e, 501,	/* Ṿ ṿ */
+	0x1e80, 501,	/* Ẁ ẁ */
+	0x1e82, 501,	/* Ẃ ẃ */
+	0x1e84, 501,	/* Ẅ ẅ */
+	0x1e86, 501,	/* Ẇ ẇ */
+	0x1e88, 501,	/* Ẉ ẉ */
+	0x1e8a, 501,	/* Ẋ ẋ */
+	0x1e8c, 501,	/* Ẍ ẍ */
+	0x1e8e, 501,	/* Ẏ ẏ */
+	0x1e90, 501,	/* Ẑ ẑ */
+	0x1e92, 501,	/* Ẓ ẓ */
+	0x1e94, 501,	/* Ẕ ẕ */
+	0x1ea0, 501,	/* Ạ ạ */
+	0x1ea2, 501,	/* Ả ả */
+	0x1ea4, 501,	/* Ấ ấ */
+	0x1ea6, 501,	/* Ầ ầ */
+	0x1ea8, 501,	/* Ẩ ẩ */
+	0x1eaa, 501,	/* Ẫ ẫ */
+	0x1eac, 501,	/* Ậ ậ */
+	0x1eae, 501,	/* Ắ ắ */
+	0x1eb0, 501,	/* Ằ ằ */
+	0x1eb2, 501,	/* Ẳ ẳ */
+	0x1eb4, 501,	/* Ẵ ẵ */
+	0x1eb6, 501,	/* Ặ ặ */
+	0x1eb8, 501,	/* Ẹ ẹ */
+	0x1eba, 501,	/* Ẻ ẻ */
+	0x1ebc, 501,	/* Ẽ ẽ */
+	0x1ebe, 501,	/* Ế ế */
+	0x1ec0, 501,	/* Ề ề */
+	0x1ec2, 501,	/* Ể ể */
+	0x1ec4, 501,	/* Ễ ễ */
+	0x1ec6, 501,	/* Ệ ệ */
+	0x1ec8, 501,	/* Ỉ ỉ */
+	0x1eca, 501,	/* Ị ị */
+	0x1ecc, 501,	/* Ọ ọ */
+	0x1ece, 501,	/* Ỏ ỏ */
+	0x1ed0, 501,	/* Ố ố */
+	0x1ed2, 501,	/* Ồ ồ */
+	0x1ed4, 501,	/* Ổ ổ */
+	0x1ed6, 501,	/* Ỗ ỗ */
+	0x1ed8, 501,	/* Ộ ộ */
+	0x1eda, 501,	/* Ớ ớ */
+	0x1edc, 501,	/* Ờ ờ */
+	0x1ede, 501,	/* Ở ở */
+	0x1ee0, 501,	/* Ỡ ỡ */
+	0x1ee2, 501,	/* Ợ ợ */
+	0x1ee4, 501,	/* Ụ ụ */
+	0x1ee6, 501,	/* Ủ ủ */
+	0x1ee8, 501,	/* Ứ ứ */
+	0x1eea, 501,	/* Ừ ừ */
+	0x1eec, 501,	/* Ử ử */
+	0x1eee, 501,	/* Ữ ữ */
+	0x1ef0, 501,	/* Ự ự */
+	0x1ef2, 501,	/* Ỳ ỳ */
+	0x1ef4, 501,	/* Ỵ ỵ */
+	0x1ef6, 501,	/* Ỷ ỷ */
+	0x1ef8, 501,	/* Ỹ ỹ */
+	0x1f59, 492,	/* Ὑ ὑ */
+	0x1f5b, 492,	/* Ὓ ὓ */
+	0x1f5d, 492,	/* Ὕ ὕ */
+	0x1f5f, 492,	/* Ὗ ὗ */
+	0x1fbc, 491,	/* ᾼ ᾳ */
+	0x1fcc, 491,	/* ῌ ῃ */
+	0x1fec, 493,	/* Ῥ ῥ */
+	0x1ffc, 491,	/* ῼ ῳ */
+};
+
+/*
+ * title characters are those between
+ * upper and lower case. ie DZ Dz dz
+ */
+static
+Rune	_totitle1[] =
+{
+	0x01c4, 501,	/* DŽ Dž */
+	0x01c6, 499,	/* dž Dž */
+	0x01c7, 501,	/* LJ Lj */
+	0x01c9, 499,	/* lj Lj */
+	0x01ca, 501,	/* NJ Nj */
+	0x01cc, 499,	/* nj Nj */
+	0x01f1, 501,	/* DZ Dz */
+	0x01f3, 499,	/* dz Dz */
+};
+
+#define bsearch xbsearch
+static
+Rune*
+bsearch(Rune c, Rune *t, int n, int ne)
+{
+	Rune *p;
+	int m;
+
+	while(n > 1) {
+		m = n/2;
+		p = t + m*ne;
+		if(c >= p[0]) {
+			t = p;
+			n = n-m;
+		} else
+			n = m;
+	}
+	if(n && c >= t[0])
+		return t;
+	return 0;
+}
+
+Rune
+tolowerrune(Rune c)
+{
+	Rune *p;
+
+	p = bsearch(c, _tolower2, nelem(_tolower2)/3, 3);
+	if(p && c >= p[0] && c <= p[1])
+		return c + p[2] - 500;
+	p = bsearch(c, _tolower1, nelem(_tolower1)/2, 2);
+	if(p && c == p[0])
+		return c + p[1] - 500;
+	return c;
+}
+
+Rune
+toupperrune(Rune c)
+{
+	Rune *p;
+
+	p = bsearch(c, _toupper2, nelem(_toupper2)/3, 3);
+	if(p && c >= p[0] && c <= p[1])
+		return c + p[2] - 500;
+	p = bsearch(c, _toupper1, nelem(_toupper1)/2, 2);
+	if(p && c == p[0])
+		return c + p[1] - 500;
+	return c;
+}
+
+Rune
+totitlerune(Rune c)
+{
+	Rune *p;
+
+	p = bsearch(c, _totitle1, nelem(_totitle1)/2, 2);
+	if(p && c == p[0])
+		return c + p[1] - 500;
+	return c;
+}
+
+int
+islowerrune(Rune c)
+{
+	Rune *p;
+
+	p = bsearch(c, _toupper2, nelem(_toupper2)/3, 3);
+	if(p && c >= p[0] && c <= p[1])
+		return 1;
+	p = bsearch(c, _toupper1, nelem(_toupper1)/2, 2);
+	if(p && c == p[0])
+		return 1;
+	return 0;
+}
+
+int
+isupperrune(Rune c)
+{
+	Rune *p;
+
+	p = bsearch(c, _tolower2, nelem(_tolower2)/3, 3);
+	if(p && c >= p[0] && c <= p[1])
+		return 1;
+	p = bsearch(c, _tolower1, nelem(_tolower1)/2, 2);
+	if(p && c == p[0])
+		return 1;
+	return 0;
+}
+
+int
+isalpharune(Rune c)
+{
+	Rune *p;
+
+	if(isupperrune(c) || islowerrune(c))
+		return 1;
+	p = bsearch(c, _alpha2, nelem(_alpha2)/2, 2);
+	if(p && c >= p[0] && c <= p[1])
+		return 1;
+	p = bsearch(c, _alpha1, nelem(_alpha1), 1);
+	if(p && c == p[0])
+		return 1;
+	return 0;
+}
+
+int
+istitlerune(Rune c)
+{
+	return isupperrune(c) && islowerrune(c);
+}
+
+int
+isspacerune(Rune c)
+{
+	Rune *p;
+
+	p = bsearch(c, _space2, nelem(_space2)/2, 2);
+	if(p && c >= p[0] && c <= p[1])
+		return 1;
+	return 0;
+}
--- /dev/null
+++ b/libc/runevseprint.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runevseprint(Rune *buf, Rune *e, char *fmt, va_list args)
+{
+	Fmt f;
+
+	if(e <= buf)
+		return nil;
+	f.runes = 1;
+	f.start = buf;
+	f.to = buf;
+	f.stop = e - 1;
+	f.flush = nil;
+	f.farg = nil;
+	f.nfmt = 0;
+	f.args = args;
+	dofmt(&f, fmt);
+	*(Rune*)f.to = '\0';
+	return f.to;
+}
+
--- /dev/null
+++ b/libc/runevsmprint.c
@@ -1,0 +1,70 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+static int
+runeFmtStrFlush(Fmt *f)
+{
+	Rune *s;
+	int n;
+
+	if(f->start == nil)
+		return 0;
+	n = (int)f->farg;
+	n *= 2;
+	s = f->start;
+	f->start = realloc(s, sizeof(Rune)*n);
+	if(f->start == nil){
+		f->farg = nil;
+		f->to = nil;
+		f->stop = nil;
+		free(s);
+		return 0;
+	}
+	f->farg = (void*)n;
+	f->to = (Rune*)f->start + ((Rune*)f->to - s);
+	f->stop = (Rune*)f->start + n - 1;
+	return 1;
+}
+
+int
+runefmtstrinit(Fmt *f)
+{
+	int n;
+
+	memset(f, 0, sizeof *f);
+	f->runes = 1;
+	n = 32;
+	f->start = malloc(sizeof(Rune)*n);
+	if(f->start == nil)
+		return -1;
+	f->to = f->start;
+	f->stop = (Rune*)f->start + n - 1;
+	f->flush = runeFmtStrFlush;
+	f->farg = (void*)n;
+	f->nfmt = 0;
+	return 0;
+}
+
+/*
+ * print into an allocated string buffer
+ */
+Rune*
+runevsmprint(char *fmt, va_list args)
+{
+	Fmt f;
+	int n;
+
+	if(runefmtstrinit(&f) < 0)
+		return nil;
+	f.args = args;
+	n = dofmt(&f, fmt);
+	if(f.start == nil)
+		return nil;
+	if(n < 0){
+		free(f.start);
+		return nil;
+	}
+	*(Rune*)f.to = '\0';
+	return f.start;
+}
--- /dev/null
+++ b/libc/runevsnprint.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+
+int
+runevsnprint(Rune *buf, int len, char *fmt, va_list args)
+{
+	Fmt f;
+
+	if(len <= 0)
+		return -1;
+	f.runes = 1;
+	f.start = buf;
+	f.to = buf;
+	f.stop = buf + len - 1;
+	f.flush = nil;
+	f.farg = nil;
+	f.nfmt = 0;
+	f.args = args;
+	dofmt(&f, fmt);
+	*(Rune*)f.to = '\0';
+	return (Rune*)f.to - buf;
+}
--- /dev/null
+++ b/libc/seprint.c
@@ -1,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+seprint(char *buf, char *e, char *fmt, ...)
+{
+	char *p;
+	va_list args;
+
+	va_start(args, fmt);
+	p = vseprint(buf, e, fmt, args);
+	va_end(args);
+	return p;
+}
--- /dev/null
+++ b/libc/smprint.c
@@ -1,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+smprint(char *fmt, ...)
+{
+	va_list args;
+	char *p;
+
+	va_start(args, fmt);
+	p = vsmprint(fmt, args);
+	va_end(args);
+	return p;
+}
--- /dev/null
+++ b/libc/snprint.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+
+int
+snprint(char *buf, int len, char *fmt, ...)
+{
+	int n;
+	va_list args;
+
+	va_start(args, fmt);
+	n = vsnprint(buf, len, fmt, args);
+	va_end(args);
+	return n;
+}
+
--- /dev/null
+++ b/libc/sprint.c
@@ -1,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+
+int
+sprint(char *buf, char *fmt, ...)
+{
+	int n;
+	va_list args;
+
+	va_start(args, fmt);
+	n = vsnprint(buf, 65536, fmt, args);	/* big number, but sprint is deprecated anyway */
+	va_end(args);
+	return n;
+}
--- /dev/null
+++ b/libc/strecpy.c
@@ -1,0 +1,17 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+strecpy(char *to, char *e, char *from)
+{
+	if(to >= e)
+		return to;
+	to = memccpy(to, from, '\0', e - to);
+	if(to == nil){
+		to = e - 1;
+		*to = '\0';
+	}else{
+		to--;
+	}
+	return to;
+}
--- /dev/null
+++ b/libc/strtod.c
@@ -1,0 +1,532 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+/*
+ * This routine will convert to arbitrary precision
+ * floating point entirely in multi-precision fixed.
+ * The answer is the closest floating point number to
+ * the given decimal number. Exactly half way are
+ * rounded ala ieee rules.
+ * Method is to scale input decimal between .500 and .999...
+ * with external power of 2, then binary search for the
+ * closest mantissa to this decimal number.
+ * Nmant is is the required precision. (53 for ieee dp)
+ * Nbits is the max number of bits/word. (must be <= 28)
+ * Prec is calculated - the number of words of fixed mantissa.
+ */
+enum
+{
+	Nbits	= 28,				// bits safely represented in a ulong
+	Nmant	= 53,				// bits of precision required
+	Bias		= 1022,
+	Prec	= (Nmant+Nbits+1)/Nbits,	// words of Nbits each to represent mantissa
+	Sigbit	= 1<<(Prec*Nbits-Nmant),	// first significant bit of Prec-th word
+	Ndig	= 1500,
+	One	= (ulong)(1<<Nbits),
+	Half	= (ulong)(One>>1),
+	Maxe	= 310,
+	Fsign	= 1<<0,		// found -
+	Fesign	= 1<<1,		// found e-
+	Fdpoint	= 1<<2,		// found .
+
+	S0	= 0,		// _		_S0	+S1	#S2	.S3
+	S1,			// _+		#S2	.S3
+	S2,			// _+#		#S2	.S4	eS5
+	S3,			// _+.		#S4
+	S4,			// _+#.#	#S4	eS5
+	S5,			// _+#.#e	+S6	#S7
+	S6,			// _+#.#e+	#S7
+	S7,			// _+#.#e+#	#S7
+};
+
+static ulong
+umuldiv(ulong a, ulong b, ulong c)
+{
+	double d;
+
+	d = ((double)a * (double)b) / (double)c;
+	if(d >= 4294967295.)
+		d = 4294967295.;
+	return d;
+}
+
+static	int	xcmp(char*, char*);
+static	int	fpcmp(char*, ulong*);
+static	void	frnorm(ulong*);
+static	void	divascii(char*, int*, int*, int*);
+static	void	mulascii(char*, int*, int*, int*);
+static	void	divby(char*, int*, int);
+
+typedef	struct	Tab	Tab;
+struct	Tab
+{
+	int	bp;
+	int	siz;
+	char*	cmp;
+};
+
+double
+strtod(char *as, char **aas)
+{
+	int na, ona, ex, dp, bp, c, i, flag, state;
+	ulong low[Prec], hig[Prec], mid[Prec], num, den;
+	double d;
+	char *s, a[Ndig];
+
+	flag = 0;	// Fsign, Fesign, Fdpoint
+	na = 0;		// number of digits of a[]
+	dp = 0;		// na of decimal point
+	ex = 0;		// exonent
+
+	state = S0;
+	for(s=as;; s++) {
+		c = *s;
+		if(c >= '0' && c <= '9') {
+			switch(state) {
+			case S0:
+			case S1:
+			case S2:
+				state = S2;
+				break;
+			case S3:
+			case S4:
+				state = S4;
+				break;
+
+			case S5:
+			case S6:
+			case S7:
+				state = S7;
+				ex = ex*10 + (c-'0');
+				continue;
+			}
+			if(na == 0 && c == '0') {
+				dp--;
+				continue;
+			}
+			if(na < Ndig-50)
+				a[na++] = c;
+			continue;
+		}
+		switch(c) {
+		case '\t':
+		case '\n':
+		case '\v':
+		case '\f':
+		case '\r':
+		case ' ':
+			if(state == S0)
+				continue;
+			break;
+		case '-':
+			if(state == S0)
+				flag |= Fsign;
+			else
+				flag |= Fesign;
+		case '+':
+			if(state == S0)
+				state = S1;
+			else
+			if(state == S5)
+				state = S6;
+			else
+				break;	// syntax
+			continue;
+		case '.':
+			flag |= Fdpoint;
+			dp = na;
+			if(state == S0 || state == S1) {
+				state = S3;
+				continue;
+			}
+			if(state == S2) {
+				state = S4;
+				continue;
+			}
+			break;
+		case 'e':
+		case 'E':
+			if(state == S2 || state == S4) {
+				state = S5;
+				continue;
+			}
+			break;
+		}
+		break;
+	}
+
+	/*
+	 * clean up return char-pointer
+	 */
+	switch(state) {
+	case S0:
+		if(xcmp(s, "nan") == 0) {
+			if(aas != nil)
+				*aas = s+3;
+			goto retnan;
+		}
+	case S1:
+		if(xcmp(s, "infinity") == 0) {
+			if(aas != nil)
+				*aas = s+8;
+			goto retinf;
+		}
+		if(xcmp(s, "inf") == 0) {
+			if(aas != nil)
+				*aas = s+3;
+			goto retinf;
+		}
+	case S3:
+		if(aas != nil)
+			*aas = as;
+		goto ret0;	// no digits found
+	case S6:
+		s--;		// back over +-
+	case S5:
+		s--;		// back over e
+		break;
+	}
+	if(aas != nil)
+		*aas = s;
+
+	if(flag & Fdpoint)
+	while(na > 0 && a[na-1] == '0')
+		na--;
+	if(na == 0)
+		goto ret0;	// zero
+	a[na] = 0;
+	if(!(flag & Fdpoint))
+		dp = na;
+	if(flag & Fesign)
+		ex = -ex;
+	dp += ex;
+	if(dp < -Maxe-Nmant/3)	/* actually -Nmant*log(2)/log(10), but Nmant/3 close enough */
+		goto ret0;	// underflow by exp
+	else
+	if(dp > +Maxe)
+		goto retinf;	// overflow by exp
+
+	/*
+	 * normalize the decimal ascii number
+	 * to range .[5-9][0-9]* e0
+	 */
+	bp = 0;		// binary exponent
+	while(dp > 0)
+		divascii(a, &na, &dp, &bp);
+	while(dp < 0 || a[0] < '5')
+		mulascii(a, &na, &dp, &bp);
+	a[na] = 0;
+
+	/*
+	 * very small numbers are represented using
+	 * bp = -Bias+1.  adjust accordingly.
+	 */
+	if(bp < -Bias+1){
+		ona = na;
+		divby(a, &na, -bp-Bias+1);
+		if(na < ona){
+			memmove(a+ona-na, a, na);
+			memset(a, '0', ona-na);
+			na = ona;
+		}
+		a[na] = 0;
+		bp = -Bias+1;
+	}
+
+	/* close approx by naive conversion */
+	num = 0;
+	den = 1;
+	for(i=0; i<9 && (c=a[i]); i++) {
+		num = num*10 + (c-'0');
+		den *= 10;
+	}
+	low[0] = umuldiv(num, One, den);
+	hig[0] = umuldiv(num+1, One, den);
+	for(i=1; i<Prec; i++) {
+		low[i] = 0;
+		hig[i] = One-1;
+	}
+
+	/* binary search for closest mantissa */
+	for(;;) {
+		/* mid = (hig + low) / 2 */
+		c = 0;
+		for(i=0; i<Prec; i++) {
+			mid[i] = hig[i] + low[i];
+			if(c)
+				mid[i] += One;
+			c = mid[i] & 1;
+			mid[i] >>= 1;
+		}
+		frnorm(mid);
+
+		/* compare */
+		c = fpcmp(a, mid);
+		if(c > 0) {
+			c = 1;
+			for(i=0; i<Prec; i++)
+				if(low[i] != mid[i]) {
+					c = 0;
+					low[i] = mid[i];
+				}
+			if(c)
+				break;	// between mid and hig
+			continue;
+		}
+		if(c < 0) {
+			for(i=0; i<Prec; i++)
+				hig[i] = mid[i];
+			continue;
+		}
+
+		/* only hard part is if even/odd roundings wants to go up */
+		c = mid[Prec-1] & (Sigbit-1);
+		if(c == Sigbit/2 && (mid[Prec-1]&Sigbit) == 0)
+			mid[Prec-1] -= c;
+		break;	// exactly mid
+	}
+
+	/* normal rounding applies */
+	c = mid[Prec-1] & (Sigbit-1);
+	mid[Prec-1] -= c;
+	if(c >= Sigbit/2) {
+		mid[Prec-1] += Sigbit;
+		frnorm(mid);
+	}
+	d = 0;
+	for(i=0; i<Prec; i++)
+		d = d*One + mid[i];
+	if(flag & Fsign)
+		d = -d;
+	d = ldexp(d, bp - Prec*Nbits);
+	return d;
+
+ret0:
+	return 0;
+
+retnan:
+	return __NaN();
+
+retinf:
+	if(flag & Fsign)
+		return __Inf(-1);
+	return __Inf(+1);
+}
+
+static void
+frnorm(ulong *f)
+{
+	int i, c;
+
+	c = 0;
+	for(i=Prec-1; i>0; i--) {
+		f[i] += c;
+		c = f[i] >> Nbits;
+		f[i] &= One-1;
+	}
+	f[0] += c;
+}
+
+static int
+fpcmp(char *a, ulong* f)
+{
+	ulong tf[Prec];
+	int i, d, c;
+
+	for(i=0; i<Prec; i++)
+		tf[i] = f[i];
+
+	for(;;) {
+		/* tf *= 10 */
+		for(i=0; i<Prec; i++)
+			tf[i] = tf[i]*10;
+		frnorm(tf);
+		d = (tf[0] >> Nbits) + '0';
+		tf[0] &= One-1;
+
+		/* compare next digit */
+		c = *a;
+		if(c == 0) {
+			if('0' < d)
+				return -1;
+			if(tf[0] != 0)
+				goto cont;
+			for(i=1; i<Prec; i++)
+				if(tf[i] != 0)
+					goto cont;
+			return 0;
+		}
+		if(c > d)
+			return +1;
+		if(c < d)
+			return -1;
+		a++;
+	cont:;
+	}
+	return 0;
+}
+
+static void
+_divby(char *a, int *na, int b)
+{
+	int n, c;
+	char *p;
+
+	p = a;
+	n = 0;
+	while(n>>b == 0) {
+		c = *a++;
+		if(c == 0) {
+			while(n) {
+				c = n*10;
+				if(c>>b)
+					break;
+				n = c;
+			}
+			goto xx;
+		}
+		n = n*10 + c-'0';
+		(*na)--;
+	}
+	for(;;) {
+		c = n>>b;
+		n -= c<<b;
+		*p++ = c + '0';
+		c = *a++;
+		if(c == 0)
+			break;
+		n = n*10 + c-'0';
+	}
+	(*na)++;
+xx:
+	while(n) {
+		n = n*10;
+		c = n>>b;
+		n -= c<<b;
+		*p++ = c + '0';
+		(*na)++;
+	}
+	*p = 0;
+}
+
+static void
+divby(char *a, int *na, int b)
+{
+	while(b > 9){
+		_divby(a, na, 9);
+		a[*na] = 0;
+		b -= 9;
+	}
+	if(b > 0)
+		_divby(a, na, b);
+}
+
+static	Tab	tab1[] =
+{
+	 1,  0, "",
+	 3,  1, "7",
+	 6,  2, "63",
+	 9,  3, "511",
+	13,  4, "8191",
+	16,  5, "65535",
+	19,  6, "524287",
+	23,  7, "8388607",
+	26,  8, "67108863",
+	27,  9, "134217727",
+};
+
+static void
+divascii(char *a, int *na, int *dp, int *bp)
+{
+	int b, d;
+	Tab *t;
+
+	d = *dp;
+	if(d >= nelem(tab1))
+		d = nelem(tab1)-1;
+	t = tab1 + d;
+	b = t->bp;
+	if(memcmp(a, t->cmp, t->siz) > 0)
+		d--;
+	*dp -= d;
+	*bp += b;
+	divby(a, na, b);
+}
+
+static void
+mulby(char *a, char *p, char *q, int b)
+{
+	int n, c;
+
+	n = 0;
+	*p = 0;
+	for(;;) {
+		q--;
+		if(q < a)
+			break;
+		c = *q - '0';
+		c = (c<<b) + n;
+		n = c/10;
+		c -= n*10;
+		p--;
+		*p = c + '0';
+	}
+	while(n) {
+		c = n;
+		n = c/10;
+		c -= n*10;
+		p--;
+		*p = c + '0';
+	}
+}
+
+static	Tab	tab2[] =
+{
+	 1,  1, "",				// dp = 0-0
+	 3,  3, "125",
+	 6,  5, "15625",
+	 9,  7, "1953125",
+	13, 10, "1220703125",
+	16, 12, "152587890625",
+	19, 14, "19073486328125",
+	23, 17, "11920928955078125",
+	26, 19, "1490116119384765625",
+	27, 19, "7450580596923828125",		// dp 8-9
+};
+
+static void
+mulascii(char *a, int *na, int *dp, int *bp)
+{
+	char *p;
+	int d, b;
+	Tab *t;
+
+	d = -*dp;
+	if(d >= nelem(tab2))
+		d = nelem(tab2)-1;
+	t = tab2 + d;
+	b = t->bp;
+	if(memcmp(a, t->cmp, t->siz) < 0)
+		d--;
+	p = a + *na;
+	*bp -= b;
+	*dp += d;
+	*na += d;
+	mulby(a, p+d, p, b);
+}
+
+static int
+xcmp(char *a, char *b)
+{
+	int c1, c2;
+
+	while(c1 = *b++) {
+		c2 = *a++;
+		if(isupper(c2))
+			c2 = tolower(c2);
+		if(c1 != c2)
+			return 1;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libc/strtod.h
@@ -1,0 +1,4 @@
+extern double __NaN(void);
+extern double __Inf(int);
+extern double __isNaN(double);
+extern double __isInf(double, int);
--- /dev/null
+++ b/libc/strtoll.c
@@ -1,0 +1,93 @@
+#include <u.h>
+#include <libc.h>
+#define VLONG_MAX	~(((vlong) 1)<<63)
+#define VLONG_MIN	(((vlong) 1)<<63)
+vlong
+strtoll(const char *nptr, char **endptr, int base)
+{
+	char *p;
+	vlong n, nn, m;
+	int c, ovfl, v, neg, ndig;
+	p = (char*)nptr;
+	neg = 0;
+	n = 0;
+	ndig = 0;
+	ovfl = 0;
+	/*
+	 * White space
+	 */
+	for(;; p++) {
+		switch(*p) {
+		case ' ':
+		case '\t':
+		case '\n':
+		case '\f':
+		case '\r':
+		case '\v':
+			continue;
+		}
+		break;
+	}
+	/*
+	 * Sign
+	 */
+	if(*p=='-' || *p=='+')
+		if(*p++ == '-')
+			neg = 1;
+	/*
+	 * Base
+	 */
+	if(base==0){
+		base = 10;
+		if(*p == '0') {
+			base = 8;
+			if(p[1]=='x' || p[1]=='X') {
+				p += 2;
+				base = 16;
+			}
+		}
+	} else
+	if(base==16 && *p=='0') {
+		if(p[1]=='x' || p[1]=='X')
+			p += 2;
+	} else
+	if(base<0 || 36<base)
+		goto Return;
+	/*
+	 * Non-empty sequence of digits
+	 */
+	m = VLONG_MAX/base;
+	for(;; p++,ndig++) {
+		c = *p;
+		v = base;
+		if('0'<=c && c<='9')
+			v = c - '0';
+		else
+		if('a'<=c && c<='z')
+			v = c - 'a' + 10;
+		else
+		if('A'<=c && c<='Z')
+			v = c - 'A' + 10;
+		if(v >= base)
+			break;
+		if(n > m)
+			ovfl = 1;
+		nn = n*base + v;
+		if(nn < n)
+			ovfl = 1;
+		n = nn;
+	}
+Return:
+	if(ndig == 0)
+		p = (char*)nptr;
+	if(endptr)
+		*endptr = p;
+	if(ovfl){
+		if(neg)
+			return VLONG_MIN;
+		return VLONG_MAX;
+	}
+	if(neg)
+		return -n;
+	return n;
+}
--- /dev/null
+++ b/libc/sysfatal.c
@@ -1,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+
+static void
+_sysfatalimpl(char *fmt, va_list arg)
+{
+	char buf[1024];
+
+	vseprint(buf, buf+sizeof(buf), fmt, arg);
+	if(argv0)
+		fprint(2, "%s: %s\n", argv0, buf);
+	else
+		fprint(2, "%s\n", buf);
+#undef write
+write(2, buf, strlen(buf));
+write(2, "\n", 1);
+	panic("sysfatal");
+}
+
+void (*_sysfatal)(char *fmt, va_list arg) = _sysfatalimpl;
+
+void
+sysfatal(char *fmt, ...)
+{
+	va_list arg;
+
+	va_start(arg, fmt);
+	(*_sysfatal)(fmt, arg);
+	va_end(arg);
+}
--- /dev/null
+++ b/libc/time.c
@@ -1,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+
+
+/*
+ *  After a fork with fd's copied, both fd's are pointing to
+ *  the same Chan structure.  Since the offset is kept in the Chan
+ *  structure, the seek's and read's in the two processes can
+ *  compete at moving the offset around.  Hence the unusual loop
+ *  in the middle of this routine.
+ */
+static long
+oldtime(long *tp)
+{
+	char b[20];
+	static int f = -1;
+	int i, retries;
+	long t;
+
+	memset(b, 0, sizeof(b));
+	for(retries = 0; retries < 100; retries++){
+		if(f < 0)
+			f = open("/dev/time", OREAD|OCEXEC);
+		if(f < 0)
+			break;
+		if(seek(f, 0, 0) < 0 || (i = read(f, b, sizeof(b))) < 0){
+			close(f);
+			f = -1;
+		} else {
+			if(i != 0)
+				break;
+		}
+	}
+	t = atol(b);
+	if(tp)
+		*tp = t;
+	return t;
+}
+
+long
+time(long *tp)
+{
+	vlong t;
+
+	t = nsec()/((vlong)1000000000);
+	if(t == 0)
+		t = oldtime(0);
+	if(tp != nil)
+		*tp = t;
+	return t;
+}
--- /dev/null
+++ b/libc/tokenize.c
@@ -1,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+
+static char qsep[] = " \t\r\n";
+
+static char*
+qtoken(char *s, char *sep)
+{
+	int quoting;
+	char *t;
+
+	quoting = 0;
+	t = s;	/* s is output string, t is input string */
+	while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
+		if(*t != '\''){
+			*s++ = *t++;
+			continue;
+		}
+		/* *t is a quote */
+		if(!quoting){
+			quoting = 1;
+			t++;
+			continue;
+		}
+		/* quoting and we're on a quote */
+		if(t[1] != '\''){
+			/* end of quoted section; absorb closing quote */
+			t++;
+			quoting = 0;
+			continue;
+		}
+		/* doubled quote; fold one quote into two */
+		t++;
+		*s++ = *t++;
+	}
+	if(*s != '\0'){
+		*s = '\0';
+		if(t == s)
+			t++;
+	}
+	return t;
+}
+
+static char*
+etoken(char *t, char *sep)
+{
+	int quoting;
+
+	/* move to end of next token */
+	quoting = 0;
+	while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
+		if(*t != '\''){
+			t++;
+			continue;
+		}
+		/* *t is a quote */
+		if(!quoting){
+			quoting = 1;
+			t++;
+			continue;
+		}
+		/* quoting and we're on a quote */
+		if(t[1] != '\''){
+			/* end of quoted section; absorb closing quote */
+			t++;
+			quoting = 0;
+			continue;
+		}
+		/* doubled quote; fold one quote into two */
+		t += 2;
+	}
+	return t;
+}
+
+int
+gettokens(char *s, char **args, int maxargs, char *sep)
+{
+	int nargs;
+
+	for(nargs=0; nargs<maxargs; nargs++){
+		while(*s!='\0' && utfrune(sep, *s)!=nil)
+			*s++ = '\0';
+		if(*s == '\0')
+			break;
+		args[nargs] = s;
+		s = etoken(s, sep);
+	}
+
+	return nargs;
+}
+
+int
+tokenize(char *s, char **args, int maxargs)
+{
+	int nargs;
+
+	for(nargs=0; nargs<maxargs; nargs++){
+		while(*s!='\0' && utfrune(qsep, *s)!=nil)
+			s++;
+		if(*s == '\0')
+			break;
+		args[nargs] = s;
+		s = qtoken(s, qsep);
+	}
+
+	return nargs;
+}
--- /dev/null
+++ b/libc/truerand.c
@@ -1,0 +1,17 @@
+#include <u.h>
+#include <libc.h>
+
+ulong
+truerand(void)
+{
+	ulong x;
+	static int randfd = -1;
+
+	if(randfd < 0)
+		randfd = open("/dev/random", OREAD|OCEXEC);
+	if(randfd < 0)
+		sysfatal("can't open /dev/random");
+	if(read(randfd, &x, sizeof(x)) != sizeof(x))
+		sysfatal("can't read /dev/random");
+	return x;
+}
--- /dev/null
+++ b/libc/u16.c
@@ -1,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+static char t16e[] = "0123456789ABCDEF";
+
+int
+dec16(uchar *out, int lim, char *in, int n)
+{
+	int c, w = 0, i = 0;
+	uchar *start = out;
+	uchar *eout = out + lim;
+
+	while(n-- > 0){
+		c = *in++;
+		if('0' <= c && c <= '9')
+			c = c - '0';
+		else if('a' <= c && c <= 'z')
+			c = c - 'a' + 10;
+		else if('A' <= c && c <= 'Z')
+			c = c - 'A' + 10;
+		else
+			continue;
+		w = (w<<4) + c;
+		i++;
+		if(i == 2){
+			if(out + 1 > eout)
+				goto exhausted;
+			*out++ = w;
+			w = 0;
+			i = 0;
+		}
+	}
+exhausted:
+	return out - start;
+}
+
+int
+enc16(char *out, int lim, uchar *in, int n)
+{
+	uint c;
+	char *eout = out + lim;
+	char *start = out;
+
+	while(n-- > 0){
+		c = *in++;
+		if(out + 2 >= eout)
+			goto exhausted;
+		*out++ = t16e[c>>4];
+		*out++ = t16e[c&0xf];
+	}
+exhausted:
+	*out = 0;
+	return out - start;
+}
--- /dev/null
+++ b/libc/u32.c
@@ -1,0 +1,110 @@
+#include <u.h>
+#include <libc.h>
+
+int
+dec32(uchar *dest, int ndest, char *src, int nsrc)
+{
+	char *s, *tab;
+	uchar *start;
+	int i, u[8];
+
+	if(ndest+1 < (5*nsrc+7)/8)
+		return -1;
+	start = dest;
+	tab = "23456789abcdefghijkmnpqrstuvwxyz";
+	while(nsrc>=8){
+		for(i=0; i<8; i++){
+			s = strchr(tab,(int)src[i]);
+			u[i] = s ? s-tab : 0;
+		}
+		*dest++ = (u[0]<<3) | (0x7 & (u[1]>>2));
+		*dest++ = ((0x3 & u[1])<<6) | (u[2]<<1) | (0x1 & (u[3]>>4));
+		*dest++ = ((0xf & u[3])<<4) | (0xf & (u[4]>>1));
+		*dest++ = ((0x1 & u[4])<<7) | (u[5]<<2) | (0x3 & (u[6]>>3));
+		*dest++ = ((0x7 & u[6])<<5) | u[7];
+		src  += 8;
+		nsrc -= 8;
+	}
+	if(nsrc > 0){
+		if(nsrc == 1 || nsrc == 3 || nsrc == 6)
+			return -1;
+		for(i=0; i<nsrc; i++){
+			s = strchr(tab,(int)src[i]);
+			u[i] = s ? s-tab : 0;
+		}
+		*dest++ = (u[0]<<3) | (0x7 & (u[1]>>2));
+		if(nsrc == 2)
+			goto out;
+		*dest++ = ((0x3 & u[1])<<6) | (u[2]<<1) | (0x1 & (u[3]>>4));
+		if(nsrc == 4)
+			goto out;
+		*dest++ = ((0xf & u[3])<<4) | (0xf & (u[4]>>1));
+		if(nsrc == 5)
+			goto out;
+		*dest++ = ((0x1 & u[4])<<7) | (u[5]<<2) | (0x3 & (u[6]>>3));
+	}
+out:
+	return dest-start;
+}
+
+int
+enc32(char *dest, int ndest, uchar *src, int nsrc)
+{
+	char *tab, *start;
+	int j;
+
+	if(ndest <= (8*nsrc+4)/5 )
+		return -1;
+	start = dest;
+	tab = "23456789abcdefghijkmnpqrstuvwxyz";
+	while(nsrc>=5){
+		j = (0x1f & (src[0]>>3));
+		*dest++ = tab[j];
+		j = (0x1c & (src[0]<<2)) | (0x03 & (src[1]>>6));
+		*dest++ = tab[j];
+		j = (0x1f & (src[1]>>1));
+		*dest++ = tab[j];
+		j = (0x10 & (src[1]<<4)) | (0x0f & (src[2]>>4));
+		*dest++ = tab[j];
+		j = (0x1e & (src[2]<<1)) | (0x01 & (src[3]>>7));
+		*dest++ = tab[j];
+		j = (0x1f & (src[3]>>2));
+		*dest++ = tab[j];
+		j = (0x18 & (src[3]<<3)) | (0x07 & (src[4]>>5));
+		*dest++ = tab[j];
+		j = (0x1f & (src[4]));
+		*dest++ = tab[j];
+		src  += 5;
+		nsrc -= 5;
+	}
+	if(nsrc){
+		j = (0x1f & (src[0]>>3));
+		*dest++ = tab[j];
+		j = (0x1c & (src[0]<<2));
+		if(nsrc == 1)
+			goto out;
+		j |= (0x03 & (src[1]>>6));
+		*dest++ = tab[j];
+		j = (0x1f & (src[1]>>1));
+		if(nsrc == 2)
+			goto out;
+		*dest++ = tab[j];
+		j = (0x10 & (src[1]<<4));
+		if(nsrc == 3)
+			goto out;
+		j |= (0x0f & (src[2]>>4));
+		*dest++ = tab[j];
+		j = (0x1e & (src[2]<<1));
+		if(nsrc == 4)
+			goto out;
+		j |= (0x01 & (src[3]>>7));
+		*dest++ = tab[j];
+		j = (0x1f & (src[3]>>2));
+		*dest++ = tab[j];
+		j = (0x18 & (src[3]<<3));
+out:
+		*dest++ = tab[j];
+	}
+	*dest = 0;
+	return dest-start;
+}
--- /dev/null
+++ b/libc/u64.c
@@ -1,0 +1,127 @@
+#include <u.h>
+#include <libc.h>
+
+enum {
+	INVAL=	255
+};
+
+static uchar t64d[256] = {
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,   62,INVAL,INVAL,INVAL,   63,
+      52,   53,   54,   55,   56,   57,   58,   59,   60,   61,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,
+      15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,
+      41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL
+};
+static char t64e[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+int
+dec64(uchar *out, int lim, char *in, int n)
+{
+	ulong b24;
+	uchar *start = out;
+	uchar *e = out + lim;
+	int i, c;
+
+	b24 = 0;
+	i = 0;
+	while(n-- > 0){
+ 
+		c = t64d[*(uchar*)in++];
+		if(c == INVAL)
+			continue;
+		switch(i){
+		case 0:
+			b24 = c<<18;
+			break;
+		case 1:
+			b24 |= c<<12;
+			break;
+		case 2:
+			b24 |= c<<6;
+			break;
+		case 3:
+			if(out + 3 > e)
+				goto exhausted;
+
+			b24 |= c;
+			*out++ = b24>>16;
+			*out++ = b24>>8;
+			*out++ = b24;
+			i = -1;
+			break;
+		}
+		i++;
+	}
+	switch(i){
+	case 2:
+		if(out + 1 > e)
+			goto exhausted;
+		*out++ = b24>>16;
+		break;
+	case 3:
+		if(out + 2 > e)
+			goto exhausted;
+		*out++ = b24>>16;
+		*out++ = b24>>8;
+		break;
+	}
+exhausted:
+	return out - start;
+}
+
+int
+enc64(char *out, int lim, uchar *in, int n)
+{
+	int i;
+	ulong b24;
+	char *start = out;
+	char *e = out + lim;
+
+	for(i = n/3; i > 0; i--){
+		b24 = (*in++)<<16;
+		b24 |= (*in++)<<8;
+		b24 |= *in++;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = t64e[(b24>>18)];
+		*out++ = t64e[(b24>>12)&0x3f];
+		*out++ = t64e[(b24>>6)&0x3f];
+		*out++ = t64e[(b24)&0x3f];
+	}
+
+	switch(n%3){
+	case 2:
+		b24 = (*in++)<<16;
+		b24 |= (*in)<<8;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = t64e[(b24>>18)];
+		*out++ = t64e[(b24>>12)&0x3f];
+		*out++ = t64e[(b24>>6)&0x3f];
+		*out++ = '=';
+		break;
+	case 1:
+		b24 = (*in)<<16;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = t64e[(b24>>18)];
+		*out++ = t64e[(b24>>12)&0x3f];
+		*out++ = '=';
+		*out++ = '=';
+		break;
+	}
+exhausted:
+	*out = 0;
+	return out - start;
+}
--- /dev/null
+++ b/libc/utf.h
@@ -1,0 +1,51 @@
+#ifndef _UTFH_
+#define _UTFH_ 1
+
+typedef unsigned short Rune;	/* 16 bits */
+
+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 */
+};
+
+/*
+ * rune routines
+ */
+extern	int	runetochar(char*, Rune*);
+extern	int	chartorune(Rune*, char*);
+extern	int	runelen(long);
+extern	int	runenlen(Rune*, int);
+extern	int	fullrune(char*, int);
+extern	int	utflen(char*);
+extern	int	utfnlen(char*, long);
+extern	char*	utfrune(char*, long);
+extern	char*	utfrrune(char*, long);
+extern	char*	utfutf(char*, char*);
+extern	char*	utfecpy(char*, char*, char*);
+
+extern	Rune*	runestrcat(Rune*, Rune*);
+extern	Rune*	runestrchr(Rune*, Rune);
+extern	int	runestrcmp(Rune*, Rune*);
+extern	Rune*	runestrcpy(Rune*, Rune*);
+extern	Rune*	runestrncpy(Rune*, Rune*, long);
+extern	Rune*	runestrecpy(Rune*, Rune*, Rune*);
+extern	Rune*	runestrdup(Rune*);
+extern	Rune*	runestrncat(Rune*, Rune*, long);
+extern	int	runestrncmp(Rune*, Rune*, long);
+extern	Rune*	runestrrchr(Rune*, Rune);
+extern	long	runestrlen(Rune*);
+extern	Rune*	runestrstr(Rune*, Rune*);
+
+extern	Rune	tolowerrune(Rune);
+extern	Rune	totitlerune(Rune);
+extern	Rune	toupperrune(Rune);
+extern	int	isalpharune(Rune);
+extern	int	islowerrune(Rune);
+extern	int	isspacerune(Rune);
+extern	int	istitlerune(Rune);
+extern	int	isupperrune(Rune);
+
+#endif
--- /dev/null
+++ b/libc/utfdef.h
@@ -1,0 +1,14 @@
+#define uchar _utfuchar
+#define ushort _utfushort
+#define uint _utfuint
+#define ulong _utfulong
+#define vlong _utfvlong
+#define uvlong _utfuvlong
+
+typedef unsigned char		uchar;
+typedef unsigned short		ushort;
+typedef unsigned int		uint;
+typedef unsigned long		ulong;
+
+#define nelem(x) (sizeof(x)/sizeof((x)[0]))
+#define nil ((void*)0)
--- /dev/null
+++ b/libc/utfecpy.c
@@ -1,0 +1,21 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+utfecpy(char *to, char *e, char *from)
+{
+	char *end;
+
+	if(to >= e)
+		return to;
+	end = memccpy(to, from, '\0', e - to);
+	if(end == nil){
+		end = e-1;
+		while(end>to && (*--end&0xC0)==0x80)
+			;
+		*end = '\0';
+	}else{
+		end--;
+	}
+	return end;
+}
--- /dev/null
+++ b/libc/utflen.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+
+int
+utflen(char *s)
+{
+	int c;
+	long n;
+	Rune rune;
+
+	n = 0;
+	for(;;) {
+		c = *(uchar*)s;
+		if(c < Runeself) {
+			if(c == 0)
+				return n;
+			s++;
+		} else
+			s += chartorune(&rune, s);
+		n++;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libc/utfnlen.c
@@ -1,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+
+int
+utfnlen(char *s, long m)
+{
+	int c;
+	long n;
+	Rune rune;
+	char *es;
+
+	es = s + m;
+	for(n = 0; s < es; n++) {
+		c = *(uchar*)s;
+		if(c < Runeself){
+			if(c == '\0')
+				break;
+			s++;
+			continue;
+		}
+		if(!fullrune(s, es-s))
+			break;
+		s += chartorune(&rune, s);
+	}
+	return n;
+}
--- /dev/null
+++ b/libc/utfrrune.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+utfrrune(char *s, long c)
+{
+	long c1;
+	Rune r;
+	char *s1;
+
+	if(c < Runesync)		/* not part of utf sequence */
+		return strrchr(s, c);
+
+	s1 = 0;
+	for(;;) {
+		c1 = *(uchar*)s;
+		if(c1 < Runeself) {	/* one byte rune */
+			if(c1 == 0)
+				return s1;
+			if(c1 == c)
+				s1 = s;
+			s++;
+			continue;
+		}
+		c1 = chartorune(&r, s);
+		if(r == c)
+			s1 = s;
+		s += c1;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libc/utfrune.c
@@ -1,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+utfrune(char *s, long c)
+{
+	long c1;
+	Rune r;
+	int n;
+
+	if(c < Runesync)		/* not part of utf sequence */
+		return strchr(s, c);
+
+	for(;;) {
+		c1 = *(uchar*)s;
+		if(c1 < Runeself) {	/* one byte rune */
+			if(c1 == 0)
+				return 0;
+			if(c1 == c)
+				return s;
+			s++;
+			continue;
+		}
+		n = chartorune(&r, s);
+		if(r == c)
+			return s;
+		s += n;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libc/utfutf.c
@@ -1,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+
+
+/*
+ * Return pointer to first occurrence of s2 in s1,
+ * 0 if none
+ */
+char*
+utfutf(char *s1, char *s2)
+{
+	char *p;
+	long f, n1, n2;
+	Rune r;
+
+	n1 = chartorune(&r, s2);
+	f = r;
+	if(f <= Runesync)		/* represents self */
+		return strstr(s1, s2);
+
+	n2 = strlen(s2);
+	for(p=s1; p=utfrune(p, f); p+=n1)
+		if(strncmp(p, s2, n2) == 0)
+			return p;
+	return 0;
+}
--- /dev/null
+++ b/libc/vfprint.c
@@ -1,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+/*
+ * generic routine for flushing a formatting buffer
+ * to a file descriptor
+ */
+int
+_fmtFdFlush(Fmt *f)
+{
+	int n;
+
+	n = (char*)f->to - (char*)f->start;
+	if(n && write((int)f->farg, f->start, n) != n)
+		return 0;
+	f->to = f->start;
+	return 1;
+}
+
+int
+vfprint(int fd, char *fmt, va_list args)
+{
+	Fmt f;
+	char buf[256];
+	int n;
+
+	fmtfdinit(&f, fd, buf, sizeof(buf));
+	f.args = args;
+	n = dofmt(&f, fmt);
+	if(n > 0 && _fmtFdFlush(&f) == 0)
+		return -1;
+	return n;
+}
--- /dev/null
+++ b/libc/vseprint.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+vseprint(char *buf, char *e, char *fmt, va_list args)
+{
+	Fmt f;
+
+	if(e <= buf)
+		return nil;
+	f.runes = 0;
+	f.start = buf;
+	f.to = buf;
+	f.stop = e - 1;
+	f.flush = nil;
+	f.farg = nil;
+	f.nfmt = 0;
+	f.args = args;
+	dofmt(&f, fmt);
+	*(char*)f.to = '\0';
+	return f.to;
+}
+
--- /dev/null
+++ b/libc/vsmprint.c
@@ -1,0 +1,70 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+static int
+fmtStrFlush(Fmt *f)
+{
+	char *s;
+	int n;
+
+	if(f->start == nil)
+		return 0;
+	n = (int)f->farg;
+	n *= 2;
+	s = f->start;
+	f->start = realloc(s, n);
+	if(f->start == nil){
+		f->farg = nil;
+		f->to = nil;
+		f->stop = nil;
+		free(s);
+		return 0;
+	}
+	f->farg = (void*)n;
+	f->to = (char*)f->start + ((char*)f->to - s);
+	f->stop = (char*)f->start + n - 1;
+	return 1;
+}
+
+int
+fmtstrinit(Fmt *f)
+{
+	int n;
+
+	memset(f, 0, sizeof *f);
+	f->runes = 0;
+	n = 32;
+	f->start = malloc(n);
+	if(f->start == nil)
+		return -1;
+	f->to = f->start;
+	f->stop = (char*)f->start + n - 1;
+	f->flush = fmtStrFlush;
+	f->farg = (void*)n;
+	f->nfmt = 0;
+	return 0;
+}
+
+/*
+ * print into an allocated string buffer
+ */
+char*
+vsmprint(char *fmt, va_list args)
+{
+	Fmt f;
+	int n;
+
+	if(fmtstrinit(&f) < 0)
+		return nil;
+	f.args = args;
+	n = dofmt(&f, fmt);
+	if(f.start == nil)
+		return nil;
+	if(n < 0){
+		free(f.start);
+		return nil;
+	}
+	*(char*)f.to = '\0';
+	return f.start;
+}
--- /dev/null
+++ b/libc/vsnprint.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+
+int
+vsnprint(char *buf, int len, char *fmt, va_list args)
+{
+	Fmt f;
+
+	if(len <= 0)
+		return -1;
+	f.runes = 0;
+	f.start = buf;
+	f.to = buf;
+	f.stop = buf + len - 1;
+	f.flush = nil;
+	f.farg = nil;
+	f.nfmt = 0;
+	f.args = args;
+	dofmt(&f, fmt);
+	*(char*)f.to = '\0';
+	return (char*)f.to - buf;
+}
--- /dev/null
+++ b/libdraw/Makefile
@@ -1,0 +1,24 @@
+LIB=libdraw.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	alloc.$O\
+	arith.$O\
+	bytesperline.$O\
+	chan.$O\
+	defont.$O\
+	drawrepl.$O\
+	icossin.$O\
+	icossin2.$O\
+	rectclip.$O\
+	rgb.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libdraw/alloc.c
@@ -1,0 +1,237 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Image*
+allocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
+{
+	return _allocimage(nil, d, r, chan, repl, val, 0, 0);
+}
+
+Image*
+_allocimage(Image *ai, Display *d, Rectangle r, ulong chan, int repl, ulong val, int screenid, int refresh)
+{
+	uchar *a;
+	char *err;
+	Image *i;
+	Rectangle clipr;
+	int id;
+	int depth;
+
+	err = 0;
+	i = 0;
+
+	if(chan == 0){
+		werrstr("bad channel descriptor");
+		return nil;
+	}
+
+	depth = chantodepth(chan);
+	if(depth == 0){
+		err = "bad channel descriptor";
+    Error:
+		if(err)
+			werrstr("allocimage: %s", err);
+		else
+			werrstr("allocimage: %r");
+		free(i);
+		return 0;
+	}
+
+	/* flush pending data so we don't get error allocating the image */
+	flushimage(d, 0);
+	a = bufimage(d, 1+4+4+1+4+1+4*4+4*4+4);
+	if(a == 0)
+		goto Error;
+	d->imageid++;
+	id = d->imageid;
+	a[0] = 'b';
+	BPLONG(a+1, id);
+	BPLONG(a+5, screenid);
+	a[9] = refresh;
+	BPLONG(a+10, chan);
+	a[14] = repl;
+	BPLONG(a+15, r.min.x);
+	BPLONG(a+19, r.min.y);
+	BPLONG(a+23, r.max.x);
+	BPLONG(a+27, r.max.y);
+	if(repl)
+		/* huge but not infinite, so various offsets will leave it huge, not overflow */
+		clipr = Rect(-0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF);
+	else
+		clipr = r;
+	BPLONG(a+31, clipr.min.x);
+	BPLONG(a+35, clipr.min.y);
+	BPLONG(a+39, clipr.max.x);
+	BPLONG(a+43, clipr.max.y);
+	BPLONG(a+47, val);
+	if(flushimage(d, 0) < 0)
+		goto Error;
+
+	if(ai)
+		i = ai;
+	else{
+		i = malloc(sizeof(Image));
+		if(i == nil){
+			a = bufimage(d, 1+4);
+			if(a){
+				a[0] = 'f';
+				BPLONG(a+1, id);
+				flushimage(d, 0);
+			}
+			goto Error;
+		}
+	}
+	i->display = d;
+	i->id = id;
+	i->depth = depth;
+	i->chan = chan;
+	i->r = r;
+	i->clipr = clipr;
+	i->repl = repl;
+	i->screen = 0;
+	i->next = 0;
+	return i;
+}
+
+Image*
+namedimage(Display *d, char *name)
+{
+	uchar *a;
+	char *err, buf[12*12+1];
+	Image *i;
+	int id, n;
+	ulong chan;
+
+	err = 0;
+	i = 0;
+
+	n = strlen(name);
+	if(n >= 256){
+		err = "name too long";
+    Error:
+		if(err)
+			werrstr("namedimage: %s", err);
+		else
+			werrstr("namedimage: %r");
+		if(i)
+			free(i);
+		return 0;
+	}
+	/* flush pending data so we don't get error allocating the image */
+	flushimage(d, 0);
+	a = bufimage(d, 1+4+1+n);
+	if(a == 0)
+		goto Error;
+	d->imageid++;
+	id = d->imageid;
+	a[0] = 'n';
+	BPLONG(a+1, id);
+	a[5] = n;
+	memmove(a+6, name, n);
+	if(flushimage(d, 0) < 0)
+		goto Error;
+
+	if(pread(d->ctlfd, buf, sizeof buf, 0) < 12*12)
+		goto Error;
+	buf[12*12] = '\0';
+
+	i = malloc(sizeof(Image));
+	if(i == nil){
+	Error1:
+		a = bufimage(d, 1+4);
+		if(a){
+			a[0] = 'f';
+			BPLONG(a+1, id);
+			flushimage(d, 0);
+		}
+		goto Error;
+	}
+	i->display = d;
+	i->id = id;
+	if((chan=strtochan(buf+2*12))==0){
+		werrstr("bad channel '%.12s' from devdraw", buf+2*12);
+		goto Error1;
+	}
+	i->chan = chan;
+	i->depth = chantodepth(chan);
+	i->repl = atoi(buf+3*12);
+	i->r.min.x = atoi(buf+4*12);
+	i->r.min.y = atoi(buf+5*12);
+	i->r.max.x = atoi(buf+6*12);
+	i->r.max.y = atoi(buf+7*12);
+	i->clipr.min.x = atoi(buf+8*12);
+	i->clipr.min.y = atoi(buf+9*12);
+	i->clipr.max.x = atoi(buf+10*12);
+	i->clipr.max.y = atoi(buf+11*12);
+	i->screen = 0;
+	i->next = 0;
+	return i;
+}
+
+int
+nameimage(Image *i, char *name, int in)
+{
+	uchar *a;
+	int n;
+
+	n = strlen(name);
+	a = bufimage(i->display, 1+4+1+1+n);
+	if(a == 0)
+		return 0;
+	a[0] = 'N';
+	BPLONG(a+1, i->id);
+	a[5] = in;
+	a[6] = n;
+	memmove(a+7, name, n);
+	if(flushimage(i->display, 0) < 0)
+		return 0;
+	return 1;
+}
+
+int
+_freeimage1(Image *i)
+{
+	uchar *a;
+	Display *d;
+	Image *w;
+
+	if(i == 0)
+		return 0;
+	/* make sure no refresh events occur on this if we block in the write */
+	d = i->display;
+	/* flush pending data so we don't get error deleting the image */
+	flushimage(d, 0);
+	a = bufimage(d, 1+4);
+	if(a == 0)
+		return -1;
+	a[0] = 'f';
+	BPLONG(a+1, i->id);
+	if(i->screen){
+		w = d->windows;
+		if(w == i)
+			d->windows = i->next;
+		else
+			while(w){
+				if(w->next == i){
+					w->next = i->next;
+					break;
+				}
+				w = w->next;
+			}
+	}
+	if(flushimage(d, i->screen!=0) < 0)
+		return -1;
+
+	return 0;
+}
+
+int
+freeimage(Image *i)
+{
+	int ret;
+
+	ret = _freeimage1(i);
+	free(i);
+	return ret;
+}
--- /dev/null
+++ b/libdraw/arith.c
@@ -1,0 +1,206 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Point
+Pt(int x, int y)
+{
+	Point p;
+
+	p.x = x;
+	p.y = y;
+	return p;
+}
+
+Rectangle
+Rect(int x, int y, int bx, int by)
+{
+	Rectangle r;
+
+	r.min.x = x;
+	r.min.y = y;
+	r.max.x = bx;
+	r.max.y = by;
+	return r;
+}
+
+Rectangle
+Rpt(Point min, Point max)
+{
+	Rectangle r;
+
+	r.min = min;
+	r.max = max;
+	return r;
+}
+
+Point
+addpt(Point a, Point b)
+{
+	a.x += b.x;
+	a.y += b.y;
+	return a;
+}
+
+Point
+subpt(Point a, Point b)
+{
+	a.x -= b.x;
+	a.y -= b.y;
+	return a;
+}
+
+Rectangle
+insetrect(Rectangle r, int n)
+{
+	r.min.x += n;
+	r.min.y += n;
+	r.max.x -= n;
+	r.max.y -= n;
+	return r;
+}
+
+Point
+divpt(Point a, int b)
+{
+	a.x /= b;
+	a.y /= b;
+	return a;
+}
+
+Point
+mulpt(Point a, int b)
+{
+	a.x *= b;
+	a.y *= b;
+	return a;
+}
+
+Rectangle
+rectsubpt(Rectangle r, Point p)
+{
+	r.min.x -= p.x;
+	r.min.y -= p.y;
+	r.max.x -= p.x;
+	r.max.y -= p.y;
+	return r;
+}
+
+Rectangle
+rectaddpt(Rectangle r, Point p)
+{
+	r.min.x += p.x;
+	r.min.y += p.y;
+	r.max.x += p.x;
+	r.max.y += p.y;
+	return r;
+}
+
+int
+eqpt(Point p, Point q)
+{
+	return p.x==q.x && p.y==q.y;
+}
+
+int
+eqrect(Rectangle r, Rectangle s)
+{
+	return r.min.x==s.min.x && r.max.x==s.max.x &&
+	       r.min.y==s.min.y && r.max.y==s.max.y;
+}
+
+int
+rectXrect(Rectangle r, Rectangle s)
+{
+	return r.min.x<s.max.x && s.min.x<r.max.x &&
+	       r.min.y<s.max.y && s.min.y<r.max.y;
+}
+
+int
+rectinrect(Rectangle r, Rectangle s)
+{
+	return s.min.x<=r.min.x && r.max.x<=s.max.x && s.min.y<=r.min.y && r.max.y<=s.max.y;
+}
+
+int
+ptinrect(Point p, Rectangle r)
+{
+	return p.x>=r.min.x && p.x<r.max.x &&
+	       p.y>=r.min.y && p.y<r.max.y;
+}
+
+Rectangle
+canonrect(Rectangle r)
+{
+	int t;
+	if (r.max.x < r.min.x) {
+		t = r.min.x;
+		r.min.x = r.max.x;
+		r.max.x = t;
+	}
+	if (r.max.y < r.min.y) {
+		t = r.min.y;
+		r.min.y = r.max.y;
+		r.max.y = t;
+	}
+	return r;
+}
+
+void
+combinerect(Rectangle *r1, Rectangle r2)
+{
+	if(r1->min.x > r2.min.x)
+		r1->min.x = r2.min.x;
+	if(r1->min.y > r2.min.y)
+		r1->min.y = r2.min.y;
+	if(r1->max.x < r2.max.x)
+		r1->max.x = r2.max.x;
+	if(r1->max.y < r2.max.y)
+		r1->max.y = r2.max.y;
+}
+
+ulong
+drawld2chan[] = {
+	GREY1,
+	GREY2,
+	GREY4,
+	CMAP8,
+};
+
+int log2[] = { -1, 0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, 4 /* BUG */, -1, -1, -1, -1, -1, -1, -1, 5 };
+
+ulong
+setalpha(ulong color, uchar alpha)
+{
+	int red, green, blue;
+
+	red = (color >> 3*8) & 0xFF;
+	green = (color >> 2*8) & 0xFF;
+	blue = (color >> 1*8) & 0xFF;
+	/* ignore incoming alpha */
+	red = (red * alpha)/255;
+	green = (green * alpha)/255;
+	blue = (blue * alpha)/255;
+	return (red<<3*8) | (green<<2*8) | (blue<<1*8) | (alpha<<0*8);
+}
+
+Point	ZP;
+Rectangle ZR;
+int
+Rfmt(Fmt *f)
+{
+	Rectangle r;
+
+	r = va_arg(f->args, Rectangle);
+	return fmtprint(f, "%P %P", r.min, r.max);
+}
+
+int
+Pfmt(Fmt *f)
+{
+	Point p;
+
+	p = va_arg(f->args, Point);
+	return fmtprint(f, "[%d %d]", p.x, p.y);
+}
+
--- /dev/null
+++ b/libdraw/bytesperline.c
@@ -1,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static
+int
+unitsperline(Rectangle r, int d, int bitsperunit)
+{
+	ulong l, t;
+
+	if(d <= 0 || d > 32)	/* being called wrong.  d is image depth. */
+		abort();
+
+	if(r.min.x >= 0){
+		l = (r.max.x*d+bitsperunit-1)/bitsperunit;
+		l -= (r.min.x*d)/bitsperunit;
+	}else{			/* make positive before divide */
+		t = (-r.min.x*d+bitsperunit-1)/bitsperunit;
+		l = t+(r.max.x*d+bitsperunit-1)/bitsperunit;
+	}
+	return l;
+}
+
+int
+wordsperline(Rectangle r, int d)
+{
+	return unitsperline(r, d, 8*sizeof(ulong));
+}
+
+int
+bytesperline(Rectangle r, int d)
+{
+	return unitsperline(r, d, 8);
+}
--- /dev/null
+++ b/libdraw/chan.c
@@ -1,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static char channames[] = "rgbkamx";
+char*
+chantostr(char *buf, ulong cc)
+{
+	ulong c, rc;
+	char *p;
+
+	if(chantodepth(cc) == 0)
+		return nil;
+
+	/* reverse the channel descriptor so we can easily generate the string in the right order */
+	rc = 0;
+	for(c=cc; c; c>>=8){
+		rc <<= 8;
+		rc |= c&0xFF;
+	}
+
+	p = buf;
+	for(c=rc; c; c>>=8) {
+		*p++ = channames[TYPE(c)];
+		*p++ = '0'+NBITS(c);
+	}
+	*p = 0;
+
+	return buf;
+}
+
+/* avoid pulling in ctype when using with drawterm etc. */
+static int
+isspace(char c)
+{
+	return c==' ' || c== '\t' || c=='\r' || c=='\n';
+}
+
+ulong
+strtochan(char *s)
+{
+	char *p, *q;
+	ulong c;
+	int t, n;
+
+	c = 0;
+	p=s;
+	while(*p && isspace(*p))
+		p++;
+
+	while(*p && !isspace(*p)){
+		if((q = strchr(channames, p[0])) == nil) 
+			return 0;
+		t = q-channames;
+		if(p[1] < '0' || p[1] > '9')
+			return 0;
+		n = p[1]-'0';
+		c = (c<<8) | __DC(t, n);
+		p += 2;
+	}
+	return c;
+}
+
+int
+chantodepth(ulong c)
+{
+	int n;
+
+	for(n=0; c; c>>=8){
+		if(TYPE(c) >= NChan || NBITS(c) > 8 || NBITS(c) <= 0)
+			return 0;
+		n += NBITS(c);
+	}
+	if(n==0 || (n>8 && n%8) || (n<8 && 8%n))
+		return 0;
+	return n;
+}
--- /dev/null
+++ b/libdraw/defont.c
@@ -1,0 +1,402 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * lucm/latin1.9, in uncompressed form
+ */
+uchar
+defontdata[] =
+{
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x30,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x30,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x30,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x32,0x33,0x30,0x34,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x31,0x35,0x20,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x30,0x06,0x06,0x03,0x42,0x40,0x00,0x00,0x00,0x18,0x03,0x03,
+0x02,0x43,0x00,0x60,0x60,0x48,0x00,0x0d,0x0c,0x01,0x81,0x80,0xd0,0x90,0x00,0x00,
+0x18,0x01,0x81,0x81,0x40,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x7f,0x9c,0x1c,
+0x0e,0x07,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x70,
+0x38,0x1c,0x0e,0x04,0x81,0xc1,0xc0,0x70,0x00,0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xaa,0x80,0xc0,0x63,0xe3,
+0xf1,0xf8,0xfe,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0x7f,0xff,0xff,0x1f,0x8f,
+0xc7,0xe3,0xf1,0xfb,0x7e,0x3e,0x3f,0x8f,0xff,0xe3,0xe3,0xff,0xff,0xff,0xff,0xff,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x0c,0x18,0x09,0x05,0x82,0x40,0xc0,0x00,0x00,0x06,0x0c,0x04,
+0x82,0x40,0xc1,0x80,0x90,0x48,0x00,0x16,0x03,0x06,0x02,0x41,0x60,0x90,0x00,0x00,
+0x06,0x06,0x02,0x41,0x41,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x7f,0xa0,0x10,
+0x08,0x04,0x02,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x48,
+0x24,0x12,0x09,0x06,0x82,0x01,0x00,0x90,0x00,0x20,0x10,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x04,0x80,0x00,0x40,0x00,0x00,0x38,0x06,0x18,0x00,0x00,0x00,0x00,0x00,
+0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x07,0xc6,0x01,0xf0,0x00,0x00,0x0c,0x00,0x18,0x00,0x00,0x30,0x00,0x3c,
+0x00,0x60,0x06,0x01,0x8c,0x07,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xe0,0xc3,0xc0,0x01,0x54,0x9c,0xc0,0x5f,0xef,
+0xf7,0xfb,0xfd,0xbf,0xff,0xff,0xff,0xff,0xff,0xff,0xfb,0x7f,0xff,0xff,0x6f,0xb7,
+0xdb,0xed,0xf6,0xf9,0x7d,0xfe,0xff,0x6f,0xff,0xdf,0xef,0xff,0xff,0xff,0xff,0xff,
+0xff,0x00,0x01,0x00,0x00,0x00,0x00,0x30,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x30,0x06,0x06,0x06,0x82,0x80,0xc0,0x00,
+0x00,0x18,0x03,0x03,0x02,0x41,0x80,0x30,0x30,0x24,0x76,0x0d,0x0c,0x00,0xc0,0xc0,
+0xd0,0x50,0x00,0x00,0x18,0x01,0x81,0x81,0x40,0x30,0x00,0x28,0x0f,0x7f,0xbc,0x1c,
+0x0e,0x07,0x03,0xc0,0x10,0x70,0x24,0x10,0x09,0x07,0x03,0x80,0xe0,0x70,0x90,0x48,
+0x24,0x12,0x09,0x05,0x81,0x81,0xc0,0x80,0x70,0x18,0x1c,0x07,0x01,0xc1,0xc0,0x90,
+0x00,0x0c,0x04,0x84,0x83,0xe1,0xc0,0xe0,0x38,0x0c,0x0c,0x02,0x00,0x00,0x00,0x00,
+0x00,0x06,0x1c,0x06,0x0f,0x87,0xc0,0x63,0xf8,0x78,0xfe,0x3e,0x0e,0x00,0x00,0x00,
+0x00,0x00,0x00,0x7c,0x1c,0x0c,0x1f,0x03,0xc7,0xc3,0xf1,0xf8,0x3c,0x63,0x3f,0x0f,
+0x8c,0x66,0x06,0x19,0x84,0x78,0x7e,0x1e,0x1f,0x07,0xcf,0xf3,0x1b,0x0d,0x86,0x63,
+0x61,0x9f,0xc6,0x06,0x00,0x30,0x00,0x00,0x10,0x00,0x18,0x00,0x00,0x30,0x00,0x60,
+0x00,0x60,0x06,0x01,0x8c,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,
+0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0xc0,0x60,0x00,0xaa,0xb6,0xc0,0x43,0xe3,
+0xf1,0xf8,0xfc,0x3f,0xef,0x8f,0xdb,0xef,0xf6,0xf8,0xfb,0xff,0x1f,0x8f,0x6f,0xb7,
+0xdb,0xed,0xf6,0xfa,0x7e,0x7e,0x3f,0x7f,0x8f,0xe7,0xe3,0xf8,0xfe,0x3e,0x3f,0x6f,
+0x00,0x00,0x01,0x01,0xc8,0x0b,0x0c,0x30,0x7c,0x14,0x0f,0x0f,0x00,0x00,0x00,0x00,
+0x78,0x00,0x1c,0x00,0x0f,0x07,0x81,0x80,0x00,0x7c,0x00,0x00,0x1c,0x0f,0x80,0x04,
+0x42,0x23,0x90,0x00,0x18,0x0c,0x06,0x03,0x01,0x80,0xc0,0x3c,0x3c,0x3f,0x1f,0x8f,
+0xc7,0xe7,0xe3,0xf1,0xf8,0xfc,0x7c,0x30,0x8f,0x07,0x83,0xc1,0xe0,0xf0,0x00,0x3d,
+0x31,0x98,0xcc,0x66,0x36,0x19,0x80,0xcc,0x0c,0x18,0x09,0x0b,0x02,0x81,0x20,0x00,
+0x00,0x06,0x0c,0x04,0x82,0x40,0x60,0xc0,0x48,0x24,0x18,0x16,0x03,0x03,0x01,0x21,
+0x60,0x50,0x00,0x00,0x06,0x06,0x02,0x41,0x40,0xc1,0x80,0x28,0x87,0x7f,0x84,0x10,
+0x08,0x04,0x02,0x40,0x38,0x48,0x24,0x10,0x09,0x04,0x04,0x81,0x00,0x80,0x90,0x48,
+0x24,0x12,0x09,0x04,0x80,0x41,0x00,0x80,0x40,0x04,0x10,0x04,0x02,0x01,0x20,0x90,
+0x00,0x0c,0x04,0x84,0x86,0x53,0x65,0xb0,0x08,0x18,0x06,0x0a,0x80,0x00,0x00,0x00,
+0x00,0x0c,0x36,0x0e,0x19,0xcc,0xe0,0xe3,0xf8,0xcc,0xfe,0x63,0x1b,0x00,0x00,0x00,
+0x00,0x00,0x00,0xc6,0x62,0x0c,0x19,0x86,0x66,0x63,0x01,0x80,0x66,0x63,0x0c,0x01,
+0x8c,0xc6,0x06,0x19,0xc4,0xcc,0x63,0x33,0x19,0x8c,0x61,0x83,0x1b,0x0d,0x86,0x63,
+0x61,0x80,0xc6,0x03,0x00,0x30,0x30,0x00,0x1c,0x00,0x18,0x00,0x00,0x30,0x00,0x60,
+0x00,0x60,0x00,0x00,0x0c,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,
+0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0xc0,0x60,0x01,0x54,0x86,0xc0,0x7b,0xef,
+0xf7,0xfb,0xfd,0xbf,0xc7,0xb7,0xdb,0xef,0xf6,0xfb,0xfb,0x7e,0xff,0x7f,0x6f,0xb7,
+0xdb,0xed,0xf6,0xfb,0x7f,0xbe,0xff,0x7f,0xbf,0xfb,0xef,0xfb,0xfd,0xfe,0xdf,0x6f,
+0xff,0x00,0x07,0x83,0x24,0x13,0x0c,0x30,0xc6,0x00,0x10,0x81,0x80,0x00,0x00,0x00,
+0x84,0x00,0x22,0x00,0x01,0x80,0xc0,0x00,0x00,0xf4,0x00,0x00,0x2c,0x18,0xc0,0x0c,
+0x46,0x20,0x90,0x00,0x18,0x0c,0x06,0x03,0x01,0x80,0xc0,0x70,0x66,0x30,0x18,0x0c,
+0x06,0x01,0x80,0xc0,0x60,0x30,0x66,0x38,0x99,0x8c,0xc6,0x63,0x31,0x98,0x00,0x66,
+0x31,0x98,0xcc,0x66,0x36,0x19,0x80,0xcc,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6c,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0xff,0x7f,0xb8,0x1c,
+0x0e,0x07,0x02,0x40,0x7c,0x70,0x3c,0x10,0x09,0x07,0x04,0x00,0xc0,0x60,0xe0,0x70,
+0x38,0x1c,0x0e,0x04,0x83,0x81,0xc0,0x70,0x70,0x38,0x1c,0x07,0x02,0xc1,0xc0,0x90,
+0x00,0x0c,0x00,0x04,0x86,0x43,0x69,0xb0,0x30,0x18,0x06,0x07,0x01,0x00,0x00,0x00,
+0x00,0x0c,0x63,0x16,0x00,0xc0,0x61,0x62,0x01,0x80,0x06,0x63,0x31,0x80,0x00,0x00,
+0x60,0x00,0xc0,0x06,0x43,0x16,0x19,0x8c,0x06,0x33,0x01,0x80,0xc0,0x63,0x0c,0x01,
+0x8c,0x86,0x07,0x39,0xc5,0x86,0x63,0x61,0x99,0x8c,0x01,0x83,0x1b,0x0d,0xb6,0x63,
+0x31,0x01,0x86,0x03,0x00,0x30,0x30,0x00,0x1c,0x3e,0x1b,0x03,0xc1,0xf0,0xf0,0x60,
+0x3e,0x6e,0x3e,0x0f,0x8c,0x60,0xc5,0xb1,0xb8,0x38,0x6c,0x0f,0x8c,0xc7,0xc1,0x83,
+0x19,0x8d,0x82,0x63,0x31,0x9f,0xc1,0x80,0xc0,0xc0,0x00,0xaa,0x86,0xc0,0x47,0xe3,
+0xf1,0xf8,0xfd,0xbf,0x83,0x8f,0xc3,0xef,0xf6,0xf8,0xfc,0xff,0x3f,0x9f,0x1f,0x8f,
+0xc7,0xe3,0xf1,0xfb,0x7c,0x7e,0x3f,0x8f,0x8f,0xc7,0xe3,0xf8,0xfd,0x3e,0x3f,0x6f,
+0x00,0x0c,0x0d,0x43,0x03,0xe1,0x88,0x30,0xc0,0x00,0x27,0x41,0x80,0x00,0x00,0x01,
+0x72,0x00,0x22,0x04,0x01,0x80,0xc0,0x03,0x18,0xf4,0x00,0x00,0x0c,0x18,0xc0,0x04,
+0x82,0x43,0x20,0x18,0x2c,0x16,0x0b,0x05,0x82,0xc1,0x60,0xb0,0xc0,0x30,0x18,0x0c,
+0x06,0x01,0x80,0xc0,0x60,0x30,0x63,0x38,0xb0,0xd8,0x6c,0x36,0x1b,0x0c,0x00,0xc7,
+0x31,0x98,0xcc,0x66,0x33,0x11,0xf8,0xc8,0x7c,0x3e,0x1f,0x0f,0x87,0xc3,0xe1,0xd8,
+0x3c,0x1e,0x0f,0x07,0x83,0xc7,0xc3,0xe1,0xf0,0xf8,0x06,0x37,0x07,0x03,0x81,0xc0,
+0xe0,0x70,0x10,0x1d,0x31,0x98,0xcc,0x66,0x33,0x19,0xb0,0xc6,0x8f,0x7f,0x87,0x03,
+0x81,0x80,0x90,0x30,0x6c,0x48,0x24,0x10,0x06,0x04,0x04,0x80,0x20,0x10,0x10,0x0e,
+0x07,0x03,0x81,0xc0,0x60,0x88,0x38,0x0c,0x40,0x09,0x03,0x84,0x02,0x41,0x40,0x90,
+0x00,0x0c,0x00,0x1f,0xe7,0x41,0xd1,0xa0,0x00,0x30,0x03,0x0a,0x81,0x00,0x00,0x00,
+0x00,0x18,0x63,0x06,0x00,0xc0,0xc2,0x62,0x01,0xb0,0x0c,0x72,0x31,0x86,0x03,0x00,
+0xc0,0x00,0x60,0x06,0x8f,0x16,0x19,0x0c,0x06,0x33,0x01,0x80,0xc0,0x63,0x0c,0x01,
+0x8d,0x06,0x07,0x39,0x65,0x86,0x63,0x61,0x99,0x0e,0x01,0x83,0x19,0x89,0xb6,0x32,
+0x33,0x03,0x06,0x01,0x80,0x30,0x78,0x00,0x00,0x03,0x1d,0x86,0x23,0x31,0x99,0xfc,
+0x66,0x77,0x06,0x01,0x8c,0x40,0xc6,0xd9,0xdc,0x6c,0x76,0x19,0x8d,0xcc,0x27,0xf3,
+0x19,0x8d,0x82,0x63,0x31,0x80,0xc0,0x80,0xc0,0x80,0x01,0x54,0x8c,0xc0,0x78,0xfc,
+0x7e,0x7f,0x6f,0xcf,0x93,0xb7,0xdb,0xef,0xf9,0xfb,0xff,0xff,0xdf,0xef,0xef,0xf1,
+0xf8,0xfc,0x7e,0x3f,0x9f,0x77,0xc7,0xf3,0xbf,0xf6,0xfc,0x7b,0xfd,0xbe,0xbf,0x6f,
+0xff,0x0c,0x19,0x03,0x03,0x61,0x98,0x30,0x78,0x00,0x28,0x4f,0x83,0x30,0x00,0x01,
+0x4a,0x00,0x1c,0x04,0x03,0x03,0x80,0x03,0x18,0xf4,0x00,0x00,0x0c,0x18,0xd9,0x84,
+0x82,0x40,0xa0,0x18,0x2c,0x16,0x0b,0x05,0x82,0xc1,0x60,0xb0,0xc0,0x30,0x18,0x0c,
+0x06,0x01,0x80,0xc0,0x60,0x30,0x63,0x2c,0xb0,0xd8,0x6c,0x36,0x1b,0x0c,0x64,0xcb,
+0x31,0x98,0xcc,0x66,0x33,0x31,0x8c,0xd8,0x06,0x03,0x01,0x80,0xc0,0x60,0x30,0x6c,
+0x62,0x33,0x19,0x8c,0xc6,0x60,0xc0,0x60,0x30,0x18,0x1e,0x3b,0x8d,0x86,0xc3,0x61,
+0xb0,0xd8,0x10,0x36,0x31,0x98,0xcc,0x66,0x33,0x19,0xd8,0xc6,0x0f,0x7f,0x82,0x01,
+0x02,0x40,0xd0,0x40,0x6c,0x70,0x24,0x1c,0x06,0x04,0x03,0x01,0xc0,0xe0,0x10,0x12,
+0x09,0x04,0x82,0x40,0x90,0x50,0x10,0x12,0x70,0x09,0x04,0x04,0x01,0xc1,0x20,0x60,
+0x00,0x0c,0x00,0x04,0x83,0xc0,0x20,0xcc,0x00,0x30,0x03,0x02,0x01,0x00,0x00,0x00,
+0x00,0x18,0x63,0x06,0x01,0x83,0x84,0x63,0xf1,0xd8,0x18,0x3c,0x31,0x86,0x03,0x01,
+0x83,0xf8,0x30,0x1c,0x9b,0x33,0x1e,0x0c,0x06,0x33,0xe1,0x80,0xc0,0x7f,0x0c,0x01,
+0x8f,0x06,0x07,0x79,0x65,0x86,0x66,0x61,0x9e,0x07,0x81,0x83,0x19,0x89,0xb6,0x1c,
+0x1a,0x03,0x06,0x01,0x80,0x30,0x48,0x00,0x00,0x03,0x18,0xcc,0x06,0x33,0x18,0x60,
+0xc6,0x63,0x06,0x01,0x8c,0x80,0xc6,0xd9,0x8c,0xc6,0x63,0x31,0x8e,0x4c,0x01,0x83,
+0x19,0x8d,0x92,0x32,0x31,0x81,0x87,0x00,0xc0,0x70,0xe4,0xaa,0x98,0xc0,0x7d,0xfe,
+0xfd,0xbf,0x2f,0xbf,0x93,0x8f,0xdb,0xe3,0xf9,0xfb,0xff,0x1e,0x3f,0x1f,0xef,0xed,
+0xf6,0xfb,0x7d,0xbf,0x6f,0xaf,0xef,0xed,0x8f,0xf6,0xfb,0xfb,0xfe,0x3e,0xdf,0x9f,
+0x00,0x00,0x19,0x0f,0xc6,0x30,0xd0,0x00,0xcc,0x00,0x28,0x59,0x86,0x67,0xf0,0x01,
+0x72,0x00,0x00,0x3f,0x86,0x00,0xc0,0x03,0x18,0xf4,0x00,0x00,0x0c,0x18,0xcc,0xc5,
+0x32,0x83,0x4c,0x00,0x66,0x33,0x19,0x8c,0xc6,0x63,0x31,0xbc,0xc0,0x3e,0x1f,0x0f,
+0x87,0xc1,0x80,0xc0,0x60,0x30,0xfb,0x2c,0xb0,0xd8,0x6c,0x36,0x1b,0x0c,0x38,0xcb,
+0x31,0x98,0xcc,0x66,0x31,0xa1,0x8c,0xcc,0x06,0x03,0x01,0x80,0xc0,0x60,0x30,0x6c,
+0xc0,0x63,0x31,0x98,0xcc,0x60,0xc0,0x60,0x30,0x18,0x37,0x31,0x98,0xcc,0x66,0x33,
+0x19,0x8c,0x00,0x67,0x31,0x98,0xcc,0x66,0x33,0x19,0x8c,0xc6,0x1f,0x7f,0x82,0x01,
+0x02,0x40,0xb0,0x40,0x6c,0x07,0x03,0x83,0x80,0xe0,0xe0,0x00,0x18,0x0e,0x10,0x10,
+0x08,0x04,0x02,0x00,0xf0,0x20,0x10,0x1e,0x08,0x89,0x03,0x00,0xe0,0x38,0x1c,0x0e,
+0x00,0x0c,0x00,0x04,0x81,0xe0,0x41,0x6c,0x00,0x30,0x03,0x00,0x0f,0xe0,0x03,0xf8,
+0x00,0x30,0x63,0x06,0x03,0x00,0xc7,0xf0,0x39,0x8c,0x30,0x3e,0x1b,0x80,0x00,0x03,
+0x00,0x00,0x18,0x30,0x9b,0x23,0x19,0x0c,0x06,0x33,0x01,0xf8,0xc6,0x63,0x0c,0x01,
+0x8d,0x86,0x05,0xd9,0x35,0x86,0x7c,0x61,0x9b,0x01,0xc1,0x83,0x19,0x99,0xb4,0x1c,
+0x0c,0x06,0x06,0x00,0xc0,0x30,0xcc,0x00,0x00,0x3f,0x18,0xcc,0x06,0x33,0xf8,0x60,
+0xc6,0x63,0x06,0x01,0x8f,0x00,0xc6,0xd9,0x8c,0xc6,0x63,0x31,0x8c,0x0f,0x81,0x83,
+0x18,0xd9,0xba,0x1c,0x1b,0x03,0x00,0x80,0xc0,0x81,0x75,0x54,0x98,0xc0,0x7d,0xfe,
+0xfd,0xbf,0x4f,0xbf,0x93,0xf8,0xfc,0x7c,0x7f,0x1f,0x1f,0x6f,0xe7,0xf1,0xef,0xef,
+0xf7,0xfb,0xfd,0xff,0x0f,0xdf,0xef,0xe1,0xf7,0x76,0xfc,0xff,0x1f,0xc7,0xe3,0xf1,
+0xff,0x08,0x19,0x03,0x06,0x31,0xf8,0x00,0xc6,0x00,0x28,0x5b,0x8c,0xc0,0x11,0xf1,
+0x4a,0x00,0x00,0x04,0x0c,0x00,0xc0,0x03,0x18,0x74,0x38,0x00,0x0c,0x18,0xc6,0x65,
+0x52,0xb8,0x54,0x18,0x46,0x23,0x11,0x88,0xc4,0x62,0x31,0x30,0xc0,0x30,0x18,0x0c,
+0x06,0x01,0x80,0xc0,0x60,0x30,0x63,0x26,0xb0,0xd8,0x6c,0x36,0x1b,0x0c,0x10,0xd3,
+0x31,0x98,0xcc,0x66,0x30,0xc1,0x8c,0xc6,0x7e,0x3f,0x1f,0x8f,0xc7,0xe3,0xf1,0xfc,
+0xc0,0x7f,0x3f,0x9f,0xcf,0xe0,0xc0,0x60,0x30,0x18,0x63,0x31,0x98,0xcc,0x66,0x33,
+0x19,0x8c,0xfe,0x6b,0x31,0x98,0xcc,0x66,0x31,0xb1,0x8c,0x6c,0x0e,0x7f,0x82,0x01,
+0x01,0x80,0x90,0x30,0xc6,0x08,0x01,0x02,0x00,0x40,0x80,0xe0,0x24,0x04,0x1c,0x10,
+0x08,0x04,0x02,0x00,0x90,0x20,0x10,0x12,0x0d,0x86,0x00,0x81,0x00,0x40,0x20,0x10,
+0x00,0x04,0x00,0x1f,0xe1,0x70,0xbb,0x28,0x00,0x30,0x03,0x00,0x01,0x00,0x00,0x00,
+0x00,0x30,0x63,0x06,0x06,0x00,0x67,0xf0,0x19,0x8c,0x30,0x67,0x0d,0x80,0x00,0x01,
+0x83,0xf8,0x30,0x30,0x9b,0x7f,0x19,0x8c,0x06,0x33,0x01,0x80,0xc6,0x63,0x0c,0x01,
+0x8c,0xc6,0x05,0xd9,0x35,0x86,0x60,0x61,0x99,0x80,0xe1,0x83,0x18,0xd0,0xdc,0x26,
+0x0c,0x0c,0x06,0x00,0xc0,0x30,0x84,0x00,0x00,0x63,0x18,0xcc,0x06,0x33,0x00,0x60,
+0xc6,0x63,0x06,0x01,0x8d,0x80,0xc6,0xd9,0x8c,0xc6,0x63,0x31,0x8c,0x03,0xe1,0x83,
+0x18,0xd9,0xba,0x1c,0x1b,0x06,0x01,0x80,0xc0,0xc1,0x38,0xaa,0x80,0xc0,0x7d,0xfe,
+0xfe,0x7f,0x6f,0xcf,0x39,0xf7,0xfe,0xfd,0xff,0xbf,0x7f,0x0f,0xdb,0xfb,0xe3,0xef,
+0xf7,0xfb,0xfd,0xff,0x6f,0xdf,0xef,0xed,0xf2,0x79,0xff,0x7e,0xff,0xbf,0xdf,0xef,
+0x00,0x0c,0x19,0x03,0x03,0x60,0x60,0x30,0x66,0x00,0x28,0x4d,0xc6,0x60,0x10,0x00,
+0x84,0x00,0x00,0x04,0x0f,0x87,0x80,0x03,0x18,0x14,0x38,0x00,0x3f,0x0f,0x8c,0xc2,
+0x90,0x84,0xa4,0x18,0xfe,0x7f,0x3f,0x9f,0xcf,0xe7,0xf1,0xf0,0xc0,0x30,0x18,0x0c,
+0x06,0x01,0x80,0xc0,0x60,0x30,0x63,0x26,0xb0,0xd8,0x6c,0x36,0x1b,0x0c,0x38,0xd3,
+0x31,0x98,0xcc,0x66,0x30,0xc1,0x98,0xc6,0xc6,0x63,0x31,0x98,0xcc,0x66,0x33,0x60,
+0xc0,0x60,0x30,0x18,0x0c,0x00,0xc0,0x60,0x30,0x18,0x63,0x31,0x98,0xcc,0x66,0x33,
+0x19,0x8c,0x00,0x6b,0x31,0x98,0xcc,0x66,0x31,0xb1,0x8c,0x6c,0x1c,0x7f,0x81,0x20,
+0x90,0x38,0x18,0x0b,0x83,0x06,0x01,0x03,0x80,0x40,0xe0,0x90,0x24,0x04,0x03,0x8e,
+0x86,0xc3,0x61,0x90,0x24,0x12,0x0e,0x04,0x8a,0x81,0xc7,0x70,0xc0,0x30,0x18,0x0c,
+0x00,0x00,0x00,0x04,0x81,0x31,0x6f,0x30,0x00,0x18,0x06,0x00,0x01,0x00,0x00,0x00,
+0x00,0x60,0x63,0x06,0x0c,0x00,0x60,0x60,0x19,0x8c,0x60,0x63,0x01,0x80,0x00,0x00,
+0xc0,0x00,0x60,0x00,0x4d,0xe1,0x99,0x8c,0x06,0x33,0x01,0x80,0xc6,0x63,0x0c,0x01,
+0x8c,0xc6,0x04,0x99,0x1d,0x86,0x60,0x61,0x99,0x80,0x61,0x83,0x18,0xd0,0xdc,0x63,
+0x0c,0x0c,0x06,0x00,0x60,0x30,0x84,0x00,0x00,0x63,0x18,0xcc,0x06,0x33,0x00,0x60,
+0x6e,0x63,0x06,0x01,0x8c,0xc0,0xc6,0xd9,0x8c,0xc6,0x63,0x31,0x8c,0x00,0x61,0x83,
+0x18,0xd0,0xcc,0x26,0x0e,0x0c,0x03,0x00,0xc0,0x60,0x01,0x54,0x98,0xc0,0x7e,0xdf,
+0x6f,0xc7,0xe7,0xf4,0x7c,0xf9,0xfe,0xfc,0x7f,0xbf,0x1f,0x5f,0xdb,0xfb,0xfc,0x71,
+0x79,0x3c,0x9e,0x6f,0xdb,0xed,0xf1,0xfb,0x75,0x7e,0x38,0x8f,0x3f,0xcf,0xe7,0xf3,
+0xff,0x0c,0x0d,0x03,0x03,0xe1,0xf8,0x30,0x3c,0x00,0x27,0x40,0x03,0x30,0x00,0x00,
+0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x18,0x14,0x00,0x00,0x00,0x00,0x19,0x82,
+0xf8,0x98,0xbe,0x70,0xc3,0x61,0xb0,0xd8,0x6c,0x36,0x1b,0x30,0xc0,0x30,0x18,0x0c,
+0x06,0x01,0x80,0xc0,0x60,0x30,0x63,0x23,0xb0,0xd8,0x6c,0x36,0x1b,0x0c,0x4c,0xe3,
+0x31,0x98,0xcc,0x66,0x30,0xc1,0xf0,0xc6,0xc6,0x63,0x31,0x98,0xcc,0x66,0x33,0x60,
+0xc0,0x60,0x30,0x18,0x0c,0x00,0xc0,0x60,0x30,0x18,0x63,0x31,0x98,0xcc,0x66,0x33,
+0x19,0x8c,0x10,0x73,0x31,0x98,0xcc,0x66,0x30,0xe1,0x8c,0x38,0x1c,0x7f,0x80,0xa0,
+0x50,0x10,0x24,0x0d,0xff,0x01,0x01,0x02,0x00,0x40,0x80,0xf0,0x24,0x04,0x02,0x01,
+0x81,0x20,0x10,0x30,0x28,0x1a,0x09,0x06,0x8a,0x81,0x20,0x90,0x20,0x08,0x04,0x02,
+0x00,0x0c,0x00,0x04,0x85,0x32,0x6f,0xb8,0x00,0x18,0x06,0x00,0x01,0x01,0xc0,0x00,
+0x70,0x60,0x36,0x06,0x1f,0xcc,0xe0,0x63,0x30,0xd8,0x60,0x63,0x33,0x06,0x03,0x00,
+0x60,0x00,0xc0,0x30,0x60,0x61,0x99,0x86,0x66,0x63,0x01,0x80,0x66,0x63,0x0c,0x03,
+0x0c,0x66,0x04,0x19,0x1c,0xcc,0x60,0x33,0x18,0xcc,0x61,0x81,0xb0,0x60,0xcc,0x63,
+0x0c,0x18,0x06,0x00,0x60,0x30,0x00,0x00,0x00,0x67,0x19,0x86,0x23,0x71,0x88,0x60,
+0x36,0x63,0x06,0x01,0x8c,0x60,0xc6,0xd9,0x8c,0x6c,0x66,0x1b,0x8c,0x08,0x61,0x83,
+0xb8,0x70,0xcc,0x63,0x0c,0x18,0x03,0x00,0xc0,0x60,0x00,0xaa,0x98,0xc0,0x7f,0x5f,
+0xaf,0xef,0xdb,0xf2,0x00,0xfe,0xfe,0xfd,0xff,0xbf,0x7f,0x6f,0xdb,0xfb,0xfd,0xfe,
+0x7e,0xdf,0xef,0xcf,0xd7,0xe5,0xf6,0xf9,0x75,0x7e,0xdf,0x6f,0xdf,0xf7,0xfb,0xfd,
+0x00,0x0c,0x07,0xc6,0x04,0x10,0x60,0x30,0x06,0x00,0x10,0x80,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x3f,0x80,0x00,0x00,0x03,0xb8,0x14,0x00,0x00,0x00,0x00,0x00,0x04,
+0x11,0x21,0x04,0xc0,0xc3,0x61,0xb0,0xd8,0x6c,0x36,0x1b,0x30,0x66,0x30,0x18,0x0c,
+0x06,0x01,0x80,0xc0,0x60,0x30,0x66,0x23,0x99,0x8c,0xc6,0x63,0x31,0x98,0x00,0x66,
+0x1b,0x0d,0x86,0xc3,0x60,0xc1,0x80,0xc6,0xce,0x67,0x33,0x99,0xcc,0xe6,0x73,0x74,
+0x62,0x31,0x18,0x8c,0x46,0x20,0xc0,0x60,0x30,0x18,0x36,0x31,0x8d,0x86,0xc3,0x61,
+0xb0,0xd8,0x10,0x36,0x3b,0x9d,0xce,0xe7,0x70,0xc1,0x98,0x30,0x00,0x7f,0x80,0xc0,
+0x60,0x10,0x24,0x0c,0x38,0x0e,0x01,0x02,0x00,0x40,0x80,0xa0,0x18,0x0e,0x03,0x00,
+0x80,0x40,0x60,0x50,0x30,0x16,0x0e,0x05,0x88,0x81,0xc0,0x81,0xc0,0x70,0x38,0x1c,
+0x00,0x0c,0x00,0x04,0x83,0xe0,0x39,0xcc,0x00,0x0c,0x0c,0x00,0x00,0x01,0xc0,0x00,
+0x70,0xc0,0x1c,0x06,0x1f,0xc7,0xc0,0x61,0xe0,0x70,0x60,0x3e,0x1e,0x06,0x03,0x00,
+0x00,0x00,0x00,0x30,0x1e,0x61,0x9f,0x03,0xc7,0xc3,0xf1,0x80,0x3e,0x63,0x3f,0x1e,
+0x0c,0x67,0xe4,0x19,0x0c,0x78,0x60,0x1e,0x18,0xc7,0xc1,0x80,0xe0,0x60,0xcc,0x63,
+0x0c,0x1f,0xc6,0x00,0x30,0x30,0x00,0x00,0x00,0x3b,0x9f,0x03,0xc1,0xb0,0xf0,0x60,
+0x06,0x63,0x06,0x01,0x8c,0x70,0xc6,0xd9,0x8c,0x38,0x7c,0x0d,0x8c,0x07,0xc0,0xf1,
+0xd8,0x60,0xcc,0x63,0x0c,0x1f,0xc3,0x00,0xc0,0x60,0x01,0x54,0x80,0xc0,0x7f,0x3f,
+0x9f,0xef,0xdb,0xf3,0xc7,0xf1,0xfe,0xfd,0xff,0xbf,0x7f,0xff,0xe7,0xf1,0xfc,0xff,
+0x7f,0xbf,0x9f,0xaf,0xcf,0xe9,0xf1,0xfa,0x77,0x7e,0x3f,0x7e,0x3f,0x8f,0xc7,0xe3,
+0xff,0x0c,0x01,0x0f,0xe8,0x08,0x60,0x30,0xc6,0x00,0x0f,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xd8,0x14,0x00,0x00,0x00,0x00,0x00,0x04,
+0x11,0x3d,0x04,0xc0,0xc3,0x61,0xb0,0xd8,0x6c,0x36,0x1b,0x3c,0x3c,0x3f,0x1f,0x8f,
+0xc7,0xe7,0xe3,0xf1,0xf8,0xfc,0x7c,0x21,0x8f,0x07,0x83,0xc1,0xe0,0xf0,0x00,0xbc,
+0x0e,0x07,0x03,0x81,0xc0,0xc1,0x80,0xcc,0x77,0x3b,0x9d,0xce,0xe7,0x73,0xb9,0x98,
+0x3c,0x1e,0x0f,0x07,0x83,0xc0,0xc0,0x60,0x30,0x18,0x1c,0x31,0x87,0x03,0x81,0xc0,
+0xe0,0x70,0x00,0x5c,0x1d,0x8e,0xc7,0x63,0xb0,0xc1,0xf0,0x30,0x00,0x7f,0x81,0x40,
+0xa0,0x10,0x28,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x02,0x00,
+0x80,0x80,0x10,0xf8,0x28,0x12,0x09,0x04,0x80,0x01,0x20,0x80,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x06,0x18,0x00,0x00,0x00,0x40,0x00,
+0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x07,0xc0,0x31,0xf0,0x01,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xcc,0x00,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x60,0x01,0x80,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x18,0x00,0x01,0xe0,0xc3,0xc0,0x00,0x00,0xff,0xc0,0x7e,0xbf,
+0x5f,0xef,0xd7,0xf5,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfd,0xff,
+0x7f,0x7f,0xef,0x07,0xd7,0xed,0xf6,0xfb,0x7f,0xfe,0xdf,0x7f,0xff,0xff,0xff,0xff,
+0x00,0x0c,0x01,0x00,0x00,0x00,0x00,0x30,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x14,0x00,0x08,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x81,0x80,0x60,0x00,0x7f,0x81,0x20,
+0x90,0x10,0x1c,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,
+0x81,0xe0,0x60,0x10,0x24,0x12,0x0e,0x04,0x80,0x01,0xc0,0x70,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x78,0x00,0x00,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x01,0x80,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xfe,0xdf,
+0x6f,0xef,0xe3,0xf5,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0x7f,
+0x7e,0x1f,0x9f,0xef,0xdb,0xed,0xf1,0xfb,0x7f,0xfe,0x3f,0x8f,0xff,0xff,0xff,0xff,
+0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x81,0x80,0x60,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x32,0x35,0x36,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x31,0x35,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x31,0x33,0x20,
+0x00,0x00,0x01,0x0c,0x00,0x09,0x09,0x00,0x01,0x0f,0x00,0x09,0x12,0x00,0x01,0x0f,
+0x00,0x09,0x1b,0x00,0x01,0x0f,0x00,0x09,0x24,0x00,0x01,0x0f,0x00,0x09,0x2d,0x00,
+0x01,0x0f,0x00,0x09,0x36,0x00,0x01,0x0f,0x00,0x09,0x3f,0x00,0x03,0x0d,0x00,0x09,
+0x48,0x00,0x03,0x0d,0x00,0x09,0x51,0x00,0x03,0x0d,0x00,0x09,0x5a,0x00,0x03,0x0d,
+0x00,0x09,0x63,0x00,0x03,0x0d,0x00,0x09,0x6c,0x00,0x03,0x0d,0x00,0x09,0x75,0x00,
+0x03,0x0e,0x00,0x09,0x7e,0x00,0x03,0x0d,0x00,0x09,0x87,0x00,0x03,0x0d,0x00,0x09,
+0x90,0x00,0x01,0x0f,0x00,0x09,0x99,0x00,0x01,0x0f,0x00,0x09,0xa2,0x00,0x01,0x0f,
+0x00,0x09,0xab,0x00,0x01,0x0f,0x00,0x09,0xb4,0x00,0x01,0x0f,0x00,0x09,0xbd,0x00,
+0x01,0x0f,0x00,0x09,0xc6,0x00,0x01,0x0f,0x00,0x09,0xcf,0x00,0x01,0x0f,0x00,0x09,
+0xd8,0x00,0x01,0x0f,0x00,0x09,0xe1,0x00,0x03,0x0d,0x00,0x09,0xea,0x00,0x01,0x0f,
+0x00,0x09,0xf3,0x00,0x01,0x0f,0x00,0x09,0xfc,0x00,0x03,0x0d,0x00,0x09,0x05,0x01,
+0x03,0x0d,0x00,0x09,0x0e,0x01,0x03,0x0d,0x00,0x09,0x17,0x01,0x03,0x0d,0x00,0x09,
+0x20,0x01,0x00,0x00,0x00,0x09,0x29,0x01,0x03,0x0d,0x00,0x09,0x32,0x01,0x02,0x05,
+0x00,0x09,0x3b,0x01,0x03,0x0d,0x00,0x09,0x44,0x01,0x02,0x0e,0x00,0x09,0x4d,0x01,
+0x03,0x0d,0x00,0x09,0x56,0x01,0x03,0x0d,0x00,0x09,0x5f,0x01,0x02,0x06,0x00,0x09,
+0x68,0x01,0x02,0x0e,0x00,0x09,0x71,0x01,0x02,0x0e,0x00,0x09,0x7a,0x01,0x03,0x08,
+0x00,0x09,0x83,0x01,0x05,0x0c,0x00,0x09,0x8c,0x01,0x0b,0x0f,0x00,0x09,0x95,0x01,
+0x08,0x09,0x00,0x09,0x9e,0x01,0x0b,0x0d,0x00,0x09,0xa7,0x01,0x02,0x0e,0x00,0x09,
+0xb0,0x01,0x03,0x0d,0x00,0x09,0xb9,0x01,0x03,0x0d,0x00,0x09,0xc2,0x01,0x03,0x0d,
+0x00,0x09,0xcb,0x01,0x03,0x0d,0x00,0x09,0xd4,0x01,0x03,0x0d,0x00,0x09,0xdd,0x01,
+0x03,0x0d,0x00,0x09,0xe6,0x01,0x03,0x0d,0x00,0x09,0xef,0x01,0x03,0x0d,0x00,0x09,
+0xf8,0x01,0x03,0x0d,0x00,0x09,0x01,0x02,0x03,0x0d,0x00,0x09,0x0a,0x02,0x06,0x0d,
+0x00,0x09,0x13,0x02,0x06,0x0f,0x00,0x09,0x1c,0x02,0x05,0x0c,0x00,0x09,0x25,0x02,
+0x07,0x0a,0x00,0x09,0x2e,0x02,0x05,0x0c,0x00,0x09,0x37,0x02,0x03,0x0d,0x00,0x09,
+0x40,0x02,0x03,0x0d,0x00,0x09,0x49,0x02,0x03,0x0d,0x00,0x09,0x52,0x02,0x03,0x0d,
+0x00,0x09,0x5b,0x02,0x03,0x0d,0x00,0x09,0x64,0x02,0x03,0x0d,0x00,0x09,0x6d,0x02,
+0x03,0x0d,0x00,0x09,0x76,0x02,0x03,0x0d,0x00,0x09,0x7f,0x02,0x03,0x0d,0x00,0x09,
+0x88,0x02,0x03,0x0d,0x00,0x09,0x91,0x02,0x03,0x0d,0x00,0x09,0x9a,0x02,0x03,0x0d,
+0x00,0x09,0xa3,0x02,0x03,0x0d,0x00,0x09,0xac,0x02,0x03,0x0d,0x00,0x09,0xb5,0x02,
+0x03,0x0d,0x00,0x09,0xbe,0x02,0x03,0x0d,0x00,0x09,0xc7,0x02,0x03,0x0d,0x00,0x09,
+0xd0,0x02,0x03,0x0d,0x00,0x09,0xd9,0x02,0x03,0x0f,0x00,0x09,0xe2,0x02,0x03,0x0d,
+0x00,0x09,0xeb,0x02,0x03,0x0d,0x00,0x09,0xf4,0x02,0x03,0x0d,0x00,0x09,0xfd,0x02,
+0x03,0x0d,0x00,0x09,0x06,0x03,0x03,0x0d,0x00,0x09,0x0f,0x03,0x03,0x0d,0x00,0x09,
+0x18,0x03,0x03,0x0d,0x00,0x09,0x21,0x03,0x03,0x0d,0x00,0x09,0x2a,0x03,0x03,0x0d,
+0x00,0x09,0x33,0x03,0x02,0x0e,0x00,0x09,0x3c,0x03,0x02,0x0e,0x00,0x09,0x45,0x03,
+0x02,0x0e,0x00,0x09,0x4e,0x03,0x04,0x0b,0x00,0x09,0x57,0x03,0x0d,0x0e,0x00,0x09,
+0x60,0x03,0x02,0x06,0x00,0x09,0x69,0x03,0x05,0x0d,0x00,0x09,0x72,0x03,0x02,0x0d,
+0x00,0x09,0x7b,0x03,0x05,0x0d,0x00,0x09,0x84,0x03,0x02,0x0d,0x00,0x09,0x8d,0x03,
+0x05,0x0d,0x00,0x09,0x96,0x03,0x02,0x0d,0x00,0x09,0x9f,0x03,0x05,0x0f,0x00,0x09,
+0xa8,0x03,0x02,0x0d,0x00,0x09,0xb1,0x03,0x02,0x0d,0x00,0x09,0xba,0x03,0x02,0x0f,
+0x00,0x09,0xc3,0x03,0x02,0x0d,0x00,0x09,0xcc,0x03,0x02,0x0d,0x00,0x09,0xd5,0x03,
+0x05,0x0d,0x00,0x09,0xde,0x03,0x05,0x0d,0x00,0x09,0xe7,0x03,0x05,0x0d,0x00,0x09,
+0xf0,0x03,0x05,0x0f,0x00,0x09,0xf9,0x03,0x05,0x0f,0x00,0x09,0x02,0x04,0x05,0x0d,
+0x00,0x09,0x0b,0x04,0x05,0x0d,0x00,0x09,0x14,0x04,0x03,0x0d,0x00,0x09,0x1d,0x04,
+0x05,0x0d,0x00,0x09,0x26,0x04,0x05,0x0d,0x00,0x09,0x2f,0x04,0x05,0x0d,0x00,0x09,
+0x38,0x04,0x05,0x0d,0x00,0x09,0x41,0x04,0x05,0x0f,0x00,0x09,0x4a,0x04,0x05,0x0d,
+0x00,0x09,0x53,0x04,0x02,0x0e,0x00,0x09,0x5c,0x04,0x02,0x0e,0x00,0x09,0x65,0x04,
+0x02,0x0e,0x00,0x09,0x6e,0x04,0x07,0x0a,0x00,0x09,0x77,0x04,0x01,0x0d,0x00,0x09,
+0x80,0x04,0x00,0x0e,0x00,0x09,0x89,0x04,0x00,0x0f,0x00,0x09,0x92,0x04,0x00,0x0f,
+0x00,0x09,0x9b,0x04,0x00,0x0f,0x00,0x09,0xa4,0x04,0x00,0x0f,0x00,0x09,0xad,0x04,
+0x00,0x0f,0x00,0x09,0xb6,0x04,0x00,0x0f,0x00,0x09,0xbf,0x04,0x00,0x0f,0x00,0x09,
+0xc8,0x04,0x00,0x0f,0x00,0x09,0xd1,0x04,0x00,0x0f,0x00,0x09,0xda,0x04,0x00,0x0f,
+0x00,0x09,0xe3,0x04,0x00,0x0f,0x00,0x09,0xec,0x04,0x00,0x0f,0x00,0x09,0xf5,0x04,
+0x00,0x0f,0x00,0x09,0xfe,0x04,0x00,0x0f,0x00,0x09,0x07,0x05,0x00,0x0f,0x00,0x09,
+0x10,0x05,0x00,0x0f,0x00,0x09,0x19,0x05,0x00,0x0f,0x00,0x09,0x22,0x05,0x00,0x0f,
+0x00,0x09,0x2b,0x05,0x00,0x0f,0x00,0x09,0x34,0x05,0x00,0x0f,0x00,0x09,0x3d,0x05,
+0x00,0x0f,0x00,0x09,0x46,0x05,0x00,0x0f,0x00,0x09,0x4f,0x05,0x00,0x0f,0x00,0x09,
+0x58,0x05,0x00,0x0f,0x00,0x09,0x61,0x05,0x00,0x0f,0x00,0x09,0x6a,0x05,0x00,0x0f,
+0x00,0x09,0x73,0x05,0x00,0x0f,0x00,0x09,0x7c,0x05,0x00,0x0f,0x00,0x09,0x85,0x05,
+0x00,0x0f,0x00,0x09,0x8e,0x05,0x00,0x0f,0x00,0x09,0x97,0x05,0x00,0x0f,0x00,0x09,
+0xa0,0x05,0x00,0x0d,0x00,0x09,0xa9,0x05,0x05,0x0f,0x00,0x09,0xb2,0x05,0x02,0x0e,
+0x00,0x09,0xbb,0x05,0x03,0x0d,0x00,0x09,0xc4,0x05,0x03,0x0d,0x00,0x09,0xcd,0x05,
+0x03,0x0d,0x00,0x09,0xd6,0x05,0x02,0x0e,0x00,0x09,0xdf,0x05,0x03,0x0e,0x00,0x09,
+0xe8,0x05,0x02,0x04,0x00,0x09,0xf1,0x05,0x03,0x0d,0x00,0x09,0xfa,0x05,0x03,0x0a,
+0x00,0x09,0x03,0x06,0x06,0x0b,0x00,0x09,0x0c,0x06,0x07,0x0a,0x00,0x09,0x15,0x06,
+0x08,0x09,0x00,0x09,0x1e,0x06,0x03,0x0b,0x00,0x09,0x27,0x06,0x02,0x03,0x00,0x09,
+0x30,0x06,0x03,0x07,0x00,0x09,0x39,0x06,0x05,0x0c,0x00,0x09,0x42,0x06,0x03,0x0a,
+0x00,0x09,0x4b,0x06,0x03,0x0a,0x00,0x09,0x54,0x06,0x02,0x04,0x00,0x09,0x5d,0x06,
+0x05,0x0f,0x00,0x09,0x66,0x06,0x03,0x0e,0x00,0x09,0x6f,0x06,0x08,0x0a,0x00,0x09,
+0x78,0x06,0x0d,0x0f,0x00,0x09,0x81,0x06,0x03,0x0a,0x00,0x09,0x8a,0x06,0x03,0x0a,
+0x00,0x09,0x93,0x06,0x06,0x0b,0x00,0x09,0x9c,0x06,0x03,0x0d,0x00,0x09,0xa5,0x06,
+0x03,0x0d,0x00,0x09,0xae,0x06,0x03,0x0d,0x00,0x09,0xb7,0x06,0x05,0x0f,0x00,0x09,
+0xc0,0x06,0x00,0x0d,0x00,0x09,0xc9,0x06,0x00,0x0d,0x00,0x09,0xd2,0x06,0x00,0x0d,
+0x00,0x09,0xdb,0x06,0x00,0x0d,0x00,0x09,0xe4,0x06,0x00,0x0d,0x00,0x09,0xed,0x06,
+0x01,0x0d,0x00,0x09,0xf6,0x06,0x03,0x0d,0x00,0x09,0xff,0x06,0x03,0x0f,0x00,0x09,
+0x08,0x07,0x00,0x0d,0x00,0x09,0x11,0x07,0x00,0x0d,0x00,0x09,0x1a,0x07,0x00,0x0d,
+0x00,0x09,0x23,0x07,0x00,0x0d,0x00,0x09,0x2c,0x07,0x00,0x0d,0x00,0x09,0x35,0x07,
+0x00,0x0d,0x00,0x09,0x3e,0x07,0x00,0x0d,0x00,0x09,0x47,0x07,0x00,0x0d,0x00,0x09,
+0x50,0x07,0x03,0x0d,0x00,0x09,0x59,0x07,0x00,0x0d,0x00,0x09,0x62,0x07,0x00,0x0d,
+0x00,0x09,0x6b,0x07,0x00,0x0d,0x00,0x09,0x74,0x07,0x00,0x0d,0x00,0x09,0x7d,0x07,
+0x00,0x0d,0x00,0x09,0x86,0x07,0x00,0x0d,0x00,0x09,0x8f,0x07,0x06,0x0b,0x00,0x09,
+0x98,0x07,0x03,0x0d,0x00,0x09,0xa1,0x07,0x00,0x0d,0x00,0x09,0xaa,0x07,0x00,0x0d,
+0x00,0x09,0xb3,0x07,0x00,0x0d,0x00,0x09,0xbc,0x07,0x00,0x0d,0x00,0x09,0xc5,0x07,
+0x00,0x0d,0x00,0x09,0xce,0x07,0x03,0x0d,0x00,0x09,0xd7,0x07,0x02,0x0d,0x00,0x09,
+0xe0,0x07,0x02,0x0d,0x00,0x09,0xe9,0x07,0x02,0x0d,0x00,0x09,0xf2,0x07,0x02,0x0d,
+0x00,0x09,0xfb,0x07,0x02,0x0d,0x00,0x09,0x04,0x08,0x02,0x0d,0x00,0x09,0x0d,0x08,
+0x02,0x0d,0x00,0x09,0x16,0x08,0x05,0x0d,0x00,0x09,0x1f,0x08,0x05,0x0f,0x00,0x09,
+0x28,0x08,0x02,0x0d,0x00,0x09,0x31,0x08,0x02,0x0d,0x00,0x09,0x3a,0x08,0x02,0x0d,
+0x00,0x09,0x43,0x08,0x02,0x0d,0x00,0x09,0x4c,0x08,0x02,0x0d,0x00,0x09,0x55,0x08,
+0x02,0x0d,0x00,0x09,0x5e,0x08,0x02,0x0d,0x00,0x09,0x67,0x08,0x02,0x0d,0x00,0x09,
+0x70,0x08,0x02,0x0d,0x00,0x09,0x79,0x08,0x02,0x0d,0x00,0x09,0x82,0x08,0x02,0x0d,
+0x00,0x09,0x8b,0x08,0x02,0x0d,0x00,0x09,0x94,0x08,0x02,0x0d,0x00,0x09,0x9d,0x08,
+0x02,0x0d,0x00,0x09,0xa6,0x08,0x02,0x0d,0x00,0x09,0xaf,0x08,0x05,0x0c,0x00,0x09,
+0xb8,0x08,0x05,0x0d,0x00,0x09,0xc1,0x08,0x02,0x0d,0x00,0x09,0xca,0x08,0x02,0x0d,
+0x00,0x09,0xd3,0x08,0x02,0x0d,0x00,0x09,0xdc,0x08,0x02,0x0d,0x00,0x09,0xe5,0x08,
+0x02,0x0f,0x00,0x09,0xee,0x08,0x03,0x0f,0x00,0x09,0xf7,0x08,0x02,0x0f,0x00,0x09,
+0x00,0x09,0x00,0x00,0x00,0x00,
+};
+
+int	sizeofdefont = sizeof defontdata;
+
+void
+_unpackinfo(Fontchar *fc, uchar *p, int n)
+{
+	int j;
+
+	for(j=0;  j<=n;  j++){
+		fc->x = p[0]|(p[1]<<8);
+		fc->top = p[2];
+		fc->bottom = p[3];
+		fc->left = p[4];
+		fc->width = p[5];
+		fc++;
+		p += 6;
+	}
+}
--- /dev/null
+++ b/libdraw/drawrepl.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+drawreplxy(int min, int max, int x)
+{
+	int sx;
+
+	sx = (x-min)%(max-min);
+	if(sx < 0)
+		sx += max-min;
+	return sx+min;
+}
+
+Point
+drawrepl(Rectangle r, Point p)
+{
+	p.x = drawreplxy(r.min.x, r.max.x, p.x);
+	p.y = drawreplxy(r.min.y, r.max.y, p.y);
+	return p;
+}
+
--- /dev/null
+++ b/libdraw/icossin.c
@@ -1,0 +1,140 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<draw.h>
+
+/*
+ * Integer sine and cosine for integral degree argument.
+ * Tables computed by (sin,cos)(PI*d/180).
+ */
+static short sinus[91] = {
+	0,	/* 0 */
+	18,	/* 1 */
+	36,	/* 2 */
+	54,	/* 3 */
+	71,	/* 4 */
+	89,	/* 5 */
+	107,	/* 6 */
+	125,	/* 7 */
+	143,	/* 8 */
+	160,	/* 9 */
+	178,	/* 10 */
+	195,	/* 11 */
+	213,	/* 12 */
+	230,	/* 13 */
+	248,	/* 14 */
+	265,	/* 15 */
+	282,	/* 16 */
+	299,	/* 17 */
+	316,	/* 18 */
+	333,	/* 19 */
+	350,	/* 20 */
+	367,	/* 21 */
+	384,	/* 22 */
+	400,	/* 23 */
+	416,	/* 24 */
+	433,	/* 25 */
+	449,	/* 26 */
+	465,	/* 27 */
+	481,	/* 28 */
+	496,	/* 29 */
+	512,	/* 30 */
+	527,	/* 31 */
+	543,	/* 32 */
+	558,	/* 33 */
+	573,	/* 34 */
+	587,	/* 35 */
+	602,	/* 36 */
+	616,	/* 37 */
+	630,	/* 38 */
+	644,	/* 39 */
+	658,	/* 40 */
+	672,	/* 41 */
+	685,	/* 42 */
+	698,	/* 43 */
+	711,	/* 44 */
+	724,	/* 45 */
+	737,	/* 46 */
+	749,	/* 47 */
+	761,	/* 48 */
+	773,	/* 49 */
+	784,	/* 50 */
+	796,	/* 51 */
+	807,	/* 52 */
+	818,	/* 53 */
+	828,	/* 54 */
+	839,	/* 55 */
+	849,	/* 56 */
+	859,	/* 57 */
+	868,	/* 58 */
+	878,	/* 59 */
+	887,	/* 60 */
+	896,	/* 61 */
+	904,	/* 62 */
+	912,	/* 63 */
+	920,	/* 64 */
+	928,	/* 65 */
+	935,	/* 66 */
+	943,	/* 67 */
+	949,	/* 68 */
+	956,	/* 69 */
+	962,	/* 70 */
+	968,	/* 71 */
+	974,	/* 72 */
+	979,	/* 73 */
+	984,	/* 74 */
+	989,	/* 75 */
+	994,	/* 76 */
+	998,	/* 77 */
+	1002,	/* 78 */
+	1005,	/* 79 */
+	1008,	/* 80 */
+	1011,	/* 81 */
+	1014,	/* 82 */
+	1016,	/* 83 */
+	1018,	/* 84 */
+	1020,	/* 85 */
+	1022,	/* 86 */
+	1023,	/* 87 */
+	1023,	/* 88 */
+	1024,	/* 89 */
+	1024,	/* 90 */
+};
+
+void
+icossin(int deg, int *cosp, int *sinp)
+{
+	int sinsign, cossign;
+	short *stp, *ctp;
+
+	deg %= 360;
+	if(deg < 0)
+		deg += 360;
+	sinsign = 1;
+	cossign = 1;
+	stp = 0;
+	ctp = 0;
+	switch(deg/90){
+	case 2:
+		sinsign = -1;
+		cossign = -1;
+		deg -= 180;
+		/* fall through */
+	case 0:
+		stp = &sinus[deg];
+		ctp = &sinus[90-deg];
+		break;
+	case 3:
+		sinsign = -1;
+		cossign = -1;
+		deg -= 180;
+		/* fall through */
+	case 1:
+		deg = 180-deg;
+		cossign = -cossign;
+		stp = &sinus[deg];
+		ctp = &sinus[90-deg];
+		break;
+	}
+	*sinp = sinsign*stp[0];
+	*cosp = cossign*ctp[0];
+}
--- /dev/null
+++ b/libdraw/icossin2.c
@@ -1,0 +1,261 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<draw.h>
+
+/*
+ * Sine and Cosine of arctangents, calculated by 
+ *   (sin(atan(index/100.0))*1024.+0.5)
+ *   (cos(atan(index/100.0))*1024.+0.5)
+ * To use, get rational tangent between 0<=tan<=1, scale by 100,
+ * and look up sin and cos, and use linear interpolation.  divide by 1024.
+ * Maximum error is 0.0020.  Without linear interpolation, it's 0.010.
+ */
+static
+short sinus[] = {
+	0,	/* 0.00 */
+	10,	/* 0.01 */
+	20,	/* 0.02 */
+	31,	/* 0.03 */
+	41,	/* 0.04 */
+	51,	/* 0.05 */
+	61,	/* 0.06 */
+	72,	/* 0.07 */
+	82,	/* 0.08 */
+	92,	/* 0.09 */
+	102,	/* 0.10 */
+	112,	/* 0.11 */
+	122,	/* 0.12 */
+	132,	/* 0.13 */
+	142,	/* 0.14 */
+	152,	/* 0.15 */
+	162,	/* 0.16 */
+	172,	/* 0.17 */
+	181,	/* 0.18 */
+	191,	/* 0.19 */
+	201,	/* 0.20 */
+	210,	/* 0.21 */
+	220,	/* 0.22 */
+	230,	/* 0.23 */
+	239,	/* 0.24 */
+	248,	/* 0.25 */
+	258,	/* 0.26 */
+	267,	/* 0.27 */
+	276,	/* 0.28 */
+	285,	/* 0.29 */
+	294,	/* 0.30 */
+	303,	/* 0.31 */
+	312,	/* 0.32 */
+	321,	/* 0.33 */
+	330,	/* 0.34 */
+	338,	/* 0.35 */
+	347,	/* 0.36 */
+	355,	/* 0.37 */
+	364,	/* 0.38 */
+	372,	/* 0.39 */
+	380,	/* 0.40 */
+	388,	/* 0.41 */
+	397,	/* 0.42 */
+	405,	/* 0.43 */
+	412,	/* 0.44 */
+	420,	/* 0.45 */
+	428,	/* 0.46 */
+	436,	/* 0.47 */
+	443,	/* 0.48 */
+	451,	/* 0.49 */
+	458,	/* 0.50 */
+	465,	/* 0.51 */
+	472,	/* 0.52 */
+	480,	/* 0.53 */
+	487,	/* 0.54 */
+	493,	/* 0.55 */
+	500,	/* 0.56 */
+	507,	/* 0.57 */
+	514,	/* 0.58 */
+	520,	/* 0.59 */
+	527,	/* 0.60 */
+	533,	/* 0.61 */
+	540,	/* 0.62 */
+	546,	/* 0.63 */
+	552,	/* 0.64 */
+	558,	/* 0.65 */
+	564,	/* 0.66 */
+	570,	/* 0.67 */
+	576,	/* 0.68 */
+	582,	/* 0.69 */
+	587,	/* 0.70 */
+	593,	/* 0.71 */
+	598,	/* 0.72 */
+	604,	/* 0.73 */
+	609,	/* 0.74 */
+	614,	/* 0.75 */
+	620,	/* 0.76 */
+	625,	/* 0.77 */
+	630,	/* 0.78 */
+	635,	/* 0.79 */
+	640,	/* 0.80 */
+	645,	/* 0.81 */
+	649,	/* 0.82 */
+	654,	/* 0.83 */
+	659,	/* 0.84 */
+	663,	/* 0.85 */
+	668,	/* 0.86 */
+	672,	/* 0.87 */
+	676,	/* 0.88 */
+	681,	/* 0.89 */
+	685,	/* 0.90 */
+	689,	/* 0.91 */
+	693,	/* 0.92 */
+	697,	/* 0.93 */
+	701,	/* 0.94 */
+	705,	/* 0.95 */
+	709,	/* 0.96 */
+	713,	/* 0.97 */
+	717,	/* 0.98 */
+	720,	/* 0.99 */
+	724,	/* 1.00 */
+	728,	/* 1.01 */
+};
+
+static
+short cosinus[] = {
+	1024,	/* 0.00 */
+	1024,	/* 0.01 */
+	1024,	/* 0.02 */
+	1024,	/* 0.03 */
+	1023,	/* 0.04 */
+	1023,	/* 0.05 */
+	1022,	/* 0.06 */
+	1022,	/* 0.07 */
+	1021,	/* 0.08 */
+	1020,	/* 0.09 */
+	1019,	/* 0.10 */
+	1018,	/* 0.11 */
+	1017,	/* 0.12 */
+	1015,	/* 0.13 */
+	1014,	/* 0.14 */
+	1013,	/* 0.15 */
+	1011,	/* 0.16 */
+	1010,	/* 0.17 */
+	1008,	/* 0.18 */
+	1006,	/* 0.19 */
+	1004,	/* 0.20 */
+	1002,	/* 0.21 */
+	1000,	/* 0.22 */
+	998,	/* 0.23 */
+	996,	/* 0.24 */
+	993,	/* 0.25 */
+	991,	/* 0.26 */
+	989,	/* 0.27 */
+	986,	/* 0.28 */
+	983,	/* 0.29 */
+	981,	/* 0.30 */
+	978,	/* 0.31 */
+	975,	/* 0.32 */
+	972,	/* 0.33 */
+	969,	/* 0.34 */
+	967,	/* 0.35 */
+	963,	/* 0.36 */
+	960,	/* 0.37 */
+	957,	/* 0.38 */
+	954,	/* 0.39 */
+	951,	/* 0.40 */
+	947,	/* 0.41 */
+	944,	/* 0.42 */
+	941,	/* 0.43 */
+	937,	/* 0.44 */
+	934,	/* 0.45 */
+	930,	/* 0.46 */
+	927,	/* 0.47 */
+	923,	/* 0.48 */
+	920,	/* 0.49 */
+	916,	/* 0.50 */
+	912,	/* 0.51 */
+	909,	/* 0.52 */
+	905,	/* 0.53 */
+	901,	/* 0.54 */
+	897,	/* 0.55 */
+	893,	/* 0.56 */
+	890,	/* 0.57 */
+	886,	/* 0.58 */
+	882,	/* 0.59 */
+	878,	/* 0.60 */
+	874,	/* 0.61 */
+	870,	/* 0.62 */
+	866,	/* 0.63 */
+	862,	/* 0.64 */
+	859,	/* 0.65 */
+	855,	/* 0.66 */
+	851,	/* 0.67 */
+	847,	/* 0.68 */
+	843,	/* 0.69 */
+	839,	/* 0.70 */
+	835,	/* 0.71 */
+	831,	/* 0.72 */
+	827,	/* 0.73 */
+	823,	/* 0.74 */
+	819,	/* 0.75 */
+	815,	/* 0.76 */
+	811,	/* 0.77 */
+	807,	/* 0.78 */
+	804,	/* 0.79 */
+	800,	/* 0.80 */
+	796,	/* 0.81 */
+	792,	/* 0.82 */
+	788,	/* 0.83 */
+	784,	/* 0.84 */
+	780,	/* 0.85 */
+	776,	/* 0.86 */
+	773,	/* 0.87 */
+	769,	/* 0.88 */
+	765,	/* 0.89 */
+	761,	/* 0.90 */
+	757,	/* 0.91 */
+	754,	/* 0.92 */
+	750,	/* 0.93 */
+	746,	/* 0.94 */
+	742,	/* 0.95 */
+	739,	/* 0.96 */
+	735,	/* 0.97 */
+	731,	/* 0.98 */
+	728,	/* 0.99 */
+	724,	/* 1.00 */
+	720,	/* 1.01 */
+};
+
+void
+icossin2(int x, int y, int *cosp, int *sinp)
+{
+	int sinsign, cossign, tan, tan10, rem;
+	short *stp, *ctp;
+
+	if(x == 0){
+		if(y >= 0)
+			*sinp = ICOSSCALE, *cosp = 0;
+		else
+			*sinp = -ICOSSCALE, *cosp = 0;
+		return;
+	}
+	sinsign = cossign = 1;
+	if(x < 0){
+		cossign = -1;
+		x = -x;
+	}
+	if(y < 0){
+		sinsign = -1;
+		y = -y;
+	}
+	if(y > x){
+		tan = 1000*x/y;
+		tan10 = tan/10;
+		stp = &cosinus[tan10];
+		ctp = &sinus[tan10];
+	}else{
+		tan = 1000*y/x;
+		tan10 = tan/10;
+		stp = &sinus[tan10];
+		ctp = &cosinus[tan10];
+	}
+	rem = tan-(tan10*10);
+	*sinp = sinsign*(stp[0]+(stp[1]-stp[0])*rem/10);
+	*cosp = cossign*(ctp[0]+(ctp[1]-ctp[0])*rem/10);
+}
--- /dev/null
+++ b/libdraw/mkfile
@@ -1,0 +1,18 @@
+<$DSRC/mkfile-$CONF
+TARG=libdraw.$L
+
+OFILES=\
+	alloc.$O\
+	arith.$O\
+	bytesperline.$O\
+	chan.$O\
+	defont.$O\
+	drawrepl.$O\
+	icossin.$O\
+	icossin2.$O\
+	rectclip.$O\
+	rgb.$O
+
+HFILES=\
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/libdraw/rectclip.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+rectclip(Rectangle *rp, Rectangle b)		/* first by reference, second by value */
+{
+	Rectangle *bp = &b;
+	/*
+	 * Expand rectXrect() in line for speed
+	 */
+	if((rp->min.x<bp->max.x && bp->min.x<rp->max.x &&
+	    rp->min.y<bp->max.y && bp->min.y<rp->max.y)==0)
+		return 0;
+	/* They must overlap */
+	if(rp->min.x < bp->min.x)
+		rp->min.x = bp->min.x;
+	if(rp->min.y < bp->min.y)
+		rp->min.y = bp->min.y;
+	if(rp->max.x > bp->max.x)
+		rp->max.x = bp->max.x;
+	if(rp->max.y > bp->max.y)
+		rp->max.y = bp->max.y;
+	return 1;
+}
--- /dev/null
+++ b/libdraw/rgb.c
@@ -1,0 +1,99 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * This original version, although fast and a true inverse of
+ * cmap2rgb, in the sense that rgb2cmap(cmap2rgb(c))
+ * returned the original color, does a terrible job for RGB
+ * triples that do not appear in the color map, so it has been
+ * replaced by the much slower version below, that loops
+ * over the color map looking for the nearest point in RGB
+ * space.  There is no visual psychology reason for that
+ * criterion, but it's easy to implement and the results are
+ * far more pleasing. 
+ *
+int
+rgb2cmap(int cr, int cg, int cb)
+{
+	int r, g, b, v, cv;
+
+	if(cr < 0)
+		cr = 0;
+	else if(cr > 255)
+		cr = 255;
+	if(cg < 0)
+		cg = 0;
+	else if(cg > 255)
+		cg = 255;
+	if(cb < 0)
+		cb = 0;
+	else if(cb > 255)
+		cb = 255;
+	r = cr>>6;
+	g = cg>>6;
+	b = cb>>6;
+	cv = cr;
+	if(cg > cv)
+		cv = cg;
+	if(cb > cv)
+		cv = cb;
+	v = (cv>>4)&3;
+	return ((((r<<2)+v)<<4)+(((g<<2)+b+v-r)&15));
+}
+*/
+
+int
+rgb2cmap(int cr, int cg, int cb)
+{
+	int i, r, g, b, sq;
+	ulong rgb;
+	int best, bestsq;
+
+	best = 0;
+	bestsq = 0x7FFFFFFF;
+	for(i=0; i<256; i++){
+		rgb = cmap2rgb(i);
+		r = (rgb>>16) & 0xFF;
+		g = (rgb>>8) & 0xFF;
+		b = (rgb>>0) & 0xFF;
+		sq = (r-cr)*(r-cr)+(g-cg)*(g-cg)+(b-cb)*(b-cb);
+		if(sq < bestsq){
+			bestsq = sq;
+			best = i;
+		}
+	}
+	return best;
+}
+
+int
+cmap2rgb(int c)
+{
+	int j, num, den, r, g, b, v, rgb;
+
+	r = c>>6;
+	v = (c>>4)&3;
+	j = (c-v+r)&15;
+	g = j>>2;
+	b = j&3;
+	den=r;
+	if(g>den)
+		den=g;
+	if(b>den)
+		den=b;
+	if(den==0) {
+		v *= 17;
+		rgb = (v<<16)|(v<<8)|v;
+	}
+	else{
+		num=17*(4*den+v);
+		rgb = ((r*num/den)<<16)|((g*num/den)<<8)|(b*num/den);
+	}
+	return rgb;
+}
+
+int
+cmap2rgba(int c)
+{
+	return (cmap2rgb(c)<<8)|0xFF;
+}
--- /dev/null
+++ b/libmemdraw/Makefile
@@ -1,0 +1,33 @@
+LIB=libmemdraw.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	alloc.$O\
+	arc.$O\
+	cload.$O\
+	cmap.$O\
+	cread.$O\
+	defont.$O\
+	draw.$O\
+	ellipse.$O\
+	fillpoly.$O\
+	hwdraw.$O\
+	line.$O\
+	load.$O\
+	openmemsubfont.$O\
+	poly.$O\
+	read.$O\
+	string.$O\
+	subfont.$O\
+	unload.$O\
+	write.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libmemdraw/alloc.c
@@ -1,0 +1,200 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+#define poolalloc(a, b) malloc(b)
+#define poolfree(a, b) free(b)
+
+void
+memimagemove(void *from, void *to)
+{
+	Memdata *md;
+
+	md = *(Memdata**)to;
+	if(md->base != from){
+		print("compacted data not right: #%p\n", md->base);
+		abort();
+	}
+	md->base = to;
+
+	/* if allocmemimage changes this must change too */
+	md->bdata = (uchar*)&md->base[2];
+}
+
+Memimage*
+allocmemimaged(Rectangle r, ulong chan, Memdata *md, void *X)
+{
+	int d;
+	ulong l;
+	Memimage *i;
+
+	if(Dx(r) <= 0 || Dy(r) <= 0){
+		werrstr("bad rectangle %R", r);
+		return nil;
+	}
+	if((d = chantodepth(chan)) == 0) {
+		werrstr("bad channel descriptor %.8lux", chan);
+		return nil;
+	}
+
+	l = wordsperline(r, d);
+
+	i = mallocz(sizeof(Memimage), 1);
+	if(i == nil)
+		return nil;
+
+	i->X = X;
+	i->data = md;
+	i->zero = sizeof(ulong)*l*r.min.y;
+	
+	if(r.min.x >= 0)
+		i->zero += (r.min.x*d)/8;
+	else
+		i->zero -= (-r.min.x*d+7)/8;
+	i->zero = -i->zero;
+	i->width = l;
+	i->r = r;
+	i->clipr = r;
+	i->flags = 0;
+	i->layer = nil;
+	i->cmap = memdefcmap;
+	if(memsetchan(i, chan) < 0){
+		free(i);
+		return nil;
+	}
+	return i;
+}
+
+Memimage*
+_allocmemimage(Rectangle r, ulong chan)
+{
+	int d;
+	ulong l, nw;
+	Memdata *md;
+	Memimage *i;
+
+	if((d = chantodepth(chan)) == 0) {
+		werrstr("bad channel descriptor %.8lux", chan);
+		return nil;
+	}
+
+	l = wordsperline(r, d);
+	nw = l*Dy(r);
+	md = malloc(sizeof(Memdata));
+	if(md == nil)
+		return nil;
+
+	md->ref = 1;
+	md->base = poolalloc(imagmem, (2+nw)*sizeof(ulong));
+	if(md->base == nil){
+		free(md);
+		return nil;
+	}
+
+	md->base[0] = (ulong)md;
+	md->base[1] = getcallerpc(&r);
+
+	/* if this changes, memimagemove must change too */
+	md->bdata = (uchar*)&md->base[2];
+
+	md->allocd = 1;
+
+	i = allocmemimaged(r, chan, md, nil);
+	if(i == nil){
+		poolfree(imagmem, md->base);
+		free(md);
+		return nil;
+	}
+	md->imref = i;
+	return i;
+}
+
+void
+_freememimage(Memimage *i)
+{
+	if(i == nil)
+		return;
+	if(i->data->ref-- == 1 && i->data->allocd){
+		if(i->data->base)
+			poolfree(imagmem, i->data->base);
+		free(i->data);
+	}
+	free(i);
+}
+
+/*
+ * Wordaddr is deprecated.
+ */
+ulong*
+wordaddr(Memimage *i, Point p)
+{
+	return (ulong*) ((ulong)byteaddr(i, p) & ~(sizeof(ulong)-1));
+}
+
+uchar*
+byteaddr(Memimage *i, Point p)
+{
+	uchar *a;
+
+	a = i->data->bdata+i->zero+sizeof(ulong)*p.y*i->width;
+
+	if(i->depth < 8){
+		/*
+		 * We need to always round down,
+		 * but C rounds toward zero.
+		 */
+		int np;
+		np = 8/i->depth;
+		if(p.x < 0)
+			return a+(p.x-np+1)/np;
+		else
+			return a+p.x/np;
+	}
+	else
+		return a+p.x*(i->depth/8);
+}
+
+int
+memsetchan(Memimage *i, ulong chan)
+{
+	int d;
+	int t, j, k;
+	ulong cc;
+	int bytes;
+
+	if((d = chantodepth(chan)) == 0) {
+		werrstr("bad channel descriptor");
+		return -1;
+	}
+
+	i->depth = d;
+	i->chan = chan;
+	i->flags &= ~(Fgrey|Falpha|Fcmap|Fbytes);
+	bytes = 1;
+	for(cc=chan, j=0, k=0; cc; j+=NBITS(cc), cc>>=8, k++){
+		t=TYPE(cc);
+		if(t < 0 || t >= NChan){
+			werrstr("bad channel string");
+			return -1;
+		}
+		if(t == CGrey)
+			i->flags |= Fgrey;
+		if(t == CAlpha)
+			i->flags |= Falpha;
+		if(t == CMap && i->cmap == nil){
+			i->cmap = memdefcmap;
+			i->flags |= Fcmap;
+		}
+
+		i->shift[t] = j;
+		i->mask[t] = (1<<NBITS(cc))-1;
+		i->nbits[t] = NBITS(cc);
+		if(NBITS(cc) != 8)
+			bytes = 0;
+	}
+	i->nchan = k;
+	if(bytes)
+		i->flags |= Fbytes;
+	return 0;
+}
--- /dev/null
+++ b/libmemdraw/alpha.hoc
@@ -1,0 +1,9 @@
+func f(x) {
+	return x-x%1
+}
+
+func pixel(dr, dg, db, da, sr, sg, sb, sa, m) {
+	M = 255-f((sa*m)/255)
+	print f((sr*m+dr*M)/255), " ", f((sg*m+dg*M)/255), " ", f((sb*m+db*M)/255),  " ", f((sa*m+da*M)/255), "\n"
+	return 0
+}
--- /dev/null
+++ b/libmemdraw/arc.c
@@ -1,0 +1,117 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * elarc(dst,c,a,b,t,src,sp,alpha,phi)
+ *   draws the part of an ellipse between rays at angles alpha and alpha+phi
+ *   measured counterclockwise from the positive x axis. other
+ *   arguments are as for ellipse(dst,c,a,b,t,src,sp)
+ */
+
+enum
+{
+	R, T, L, B	/* right, top, left, bottom */
+};
+
+static
+Point corners[] = {
+	{1,1},
+	{-1,1},
+	{-1,-1},
+	{1,-1}
+};
+
+static
+Point p00;
+
+/*
+ * make a "wedge" mask covering the desired angle and contained in
+ * a surrounding square; draw a full ellipse; intersect that with the
+ * wedge to make a mask through which to copy src to dst.
+ */
+void
+memarc(Memimage *dst, Point c, int a, int b, int t, Memimage *src, Point sp, int alpha, int phi, int op)
+{
+	int i, w, beta, tmp, c1, c2, m, m1;
+	Rectangle rect;
+	Point p,	bnd[8];
+	Memimage *wedge, *figure, *mask;
+
+	if(a < 0)
+		a = -a;
+	if(b < 0)
+		b = -b;
+	w = t;
+	if(w < 0)
+		w = 0;
+	alpha = -alpha;		/* compensate for upside-down coords */
+	phi = -phi;
+	beta = alpha + phi;
+	if(phi < 0){
+		tmp = alpha;
+		alpha = beta;
+		beta = tmp;
+		phi = -phi;
+	}
+	if(phi >= 360){
+		memellipse(dst, c, a, b, t, src, sp, op);
+		return;
+	}
+	while(alpha < 0)
+		alpha += 360;
+	while(beta < 0)
+		beta += 360;
+	c1 = alpha/90 & 3;	/* number of nearest corner */
+	c2 = beta/90 & 3;
+		/*
+		 * icossin returns point at radius ICOSSCALE.
+		 * multiplying by m1 moves it outside the ellipse
+		*/
+	rect = Rect(-a-w, -b-w, a+w+1, b+w+1);
+	m = rect.max.x;	/* inradius of bounding square */
+	if(m < rect.max.y)
+		m = rect.max.y;
+	m1 = (m+ICOSSCALE-1) >> 10;
+	m = m1 << 10;		/* assure m1*cossin is inside */
+	i = 0;
+	bnd[i++] = Pt(0,0);
+	icossin(alpha, &p.x, &p.y);
+	bnd[i++] = mulpt(p, m1);
+	for(;;) {
+		bnd[i++] = mulpt(corners[c1], m);
+		if(c1==c2 && phi<180)
+			break;
+		c1 = (c1+1) & 3;
+		phi -= 90;
+	}
+	icossin(beta, &p.x, &p.y);
+	bnd[i++] = mulpt(p, m1);
+
+	figure = nil;
+	mask = nil;
+	wedge = allocmemimage(rect, GREY1);
+	if(wedge == nil)
+		goto Return;
+	memfillcolor(wedge, DTransparent);
+	memfillpoly(wedge, bnd, i, ~0, memopaque, p00, S);
+	figure = allocmemimage(rect, GREY1);
+	if(figure == nil)
+		goto Return;
+	memfillcolor(figure, DTransparent);
+	memellipse(figure, p00, a, b, t, memopaque, p00, S);
+	mask = allocmemimage(rect, GREY1);
+	if(mask == nil)
+		goto Return;
+	memfillcolor(mask, DTransparent);
+	memimagedraw(mask, rect, figure, rect.min, wedge, rect.min, S);
+	c = subpt(c, dst->r.min);
+	memdraw(dst, dst->r, src, subpt(sp, c), mask, subpt(p00, c), op);
+
+    Return:
+	freememimage(wedge);
+	freememimage(figure);
+	freememimage(mask);
+}
--- /dev/null
+++ b/libmemdraw/arctest.c
@@ -1,0 +1,62 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+extern int drawdebug;
+void
+main(int argc, char **argv)
+{
+	char cc;
+	Memimage *x;
+	Point c = {208,871};
+	int a = 441;
+	int b = 441;
+	int thick = 0;
+	Point sp = {0,0};
+	int alpha = 51;
+	int phi = 3;
+	vlong t0, t1;
+	int i, n;
+	vlong del;
+
+	memimageinit();
+
+	x = allocmemimage(Rect(0,0,1000,1000), CMAP8);
+	n = atoi(argv[1]);
+
+	t0 = nsec();
+	t0 = nsec();
+	t0 = nsec();
+	t1 = nsec();
+	del = t1-t0;
+	t0 = nsec();
+	for(i=0; i<n; i++)
+		memarc(x, c, a, b, thick, memblack, sp, alpha, phi, SoverD);
+	t1 = nsec();
+	print("%lld %lld\n", t1-t0-del, del);
+}
+
+int drawdebug = 0;
+
+void
+rdb(void)
+{
+}
+
+int
+iprint(char *fmt, ...)
+{
+	int n;	
+	va_list va;
+	char buf[1024];
+
+	va_start(va, fmt);
+	n = doprint(buf, buf+sizeof buf, fmt, va) - buf;
+	va_end(va);
+
+	write(1,buf,n);
+	return 1;
+}
+
--- /dev/null
+++ b/libmemdraw/cload.c
@@ -1,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+_cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int y, bpl, c, cnt, offs;
+	uchar mem[NMEM], *memp, *omemp, *emem, *linep, *elinep, *u, *eu;
+
+	if(!rectinrect(r, i->r))
+		return -1;
+	bpl = bytesperline(r, i->depth);
+	u = data;
+	eu = data+ndata;
+	memp = mem;
+	emem = mem+NMEM;
+	y = r.min.y;
+	linep = byteaddr(i, Pt(r.min.x, y));
+	elinep = linep+bpl;
+	for(;;){
+		if(linep == elinep){
+			if(++y == r.max.y)
+				break;
+			linep = byteaddr(i, Pt(r.min.x, y));
+			elinep = linep+bpl;
+		}
+		if(u == eu){	/* buffer too small */
+			return -1;
+		}
+		c = *u++;
+		if(c >= 128){
+			for(cnt=c-128+1; cnt!=0 ;--cnt){
+				if(u == eu){		/* buffer too small */
+					return -1;
+				}
+				if(linep == elinep){	/* phase error */
+					return -1;
+				}
+				*linep++ = *u;
+				*memp++ = *u++;
+				if(memp == emem)
+					memp = mem;
+			}
+		}
+		else{
+			if(u == eu)	/* short buffer */
+				return -1;
+			offs = *u++ + ((c&3)<<8)+1;
+			if(memp-mem < offs)
+				omemp = memp+(NMEM-offs);
+			else
+				omemp = memp-offs;
+			for(cnt=(c>>2)+NMATCH; cnt!=0; --cnt){
+				if(linep == elinep)	/* phase error */
+					return -1;
+				*linep++ = *omemp;
+				*memp++ = *omemp++;
+				if(omemp == emem)
+					omemp = mem;
+				if(memp == emem)
+					memp = mem;
+			}
+		}
+	}
+	return u-data;
+}
--- /dev/null
+++ b/libmemdraw/cmap.c
@@ -1,0 +1,320 @@
+/*
+ * generated by mkcmap.c
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+static Memcmap def = {
+/* cmap2rgb */ {
+	0x00,0x00,0x00,0x00,0x00,0x44,0x00,0x00,0x88,0x00,0x00,0xcc,0x00,0x44,0x00,0x00,
+	0x44,0x44,0x00,0x44,0x88,0x00,0x44,0xcc,0x00,0x88,0x00,0x00,0x88,0x44,0x00,0x88,
+	0x88,0x00,0x88,0xcc,0x00,0xcc,0x00,0x00,0xcc,0x44,0x00,0xcc,0x88,0x00,0xcc,0xcc,
+	0x00,0xdd,0xdd,0x11,0x11,0x11,0x00,0x00,0x55,0x00,0x00,0x99,0x00,0x00,0xdd,0x00,
+	0x55,0x00,0x00,0x55,0x55,0x00,0x4c,0x99,0x00,0x49,0xdd,0x00,0x99,0x00,0x00,0x99,
+	0x4c,0x00,0x99,0x99,0x00,0x93,0xdd,0x00,0xdd,0x00,0x00,0xdd,0x49,0x00,0xdd,0x93,
+	0x00,0xee,0x9e,0x00,0xee,0xee,0x22,0x22,0x22,0x00,0x00,0x66,0x00,0x00,0xaa,0x00,
+	0x00,0xee,0x00,0x66,0x00,0x00,0x66,0x66,0x00,0x55,0xaa,0x00,0x4f,0xee,0x00,0xaa,
+	0x00,0x00,0xaa,0x55,0x00,0xaa,0xaa,0x00,0x9e,0xee,0x00,0xee,0x00,0x00,0xee,0x4f,
+	0x00,0xff,0x55,0x00,0xff,0xaa,0x00,0xff,0xff,0x33,0x33,0x33,0x00,0x00,0x77,0x00,
+	0x00,0xbb,0x00,0x00,0xff,0x00,0x77,0x00,0x00,0x77,0x77,0x00,0x5d,0xbb,0x00,0x55,
+	0xff,0x00,0xbb,0x00,0x00,0xbb,0x5d,0x00,0xbb,0xbb,0x00,0xaa,0xff,0x00,0xff,0x00,
+	0x44,0x00,0x44,0x44,0x00,0x88,0x44,0x00,0xcc,0x44,0x44,0x00,0x44,0x44,0x44,0x44,
+	0x44,0x88,0x44,0x44,0xcc,0x44,0x88,0x00,0x44,0x88,0x44,0x44,0x88,0x88,0x44,0x88,
+	0xcc,0x44,0xcc,0x00,0x44,0xcc,0x44,0x44,0xcc,0x88,0x44,0xcc,0xcc,0x44,0x00,0x00,
+	0x55,0x00,0x00,0x55,0x00,0x55,0x4c,0x00,0x99,0x49,0x00,0xdd,0x55,0x55,0x00,0x55,
+	0x55,0x55,0x4c,0x4c,0x99,0x49,0x49,0xdd,0x4c,0x99,0x00,0x4c,0x99,0x4c,0x4c,0x99,
+	0x99,0x49,0x93,0xdd,0x49,0xdd,0x00,0x49,0xdd,0x49,0x49,0xdd,0x93,0x49,0xdd,0xdd,
+	0x4f,0xee,0xee,0x66,0x00,0x00,0x66,0x00,0x66,0x55,0x00,0xaa,0x4f,0x00,0xee,0x66,
+	0x66,0x00,0x66,0x66,0x66,0x55,0x55,0xaa,0x4f,0x4f,0xee,0x55,0xaa,0x00,0x55,0xaa,
+	0x55,0x55,0xaa,0xaa,0x4f,0x9e,0xee,0x4f,0xee,0x00,0x4f,0xee,0x4f,0x4f,0xee,0x9e,
+	0x55,0xff,0xaa,0x55,0xff,0xff,0x77,0x00,0x00,0x77,0x00,0x77,0x5d,0x00,0xbb,0x55,
+	0x00,0xff,0x77,0x77,0x00,0x77,0x77,0x77,0x5d,0x5d,0xbb,0x55,0x55,0xff,0x5d,0xbb,
+	0x00,0x5d,0xbb,0x5d,0x5d,0xbb,0xbb,0x55,0xaa,0xff,0x55,0xff,0x00,0x55,0xff,0x55,
+	0x88,0x00,0x88,0x88,0x00,0xcc,0x88,0x44,0x00,0x88,0x44,0x44,0x88,0x44,0x88,0x88,
+	0x44,0xcc,0x88,0x88,0x00,0x88,0x88,0x44,0x88,0x88,0x88,0x88,0x88,0xcc,0x88,0xcc,
+	0x00,0x88,0xcc,0x44,0x88,0xcc,0x88,0x88,0xcc,0xcc,0x88,0x00,0x00,0x88,0x00,0x44,
+	0x99,0x00,0x4c,0x99,0x00,0x99,0x93,0x00,0xdd,0x99,0x4c,0x00,0x99,0x4c,0x4c,0x99,
+	0x4c,0x99,0x93,0x49,0xdd,0x99,0x99,0x00,0x99,0x99,0x4c,0x99,0x99,0x99,0x93,0x93,
+	0xdd,0x93,0xdd,0x00,0x93,0xdd,0x49,0x93,0xdd,0x93,0x93,0xdd,0xdd,0x99,0x00,0x00,
+	0xaa,0x00,0x00,0xaa,0x00,0x55,0xaa,0x00,0xaa,0x9e,0x00,0xee,0xaa,0x55,0x00,0xaa,
+	0x55,0x55,0xaa,0x55,0xaa,0x9e,0x4f,0xee,0xaa,0xaa,0x00,0xaa,0xaa,0x55,0xaa,0xaa,
+	0xaa,0x9e,0x9e,0xee,0x9e,0xee,0x00,0x9e,0xee,0x4f,0x9e,0xee,0x9e,0x9e,0xee,0xee,
+	0xaa,0xff,0xff,0xbb,0x00,0x00,0xbb,0x00,0x5d,0xbb,0x00,0xbb,0xaa,0x00,0xff,0xbb,
+	0x5d,0x00,0xbb,0x5d,0x5d,0xbb,0x5d,0xbb,0xaa,0x55,0xff,0xbb,0xbb,0x00,0xbb,0xbb,
+	0x5d,0xbb,0xbb,0xbb,0xaa,0xaa,0xff,0xaa,0xff,0x00,0xaa,0xff,0x55,0xaa,0xff,0xaa,
+	0xcc,0x00,0xcc,0xcc,0x44,0x00,0xcc,0x44,0x44,0xcc,0x44,0x88,0xcc,0x44,0xcc,0xcc,
+	0x88,0x00,0xcc,0x88,0x44,0xcc,0x88,0x88,0xcc,0x88,0xcc,0xcc,0xcc,0x00,0xcc,0xcc,
+	0x44,0xcc,0xcc,0x88,0xcc,0xcc,0xcc,0xcc,0x00,0x00,0xcc,0x00,0x44,0xcc,0x00,0x88,
+	0xdd,0x00,0x93,0xdd,0x00,0xdd,0xdd,0x49,0x00,0xdd,0x49,0x49,0xdd,0x49,0x93,0xdd,
+	0x49,0xdd,0xdd,0x93,0x00,0xdd,0x93,0x49,0xdd,0x93,0x93,0xdd,0x93,0xdd,0xdd,0xdd,
+	0x00,0xdd,0xdd,0x49,0xdd,0xdd,0x93,0xdd,0xdd,0xdd,0xdd,0x00,0x00,0xdd,0x00,0x49,
+	0xee,0x00,0x4f,0xee,0x00,0x9e,0xee,0x00,0xee,0xee,0x4f,0x00,0xee,0x4f,0x4f,0xee,
+	0x4f,0x9e,0xee,0x4f,0xee,0xee,0x9e,0x00,0xee,0x9e,0x4f,0xee,0x9e,0x9e,0xee,0x9e,
+	0xee,0xee,0xee,0x00,0xee,0xee,0x4f,0xee,0xee,0x9e,0xee,0xee,0xee,0xee,0x00,0x00,
+	0xff,0x00,0x00,0xff,0x00,0x55,0xff,0x00,0xaa,0xff,0x00,0xff,0xff,0x55,0x00,0xff,
+	0x55,0x55,0xff,0x55,0xaa,0xff,0x55,0xff,0xff,0xaa,0x00,0xff,0xaa,0x55,0xff,0xaa,
+	0xaa,0xff,0xaa,0xff,0xff,0xff,0x00,0xff,0xff,0x55,0xff,0xff,0xaa,0xff,0xff,0xff,
+},
+/* rgb2cmap */ {
+	0x00,0x00,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x00,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x04,0x04,0x04,0x05,0x05,0x05,0x05,0x06,0x06,0x06,0x17,0x07,0x07,0x18,0x18,0x29,
+	0x04,0x04,0x04,0x05,0x05,0x05,0x16,0x06,0x06,0x17,0x28,0x07,0x07,0x18,0x29,0x3a,
+	0x15,0x15,0x15,0x05,0x05,0x16,0x16,0x06,0x06,0x17,0x28,0x39,0x07,0x18,0x29,0x3a,
+	0x26,0x26,0x26,0x05,0x16,0x16,0x27,0x27,0x38,0x28,0x28,0x39,0x39,0x29,0x29,0x3a,
+	0x37,0x37,0x37,0x09,0x09,0x09,0x27,0x38,0x0a,0x0a,0x39,0x0b,0x0b,0x0b,0x1c,0x3a,
+	0x08,0x08,0x08,0x09,0x09,0x09,0x38,0x0a,0x0a,0x0a,0x1b,0x0b,0x0b,0x1c,0x1c,0x2d,
+	0x19,0x19,0x19,0x09,0x1a,0x1a,0x2b,0x0a,0x0a,0x1b,0x1b,0x0b,0x0b,0x1c,0x2d,0x3e,
+	0x2a,0x2a,0x2a,0x1a,0x2b,0x2b,0x2b,0x3c,0x1b,0x1b,0x2c,0x2c,0x3d,0x2d,0x2d,0x3e,
+	0x3b,0x3b,0x3b,0x0d,0x0d,0x3c,0x3c,0x0e,0x0e,0x0e,0x2c,0x3d,0x0f,0x0f,0x3e,0x3e,
+	0x0c,0x0c,0x0c,0x0d,0x0d,0x0d,0x3c,0x0e,0x0e,0x0e,0x3d,0x0f,0x0f,0x0f,0x10,0x3e,
+	0x1d,0x1d,0x1d,0x1e,0x1e,0x1e,0x2f,0x0e,0x1f,0x1f,0x20,0x0f,0x0f,0x10,0x10,0x21,
+	0x2e,0x2e,0x2e,0x1e,0x2f,0x2f,0x2f,0x1f,0x1f,0x20,0x20,0x31,0x10,0x10,0x21,0x21,
+	0x3f,0x3f,0x3f,0x2f,0x30,0x30,0x30,0x30,0x20,0x31,0x31,0x31,0x31,0x21,0x21,0x32,
+	0x00,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x22,0x22,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x04,0x04,0x22,0x05,0x05,0x05,0x05,0x06,0x06,0x06,0x17,0x07,0x07,0x18,0x18,0x29,
+	0x04,0x04,0x04,0x05,0x05,0x05,0x16,0x06,0x06,0x17,0x28,0x07,0x07,0x18,0x29,0x3a,
+	0x15,0x15,0x15,0x05,0x05,0x16,0x16,0x06,0x06,0x17,0x28,0x39,0x07,0x18,0x29,0x3a,
+	0x26,0x26,0x26,0x05,0x16,0x16,0x27,0x27,0x38,0x28,0x28,0x39,0x39,0x29,0x29,0x3a,
+	0x37,0x37,0x37,0x09,0x09,0x09,0x27,0x38,0x0a,0x0a,0x39,0x0b,0x0b,0x0b,0x1c,0x3a,
+	0x08,0x08,0x08,0x09,0x09,0x09,0x38,0x0a,0x0a,0x0a,0x1b,0x0b,0x0b,0x1c,0x1c,0x2d,
+	0x19,0x19,0x19,0x09,0x1a,0x1a,0x2b,0x0a,0x0a,0x1b,0x1b,0x0b,0x0b,0x1c,0x2d,0x3e,
+	0x2a,0x2a,0x2a,0x1a,0x2b,0x2b,0x2b,0x3c,0x1b,0x1b,0x2c,0x2c,0x3d,0x2d,0x2d,0x3e,
+	0x3b,0x3b,0x3b,0x0d,0x0d,0x3c,0x3c,0x0e,0x0e,0x0e,0x2c,0x3d,0x0f,0x0f,0x3e,0x3e,
+	0x0c,0x0c,0x0c,0x0d,0x0d,0x0d,0x3c,0x0e,0x0e,0x0e,0x3d,0x0f,0x0f,0x0f,0x10,0x3e,
+	0x1d,0x1d,0x1d,0x1e,0x1e,0x1e,0x2f,0x0e,0x1f,0x1f,0x20,0x0f,0x0f,0x10,0x10,0x21,
+	0x2e,0x2e,0x2e,0x1e,0x2f,0x2f,0x2f,0x1f,0x1f,0x20,0x20,0x31,0x10,0x10,0x21,0x21,
+	0x3f,0x3f,0x3f,0x2f,0x30,0x30,0x30,0x30,0x20,0x31,0x31,0x31,0x31,0x21,0x21,0x32,
+	0x11,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x22,0x22,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x22,0x22,0x22,0x33,0x33,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x04,0x22,0x22,0x33,0x33,0x33,0x05,0x06,0x06,0x06,0x17,0x07,0x07,0x18,0x18,0x29,
+	0x04,0x04,0x33,0x33,0x33,0x05,0x16,0x06,0x06,0x17,0x28,0x07,0x07,0x18,0x29,0x3a,
+	0x15,0x15,0x33,0x33,0x05,0x16,0x16,0x06,0x06,0x17,0x28,0x39,0x07,0x18,0x29,0x3a,
+	0x26,0x26,0x26,0x05,0x16,0x16,0x27,0x27,0x38,0x28,0x28,0x39,0x39,0x29,0x29,0x3a,
+	0x37,0x37,0x37,0x09,0x09,0x09,0x27,0x38,0x0a,0x0a,0x39,0x0b,0x0b,0x0b,0x1c,0x3a,
+	0x08,0x08,0x08,0x09,0x09,0x09,0x38,0x0a,0x0a,0x0a,0x1b,0x0b,0x0b,0x1c,0x1c,0x2d,
+	0x19,0x19,0x19,0x09,0x1a,0x1a,0x2b,0x0a,0x0a,0x1b,0x1b,0x0b,0x0b,0x1c,0x2d,0x3e,
+	0x2a,0x2a,0x2a,0x1a,0x2b,0x2b,0x2b,0x3c,0x1b,0x1b,0x2c,0x2c,0x3d,0x2d,0x2d,0x3e,
+	0x3b,0x3b,0x3b,0x0d,0x0d,0x3c,0x3c,0x0e,0x0e,0x0e,0x2c,0x3d,0x0f,0x0f,0x3e,0x3e,
+	0x0c,0x0c,0x0c,0x0d,0x0d,0x0d,0x3c,0x0e,0x0e,0x0e,0x3d,0x0f,0x0f,0x0f,0x10,0x3e,
+	0x1d,0x1d,0x1d,0x1e,0x1e,0x1e,0x2f,0x0e,0x1f,0x1f,0x20,0x0f,0x0f,0x10,0x10,0x21,
+	0x2e,0x2e,0x2e,0x1e,0x2f,0x2f,0x2f,0x1f,0x1f,0x20,0x20,0x31,0x10,0x10,0x21,0x21,
+	0x3f,0x3f,0x3f,0x2f,0x30,0x30,0x30,0x30,0x20,0x31,0x31,0x31,0x31,0x21,0x21,0x32,
+	0x4f,0x4f,0x22,0x40,0x40,0x40,0x40,0x41,0x41,0x41,0x52,0x42,0x42,0x53,0x53,0x64,
+	0x4f,0x22,0x22,0x22,0x40,0x40,0x40,0x41,0x41,0x41,0x52,0x42,0x42,0x53,0x53,0x64,
+	0x22,0x22,0x22,0x33,0x33,0x33,0x40,0x41,0x41,0x41,0x52,0x42,0x42,0x53,0x53,0x64,
+	0x43,0x22,0x33,0x33,0x33,0x44,0x44,0x45,0x45,0x45,0x56,0x46,0x46,0x46,0x57,0x68,
+	0x43,0x43,0x33,0x33,0x44,0x44,0x44,0x45,0x45,0x45,0x56,0x46,0x46,0x57,0x57,0x68,
+	0x43,0x43,0x33,0x44,0x44,0x44,0x55,0x45,0x45,0x56,0x56,0x46,0x46,0x57,0x68,0x68,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x49,0x4a,0x4a,0x4a,0x5b,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x5a,0x4a,0x4a,0x4a,0x5b,0x6c,
+	0x47,0x47,0x47,0x48,0x48,0x59,0x59,0x49,0x49,0x5a,0x5a,0x4a,0x4a,0x5b,0x5b,0x6c,
+	0x58,0x58,0x58,0x59,0x59,0x59,0x6a,0x49,0x5a,0x5a,0x6b,0x6b,0x5b,0x5b,0x6c,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x4c,0x4d,0x4d,0x4d,0x6b,0x4e,0x4e,0x4e,0x6c,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x4c,0x4d,0x4d,0x4d,0x5e,0x4e,0x4e,0x4e,0x5f,0x5f,
+	0x5c,0x5c,0x5c,0x4c,0x5d,0x5d,0x5d,0x4d,0x4d,0x5e,0x5e,0x4e,0x4e,0x5f,0x5f,0x60,
+	0x5c,0x5c,0x5c,0x5d,0x5d,0x6e,0x6e,0x5e,0x5e,0x5e,0x6f,0x6f,0x5f,0x5f,0x60,0x60,
+	0x6d,0x6d,0x6d,0x6e,0x6e,0x6e,0x7f,0x7f,0x6f,0x6f,0x70,0x70,0x5f,0x60,0x60,0x71,
+	0x4f,0x4f,0x40,0x40,0x40,0x40,0x51,0x41,0x41,0x52,0x63,0x42,0x42,0x53,0x64,0x75,
+	0x4f,0x4f,0x22,0x40,0x40,0x40,0x51,0x41,0x41,0x52,0x63,0x42,0x42,0x53,0x64,0x75,
+	0x43,0x22,0x33,0x33,0x33,0x40,0x51,0x41,0x41,0x52,0x63,0x42,0x42,0x53,0x64,0x75,
+	0x43,0x43,0x33,0x33,0x44,0x44,0x44,0x45,0x45,0x45,0x56,0x46,0x46,0x57,0x57,0x68,
+	0x43,0x43,0x33,0x44,0x44,0x44,0x55,0x45,0x45,0x56,0x56,0x46,0x46,0x57,0x68,0x68,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x44,0x55,0x55,0x55,0x45,0x56,0x56,0x67,0x78,0x78,0x57,0x68,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x49,0x4a,0x4a,0x4a,0x5b,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x59,0x49,0x49,0x49,0x5a,0x4a,0x4a,0x5b,0x5b,0x6c,
+	0x58,0x58,0x58,0x48,0x59,0x59,0x59,0x49,0x49,0x5a,0x5a,0x4a,0x4a,0x5b,0x6c,0x6c,
+	0x69,0x69,0x69,0x59,0x59,0x6a,0x6a,0x49,0x5a,0x5a,0x6b,0x6b,0x5b,0x5b,0x6c,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x7b,0x4d,0x4d,0x4d,0x6b,0x4e,0x4e,0x4e,0x7d,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x7b,0x4d,0x4d,0x4d,0x5e,0x4e,0x4e,0x4e,0x5f,0x7d,
+	0x5c,0x5c,0x5c,0x5d,0x5d,0x5d,0x5d,0x4d,0x5e,0x5e,0x5e,0x4e,0x4e,0x5f,0x5f,0x60,
+	0x6d,0x6d,0x6d,0x5d,0x6e,0x6e,0x6e,0x5e,0x5e,0x6f,0x6f,0x70,0x5f,0x5f,0x60,0x60,
+	0x7e,0x7e,0x7e,0x6e,0x6e,0x7f,0x7f,0x7f,0x6f,0x6f,0x70,0x70,0x70,0x60,0x60,0x71,
+	0x50,0x50,0x50,0x40,0x40,0x51,0x51,0x41,0x41,0x52,0x63,0x74,0x42,0x53,0x64,0x75,
+	0x50,0x50,0x50,0x40,0x40,0x51,0x51,0x41,0x41,0x52,0x63,0x74,0x42,0x53,0x64,0x75,
+	0x50,0x50,0x33,0x33,0x40,0x51,0x51,0x41,0x41,0x52,0x63,0x74,0x42,0x53,0x64,0x75,
+	0x43,0x43,0x33,0x44,0x44,0x44,0x55,0x45,0x45,0x56,0x56,0x46,0x46,0x57,0x68,0x68,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x44,0x55,0x55,0x55,0x45,0x56,0x56,0x67,0x78,0x78,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x55,0x55,0x55,0x66,0x66,0x56,0x67,0x67,0x78,0x78,0x68,0x68,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x66,0x49,0x49,0x49,0x78,0x78,0x4a,0x4a,0x5b,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x59,0x59,0x49,0x49,0x5a,0x5a,0x4a,0x4a,0x5b,0x6c,0x6c,
+	0x58,0x58,0x58,0x59,0x59,0x59,0x6a,0x49,0x5a,0x5a,0x6b,0x6b,0x5b,0x5b,0x6c,0x7d,
+	0x69,0x69,0x69,0x59,0x6a,0x6a,0x6a,0x7b,0x5a,0x6b,0x6b,0x6b,0x7c,0x6c,0x6c,0x7d,
+	0x7a,0x7a,0x7a,0x4c,0x4c,0x7b,0x7b,0x7b,0x4d,0x6b,0x6b,0x7c,0x7c,0x4e,0x7d,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x7b,0x7b,0x4d,0x4d,0x5e,0x7c,0x7c,0x4e,0x5f,0x5f,0x7d,
+	0x5c,0x5c,0x5c,0x5d,0x5d,0x5d,0x6e,0x4d,0x5e,0x5e,0x6f,0x4e,0x5f,0x5f,0x60,0x60,
+	0x6d,0x6d,0x6d,0x6e,0x6e,0x6e,0x6e,0x5e,0x6f,0x6f,0x6f,0x70,0x5f,0x60,0x60,0x71,
+	0x7e,0x7e,0x7e,0x6e,0x7f,0x7f,0x7f,0x7f,0x6f,0x70,0x70,0x70,0x70,0x60,0x71,0x71,
+	0x61,0x61,0x61,0x40,0x51,0x51,0x62,0x62,0x73,0x63,0x63,0x74,0x74,0x64,0x64,0x75,
+	0x61,0x61,0x61,0x40,0x51,0x51,0x62,0x62,0x73,0x63,0x63,0x74,0x74,0x64,0x64,0x75,
+	0x61,0x61,0x61,0x40,0x51,0x51,0x62,0x62,0x73,0x63,0x63,0x74,0x74,0x64,0x64,0x75,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x44,0x55,0x55,0x55,0x45,0x56,0x56,0x67,0x78,0x78,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x55,0x55,0x55,0x66,0x66,0x56,0x67,0x67,0x78,0x78,0x68,0x68,0x79,
+	0x65,0x65,0x65,0x55,0x55,0x66,0x66,0x66,0x77,0x67,0x78,0x78,0x78,0x78,0x79,0x79,
+	0x65,0x65,0x65,0x48,0x48,0x66,0x66,0x77,0x77,0x77,0x78,0x78,0x78,0x5b,0x79,0x79,
+	0x76,0x76,0x76,0x48,0x59,0x59,0x77,0x77,0x77,0x5a,0x5a,0x4a,0x4a,0x5b,0x6c,0x6c,
+	0x69,0x69,0x69,0x59,0x59,0x6a,0x6a,0x77,0x5a,0x5a,0x6b,0x6b,0x5b,0x6c,0x6c,0x7d,
+	0x69,0x69,0x69,0x6a,0x6a,0x6a,0x7b,0x7b,0x5a,0x6b,0x6b,0x7c,0x7c,0x6c,0x7d,0x7d,
+	0x7a,0x7a,0x7a,0x4c,0x7b,0x7b,0x7b,0x7b,0x4d,0x6b,0x7c,0x7c,0x7c,0x7c,0x7d,0x7d,
+	0x7a,0x7a,0x7a,0x4c,0x7b,0x7b,0x7b,0x7b,0x4d,0x5e,0x7c,0x7c,0x7c,0x5f,0x5f,0x7d,
+	0x6d,0x6d,0x6d,0x5d,0x5d,0x6e,0x7b,0x5e,0x5e,0x6f,0x6f,0x7c,0x5f,0x5f,0x60,0x60,
+	0x6d,0x6d,0x6d,0x6e,0x6e,0x6e,0x7f,0x7f,0x6f,0x6f,0x70,0x70,0x5f,0x60,0x60,0x71,
+	0x7e,0x7e,0x7e,0x7f,0x7f,0x7f,0x7f,0x7f,0x6f,0x70,0x70,0x70,0x70,0x60,0x71,0x71,
+	0x72,0x72,0x72,0x8f,0x8f,0x62,0x62,0x73,0x73,0x80,0x74,0x81,0x81,0x81,0x92,0x75,
+	0x72,0x72,0x72,0x8f,0x8f,0x62,0x62,0x73,0x73,0x80,0x74,0x81,0x81,0x81,0x92,0x75,
+	0x72,0x72,0x72,0x83,0x83,0x62,0x62,0x73,0x73,0x80,0x74,0x81,0x81,0x81,0x92,0x75,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x83,0x84,0x84,0x84,0x84,0x85,0x85,0x85,0x96,0x79,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x66,0x84,0x84,0x84,0x67,0x85,0x85,0x85,0x96,0x79,
+	0x65,0x65,0x65,0x83,0x83,0x66,0x66,0x66,0x84,0x84,0x78,0x78,0x85,0x85,0x96,0x79,
+	0x65,0x65,0x65,0x83,0x66,0x66,0x66,0x77,0x77,0x77,0x78,0x78,0x78,0x96,0x79,0x79,
+	0x76,0x76,0x76,0x87,0x87,0x66,0x77,0x77,0x77,0x88,0x78,0x89,0x89,0x89,0x89,0x79,
+	0x76,0x76,0x76,0x87,0x87,0x87,0x77,0x77,0x88,0x88,0x88,0x89,0x89,0x89,0x9a,0x9a,
+	0x86,0x86,0x86,0x87,0x87,0x87,0x77,0x88,0x88,0x88,0x6b,0x89,0x89,0x9a,0x9a,0x7d,
+	0x7a,0x7a,0x7a,0x87,0x6a,0x7b,0x7b,0x7b,0x88,0x6b,0x6b,0x7c,0x7c,0x9a,0x7d,0x7d,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x7b,0x7b,0x8c,0x8c,0x8c,0x7c,0x7c,0x8d,0x8d,0x7d,0x7d,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x7b,0x8c,0x8c,0x8c,0x7c,0x8d,0x8d,0x8d,0x9e,0x9e,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x9c,0x8c,0x8c,0x9d,0x9d,0x8d,0x8d,0x9e,0x9e,0x9e,
+	0x9b,0x9b,0x9b,0x9c,0x9c,0x9c,0x7f,0x8c,0x9d,0x9d,0x70,0x70,0x9e,0x9e,0x9e,0x71,
+	0x7e,0x7e,0x7e,0x7f,0x7f,0x7f,0x7f,0x7f,0x9d,0x70,0x70,0x70,0x9e,0x9e,0x71,0x71,
+	0x8e,0x8e,0x8e,0x8f,0x8f,0x8f,0x73,0x73,0x80,0x80,0x91,0x81,0x81,0x92,0x92,0xa3,
+	0x8e,0x8e,0x8e,0x8f,0x8f,0x8f,0x73,0x73,0x80,0x80,0x91,0x81,0x81,0x92,0x92,0xa3,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x73,0x73,0x80,0x80,0x91,0x81,0x81,0x92,0x92,0xa3,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x83,0x84,0x84,0x84,0x95,0x85,0x85,0x85,0x96,0xa7,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x94,0x84,0x84,0x84,0x95,0x85,0x85,0x96,0x96,0xa7,
+	0x82,0x82,0x82,0x83,0x83,0x94,0x94,0x84,0x84,0x95,0x95,0x85,0x85,0x96,0xa7,0xa7,
+	0x76,0x76,0x76,0x83,0x94,0x94,0x77,0x77,0x77,0x95,0x95,0x85,0x85,0x96,0xa7,0xa7,
+	0x76,0x76,0x76,0x87,0x87,0x87,0x77,0x77,0x88,0x88,0x88,0x89,0x89,0x89,0x9a,0x9a,
+	0x86,0x86,0x86,0x87,0x87,0x87,0x77,0x88,0x88,0x88,0x99,0x89,0x89,0x9a,0x9a,0xab,
+	0x86,0x86,0x86,0x87,0x87,0x98,0x98,0x88,0x88,0x99,0x99,0x89,0x89,0x9a,0x9a,0xab,
+	0x97,0x97,0x97,0x98,0x98,0x98,0x98,0x88,0x99,0x99,0x99,0x89,0x9a,0x9a,0xab,0xab,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8c,0x8c,0x8c,0x8d,0x8d,0x8d,0xab,0xbc,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8c,0x8c,0x9d,0x8d,0x8d,0x8d,0x9e,0x9e,
+	0x9b,0x9b,0x9b,0x8b,0x9c,0x9c,0x9c,0x8c,0x9d,0x9d,0x9d,0x8d,0x8d,0x9e,0x9e,0xaf,
+	0x9b,0x9b,0x9b,0x9c,0x9c,0xad,0xad,0x9d,0x9d,0x9d,0xae,0xae,0x9e,0x9e,0xaf,0xaf,
+	0xac,0xac,0xac,0xad,0xad,0xad,0xad,0x9d,0xae,0xae,0xae,0xbf,0x9e,0xaf,0xaf,0xaf,
+	0x9f,0x9f,0x9f,0x8f,0x90,0x90,0xa1,0x80,0x80,0x91,0x91,0x81,0x81,0x92,0xa3,0xb4,
+	0x9f,0x9f,0x9f,0x8f,0x90,0x90,0xa1,0x80,0x80,0x91,0x91,0x81,0x81,0x92,0xa3,0xb4,
+	0x9f,0x9f,0x9f,0x83,0x90,0x90,0xa1,0x80,0x80,0x91,0x91,0x81,0x81,0x92,0xa3,0xb4,
+	0x82,0x82,0x82,0x83,0x83,0x94,0x94,0x84,0x84,0x95,0x95,0x85,0x85,0x96,0x96,0xa7,
+	0x93,0x93,0x93,0x83,0x94,0x94,0x94,0x84,0x84,0x95,0x95,0x85,0x85,0x96,0xa7,0xa7,
+	0x93,0x93,0x93,0x94,0x94,0x94,0xa5,0x84,0x95,0x95,0xa6,0xa6,0x96,0x96,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0x94,0x94,0xa5,0xa5,0x77,0x95,0x95,0xa6,0xa6,0x96,0xa7,0xa7,0xb8,
+	0x86,0x86,0x86,0x87,0x87,0x87,0x77,0x88,0x88,0x88,0x99,0x89,0x89,0x9a,0x9a,0xb8,
+	0x86,0x86,0x86,0x87,0x87,0x98,0x98,0x88,0x88,0x99,0x99,0x89,0x89,0x9a,0x9a,0xab,
+	0x97,0x97,0x97,0x98,0x98,0x98,0x98,0x88,0x99,0x99,0x99,0x89,0x9a,0x9a,0xab,0xab,
+	0x97,0x97,0x97,0x98,0x98,0xa9,0xa9,0x99,0x99,0x99,0xaa,0xaa,0x9a,0xab,0xab,0xbc,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0xa9,0xa9,0x8c,0x8c,0x8c,0xaa,0x8d,0x8d,0x8d,0xab,0xbc,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x9c,0x9c,0x8c,0x8c,0x9d,0x9d,0x8d,0x8d,0x9e,0x9e,0xbc,
+	0x9b,0x9b,0x9b,0x9c,0x9c,0x9c,0xad,0x9d,0x9d,0x9d,0xae,0x8d,0x9e,0x9e,0xaf,0xaf,
+	0xac,0xac,0xac,0x9c,0xad,0xad,0xad,0x9d,0x9d,0xae,0xae,0xae,0x9e,0xaf,0xaf,0xaf,
+	0xbd,0xbd,0xbd,0xad,0xad,0xbe,0xbe,0xbe,0xae,0xae,0xbf,0xbf,0xbf,0xaf,0xaf,0xb0,
+	0xa0,0xa0,0xa0,0x90,0xa1,0xa1,0xa1,0xb2,0x91,0x91,0xa2,0xa2,0xb3,0xa3,0xa3,0xb4,
+	0xa0,0xa0,0xa0,0x90,0xa1,0xa1,0xa1,0xb2,0x91,0x91,0xa2,0xa2,0xb3,0xa3,0xa3,0xb4,
+	0xa0,0xa0,0xa0,0x90,0xa1,0xa1,0xa1,0xb2,0x91,0x91,0xa2,0xa2,0xb3,0xa3,0xa3,0xb4,
+	0x93,0x93,0x93,0x94,0x94,0x94,0xa5,0x84,0x95,0x95,0xa6,0xa6,0x96,0x96,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0x94,0x94,0xa5,0xa5,0x84,0x95,0x95,0xa6,0xa6,0x96,0x96,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0x94,0xa5,0xa5,0xa5,0xb6,0x95,0xa6,0xa6,0xa6,0xb7,0xa7,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0xa5,0xa5,0xa5,0xb6,0xb6,0x95,0xa6,0xa6,0xb7,0xb7,0xa7,0xb8,0xb8,
+	0xb5,0xb5,0xb5,0x87,0x87,0xb6,0xb6,0xb6,0x88,0x99,0xa6,0xb7,0xb7,0x9a,0xb8,0xb8,
+	0x97,0x97,0x97,0x98,0x98,0x98,0x98,0x88,0x99,0x99,0x99,0x89,0x9a,0x9a,0xab,0xab,
+	0x97,0x97,0x97,0x98,0x98,0xa9,0xa9,0x99,0x99,0x99,0xaa,0xaa,0x9a,0xab,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xa9,0xa9,0xa9,0xa9,0xa9,0x99,0xaa,0xaa,0xaa,0xbb,0xab,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xa9,0xa9,0xa9,0xba,0xba,0x8c,0xaa,0xaa,0xbb,0xbb,0xab,0xbc,0xbc,
+	0xb9,0xb9,0xb9,0x9c,0x9c,0xba,0xba,0xba,0x9d,0x9d,0xbb,0xbb,0xbb,0x9e,0x9e,0xbc,
+	0xac,0xac,0xac,0x9c,0x9c,0xad,0xad,0x9d,0x9d,0xae,0xae,0xae,0x9e,0x9e,0xaf,0xaf,
+	0xac,0xac,0xac,0xad,0xad,0xad,0xbe,0xbe,0xae,0xae,0xae,0xbf,0x9e,0xaf,0xaf,0xb0,
+	0xbd,0xbd,0xbd,0xbe,0xbe,0xbe,0xbe,0xbe,0xae,0xbf,0xbf,0xbf,0xbf,0xaf,0xb0,0xb0,
+	0xb1,0xb1,0xb1,0xce,0xce,0xb2,0xb2,0xcf,0xcf,0xa2,0xa2,0xb3,0xb3,0xc0,0xb4,0xb4,
+	0xb1,0xb1,0xb1,0xce,0xce,0xb2,0xb2,0xcf,0xcf,0xa2,0xa2,0xb3,0xb3,0xc0,0xb4,0xb4,
+	0xb1,0xb1,0xb1,0xc2,0xc2,0xb2,0xb2,0xc3,0xc3,0xa2,0xa2,0xb3,0xb3,0xc0,0xb4,0xb4,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xa5,0xc3,0xc3,0xc3,0xa6,0xc4,0xc4,0xc4,0xa7,0xb8,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xa5,0xb6,0xc3,0xc3,0xc3,0xa6,0xc4,0xc4,0xc4,0xb8,0xb8,
+	0xb5,0xb5,0xb5,0xc2,0xa5,0xb6,0xb6,0xb6,0xc3,0xa6,0xa6,0xb7,0xb7,0xc4,0xb8,0xb8,
+	0xb5,0xb5,0xb5,0xa5,0xb6,0xb6,0xb6,0xb6,0xc3,0xa6,0xb7,0xb7,0xb7,0xb7,0xb8,0xb8,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xb6,0xb6,0xc7,0xc7,0xc7,0xb7,0xb7,0xc8,0xc8,0xb8,0xb8,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xc6,0xc7,0xc7,0xc7,0xaa,0xc8,0xc8,0xc8,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xc6,0xc6,0xa9,0xa9,0xc7,0xc7,0xaa,0xaa,0xaa,0xc8,0xc8,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xa9,0xa9,0xa9,0xba,0xba,0xaa,0xaa,0xaa,0xbb,0xbb,0xab,0xbc,0xbc,
+	0xb9,0xb9,0xb9,0xca,0xca,0xba,0xba,0xba,0xcb,0xaa,0xbb,0xbb,0xbb,0xcc,0xbc,0xbc,
+	0xb9,0xb9,0xb9,0xca,0xca,0xba,0xba,0xcb,0xcb,0xcb,0xbb,0xbb,0xcc,0xcc,0xcc,0xbc,
+	0xc9,0xc9,0xc9,0xca,0xca,0xca,0xba,0xcb,0xcb,0xcb,0xae,0xcc,0xcc,0xcc,0xaf,0xaf,
+	0xbd,0xbd,0xbd,0xad,0xbe,0xbe,0xbe,0xbe,0xae,0xae,0xbf,0xbf,0xcc,0xaf,0xaf,0xb0,
+	0xbd,0xbd,0xbd,0xbe,0xbe,0xbe,0xbe,0xbe,0xbf,0xbf,0xbf,0xbf,0xbf,0xaf,0xb0,0xb0,
+	0xcd,0xcd,0xcd,0xce,0xce,0xce,0xb2,0xcf,0xcf,0xcf,0xb3,0xb3,0xc0,0xc0,0xd1,0xb4,
+	0xcd,0xcd,0xcd,0xce,0xce,0xce,0xb2,0xcf,0xcf,0xcf,0xb3,0xb3,0xc0,0xc0,0xd1,0xb4,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xb2,0xc3,0xc3,0xc3,0xb3,0xb3,0xc0,0xc0,0xd1,0xb4,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xc2,0xc3,0xc3,0xc3,0xd4,0xc4,0xc4,0xc4,0xd5,0xd5,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xb6,0xc3,0xc3,0xc3,0xd4,0xc4,0xc4,0xc4,0xd5,0xb8,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xb6,0xb6,0xc3,0xc3,0xd4,0xb7,0xb7,0xc4,0xd5,0xd5,0xb8,
+	0xb5,0xb5,0xb5,0xc2,0xb6,0xb6,0xb6,0xb6,0xc3,0xd4,0xb7,0xb7,0xb7,0xd5,0xd5,0xb8,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xb6,0xc7,0xc7,0xc7,0xb7,0xc8,0xc8,0xc8,0xd9,0xd9,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xc6,0xc7,0xc7,0xc7,0xd8,0xc8,0xc8,0xc8,0xd9,0xd9,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xd7,0xd7,0xc7,0xc7,0xd8,0xd8,0xc8,0xc8,0xd9,0xd9,0xbc,
+	0xb9,0xb9,0xb9,0xd7,0xd7,0xba,0xba,0xba,0xd8,0xd8,0xbb,0xbb,0xbb,0xd9,0xd9,0xbc,
+	0xb9,0xb9,0xb9,0xca,0xca,0xba,0xba,0xcb,0xcb,0xcb,0xbb,0xbb,0xcc,0xcc,0xcc,0xbc,
+	0xc9,0xc9,0xc9,0xca,0xca,0xca,0xba,0xcb,0xcb,0xcb,0xbb,0xcc,0xcc,0xcc,0xdd,0xdd,
+	0xc9,0xc9,0xc9,0xca,0xca,0xdb,0xdb,0xcb,0xcb,0xdc,0xdc,0xcc,0xcc,0xdd,0xdd,0xdd,
+	0xda,0xda,0xda,0xdb,0xdb,0xdb,0xdb,0xdc,0xdc,0xdc,0xdc,0xcc,0xdd,0xdd,0xdd,0xb0,
+	0xbd,0xbd,0xbd,0xdb,0xbe,0xbe,0xbe,0xdc,0xdc,0xbf,0xbf,0xbf,0xdd,0xdd,0xb0,0xb0,
+	0xde,0xde,0xde,0xdf,0xdf,0xdf,0xe0,0xcf,0xd0,0xd0,0xe1,0xc0,0xc0,0xd1,0xd1,0xe2,
+	0xde,0xde,0xde,0xdf,0xdf,0xdf,0xe0,0xcf,0xd0,0xd0,0xe1,0xc0,0xc0,0xd1,0xd1,0xe2,
+	0xde,0xde,0xde,0xdf,0xdf,0xdf,0xe0,0xc3,0xd0,0xd0,0xe1,0xc0,0xc0,0xd1,0xd1,0xe2,
+	0xd2,0xd2,0xd2,0xc2,0xd3,0xd3,0xd3,0xc3,0xc3,0xd4,0xd4,0xc4,0xc4,0xd5,0xd5,0xe6,
+	0xd2,0xd2,0xd2,0xd3,0xd3,0xd3,0xd3,0xc3,0xd4,0xd4,0xd4,0xc4,0xc4,0xd5,0xd5,0xe6,
+	0xd2,0xd2,0xd2,0xd3,0xd3,0xd3,0xe4,0xc3,0xd4,0xd4,0xe5,0xc4,0xd5,0xd5,0xe6,0xe6,
+	0xe3,0xe3,0xe3,0xd3,0xd3,0xe4,0xb6,0xd4,0xd4,0xe5,0xe5,0xb7,0xd5,0xd5,0xe6,0xe6,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xd7,0xc7,0xc7,0xd8,0xd8,0xc8,0xc8,0xd9,0xd9,0xd9,
+	0xd6,0xd6,0xd6,0xc6,0xd7,0xd7,0xd7,0xc7,0xd8,0xd8,0xd8,0xc8,0xc8,0xd9,0xd9,0xea,
+	0xd6,0xd6,0xd6,0xd7,0xd7,0xd7,0xe8,0xd8,0xd8,0xd8,0xe9,0xc8,0xd9,0xd9,0xea,0xea,
+	0xe7,0xe7,0xe7,0xd7,0xd7,0xe8,0xe8,0xd8,0xd8,0xe9,0xe9,0xe9,0xd9,0xd9,0xea,0xea,
+	0xc9,0xc9,0xc9,0xca,0xca,0xca,0xba,0xcb,0xcb,0xcb,0xe9,0xcc,0xcc,0xcc,0xea,0xea,
+	0xc9,0xc9,0xc9,0xca,0xca,0xdb,0xdb,0xcb,0xcb,0xdc,0xdc,0xcc,0xcc,0xdd,0xdd,0xdd,
+	0xda,0xda,0xda,0xdb,0xdb,0xdb,0xdb,0xdc,0xdc,0xdc,0xdc,0xcc,0xdd,0xdd,0xdd,0xee,
+	0xda,0xda,0xda,0xdb,0xdb,0xec,0xec,0xdc,0xdc,0xed,0xed,0xed,0xdd,0xdd,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xec,0xec,0xdc,0xed,0xed,0xed,0xed,0xdd,0xee,0xee,0xee,
+	0xef,0xef,0xef,0xdf,0xe0,0xe0,0xe0,0xd0,0xd0,0xe1,0xe1,0xf2,0xd1,0xd1,0xe2,0xe2,
+	0xef,0xef,0xef,0xdf,0xe0,0xe0,0xe0,0xd0,0xd0,0xe1,0xe1,0xf2,0xd1,0xd1,0xe2,0xe2,
+	0xef,0xef,0xef,0xdf,0xe0,0xe0,0xe0,0xd0,0xd0,0xe1,0xe1,0xf2,0xd1,0xd1,0xe2,0xe2,
+	0xd2,0xd2,0xd2,0xd3,0xd3,0xe4,0xe4,0xd4,0xd4,0xd4,0xe5,0xe5,0xd5,0xd5,0xe6,0xe6,
+	0xe3,0xe3,0xe3,0xd3,0xe4,0xe4,0xe4,0xd4,0xd4,0xe5,0xe5,0xf6,0xd5,0xd5,0xe6,0xe6,
+	0xe3,0xe3,0xe3,0xe4,0xe4,0xe4,0xe4,0xd4,0xe5,0xe5,0xe5,0xf6,0xd5,0xe6,0xe6,0xf7,
+	0xe3,0xe3,0xe3,0xe4,0xe4,0xe4,0xf5,0xf5,0xe5,0xe5,0xf6,0xf6,0xd5,0xe6,0xe6,0xf7,
+	0xd6,0xd6,0xd6,0xd7,0xd7,0xd7,0xf5,0xc7,0xd8,0xd8,0xf6,0xc8,0xd9,0xd9,0xd9,0xf7,
+	0xd6,0xd6,0xd6,0xd7,0xd7,0xe8,0xe8,0xd8,0xd8,0xd8,0xe9,0xe9,0xd9,0xd9,0xea,0xea,
+	0xe7,0xe7,0xe7,0xd7,0xe8,0xe8,0xe8,0xd8,0xd8,0xe9,0xe9,0xe9,0xd9,0xea,0xea,0xea,
+	0xe7,0xe7,0xe7,0xe8,0xe8,0xe8,0xf9,0xf9,0xe9,0xe9,0xe9,0xfa,0xd9,0xea,0xea,0xfb,
+	0xf8,0xf8,0xf8,0xe8,0xf9,0xf9,0xf9,0xcb,0xe9,0xe9,0xfa,0xfa,0xcc,0xea,0xea,0xfb,
+	0xda,0xda,0xda,0xdb,0xdb,0xdb,0xdb,0xdc,0xdc,0xdc,0xdc,0xcc,0xdd,0xdd,0xdd,0xee,
+	0xda,0xda,0xda,0xdb,0xdb,0xec,0xec,0xdc,0xdc,0xed,0xed,0xed,0xdd,0xdd,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xec,0xec,0xdc,0xed,0xed,0xed,0xed,0xdd,0xee,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xfd,0xfd,0xfd,0xed,0xed,0xfe,0xfe,0xee,0xee,0xee,0xff,
+	0xf0,0xf0,0xf0,0xe0,0xf1,0xf1,0xf1,0xf1,0xe1,0xf2,0xf2,0xf2,0xf2,0xe2,0xe2,0xf3,
+	0xf0,0xf0,0xf0,0xe0,0xf1,0xf1,0xf1,0xf1,0xe1,0xf2,0xf2,0xf2,0xf2,0xe2,0xe2,0xf3,
+	0xf0,0xf0,0xf0,0xe0,0xf1,0xf1,0xf1,0xf1,0xe1,0xf2,0xf2,0xf2,0xf2,0xe2,0xe2,0xf3,
+	0xe3,0xe3,0xe3,0xe4,0xe4,0xe4,0xf5,0xf5,0xe5,0xe5,0xf6,0xf6,0xd5,0xe6,0xe6,0xf7,
+	0xf4,0xf4,0xf4,0xe4,0xe4,0xf5,0xf5,0xf5,0xe5,0xe5,0xf6,0xf6,0xf6,0xe6,0xe6,0xf7,
+	0xf4,0xf4,0xf4,0xe4,0xf5,0xf5,0xf5,0xf5,0xe5,0xf6,0xf6,0xf6,0xf6,0xe6,0xf7,0xf7,
+	0xf4,0xf4,0xf4,0xf5,0xf5,0xf5,0xf5,0xf5,0xe5,0xf6,0xf6,0xf6,0xf6,0xe6,0xf7,0xf7,
+	0xf4,0xf4,0xf4,0xf5,0xf5,0xf5,0xf5,0xf5,0xd8,0xf6,0xf6,0xf6,0xd9,0xd9,0xf7,0xf7,
+	0xe7,0xe7,0xe7,0xe8,0xe8,0xe8,0xe8,0xd8,0xe9,0xe9,0xe9,0xfa,0xd9,0xea,0xea,0xea,
+	0xf8,0xf8,0xf8,0xe8,0xe8,0xf9,0xf9,0xf9,0xe9,0xe9,0xfa,0xfa,0xfa,0xea,0xea,0xfb,
+	0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xe9,0xfa,0xfa,0xfa,0xfa,0xea,0xfb,0xfb,
+	0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xfa,0xfa,0xfa,0xfa,0xfa,0xea,0xfb,0xfb,
+	0xf8,0xf8,0xf8,0xdb,0xf9,0xf9,0xf9,0xdc,0xdc,0xfa,0xfa,0xfa,0xdd,0xdd,0xee,0xfb,
+	0xeb,0xeb,0xeb,0xec,0xec,0xec,0xec,0xdc,0xed,0xed,0xed,0xed,0xdd,0xee,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xfd,0xfd,0xfd,0xed,0xed,0xfe,0xfe,0xee,0xee,0xee,0xff,
+	0xfc,0xfc,0xfc,0xfd,0xfd,0xfd,0xfd,0xfd,0xed,0xfe,0xfe,0xfe,0xfe,0xee,0xff,0xff,
+}
+};
+Memcmap *memdefcmap = &def;
+void _memmkcmap(void){}
--- /dev/null
+++ b/libmemdraw/cread.c
@@ -1,0 +1,96 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memimage*
+creadmemimage(int fd)
+{
+	char hdr[5*12+1];
+	Rectangle r;
+	int m, nb, miny, maxy, new, ldepth, ncblock;
+	uchar *buf;
+	Memimage *i;
+	ulong chan;
+
+	if(readn(fd, hdr, 5*12) != 5*12){
+		werrstr("readmemimage: short header (2)");
+		return nil;
+	}
+
+	/*
+	 * distinguish new channel descriptor from old ldepth.
+	 * channel descriptors have letters as well as numbers,
+	 * while ldepths are a single digit formatted as %-11d.
+	 */
+	new = 0;
+	for(m=0; m<10; m++){
+		if(hdr[m] != ' '){
+			new = 1;
+			break;
+		}
+	}
+	if(hdr[11] != ' '){
+		werrstr("creadimage: bad format");
+		return nil;
+	}
+	if(new){
+		hdr[11] = '\0';
+		if((chan = strtochan(hdr)) == 0){
+			werrstr("creadimage: bad channel string %s", hdr);
+			return nil;
+		}
+	}else{
+		ldepth = ((int)hdr[10])-'0';
+		if(ldepth<0 || ldepth>3){
+			werrstr("creadimage: bad ldepth %d", ldepth);
+			return nil;
+		}
+		chan = drawld2chan[ldepth];
+	}
+	r.min.x=atoi(hdr+1*12);
+	r.min.y=atoi(hdr+2*12);
+	r.max.x=atoi(hdr+3*12);
+	r.max.y=atoi(hdr+4*12);
+	if(r.min.x>r.max.x || r.min.y>r.max.y){
+		werrstr("creadimage: bad rectangle");
+		return nil;
+	}
+
+	i = allocmemimage(r, chan);
+	if(i == nil)
+		return nil;
+	ncblock = _compblocksize(r, i->depth);
+	buf = malloc(ncblock);
+	if(buf == nil)
+		goto Errout;
+	miny = r.min.y;
+	while(miny != r.max.y){
+		if(readn(fd, hdr, 2*12) != 2*12){
+		Shortread:
+			werrstr("readmemimage: short read");
+		Errout:
+			freememimage(i);
+			free(buf);
+			return nil;
+		}
+		maxy = atoi(hdr+0*12);
+		nb = atoi(hdr+1*12);
+		if(maxy<=miny || r.max.y<maxy){
+			werrstr("readimage: bad maxy %d", maxy);
+			goto Errout;
+		}
+		if(nb<=0 || ncblock<nb){
+			werrstr("readimage: bad count %d", nb);
+			goto Errout;
+		}
+		if(readn(fd, buf, nb)!=nb)
+			goto Shortread;
+		if(!new)	/* old image: flip the data bits */
+			_twiddlecompressed(buf, nb);
+		cloadmemimage(i, Rect(r.min.x, miny, r.max.x, maxy), buf, nb);
+		miny = maxy;
+	}
+	free(buf);
+	return i;
+}
--- /dev/null
+++ b/libmemdraw/defont.c
@@ -1,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memsubfont*
+getmemdefont(void)
+{
+	char *hdr, *p;
+	int n;
+	Fontchar *fc;
+	Memsubfont *f;
+	int ld;
+	Rectangle r;
+	Memdata *md;
+	Memimage *i;
+
+	/*
+	 * make sure data is word-aligned.  this is true with Plan 9 compilers
+	 * but not in general.  the byte order is right because the data is
+	 * declared as char*, not ulong*.
+	 */
+	p = (char*)defontdata;
+	n = (ulong)p & 3;
+	if(n != 0){
+		memmove(p+(4-n), p, sizeofdefont-n);
+		p += 4-n;
+	}
+	ld = atoi(p+0*12);
+	r.min.x = atoi(p+1*12);
+	r.min.y = atoi(p+2*12);
+	r.max.x = atoi(p+3*12);
+	r.max.y = atoi(p+4*12);
+
+	md = mallocz(sizeof(Memdata), 1);
+	if(md == nil)
+		return nil;
+	
+	p += 5*12;
+
+	md->base = nil;		/* so freememimage doesn't free p */
+	md->bdata = (uchar*)p;	/* ick */
+	md->ref = 1;
+	md->allocd = 1;		/* so freememimage does free md */
+
+	i = allocmemimaged(r, drawld2chan[ld], md, nil);
+	if(i == nil){
+		free(md);
+		return nil;
+	}
+
+	hdr = p+Dy(r)*i->width*sizeof(ulong);
+	n = atoi(hdr);
+	p = hdr+3*12;
+	fc = malloc(sizeof(Fontchar)*(n+1));
+	if(fc == 0){
+		freememimage(i);
+		return 0;
+	}
+	_unpackinfo(fc, (uchar*)p, n);
+	f = allocmemsubfont("*default*", n, atoi(hdr+12), atoi(hdr+24), fc, i);
+	if(f == 0){
+		freememimage(i);
+		free(fc);
+		return 0;
+	}
+	return f;
+}
--- /dev/null
+++ b/libmemdraw/draw.c
@@ -1,0 +1,2499 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int drawdebug;
+static int	tablesbuilt;
+
+/* 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)
+
+/*
+ * for 0 ≤ x ≤ 255*255, (x*0x0101+0x100)>>16 is a perfect approximation.
+ * for 0 ≤ x < (1<<16), x/255 = ((x+1)*0x0101)>>16 is a perfect approximation.
+ * the last one is perfect for all up to 1<<16, avoids a multiply, but requires a rathole.
+ */
+/* #define DIV255(x) (((x)*257+256)>>16)  */
+#define DIV255(x) ((((x)+1)*257)>>16)
+/* #define DIV255(x) (tmp=(x)+1, (tmp+(tmp>>8))>>8) */
+
+#define MUL(x, y, t)	(t = (x)*(y)+128, (t+(t>>8))>>8)
+#define MASK13	0xFF00FF00
+#define MASK02	0x00FF00FF
+#define MUL13(a, x, t)		(t = (a)*(((x)&MASK13)>>8)+128, ((t+((t>>8)&MASK02))>>8)&MASK02)
+#define MUL02(a, x, t)		(t = (a)*(((x)&MASK02)>>0)+128, ((t+((t>>8)&MASK02))>>8)&MASK02)
+#define MUL0123(a, x, s, t)	((MUL13(a, x, s)<<8)|MUL02(a, x, t))
+
+#define MUL2(u, v, x, y)	(t = (u)*(v)+(x)*(y)+256, (t+(t>>8))>>8)
+
+static void mktables(void);
+typedef int Subdraw(Memdrawparam*);
+static Subdraw chardraw, alphadraw, memoptdraw;
+
+static Memimage*	memones;
+static Memimage*	memzeros;
+Memimage *memwhite;
+Memimage *memblack;
+Memimage *memtransparent;
+Memimage *memopaque;
+
+int	_ifmt(Fmt*);
+
+void
+_memimageinit(void)
+{
+	static int didinit = 0;
+
+	if(didinit)
+		return;
+
+	didinit = 1;
+
+	mktables();
+	_memmkcmap();
+
+	fmtinstall('R', Rfmt); 
+	fmtinstall('P', Pfmt);
+	fmtinstall('b', _ifmt);
+
+	memones = allocmemimage(Rect(0,0,1,1), GREY1);
+	memones->flags |= Frepl;
+	memones->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+	*byteaddr(memones, ZP) = ~0;
+
+	memzeros = allocmemimage(Rect(0,0,1,1), GREY1);
+	memzeros->flags |= Frepl;
+	memzeros->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+	*byteaddr(memzeros, ZP) = 0;
+
+	if(memones == nil || memzeros == nil)
+		assert(0 /*cannot initialize memimage library */);	/* RSC BUG */
+
+	memwhite = memones;
+	memblack = memzeros;
+	memopaque = memones;
+	memtransparent = memzeros;
+}
+
+ulong _imgtorgba(Memimage*, ulong);
+ulong _rgbatoimg(Memimage*, ulong);
+ulong _pixelbits(Memimage*, Point);
+
+#define DBG if(0)
+static Memdrawparam par;
+
+Memdrawparam*
+_memimagedrawsetup(Memimage *dst, Rectangle r, Memimage *src, Point p0, Memimage *mask, Point p1, int op)
+{
+	static int n = 0;
+
+	if(mask == nil)
+		mask = memopaque;
+
+DBG	print("memimagedraw %p/%luX %R @ %p %p/%luX %P %p/%luX %P... ", dst, dst->chan, r, dst->data->bdata, src, src->chan, p0, mask, mask->chan, p1);
+
+	if(drawclip(dst, &r, src, &p0, mask, &p1, &par.sr, &par.mr) == 0){
+//		if(drawdebug)
+//			iprint("empty clipped rectangle\n");
+		return nil;
+	}
+
+	if(op < Clear || op > SoverD){
+//		if(drawdebug)
+//			iprint("op out of range: %d\n", op);
+		return nil;
+	}
+
+	par.op = op;
+	par.dst = dst;
+	par.r = r;
+	par.src = src;
+	/* par.sr set by drawclip */
+	par.mask = mask;
+	/* par.mr set by drawclip */
+
+	par.state = 0;
+	if(src->flags&Frepl){
+		par.state |= Replsrc;
+		if(Dx(src->r)==1 && Dy(src->r)==1){
+			par.sval = _pixelbits(src, src->r.min);
+			par.state |= Simplesrc;
+			par.srgba = _imgtorgba(src, par.sval);
+			par.sdval = _rgbatoimg(dst, par.srgba);
+			if((par.srgba&0xFF) == 0 && (op&DoutS)){
+//				if (drawdebug) iprint("fill with transparent source\n");
+				return nil;	/* no-op successfully handled */
+			}
+		}
+	}
+
+	if(mask->flags & Frepl){
+		par.state |= Replmask;
+		if(Dx(mask->r)==1 && Dy(mask->r)==1){
+			par.mval = _pixelbits(mask, mask->r.min);
+			if(par.mval == 0 && (op&DoutS)){
+//				if(drawdebug) iprint("fill with zero mask\n");
+				return nil;	/* no-op successfully handled */
+			}
+			par.state |= Simplemask;
+			if(par.mval == ~0)
+				par.state |= Fullmask;
+			par.mrgba = _imgtorgba(mask, par.mval);
+		}
+	}
+
+//	if(drawdebug)
+//		iprint("dr %R sr %R mr %R...", r, par.sr, par.mr);
+DBG print("draw dr %R sr %R mr %R %lux\n", r, par.sr, par.mr, par.state);
+
+	return &par;
+}
+
+void
+_memimagedraw(Memdrawparam *par)
+{
+	if (par == nil)
+		return;
+
+	/*
+	 * Now that we've clipped the parameters down to be consistent, we 
+	 * simply try sub-drawing routines in order until we find one that was able
+	 * to handle us.  If the sub-drawing routine returns zero, it means it was
+	 * unable to satisfy the request, so we do not return.
+	 */
+
+	/*
+	 * Hardware support.  Each video driver provides this function,
+	 * which checks to see if there is anything it can help with.
+	 * There could be an if around this checking to see if dst is in video memory.
+	 */
+DBG print("test hwdraw\n");
+	if(hwdraw(par)){
+//if(drawdebug) iprint("hw handled\n");
+DBG print("hwdraw handled\n");
+		return;
+	}
+	/*
+	 * Optimizations using memmove and memset.
+	 */
+DBG print("test memoptdraw\n");
+	if(memoptdraw(par)){
+//if(drawdebug) iprint("memopt handled\n");
+DBG print("memopt handled\n");
+		return;
+	}
+
+	/*
+	 * Character drawing.
+	 * Solid source color being painted through a boolean mask onto a high res image.
+	 */
+DBG print("test chardraw\n");
+	if(chardraw(par)){
+//if(drawdebug) iprint("chardraw handled\n");
+DBG print("chardraw handled\n");
+		return;
+	}
+
+	/*
+	 * General calculation-laden case that does alpha for each pixel.
+	 */
+DBG print("do alphadraw\n");
+	alphadraw(par);
+//if(drawdebug) iprint("alphadraw handled\n");
+DBG print("alphadraw handled\n");
+}
+#undef DBG
+
+/*
+ * Clip the destination rectangle further based on the properties of the 
+ * source and mask rectangles.  Once the destination rectangle is properly
+ * clipped, adjust the source and mask rectangles to be the same size.
+ * Then if source or mask is replicated, move its clipped rectangle
+ * so that its minimum point falls within the repl rectangle.
+ *
+ * Return zero if the final rectangle is null.
+ */
+int
+drawclip(Memimage *dst, Rectangle *r, Memimage *src, Point *p0, Memimage *mask, Point *p1, Rectangle *sr, Rectangle *mr)
+{
+	Point rmin, delta;
+	int splitcoords;
+	Rectangle omr;
+
+	if(r->min.x>=r->max.x || r->min.y>=r->max.y)
+		return 0;
+	splitcoords = (p0->x!=p1->x) || (p0->y!=p1->y);
+	/* clip to destination */
+	rmin = r->min;
+	if(!rectclip(r, dst->r) || !rectclip(r, dst->clipr))
+		return 0;
+	/* move mask point */
+	p1->x += r->min.x-rmin.x;
+	p1->y += r->min.y-rmin.y;
+	/* move source point */
+	p0->x += r->min.x-rmin.x;
+	p0->y += r->min.y-rmin.y;
+	/* map destination rectangle into source */
+	sr->min = *p0;
+	sr->max.x = p0->x+Dx(*r);
+	sr->max.y = p0->y+Dy(*r);
+	/* sr is r in source coordinates; clip to source */
+	if(!(src->flags&Frepl) && !rectclip(sr, src->r))
+		return 0;
+	if(!rectclip(sr, src->clipr))
+		return 0;
+	/* compute and clip rectangle in mask */
+	if(splitcoords){
+		/* move mask point with source */
+		p1->x += sr->min.x-p0->x;
+		p1->y += sr->min.y-p0->y;
+		mr->min = *p1;
+		mr->max.x = p1->x+Dx(*sr);
+		mr->max.y = p1->y+Dy(*sr);
+		omr = *mr;
+		/* mr is now rectangle in mask; clip it */
+		if(!(mask->flags&Frepl) && !rectclip(mr, mask->r))
+			return 0;
+		if(!rectclip(mr, mask->clipr))
+			return 0;
+		/* reflect any clips back to source */
+		sr->min.x += mr->min.x-omr.min.x;
+		sr->min.y += mr->min.y-omr.min.y;
+		sr->max.x += mr->max.x-omr.max.x;
+		sr->max.y += mr->max.y-omr.max.y;
+		*p1 = mr->min;
+	}else{
+		if(!(mask->flags&Frepl) && !rectclip(sr, mask->r))
+			return 0;
+		if(!rectclip(sr, mask->clipr))
+			return 0;
+		*p1 = sr->min;
+	}
+
+	/* move source clipping back to destination */
+	delta.x = r->min.x - p0->x;
+	delta.y = r->min.y - p0->y;
+	r->min.x = sr->min.x + delta.x;
+	r->min.y = sr->min.y + delta.y;
+	r->max.x = sr->max.x + delta.x;
+	r->max.y = sr->max.y + delta.y;
+
+	/* move source rectangle so sr->min is in src->r */
+	if(src->flags&Frepl) {
+		delta.x = drawreplxy(src->r.min.x, src->r.max.x, sr->min.x) - sr->min.x;
+		delta.y = drawreplxy(src->r.min.y, src->r.max.y, sr->min.y) - sr->min.y;
+		sr->min.x += delta.x;
+		sr->min.y += delta.y;
+		sr->max.x += delta.x;
+		sr->max.y += delta.y;
+	}
+	*p0 = sr->min;
+
+	/* move mask point so it is in mask->r */
+	*p1 = drawrepl(mask->r, *p1);
+	mr->min = *p1;
+	mr->max.x = p1->x+Dx(*sr);
+	mr->max.y = p1->y+Dy(*sr);
+
+	assert(Dx(*sr) == Dx(*mr) && Dx(*mr) == Dx(*r));
+	assert(Dy(*sr) == Dy(*mr) && Dy(*mr) == Dy(*r));
+	assert(ptinrect(*p0, src->r));
+	assert(ptinrect(*p1, mask->r));
+	assert(ptinrect(r->min, dst->r));
+
+	return 1;
+}
+
+/*
+ * Conversion tables.
+ */
+static uchar replbit[1+8][256];		/* replbit[x][y] is the replication of the x-bit quantity y to 8-bit depth */
+static uchar conv18[256][8];		/* conv18[x][y] is the yth pixel in the depth-1 pixel x */
+static uchar conv28[256][4];		/* ... */
+static uchar conv48[256][2];
+
+/*
+ * bitmap of how to replicate n bits to fill 8, for 1 ≤ n ≤ 8.
+ * the X's are where to put the bottom (ones) bit of the n-bit pattern.
+ * only the top 8 bits of the result are actually used.
+ * (the lower 8 bits are needed to get bits in the right place
+ * when n is not a divisor of 8.)
+ *
+ * Should check to see if its easier to just refer to replmul than
+ * use the precomputed values in replbit.  On PCs it may well
+ * be; on machines with slow multiply instructions it probably isn't.
+ */
+#define a ((((((((((((((((0
+#define X *2+1)
+#define _ *2)
+static int replmul[1+8] = {
+	0,
+	a X X X X X X X X X X X X X X X X,
+	a _ X _ X _ X _ X _ X _ X _ X _ X,
+	a _ _ X _ _ X _ _ X _ _ X _ _ X _,
+	a _ _ _ X _ _ _ X _ _ _ X _ _ _ X,
+	a _ _ _ _ X _ _ _ _ X _ _ _ _ X _,
+	a _ _ _ _ _ X _ _ _ _ _ X _ _ _ _, 
+	a _ _ _ _ _ _ X _ _ _ _ _ _ X _ _,
+	a _ _ _ _ _ _ _ X _ _ _ _ _ _ _ X,
+};
+#undef a
+#undef X
+#undef _
+
+static void
+mktables(void)
+{
+	int i, j, mask, sh, small;
+		
+	if(tablesbuilt)
+		return;
+
+	fmtinstall('R', Rfmt);
+	fmtinstall('P', Pfmt);
+	tablesbuilt = 1;
+
+	/* bit replication up to 8 bits */
+	for(i=0; i<256; i++){
+		for(j=0; j<=8; j++){	/* j <= 8 [sic] */
+			small = i & ((1<<j)-1);
+			replbit[j][i] = (small*replmul[j])>>8;
+		}
+	}
+
+	/* bit unpacking up to 8 bits, only powers of 2 */
+	for(i=0; i<256; i++){
+		for(j=0, sh=7, mask=1; j<8; j++, sh--)
+			conv18[i][j] = replbit[1][(i>>sh)&mask];
+
+		for(j=0, sh=6, mask=3; j<4; j++, sh-=2)
+			conv28[i][j] = replbit[2][(i>>sh)&mask];
+
+		for(j=0, sh=4, mask=15; j<2; j++, sh-=4)
+			conv48[i][j] = replbit[4][(i>>sh)&mask];
+	}
+}
+
+static uchar ones = 0xff;
+
+/*
+ * General alpha drawing case.  Can handle anything.
+ */
+typedef struct	Buffer	Buffer;
+struct Buffer {
+	/* used by most routines */
+	uchar	*red;
+	uchar	*grn;
+	uchar	*blu;
+	uchar	*alpha;
+	uchar	*grey;
+	ulong	*rgba;
+	int	delta;	/* number of bytes to add to pointer to get next pixel to the right */
+
+	/* used by boolcalc* for mask data */
+	uchar	*m;		/* ptr to mask data r.min byte; like p->bytermin */
+	int		mskip;	/* no. of left bits to skip in *m */
+	uchar	*bm;		/* ptr to mask data img->r.min byte; like p->bytey0s */
+	int		bmskip;	/* no. of left bits to skip in *bm */
+	uchar	*em;		/* ptr to mask data img->r.max.x byte; like p->bytey0e */
+	int		emskip;	/* no. of right bits to skip in *em */
+};
+
+typedef struct	Param	Param;
+typedef Buffer	Readfn(Param*, uchar*, int);
+typedef void	Writefn(Param*, uchar*, Buffer);
+typedef Buffer	Calcfn(Buffer, Buffer, Buffer, int, int, int);
+
+enum {
+	MAXBCACHE = 16
+};
+
+/* giant rathole to customize functions with */
+struct Param {
+	Readfn	*replcall;
+	Readfn	*greymaskcall;	
+	Readfn	*convreadcall;
+	Writefn	*convwritecall;
+
+	Memimage *img;
+	Rectangle	r;
+	int	dx;	/* of r */
+	int	needbuf;
+	int	convgrey;
+	int	alphaonly;
+
+	uchar	*bytey0s;		/* byteaddr(Pt(img->r.min.x, img->r.min.y)) */
+	uchar	*bytermin;	/* byteaddr(Pt(r.min.x, img->r.min.y)) */
+	uchar	*bytey0e;		/* byteaddr(Pt(img->r.max.x, img->r.min.y)) */
+	int		bwidth;
+
+	int	replcache;	/* if set, cache buffers */
+	Buffer	bcache[MAXBCACHE];
+	ulong	bfilled;
+	uchar	*bufbase;
+	int	bufoff;
+	int	bufdelta;
+
+	int	dir;
+
+	int	convbufoff;
+	uchar	*convbuf;
+	Param	*convdpar;
+	int	convdx;
+};
+
+static uchar *drawbuf;
+static int	ndrawbuf;
+static int	mdrawbuf;
+static Param spar, mpar, dpar;	/* easier on the stacks */
+static Readfn	greymaskread, replread, readptr;
+static Writefn	nullwrite;
+static Calcfn	alphacalc0, alphacalc14, alphacalc2810, alphacalc3679, alphacalc5, alphacalc11, alphacalcS;
+static Calcfn	boolcalc14, boolcalc236789, boolcalc1011;
+
+static Readfn*	readfn(Memimage*);
+static Readfn*	readalphafn(Memimage*);
+static Writefn*	writefn(Memimage*);
+
+static Calcfn*	boolcopyfn(Memimage*, Memimage*);
+static Readfn*	convfn(Memimage*, Param*, Memimage*, Param*);
+static Readfn*	ptrfn(Memimage*);
+
+static Calcfn *alphacalc[Ncomp] = 
+{
+	alphacalc0,		/* Clear */
+	alphacalc14,		/* DoutS */
+	alphacalc2810,		/* SoutD */
+	alphacalc3679,		/* DxorS */
+	alphacalc14,		/* DinS */
+	alphacalc5,		/* D */
+	alphacalc3679,		/* DatopS */
+	alphacalc3679,		/* DoverS */
+	alphacalc2810,		/* SinD */
+	alphacalc3679,		/* SatopD */
+	alphacalc2810,		/* S */
+	alphacalc11,		/* SoverD */
+};
+
+static Calcfn *boolcalc[Ncomp] =
+{
+	alphacalc0,		/* Clear */
+	boolcalc14,		/* DoutS */
+	boolcalc236789,		/* SoutD */
+	boolcalc236789,		/* DxorS */
+	boolcalc14,		/* DinS */
+	alphacalc5,		/* D */
+	boolcalc236789,		/* DatopS */
+	boolcalc236789,		/* DoverS */
+	boolcalc236789,		/* SinD */
+	boolcalc236789,		/* SatopD */
+	boolcalc1011,		/* S */
+	boolcalc1011,		/* SoverD */
+};
+
+static int
+allocdrawbuf(void)
+{
+	uchar *p;
+
+	if(ndrawbuf > mdrawbuf){
+		p = realloc(drawbuf, ndrawbuf);
+		if(p == nil){
+			werrstr("memimagedraw out of memory");
+			return -1;
+		}
+		drawbuf = p;
+		mdrawbuf = ndrawbuf;
+	}
+	return 0;
+}
+
+static Param
+getparam(Memimage *img, Rectangle r, int convgrey, int needbuf)
+{
+	Param p;
+	int nbuf;
+
+	memset(&p, 0, sizeof p);
+
+	p.img = img;
+	p.r = r;
+	p.dx = Dx(r);
+	p.needbuf = needbuf;
+	p.convgrey = convgrey;
+
+	assert(img->r.min.x <= r.min.x && r.min.x < img->r.max.x);
+
+	p.bytey0s = byteaddr(img, Pt(img->r.min.x, img->r.min.y));
+	p.bytermin = byteaddr(img, Pt(r.min.x, img->r.min.y));
+	p.bytey0e = byteaddr(img, Pt(img->r.max.x, img->r.min.y));
+	p.bwidth = sizeof(ulong)*img->width;
+
+	assert(p.bytey0s <= p.bytermin && p.bytermin <= p.bytey0e);
+
+	if(p.r.min.x == p.img->r.min.x)
+		assert(p.bytermin == p.bytey0s);
+
+	nbuf = 1;
+	if((img->flags&Frepl) && Dy(img->r) <= MAXBCACHE && Dy(img->r) < Dy(r)){
+		p.replcache = 1;
+		nbuf = Dy(img->r);
+	}
+	p.bufdelta = 4*p.dx;
+	p.bufoff = ndrawbuf;
+	ndrawbuf += p.bufdelta*nbuf;
+
+	return p;
+}
+
+static void
+clipy(Memimage *img, int *y)
+{
+	int dy;
+
+	dy = Dy(img->r);
+	if(*y == dy)
+		*y = 0;
+	else if(*y == -1)
+		*y = dy-1;
+	assert(0 <= *y && *y < dy);
+}
+
+static void
+dumpbuf(char *s, Buffer b, int n)
+{
+	int i;
+	uchar *p;
+	
+	print("%s", s);
+	for(i=0; i<n; i++){
+		print(" ");
+		if(p=b.grey){
+			print(" k%.2uX", *p);
+			b.grey += b.delta;
+		}else{	
+			if(p=b.red){
+				print(" r%.2uX", *p);
+				b.red += b.delta;
+			}
+			if(p=b.grn){
+				print(" g%.2uX", *p);
+				b.grn += b.delta;
+			}
+			if(p=b.blu){
+				print(" b%.2uX", *p);
+				b.blu += b.delta;
+			}
+		}
+		if((p=b.alpha) != &ones){
+			print(" α%.2uX", *p);
+			b.alpha += b.delta;
+		}
+	}
+	print("\n");
+}
+
+/*
+ * For each scan line, we expand the pixels from source, mask, and destination
+ * into byte-aligned red, green, blue, alpha, and grey channels.  If buffering is not
+ * needed and the channels were already byte-aligned (grey8, rgb24, rgba32, rgb32),
+ * the readers need not copy the data: they can simply return pointers to the data.
+ * If the destination image is grey and the source is not, it is converted using the NTSC
+ * formula.
+ *
+ * Once we have all the channels, we call either rgbcalc or greycalc, depending on 
+ * whether the destination image is color.  This is allowed to overwrite the dst buffer (perhaps
+ * the actual data, perhaps a copy) with its result.  It should only overwrite the dst buffer
+ * with the same format (i.e. red bytes with red bytes, etc.)  A new buffer is returned from
+ * the calculator, and that buffer is passed to a function to write it to the destination.
+ * If the buffer is already pointing at the destination, the writing function is a no-op.
+ */
+#define DBG if(0)
+static int
+alphadraw(Memdrawparam *par)
+{
+	int isgrey, starty, endy, op;
+	int needbuf, dsty, srcy, masky;
+	int y, dir, dx, dy;
+	Buffer bsrc, bdst, bmask;
+	Readfn *rdsrc, *rdmask, *rddst;
+	Calcfn *calc;
+	Writefn *wrdst;
+	Memimage *src, *mask, *dst;
+	Rectangle r, sr, mr;
+
+	r = par->r;
+	dx = Dx(r);
+	dy = Dy(r);
+
+	ndrawbuf = 0;
+
+	src = par->src;
+	mask = par->mask;	
+	dst = par->dst;
+	sr = par->sr;
+	mr = par->mr;
+	op = par->op;
+
+	isgrey = dst->flags&Fgrey;
+
+	/*
+	 * Buffering when src and dst are the same bitmap is sufficient but not 
+	 * necessary.  There are stronger conditions we could use.  We could
+	 * check to see if the rectangles intersect, and if simply moving in the
+	 * correct y direction can avoid the need to buffer.
+	 */
+	needbuf = (src->data == dst->data);
+
+	spar = getparam(src, sr, isgrey, needbuf);
+	dpar = getparam(dst, r, isgrey, needbuf);
+	mpar = getparam(mask, mr, 0, needbuf);
+
+	dir = (needbuf && byteaddr(dst, r.min) > byteaddr(src, sr.min)) ? -1 : 1;
+	spar.dir = mpar.dir = dpar.dir = dir;
+
+	/*
+	 * If the mask is purely boolean, we can convert from src to dst format
+	 * when we read src, and then just copy it to dst where the mask tells us to.
+	 * This requires a boolean (1-bit grey) mask and lack of a source alpha channel.
+	 *
+	 * The computation is accomplished by assigning the function pointers as follows:
+	 *	rdsrc - read and convert source into dst format in a buffer
+	 * 	rdmask - convert mask to bytes, set pointer to it
+	 * 	rddst - fill with pointer to real dst data, but do no reads
+	 *	calc - copy src onto dst when mask says to.
+	 *	wrdst - do nothing
+	 * This is slightly sleazy, since things aren't doing exactly what their names say,
+	 * but it avoids a fair amount of code duplication to make this a case here
+	 * rather than have a separate booldraw.
+	 */
+//if(drawdebug) iprint("flag %lud mchan %lux=?%x dd %d\n", src->flags&Falpha, mask->chan, GREY1, dst->depth);
+	if(!(src->flags&Falpha) && mask->chan == GREY1 && dst->depth >= 8 && op == SoverD){
+//if(drawdebug) iprint("boolcopy...");
+		rdsrc = convfn(dst, &dpar, src, &spar);
+		rddst = readptr;
+		rdmask = readfn(mask);
+		calc = boolcopyfn(dst, mask);
+		wrdst = nullwrite;
+	}else{
+		/* usual alphadraw parameter fetching */
+		rdsrc = readfn(src);
+		rddst = readfn(dst);
+		wrdst = writefn(dst);
+		calc = alphacalc[op];
+
+		/*
+		 * If there is no alpha channel, we'll ask for a grey channel
+		 * and pretend it is the alpha.
+		 */
+		if(mask->flags&Falpha){
+			rdmask = readalphafn(mask);
+			mpar.alphaonly = 1;
+		}else{
+			mpar.greymaskcall = readfn(mask);
+			mpar.convgrey = 1;
+			rdmask = greymaskread;
+
+			/*
+			 * Should really be above, but then boolcopyfns would have
+			 * to deal with bit alignment, and I haven't written that.
+			 *
+			 * This is a common case for things like ellipse drawing.
+			 * When there's no alpha involved and the mask is boolean,
+			 * we can avoid all the division and multiplication.
+			 */
+			if(mask->chan == GREY1 && !(src->flags&Falpha))
+				calc = boolcalc[op];
+			else if(op == SoverD && !(src->flags&Falpha))
+				calc = alphacalcS;
+		}
+	}
+
+	/*
+	 * If the image has a small enough repl rectangle,
+	 * we can just read each line once and cache them.
+	 */
+	if(spar.replcache){
+		spar.replcall = rdsrc;
+		rdsrc = replread;
+	}
+	if(mpar.replcache){
+		mpar.replcall = rdmask;
+		rdmask = replread;
+	}
+
+	if(allocdrawbuf() < 0)
+		return 0;
+
+	/*
+	 * Before we were saving only offsets from drawbuf in the parameter
+	 * structures; now that drawbuf has been grown to accomodate us,
+	 * we can fill in the pointers.
+	 */
+	spar.bufbase = drawbuf+spar.bufoff;
+	mpar.bufbase = drawbuf+mpar.bufoff;
+	dpar.bufbase = drawbuf+dpar.bufoff;
+	spar.convbuf = drawbuf+spar.convbufoff;
+
+	if(dir == 1){
+		starty = 0;
+		endy = dy;
+	}else{
+		starty = dy-1;
+		endy = -1;
+	}
+
+	/*
+	 * srcy, masky, and dsty are offsets from the top of their
+	 * respective Rectangles.  they need to be contained within
+	 * the rectangles, so clipy can keep them there without division.
+ 	 */
+	srcy = (starty + sr.min.y - src->r.min.y)%Dy(src->r);
+	masky = (starty + mr.min.y - mask->r.min.y)%Dy(mask->r);
+	dsty = starty + r.min.y - dst->r.min.y;
+
+	assert(0 <= srcy && srcy < Dy(src->r));
+	assert(0 <= masky && masky < Dy(mask->r));
+	assert(0 <= dsty && dsty < Dy(dst->r));
+
+	for(y=starty; y!=endy; y+=dir, srcy+=dir, masky+=dir, dsty+=dir){
+		clipy(src, &srcy);
+		clipy(dst, &dsty);
+		clipy(mask, &masky);
+
+		bsrc = rdsrc(&spar, spar.bufbase, srcy);
+DBG print("[");
+		bmask = rdmask(&mpar, mpar.bufbase, masky);
+DBG print("]\n");
+		bdst = rddst(&dpar, dpar.bufbase, dsty);
+DBG		dumpbuf("src", bsrc, dx);
+DBG		dumpbuf("mask", bmask, dx);
+DBG		dumpbuf("dst", bdst, dx);
+		bdst = calc(bdst, bsrc, bmask, dx, isgrey, op);
+		wrdst(&dpar, dpar.bytermin+dsty*dpar.bwidth, bdst);
+	}
+
+	return 1;
+}
+#undef DBG
+
+static Buffer
+alphacalc0(Buffer bdst, Buffer b1, Buffer b2, int dx, int grey, int op)
+{
+	USED(grey);
+	USED(op);
+	memset(bdst.rgba, 0, dx*bdst.delta);
+	return bdst;
+}
+
+static Buffer
+alphacalc14(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fd, sadelta;
+	int i, sa, ma, q;
+	ulong s, t;
+
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		fd = MUL(sa, ma, t);
+		if(op == DoutS)
+			fd = 255-fd;
+
+		if(grey){
+			*bdst.grey = MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(fd, *bdst.rgba, s, t);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				continue;
+			}
+			*bdst.red = MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+static Buffer
+alphacalc2810(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fs, sadelta;
+	int i, ma, da, q;
+	ulong s, t;
+
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		if(op == SoutD)
+			da = 255-da;
+		fs = ma;
+		if(op != S)
+			fs = MUL(fs, da, t);
+
+		if(grey){
+			*bdst.grey = MUL(fs, *bsrc.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(fs, *bsrc.rgba, s, t);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bmask.alpha += bmask.delta;
+				bdst.alpha += bdst.delta;
+				continue;
+			}
+			*bdst.red = MUL(fs, *bsrc.red, t);
+			*bdst.grn = MUL(fs, *bsrc.grn, t);
+			*bdst.blu = MUL(fs, *bsrc.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(fs, *bsrc.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+static Buffer
+alphacalc3679(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fs, fd, sadelta;
+	int i, sa, ma, da, q;
+	ulong s, t, u, v;
+
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		if(op == SatopD)
+			fs = MUL(ma, da, t);
+		else
+			fs = MUL(ma, 255-da, t);
+		if(op == DoverS)
+			fd = 255;
+		else{
+			fd = MUL(sa, ma, t);
+			if(op != DatopS)
+				fd = 255-fd;
+		}
+
+		if(grey){
+			*bdst.grey = MUL(fs, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(fs, *bsrc.rgba, s, t)+MUL0123(fd, *bdst.rgba, u, v);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				bdst.alpha += bdst.delta;
+				continue;
+			}
+			*bdst.red = MUL(fs, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(fs, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(fs, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(fs, sa, s)+MUL(fd, da, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+static Buffer
+alphacalc5(Buffer bdst, Buffer b1, Buffer b2, int dx, int grey, int op)
+{
+	USED(dx);
+	USED(grey);
+	USED(op);
+	return bdst;
+}
+
+static Buffer
+alphacalc11(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fd, sadelta;
+	int i, sa, ma, q;
+	ulong s, t, u, v;
+
+	USED(op);
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		fd = 255-MUL(sa, ma, t);
+
+		if(grey){
+			*bdst.grey = MUL(ma, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(ma, *bsrc.rgba, s, t)+MUL0123(fd, *bdst.rgba, u, v);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				continue;
+			}
+			*bdst.red = MUL(ma, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(ma, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(ma, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(ma, sa, s)+MUL(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+/*
+not used yet
+source and mask alpha 1
+static Buffer
+alphacalcS0(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int i;
+
+	USED(op);
+	obdst = bdst;
+	if(bsrc.delta == bdst.delta){
+		memmove(bdst.rgba, bsrc.rgba, dx*bdst.delta);
+		return obdst;
+	}
+	for(i=0; i<dx; i++){
+		if(grey){
+			*bdst.grey = *bsrc.grey;
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			*bdst.red = *bsrc.red;
+			*bdst.grn = *bsrc.grn;
+			*bdst.blu = *bsrc.blu;
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = 255;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+*/
+
+/* source alpha 1 */
+static Buffer
+alphacalcS(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fd;
+	int i, ma;
+	ulong s, t;
+
+	USED(op);
+	obdst = bdst;
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		fd = 255-ma;
+
+		if(grey){
+			*bdst.grey = MUL(ma, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			*bdst.red = MUL(ma, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(ma, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(ma, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = ma+MUL(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+	}
+	return obdst;
+}
+
+static Buffer
+boolcalc14(Buffer bdst, Buffer b1, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int i, ma, zero;
+
+	obdst = bdst;
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		zero = ma ? op == DoutS : op == DinS;
+
+		if(grey){
+			if(zero)
+				*bdst.grey = 0;
+			bdst.grey += bdst.delta;
+		}else{
+			if(zero)
+				*bdst.red = *bdst.grn = *bdst.blu = 0;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		if(bdst.alpha != &ones){
+			if(zero)
+				*bdst.alpha = 0;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+
+static Buffer
+boolcalc236789(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fs, fd;
+	int i, ma, da, zero;
+	ulong s, t;
+
+	obdst = bdst;
+	zero = !(op&1);
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		fs = da;
+		if(op&2)
+			fs = 255-da;
+		fd = 0;
+		if(op&4)
+			fd = 255;
+
+		if(grey){
+			if(ma)
+				*bdst.grey = MUL(fs, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			else if(zero)
+				*bdst.grey = 0;
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(ma){
+				*bdst.red = MUL(fs, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+				*bdst.grn = MUL(fs, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+				*bdst.blu = MUL(fs, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			}
+			else if(zero)
+				*bdst.red = *bdst.grn = *bdst.blu = 0;
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		if(bdst.alpha != &ones){
+			if(ma)
+				*bdst.alpha = fs+MUL(fd, da, t);
+			else if(zero)
+				*bdst.alpha = 0;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+
+static Buffer
+boolcalc1011(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int i, ma, zero;
+
+	obdst = bdst;
+	zero = !(op&1);
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+
+		if(grey){
+			if(ma)
+				*bdst.grey = *bsrc.grey;
+			else if(zero)
+				*bdst.grey = 0;
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(ma){
+				*bdst.red = *bsrc.red;
+				*bdst.grn = *bsrc.grn;
+				*bdst.blu = *bsrc.blu;
+			}
+			else if(zero)
+				*bdst.red = *bdst.grn = *bdst.blu = 0;
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		if(bdst.alpha != &ones){
+			if(ma)
+				*bdst.alpha = 255;
+			else if(zero)
+				*bdst.alpha = 0;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+/*
+ * Replicated cached scan line read.  Call the function listed in the Param,
+ * but cache the result so that for replicated images we only do the work once.
+ */
+static Buffer
+replread(Param *p, uchar *s, int y)
+{
+	Buffer *b;
+
+	USED(s);
+	b = &p->bcache[y];
+	if((p->bfilled & (1<<y)) == 0){
+		p->bfilled |= 1<<y;
+		*b = p->replcall(p, p->bufbase+y*p->bufdelta, y);
+	}
+	return *b;
+}
+
+/*
+ * Alpha reading function that simply relabels the grey pointer.
+ */
+static Buffer
+greymaskread(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+
+	b = p->greymaskcall(p, buf, y);
+	b.alpha = b.grey;
+	return b;
+}
+
+#define DBG if(0)
+static Buffer
+readnbit(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+	Memimage *img;
+	uchar *repl, *r, *w, *ow, bits;
+	int i, n, sh, depth, x, dx, npack, nbits;
+
+	b.rgba = (ulong*)buf;
+	b.grey = w = buf;
+	b.red = b.blu = b.grn = w;
+	b.alpha = &ones;
+	b.delta = 1;
+
+	dx = p->dx;
+	img = p->img;
+	depth = img->depth;
+	repl = &replbit[depth][0];
+	npack = 8/depth;
+	sh = 8-depth;
+
+	/* copy from p->r.min.x until end of repl rectangle */
+	x = p->r.min.x;
+	n = dx;
+	if(n > p->img->r.max.x - x)
+		n = p->img->r.max.x - x;
+
+	r = p->bytermin + y*p->bwidth;
+DBG print("readnbit dx %d %p=%p+%d*%d, *r=%d fetch %d ", dx, r, p->bytermin, y, p->bwidth, *r, n);
+	bits = *r++;
+	nbits = 8;
+	if(i=x&(npack-1)){
+DBG print("throwaway %d...", i);
+		bits <<= depth*i;
+		nbits -= depth*i;
+	}
+	for(i=0; i<n; i++){
+		if(nbits == 0){
+DBG print("(%.2ux)...", *r);
+			bits = *r++;
+			nbits = 8;
+		}
+		*w++ = repl[bits>>sh];
+DBG print("bit %x...", repl[bits>>sh]);
+		bits <<= depth;
+		nbits -= depth;
+	}
+	dx -= n;
+	if(dx == 0)
+		return b;
+
+	assert(x+i == p->img->r.max.x);
+
+	/* copy from beginning of repl rectangle until where we were before. */
+	x = p->img->r.min.x;
+	n = dx;
+	if(n > p->r.min.x - x)
+		n = p->r.min.x - x;
+
+	r = p->bytey0s + y*p->bwidth;
+DBG print("x=%d r=%p...", x, r);
+	bits = *r++;
+	nbits = 8;
+	if(i=x&(npack-1)){
+		bits <<= depth*i;
+		nbits -= depth*i;
+	}
+DBG print("nbits=%d...", nbits);
+	for(i=0; i<n; i++){
+		if(nbits == 0){
+			bits = *r++;
+			nbits = 8;
+		}
+		*w++ = repl[bits>>sh];
+DBG print("bit %x...", repl[bits>>sh]);
+		bits <<= depth;
+		nbits -= depth;
+DBG print("bits %x nbits %d...", bits, nbits);
+	}
+	dx -= n;
+	if(dx == 0)
+		return b;
+
+	assert(dx > 0);
+	/* now we have exactly one full scan line: just replicate the buffer itself until we are done */
+	ow = buf;
+	while(dx--)
+		*w++ = *ow++;
+
+	return b;
+}
+#undef DBG
+
+#define DBG if(0)
+static void
+writenbit(Param *p, uchar *w, Buffer src)
+{
+	uchar *r;
+	ulong bits;
+	int i, sh, depth, npack, nbits, x, ex;
+
+	assert(src.grey != nil && src.delta == 1);
+
+	x = p->r.min.x;
+	ex = x+p->dx;
+	depth = p->img->depth;
+	npack = 8/depth;
+
+	i=x&(npack-1);
+	bits = i ? (*w >> (8-depth*i)) : 0;
+	nbits = depth*i;
+	sh = 8-depth;
+	r = src.grey;
+
+	for(; x<ex; x++){
+		bits <<= depth;
+DBG print(" %x", *r);
+		bits |= (*r++ >> sh);
+		nbits += depth;
+		if(nbits == 8){
+			*w++ = bits;
+			nbits = 0;
+		}
+	}
+
+	if(nbits){
+		sh = 8-nbits;
+		bits <<= sh;
+		bits |= *w & ((1<<sh)-1);
+		*w = bits;
+	}
+DBG print("\n");
+	return;
+}
+#undef DBG
+
+static Buffer
+readcmap(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+	int a, convgrey, copyalpha, dx, i, m;
+	uchar *q, *cmap, *begin, *end, *r, *w;
+
+	begin = p->bytey0s + y*p->bwidth;
+	r = p->bytermin + y*p->bwidth;
+	end = p->bytey0e + y*p->bwidth;
+	cmap = p->img->cmap->cmap2rgb;
+	convgrey = p->convgrey;
+	copyalpha = (p->img->flags&Falpha) ? 1 : 0;
+
+	w = buf;
+	dx = p->dx;
+	if(copyalpha){
+		b.alpha = buf++;
+		a = p->img->shift[CAlpha]/8;
+		m = p->img->shift[CMap]/8;
+		for(i=0; i<dx; i++){
+			*w++ = r[a];
+			q = cmap+r[m]*3;
+			r += 2;
+			if(r == end)
+				r = begin;
+			if(convgrey){
+				*w++ = RGB2K(q[0], q[1], q[2]);
+			}else{
+				*w++ = q[2];	/* blue */
+				*w++ = q[1];	/* green */
+				*w++ = q[0];	/* red */
+			}
+		}
+	}else{
+		b.alpha = &ones;
+		for(i=0; i<dx; i++){
+			q = cmap+*r++*3;
+			if(r == end)
+				r = begin;
+			if(convgrey){
+				*w++ = RGB2K(q[0], q[1], q[2]);
+			}else{
+				*w++ = q[2];	/* blue */
+				*w++ = q[1];	/* green */
+				*w++ = q[0];	/* red */
+			}
+		}
+	}
+
+	b.rgba = (ulong*)(buf-copyalpha);
+
+	if(convgrey){
+		b.grey = buf;
+		b.red = b.blu = b.grn = buf;
+		b.delta = 1+copyalpha;
+	}else{
+		b.blu = buf;
+		b.grn = buf+1;
+		b.red = buf+2;
+		b.grey = nil;
+		b.delta = 3+copyalpha;
+	}
+	return b;
+}
+
+static void
+writecmap(Param *p, uchar *w, Buffer src)
+{
+	uchar *cmap, *red, *grn, *blu;
+	int i, dx, delta;
+
+	cmap = p->img->cmap->rgb2cmap;
+	
+	delta = src.delta;
+	red= src.red;
+	grn = src.grn;
+	blu = src.blu;
+
+	dx = p->dx;
+	for(i=0; i<dx; i++, red+=delta, grn+=delta, blu+=delta)
+		*w++ = cmap[(*red>>4)*256+(*grn>>4)*16+(*blu>>4)];
+}
+
+#define DBG if(0)
+static Buffer
+readbyte(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+	Memimage *img;
+	int dx, isgrey, convgrey, alphaonly, copyalpha, i, nb;
+	uchar *begin, *end, *r, *w, *rrepl, *grepl, *brepl, *arepl, *krepl;
+	uchar ured, ugrn, ublu;
+	ulong u;
+
+	img = p->img;
+	begin = p->bytey0s + y*p->bwidth;
+	r = p->bytermin + y*p->bwidth;
+	end = p->bytey0e + y*p->bwidth;
+
+	w = buf;
+	dx = p->dx;
+	nb = img->depth/8;
+
+	convgrey = p->convgrey;	/* convert rgb to grey */
+	isgrey = img->flags&Fgrey;
+	alphaonly = p->alphaonly;
+	copyalpha = (img->flags&Falpha) ? 1 : 0;
+
+DBG print("copyalpha %d alphaonly %d convgrey %d isgrey %d\n", copyalpha, alphaonly, convgrey, isgrey);
+	/* if we can, avoid processing everything */
+	if(!(img->flags&Frepl) && !convgrey && (img->flags&Fbytes)){
+		memset(&b, 0, sizeof b);
+		if(p->needbuf){
+			memmove(buf, r, dx*nb);
+			r = buf;
+		}
+		b.rgba = (ulong*)r;
+		if(copyalpha)
+			b.alpha = r+img->shift[CAlpha]/8;
+		else
+			b.alpha = &ones;
+		if(isgrey){
+			b.grey = r+img->shift[CGrey]/8;
+			b.red = b.grn = b.blu = b.grey;
+		}else{
+			b.red = r+img->shift[CRed]/8;
+			b.grn = r+img->shift[CGreen]/8;
+			b.blu = r+img->shift[CBlue]/8;
+		}
+		b.delta = nb;
+		return b;
+	}
+
+DBG print("2\n");
+	rrepl = replbit[img->nbits[CRed]];
+	grepl = replbit[img->nbits[CGreen]];
+	brepl = replbit[img->nbits[CBlue]];
+	arepl = replbit[img->nbits[CAlpha]];
+	krepl = replbit[img->nbits[CGrey]];
+
+	for(i=0; i<dx; i++){
+		u = r[0] | (r[1]<<8) | (r[2]<<16) | (r[3]<<24);
+		if(copyalpha) {
+			*w++ = arepl[(u>>img->shift[CAlpha]) & img->mask[CAlpha]];
+DBG print("a %x\n", w[-1]);
+		}
+
+		if(isgrey)
+			*w++ = krepl[(u >> img->shift[CGrey]) & img->mask[CGrey]];
+		else if(!alphaonly){
+			ured = rrepl[(u >> img->shift[CRed]) & img->mask[CRed]];
+			ugrn = grepl[(u >> img->shift[CGreen]) & img->mask[CGreen]];
+			ublu = brepl[(u >> img->shift[CBlue]) & img->mask[CBlue]];
+			if(convgrey){
+DBG print("g %x %x %x\n", ured, ugrn, ublu);
+				*w++ = RGB2K(ured, ugrn, ublu);
+DBG print("%x\n", w[-1]);
+			}else{
+				*w++ = brepl[(u >> img->shift[CBlue]) & img->mask[CBlue]];
+				*w++ = grepl[(u >> img->shift[CGreen]) & img->mask[CGreen]];
+				*w++ = rrepl[(u >> img->shift[CRed]) & img->mask[CRed]];
+			}
+		}
+		r += nb;
+		if(r == end)
+			r = begin;
+	}
+	
+	b.alpha = copyalpha ? buf : &ones;
+	b.rgba = (ulong*)buf;
+	if(alphaonly){
+		b.red = b.grn = b.blu = b.grey = nil;
+		if(!copyalpha)
+			b.rgba = nil;
+		b.delta = 1;
+	}else if(isgrey || convgrey){
+		b.grey = buf+copyalpha;
+		b.red = b.grn = b.blu = buf+copyalpha;
+		b.delta = copyalpha+1;
+DBG print("alpha %x grey %x\n", b.alpha ? *b.alpha : 0xFF, *b.grey);
+	}else{
+		b.blu = buf+copyalpha;
+		b.grn = buf+copyalpha+1;
+		b.grey = nil;
+		b.red = buf+copyalpha+2;
+		b.delta = copyalpha+3;
+	}
+	return b;
+}
+#undef DBG
+
+#define DBG if(0)
+static void
+writebyte(Param *p, uchar *w, Buffer src)
+{
+	Memimage *img;
+	int i, isalpha, isgrey, nb, delta, dx, adelta;
+	uchar ff, *red, *grn, *blu, *grey, *alpha;
+	ulong u, mask;
+
+	img = p->img;
+
+	red = src.red;
+	grn = src.grn;
+	blu = src.blu;
+	alpha = src.alpha;
+	delta = src.delta;
+	grey = src.grey;
+	dx = p->dx;
+
+	nb = img->depth/8;
+	mask = (nb==4) ? 0 : ~((1<<img->depth)-1);
+
+	isalpha = img->flags&Falpha;
+	isgrey = img->flags&Fgrey;
+	adelta = src.delta;
+
+	if(isalpha && (alpha == nil || alpha == &ones)){
+		ff = 0xFF;
+		alpha = &ff;
+		adelta = 0;
+	}
+
+	for(i=0; i<dx; i++){
+		u = w[0] | (w[1]<<8) | (w[2]<<16) | (w[3]<<24);
+DBG print("u %.8lux...", u);
+		u &= mask;
+DBG print("&mask %.8lux...", u);
+		if(isgrey){
+			u |= ((*grey >> (8-img->nbits[CGrey])) & img->mask[CGrey]) << img->shift[CGrey];
+DBG print("|grey %.8lux...", u);
+			grey += delta;
+		}else{
+			u |= ((*red >> (8-img->nbits[CRed])) & img->mask[CRed]) << img->shift[CRed];
+			u |= ((*grn >> (8-img->nbits[CGreen])) & img->mask[CGreen]) << img->shift[CGreen];
+			u |= ((*blu >> (8-img->nbits[CBlue])) & img->mask[CBlue]) << img->shift[CBlue];
+			red += delta;
+			grn += delta;
+			blu += delta;
+DBG print("|rgb %.8lux...", u);
+		}
+
+		if(isalpha){
+			u |= ((*alpha >> (8-img->nbits[CAlpha])) & img->mask[CAlpha]) << img->shift[CAlpha];
+			alpha += adelta;
+DBG print("|alpha %.8lux...", u);
+		}
+
+		w[0] = u;
+		w[1] = u>>8;
+		w[2] = u>>16;
+		w[3] = u>>24;
+		w += nb;
+	}
+}
+#undef DBG
+
+static Readfn*
+readfn(Memimage *img)
+{
+	if(img->depth < 8)
+		return readnbit;
+	if(img->nbits[CMap] == 8)
+		return readcmap;
+	return readbyte;
+}
+
+static Readfn*
+readalphafn(Memimage *m)
+{
+	USED(m);
+	return readbyte;
+}
+
+static Writefn*
+writefn(Memimage *img)
+{
+	if(img->depth < 8)
+		return writenbit;
+	if(img->chan == CMAP8)
+		return writecmap;
+	return writebyte;
+}
+
+static void
+nullwrite(Param *p, uchar *s, Buffer b)
+{
+	USED(p);
+	USED(s);
+}
+
+static Buffer
+readptr(Param *p, uchar *s, int y)
+{
+	Buffer b;
+	uchar *q;
+
+	USED(s);
+	q = p->bytermin + y*p->bwidth;
+	b.red = q;	/* ptr to data */
+	b.grn = b.blu = b.grey = b.alpha = nil;
+	b.rgba = (ulong*)q;
+	b.delta = p->img->depth/8;
+	return b;
+}
+
+static Buffer
+boolmemmove(Buffer bdst, Buffer bsrc, Buffer b1, int dx, int i, int o)
+{
+	USED(i);
+	USED(o);
+	memmove(bdst.red, bsrc.red, dx*bdst.delta);
+	return bdst;
+}
+
+static Buffer
+boolcopy8(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+	uchar *m, *r, *w, *ew;
+
+	USED(i);
+	USED(o);
+	m = bmask.grey;
+	w = bdst.red;
+	r = bsrc.red;
+	ew = w+dx;
+	for(; w < ew; w++,r++)
+		if(*m++)
+			*w = *r;
+	return bdst;	/* not used */
+}
+
+static Buffer
+boolcopy16(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+	uchar *m;
+	ushort *r, *w, *ew;
+
+	USED(i);
+	USED(o);
+	m = bmask.grey;
+	w = (ushort*)bdst.red;
+	r = (ushort*)bsrc.red;
+	ew = w+dx;
+	for(; w < ew; w++,r++)
+		if(*m++)
+			*w = *r;
+	return bdst;	/* not used */
+}
+
+static Buffer
+boolcopy24(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+	uchar *m;
+	uchar *r, *w, *ew;
+
+	USED(i);
+	USED(o);
+	m = bmask.grey;
+	w = bdst.red;
+	r = bsrc.red;
+	ew = w+dx*3;
+	while(w < ew){
+		if(*m++){
+			*w++ = *r++;
+			*w++ = *r++;
+			*w++ = *r++;
+		}else{
+			w += 3;
+			r += 3;
+		}
+	}
+	return bdst;	/* not used */
+}
+
+static Buffer
+boolcopy32(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+	uchar *m;
+	ulong *r, *w, *ew;
+
+	USED(i);
+	USED(o);
+	m = bmask.grey;
+	w = (ulong*)bdst.red;
+	r = (ulong*)bsrc.red;
+	ew = w+dx;
+	for(; w < ew; w++,r++)
+		if(*m++)
+			*w = *r;
+	return bdst;	/* not used */
+}
+
+static Buffer
+genconv(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+	int nb;
+	uchar *r, *w, *ew;
+
+	/* read from source into RGB format in convbuf */
+	b = p->convreadcall(p, p->convbuf, y);
+
+	/* write RGB format into dst format in buf */
+	p->convwritecall(p->convdpar, buf, b);
+
+	if(p->convdx){
+		nb = p->convdpar->img->depth/8;
+		r = buf;
+		w = buf+nb*p->dx;
+		ew = buf+nb*p->convdx;
+		while(w<ew)
+			*w++ = *r++;
+	}
+
+	b.red = buf;
+	b.blu = b.grn = b.grey = b.alpha = nil;
+	b.rgba = (ulong*)buf;
+	b.delta = 0;
+	
+	return b;
+}
+
+static Readfn*
+convfn(Memimage *dst, Param *dpar, Memimage *src, Param *spar)
+{
+	if(dst->chan == src->chan && !(src->flags&Frepl)){
+//if(drawdebug) iprint("readptr...");
+		return readptr;
+	}
+
+	if(dst->chan==CMAP8 && (src->chan==GREY1||src->chan==GREY2||src->chan==GREY4)){
+		/* cheat because we know the replicated value is exactly the color map entry. */
+//if(drawdebug) iprint("Readnbit...");
+		return readnbit;
+	}
+
+	spar->convreadcall = readfn(src);
+	spar->convwritecall = writefn(dst);
+	spar->convdpar = dpar;
+
+	/* allocate a conversion buffer */
+	spar->convbufoff = ndrawbuf;
+	ndrawbuf += spar->dx*4;
+
+	if(spar->dx > Dx(spar->img->r)){
+		spar->convdx = spar->dx;
+		spar->dx = Dx(spar->img->r);
+	}
+
+//if(drawdebug) iprint("genconv...");
+	return genconv;
+}
+
+ulong
+_pixelbits(Memimage *i, Point pt)
+{
+	uchar *p;
+	ulong val;
+	int off, bpp, npack;
+
+	val = 0;
+	p = byteaddr(i, pt);
+	switch(bpp=i->depth){
+	case 1:
+	case 2:
+	case 4:
+		npack = 8/bpp;
+		off = pt.x%npack;
+		val = p[0] >> bpp*(npack-1-off);
+		val &= (1<<bpp)-1;
+		break;
+	case 8:
+		val = p[0];
+		break;
+	case 16:
+		val = p[0]|(p[1]<<8);
+		break;
+	case 24:
+		val = p[0]|(p[1]<<8)|(p[2]<<16);
+		break;
+	case 32:
+		val = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
+		break;
+	}
+	while(bpp<32){
+		val |= val<<bpp;
+		bpp *= 2;
+	}
+	return val;
+}
+
+static Calcfn*
+boolcopyfn(Memimage *img, Memimage *mask)
+{
+	if(mask->flags&Frepl && Dx(mask->r)==1 && Dy(mask->r)==1 && pixelbits(mask, mask->r.min)==~0)
+		return boolmemmove;
+
+	switch(img->depth){
+	case 8:
+		return boolcopy8;
+	case 16:
+		return boolcopy16;
+	case 24:
+		return boolcopy24;
+	case 32:
+		return boolcopy32;
+	default:
+		assert(0 /* boolcopyfn */);
+	}
+	return nil;
+}
+
+/*
+ * Optimized draw for filling and scrolling; uses memset and memmove.
+ */
+static void
+memsetb(void *vp, uchar val, int n)
+{
+	uchar *p, *ep;
+
+	p = vp;
+	ep = p+n;
+	while(p<ep)
+		*p++ = val;
+}
+
+static void
+memsets(void *vp, ushort val, int n)
+{
+	ushort *p, *ep;
+
+	p = vp;
+	ep = p+n;
+	while(p<ep)
+		*p++ = val;
+}
+
+static void
+memsetl(void *vp, ulong val, int n)
+{
+	ulong *p, *ep;
+
+	p = vp;
+	ep = p+n;
+	while(p<ep)
+		*p++ = val;
+}
+
+static void
+memset24(void *vp, ulong val, int n)
+{
+	uchar *p, *ep;
+	uchar a,b,c;
+
+	p = vp;
+	ep = p+3*n;
+	a = val;
+	b = val>>8;
+	c = val>>16;
+	while(p<ep){
+		*p++ = a;
+		*p++ = b;
+		*p++ = c;
+	}
+}
+
+ulong
+_imgtorgba(Memimage *img, ulong val)
+{
+	uchar r, g, b, a;
+	int nb, ov, v;
+	ulong chan;
+	uchar *p;
+
+	a = 0xFF;
+	r = g = b = 0xAA;	/* garbage */
+	for(chan=img->chan; chan; chan>>=8){
+		nb = NBITS(chan);
+		ov = v = val&((1<<nb)-1);
+		val >>= nb;
+
+		while(nb < 8){
+			v |= v<<nb;
+			nb *= 2;
+		}
+		v >>= (nb-8);
+
+		switch(TYPE(chan)){
+		case CRed:
+			r = v;
+			break;
+		case CGreen:
+			g = v;
+			break;
+		case CBlue:
+			b = v;
+			break;
+		case CAlpha:
+			a = v;
+			break;
+		case CGrey:
+			r = g = b = v;
+			break;
+		case CMap:
+			p = img->cmap->cmap2rgb+3*ov;
+			r = *p++;
+			g = *p++;	
+			b = *p;
+			break;
+		}
+	}
+	return (r<<24)|(g<<16)|(b<<8)|a;	
+}
+
+ulong
+_rgbatoimg(Memimage *img, ulong rgba)
+{
+	ulong chan;
+	int d, nb;
+	ulong v;
+	uchar *p, r, g, b, a, m;
+
+	v = 0;
+	r = rgba>>24;
+	g = rgba>>16;
+	b = rgba>>8;
+	a = rgba;
+	d = 0;
+	for(chan=img->chan; chan; chan>>=8){
+		nb = NBITS(chan);
+		switch(TYPE(chan)){
+		case CRed:
+			v |= (r>>(8-nb))<<d;
+			break;
+		case CGreen:
+			v |= (g>>(8-nb))<<d;
+			break;
+		case CBlue:
+			v |= (b>>(8-nb))<<d;
+			break;
+		case CAlpha:
+			v |= (a>>(8-nb))<<d;
+			break;
+		case CMap:
+			p = img->cmap->rgb2cmap;
+			m = p[(r>>4)*256+(g>>4)*16+(b>>4)];
+			v |= (m>>(8-nb))<<d;
+			break;
+		case CGrey:
+			m = RGB2K(r,g,b);
+			v |= (m>>(8-nb))<<d;
+			break;
+		}
+		d += nb;
+	}
+//	print("rgba2img %.8lux = %.*lux\n", rgba, 2*d/8, v);
+	return v;
+}
+
+#define DBG if(0)
+static int
+memoptdraw(Memdrawparam *par)
+{
+	int m, y, dy, dx, op;
+	ulong v;
+	Memimage *src;
+	Memimage *dst;
+
+	dx = Dx(par->r);
+	dy = Dy(par->r);
+	src = par->src;
+	dst = par->dst;
+	op = par->op;
+
+DBG print("state %lux mval %lux dd %d\n", par->state, par->mval, dst->depth);
+	/*
+	 * If we have an opaque mask and source is one opaque pixel we can convert to the
+	 * destination format and just replicate with memset.
+	 */
+	m = Simplesrc|Simplemask|Fullmask;
+	if((par->state&m)==m && (par->srgba&0xFF) == 0xFF && (op ==S || op == SoverD)){
+		uchar *dp, p[4];
+		int d, dwid, ppb, np, nb;
+		uchar lm, rm;
+
+DBG print("memopt, dst %p, dst->data->bdata %p\n", dst, dst->data->bdata);
+		dwid = dst->width*sizeof(ulong);
+		dp = byteaddr(dst, par->r.min);
+		v = par->sdval;
+DBG print("sdval %lud, depth %d\n", v, dst->depth);
+		switch(dst->depth){
+		case 1:
+		case 2:
+		case 4:
+			for(d=dst->depth; d<8; d*=2)
+				v |= (v<<d);
+			ppb = 8/dst->depth;	/* pixels per byte */
+			m = ppb-1;
+			/* left edge */
+			np = par->r.min.x&m;		/* no. pixels unused on left side of word */
+			dx -= (ppb-np);
+			nb = 8 - np * dst->depth;		/* no. bits used on right side of word */
+			lm = (1<<nb)-1;
+DBG print("np %d x %d nb %d lm %ux ppb %d m %ux\n", np, par->r.min.x, nb, lm, ppb, m);	
+
+			/* right edge */
+			np = par->r.max.x&m;	/* no. pixels used on left side of word */
+			dx -= np;
+			nb = 8 - np * dst->depth;		/* no. bits unused on right side of word */
+			rm = ~((1<<nb)-1);
+DBG print("np %d x %d nb %d rm %ux ppb %d m %ux\n", np, par->r.max.x, nb, rm, ppb, m);	
+
+DBG print("dx %d Dx %d\n", dx, Dx(par->r));
+			/* lm, rm are masks that are 1 where we should touch the bits */
+			if(dx < 0){	/* just one byte */
+				lm &= rm;
+				for(y=0; y<dy; y++, dp+=dwid)
+					*dp ^= (v ^ *dp) & lm;
+			}else if(dx == 0){	/* no full bytes */
+				if(lm)
+					dwid--;
+
+				for(y=0; y<dy; y++, dp+=dwid){
+					if(lm){
+DBG print("dp %p v %lux lm %ux (v ^ *dp) & lm %lux\n", dp, v, lm, (v^*dp)&lm);
+						*dp ^= (v ^ *dp) & lm;
+						dp++;
+					}
+					*dp ^= (v ^ *dp) & rm;
+				}
+			}else{		/* full bytes in middle */
+				dx /= ppb;
+				if(lm)
+					dwid--;
+				dwid -= dx;
+
+				for(y=0; y<dy; y++, dp+=dwid){
+					if(lm){
+						*dp ^= (v ^ *dp) & lm;
+						dp++;
+					}
+					memset(dp, v, dx);
+					dp += dx;
+					*dp ^= (v ^ *dp) & rm;
+				}
+			}
+			return 1;
+		case 8:
+			for(y=0; y<dy; y++, dp+=dwid)
+				memset(dp, v, dx);
+			return 1;
+		case 16:
+			p[0] = v;		/* make little endian */
+			p[1] = v>>8;
+			v = *(ushort*)p;
+DBG print("dp=%p; dx=%d; for(y=0; y<%d; y++, dp+=%d)\nmemsets(dp, v, dx);\n",
+	dp, dx, dy, dwid);
+			for(y=0; y<dy; y++, dp+=dwid)
+				memsets(dp, v, dx);
+			return 1;
+		case 24:
+			for(y=0; y<dy; y++, dp+=dwid)
+				memset24(dp, v, dx);
+			return 1;
+		case 32:
+			p[0] = v;		/* make little endian */
+			p[1] = v>>8;
+			p[2] = v>>16;
+			p[3] = v>>24;
+			v = *(ulong*)p;
+			for(y=0; y<dy; y++, dp+=dwid)
+				memsetl(dp, v, dx);
+			return 1;
+		default:
+			assert(0 /* bad dest depth in memoptdraw */);
+		}
+	}
+
+	/*
+	 * 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, memmove suffices.
+	 */
+	m = Simplemask|Fullmask;
+	if((par->state&(m|Replsrc))==m && src->depth >= 8 
+	&& src->chan == dst->chan && !(src->flags&Falpha) && (op == S || op == SoverD)){
+		uchar *sp, *dp;
+		long swid, dwid, nb;
+		int dir;
+
+		if(src->data == dst->data && byteaddr(dst, par->r.min) > byteaddr(src, par->sr.min))
+			dir = -1;
+		else
+			dir = 1;
+
+		swid = src->width*sizeof(ulong);
+		dwid = dst->width*sizeof(ulong);
+		sp = byteaddr(src, par->sr.min);
+		dp = byteaddr(dst, par->r.min);
+		if(dir == -1){
+			sp += (dy-1)*swid;
+			dp += (dy-1)*dwid;
+			swid = -swid;
+			dwid = -dwid;
+		}
+		nb = (dx*src->depth)/8;
+		for(y=0; y<dy; y++, sp+=swid, dp+=dwid)
+			memmove(dp, sp, nb);
+		return 1;
+	}
+
+	/*
+	 * If we have a 1-bit mask, 1-bit source, and 1-bit destination, and
+	 * they're all bit aligned, we can just use bit operators.  This happens
+	 * when we're manipulating boolean masks, e.g. in the arc code.
+	 */
+	if((par->state&(Simplemask|Simplesrc|Replmask|Replsrc))==0 
+	&& dst->chan==GREY1 && src->chan==GREY1 && par->mask->chan==GREY1 
+	&& (par->r.min.x&7)==(par->sr.min.x&7) && (par->r.min.x&7)==(par->mr.min.x&7)){
+		uchar *sp, *dp, *mp;
+		uchar lm, rm;
+		long swid, dwid, mwid;
+		int i, x, dir;
+
+		sp = byteaddr(src, par->sr.min);
+		dp = byteaddr(dst, par->r.min);
+		mp = byteaddr(par->mask, par->mr.min);
+		swid = src->width*sizeof(ulong);
+		dwid = dst->width*sizeof(ulong);
+		mwid = par->mask->width*sizeof(ulong);
+
+		if(src->data == dst->data && byteaddr(dst, par->r.min) > byteaddr(src, par->sr.min)){
+			dir = -1;
+		}else
+			dir = 1;
+
+		lm = 0xFF>>(par->r.min.x&7);
+		rm = 0xFF<<(8-(par->r.max.x&7));
+		dx -= (8-(par->r.min.x&7)) + (par->r.max.x&7);
+
+		if(dx < 0){	/* one byte wide */
+			lm &= rm;
+			if(dir == -1){
+				dp += dwid*(dy-1);
+				sp += swid*(dy-1);
+				mp += mwid*(dy-1);
+				dwid = -dwid;
+				swid = -swid;
+				mwid = -mwid;
+			}
+			for(y=0; y<dy; y++){
+				*dp ^= (*dp ^ *sp) & *mp & lm;
+				dp += dwid;
+				sp += swid;
+				mp += mwid;
+			}
+			return 1;
+		}
+
+		dx /= 8;
+		if(dir == 1){
+			i = (lm!=0)+dx+(rm!=0);
+			mwid -= i;
+			swid -= i;
+			dwid -= i;
+			for(y=0; y<dy; y++, dp+=dwid, sp+=swid, mp+=mwid){
+				if(lm){
+					*dp ^= (*dp ^ *sp++) & *mp++ & lm;
+					dp++;
+				}
+				for(x=0; x<dx; x++){
+					*dp ^= (*dp ^ *sp++) & *mp++;
+					dp++;
+				}
+				if(rm){
+					*dp ^= (*dp ^ *sp++) & *mp++ & rm;
+					dp++;
+				}
+			}
+			return 1;
+		}else{
+		/* dir == -1 */
+			i = (lm!=0)+dx+(rm!=0);
+			dp += dwid*(dy-1)+i-1;
+			sp += swid*(dy-1)+i-1;
+			mp += mwid*(dy-1)+i-1;
+			dwid = -dwid+i;
+			swid = -swid+i;
+			mwid = -mwid+i;
+			for(y=0; y<dy; y++, dp+=dwid, sp+=swid, mp+=mwid){
+				if(rm){
+					*dp ^= (*dp ^ *sp--) & *mp-- & rm;
+					dp--;
+				}
+				for(x=0; x<dx; x++){
+					*dp ^= (*dp ^ *sp--) & *mp--;
+					dp--;
+				}
+				if(lm){
+					*dp ^= (*dp ^ *sp--) & *mp-- & lm;
+					dp--;
+				}
+			}
+		}
+		return 1;
+	}
+	return 0;	
+}
+#undef DBG
+
+/*
+ * Boolean character drawing.
+ * Solid opaque color through a 1-bit greyscale mask.
+ */
+#define DBG if(0)
+static int
+chardraw(Memdrawparam *par)
+{
+	ulong bits;
+	int i, ddepth, dy, dx, x, bx, ex, y, npack, bsh, depth, op;
+	ulong v, maskwid, dstwid;
+	uchar *wp, *rp, *q, *wc;
+	ushort *ws;
+	ulong *wl;
+	uchar sp[4];
+	Rectangle r, mr;
+	Memimage *mask, *src, *dst;
+
+if(0) if(drawdebug) iprint("chardraw? mf %lux md %d sf %lux dxs %d dys %d dd %d ddat %p sdat %p\n",
+		par->mask->flags, par->mask->depth, par->src->flags, 
+		Dx(par->src->r), Dy(par->src->r), par->dst->depth, par->dst->data, par->src->data);
+
+	mask = par->mask;
+	src = par->src;
+	dst = par->dst;
+	r = par->r;
+	mr = par->mr;
+	op = par->op;
+
+	if((par->state&(Replsrc|Simplesrc|Replmask)) != (Replsrc|Simplesrc)
+	|| mask->depth != 1 || src->flags&Falpha || dst->depth<8 || dst->data==src->data
+	|| op != SoverD)
+		return 0;
+
+//if(drawdebug) iprint("chardraw...");
+
+	depth = mask->depth;
+	maskwid = mask->width*sizeof(ulong);
+	rp = byteaddr(mask, mr.min);
+	npack = 8/depth;
+	bsh = (mr.min.x % npack) * depth;
+
+	wp = byteaddr(dst, r.min);
+	dstwid = dst->width*sizeof(ulong);
+DBG print("bsh %d\n", bsh);
+	dy = Dy(r);
+	dx = Dx(r);
+
+	ddepth = dst->depth;
+
+	/*
+	 * for loop counts from bsh to bsh+dx
+	 *
+	 * we want the bottom bits to be the amount
+	 * to shift the pixels down, so for n≡0 (mod 8) we want 
+	 * bottom bits 7.  for n≡1, 6, etc.
+	 * the bits come from -n-1.
+	 */
+
+	bx = -bsh-1;
+	ex = -bsh-1-dx;
+	SET(bits);
+	v = par->sdval;
+
+	/* make little endian */
+	sp[0] = v;
+	sp[1] = v>>8;
+	sp[2] = v>>16;
+	sp[3] = v>>24;
+
+//print("sp %x %x %x %x\n", sp[0], sp[1], sp[2], sp[3]);
+	for(y=0; y<dy; y++, rp+=maskwid, wp+=dstwid){
+		q = rp;
+		if(bsh)
+			bits = *q++;
+		switch(ddepth){
+		case 8:
+//if(drawdebug) iprint("8loop...");
+			wc = wp;
+			for(x=bx; x>ex; x--, wc++){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+				if((bits>>i)&1)
+					*wc = v;
+			}
+			break;
+		case 16:
+			ws = (ushort*)wp;
+			v = *(ushort*)sp;
+			for(x=bx; x>ex; x--, ws++){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+				if((bits>>i)&1)
+					*ws = v;
+			}
+			break;
+		case 24:
+			wc = wp;
+			for(x=bx; x>ex; x--, wc+=3){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+				if((bits>>i)&1){
+					wc[0] = sp[0];
+					wc[1] = sp[1];
+					wc[2] = sp[2];
+				}
+			}
+			break;
+		case 32:
+			wl = (ulong*)wp;
+			v = *(ulong*)sp;
+			for(x=bx; x>ex; x--, wl++){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+DBG iprint("bits %lux sh %d...", bits, i);
+				if((bits>>i)&1)
+					*wl = v;
+			}
+			break;
+		}
+	}
+
+DBG print("\n");	
+	return 1;	
+}
+#undef DBG
+
+
+/*
+ * Fill entire byte with replicated (if necessary) copy of source pixel,
+ * assuming destination ldepth is >= source ldepth.
+ *
+ * This code is just plain wrong for >8bpp.
+ *
+ulong
+membyteval(Memimage *src)
+{
+	int i, val, bpp;
+	uchar uc;
+
+	unloadmemimage(src, src->r, &uc, 1);
+	bpp = src->depth;
+	uc <<= (src->r.min.x&(7/src->depth))*src->depth;
+	uc &= ~(0xFF>>bpp);
+	/* pixel value is now in high part of byte. repeat throughout byte 
+	val = uc;
+	for(i=bpp; i<8; i<<=1)
+		val |= val>>i;
+	return val;
+}
+ * 
+ */
+
+void
+_memfillcolor(Memimage *i, ulong val)
+{
+	ulong bits;
+	int d, y;
+	uchar p[4];
+
+	if(val == DNofill)
+		return;
+
+	bits = _rgbatoimg(i, val);
+	switch(i->depth){
+	case 24:	/* 24-bit images suck */
+		for(y=i->r.min.y; y<i->r.max.y; y++)
+			memset24(byteaddr(i, Pt(i->r.min.x, y)), bits, Dx(i->r));
+		break;
+	default:	/* 1, 2, 4, 8, 16, 32 */
+		for(d=i->depth; d<32; d*=2)
+			bits = (bits << d) | bits;
+		p[0] = bits;		/* make little endian */
+		p[1] = bits>>8;
+		p[2] = bits>>16;
+		p[3] = bits>>24;
+		bits = *(ulong*)p;
+		memsetl(wordaddr(i, i->r.min), bits, i->width*Dy(i->r));
+		break;
+	}
+}
+
--- /dev/null
+++ b/libmemdraw/drawtest.c
@@ -1,0 +1,1004 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+
+#define DBG if(0)
+#define RGB2K(r,g,b)	((299*((ulong)(r))+587*((ulong)(g))+114*((ulong)(b)))/1000)
+
+/*
+ * This program tests the 'memimagedraw' primitive stochastically.
+ * It tests the combination aspects of it thoroughly, but since the
+ * three images it uses are disjoint, it makes no check of the
+ * correct behavior when images overlap.  That is, however, much
+ * easier to get right and to test.
+ */
+
+void	drawonepixel(Memimage*, Point, Memimage*, Point, Memimage*, Point);
+void	verifyone(void);
+void	verifyline(void);
+void	verifyrect(void);
+void	verifyrectrepl(int, int);
+void putpixel(Memimage *img, Point pt, ulong nv);
+ulong rgbatopix(uchar, uchar, uchar, uchar);
+
+char *dchan, *schan, *mchan;
+int dbpp, sbpp, mbpp;
+
+int drawdebug=0;
+int	seed;
+int	niters = 100;
+int	dbpp;	/* bits per pixel in destination */
+int	sbpp;	/* bits per pixel in src */
+int	mbpp;	/* bits per pixel in mask */
+int	dpm;	/* pixel mask at high part of byte, in destination */
+int	nbytes;	/* in destination */
+
+int	Xrange	= 64;
+int	Yrange	= 8;
+
+Memimage	*dst;
+Memimage	*src;
+Memimage	*mask;
+Memimage	*stmp;
+Memimage	*mtmp;
+Memimage	*ones;
+uchar	*dstbits;
+uchar	*srcbits;
+uchar	*maskbits;
+ulong	*savedstbits;
+
+void
+rdb(void)
+{
+}
+
+int
+iprint(char *fmt, ...)
+{
+	int n;	
+	va_list va;
+	char buf[1024];
+
+	va_start(va, fmt);
+	n = doprint(buf, buf+sizeof buf, fmt, va) - buf;
+	va_end(va);
+
+	write(1,buf,n);
+	return 1;
+}
+
+void
+main(int argc, char *argv[])
+{
+	memimageinit();
+	seed = time(0);
+
+	ARGBEGIN{
+	case 'x':
+		Xrange = atoi(ARGF());
+		break;
+	case 'y':
+		Yrange = atoi(ARGF());
+		break;
+	case 'n':
+		niters = atoi(ARGF());
+		break;
+	case 's':
+		seed = atoi(ARGF());
+		break;
+	}ARGEND
+
+	dchan = "r8g8b8";
+	schan = "r8g8b8";
+	mchan = "r8g8b8";
+	switch(argc){
+	case 3:	mchan = argv[2];
+	case 2:	schan = argv[1];
+	case 1:	dchan = argv[0];
+	case 0:	break;
+	default:	goto Usage;
+	Usage:
+		fprint(2, "usage: dtest [dchan [schan [mchan]]]\n");
+		exits("usage");
+	}
+
+	fmtinstall('b', numbconv);	/* binary! */
+
+	fprint(2, "%s -x %d -y %d -s 0x%x %s %s %s\n", argv0, Xrange, Yrange, seed, dchan, schan, mchan);
+	srand(seed);
+
+	dst = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(dchan));
+	src = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(schan));
+	mask = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
+	stmp = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(schan));
+	mtmp = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
+	ones = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
+//	print("chan %lux %lux %lux %lux %lux %lux\n", dst->chan, src->chan, mask->chan, stmp->chan, mtmp->chan, ones->chan);
+	if(dst==0 || src==0 || mask==0 || mtmp==0 || ones==0) {
+	Alloc:
+		fprint(2, "dtest: allocation failed: %r\n");
+		exits("alloc");
+	}
+	nbytes = (4*Xrange+4)*Yrange;
+	srcbits = malloc(nbytes);
+	dstbits = malloc(nbytes);
+	maskbits = malloc(nbytes);
+	savedstbits = malloc(nbytes);
+	if(dstbits==0 || srcbits==0 || maskbits==0 || savedstbits==0)
+		goto Alloc;
+	dbpp = dst->depth;
+	sbpp = src->depth;
+	mbpp = mask->depth;
+	dpm = 0xFF ^ (0xFF>>dbpp);
+	memset(ones->data->bdata, 0xFF, ones->width*sizeof(ulong)*Yrange);
+
+
+	fprint(2, "dtest: verify single pixel operation\n");
+	verifyone();
+
+	fprint(2, "dtest: verify full line non-replicated\n");
+	verifyline();
+
+	fprint(2, "dtest: verify full rectangle non-replicated\n");
+	verifyrect();
+
+	fprint(2, "dtest: verify full rectangle source replicated\n");
+	verifyrectrepl(1, 0);
+
+	fprint(2, "dtest: verify full rectangle mask replicated\n");
+	verifyrectrepl(0, 1);
+
+	fprint(2, "dtest: verify full rectangle source and mask replicated\n");
+	verifyrectrepl(1, 1);
+
+	exits(0);
+}
+
+/*
+ * Dump out an ASCII representation of an image.  The label specifies
+ * a list of characters to put at various points in the picture.
+ */
+static void
+Bprintr5g6b5(Biobuf *bio, char*, ulong v)
+{
+	int r,g,b;
+	r = (v>>11)&31;
+	g = (v>>5)&63;
+	b = v&31;
+	Bprint(bio, "%.2x%.2x%.2x", r,g,b);
+}
+
+static void
+Bprintr5g5b5a1(Biobuf *bio, char*, ulong v)
+{
+	int r,g,b,a;
+	r = (v>>11)&31;
+	g = (v>>6)&31;
+	b = (v>>1)&31;
+	a = v&1;
+	Bprint(bio, "%.2x%.2x%.2x%.2x", r,g,b,a);
+}
+
+void
+dumpimage(char *name, Memimage *img, void *vdata, Point labelpt)
+{
+	Biobuf b;
+	uchar *data;
+	uchar *p;
+	char *arg;
+	void (*fmt)(Biobuf*, char*, ulong);
+	int npr, x, y, nb, bpp;
+	ulong v, mask;
+	Rectangle r;
+
+	fmt = nil;
+	arg = nil;
+	switch(img->depth){
+	case 1:
+	case 2:
+	case 4:
+		fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
+		arg = "%.1ux";
+		break;
+	case 8:
+		fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
+		arg = "%.2ux";
+		break;
+	case 16:
+		arg = nil;
+		if(img->chan == RGB16)
+			fmt = Bprintr5g6b5;
+		else{
+			fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
+			arg = "%.4ux";
+		}
+		break;
+	case 24:
+		fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
+		arg = "%.6lux";
+		break;
+	case 32:
+		fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
+		arg = "%.8lux";
+		break;
+	}
+	if(fmt == nil){
+		fprint(2, "bad format\n");
+		abort();
+	}
+
+	r  = img->r;
+	Binit(&b, 2, OWRITE);
+	data = vdata;
+	bpp = img->depth;
+	Bprint(&b, "%s\t%d\tr %R clipr %R repl %d data %p *%P\n", name, r.min.x, r, img->clipr, (img->flags&Frepl) ? 1 : 0, vdata, labelpt);
+	mask = (1ULL<<bpp)-1;
+//	for(y=r.min.y; y<r.max.y; y++){
+	for(y=0; y<Yrange; y++){
+		nb = 0;
+		v = 0;
+		p = data+(byteaddr(img, Pt(0,y))-(uchar*)img->data->bdata);
+		Bprint(&b, "%-4d\t", y);
+//		for(x=r.min.x; x<r.max.x; x++){
+		for(x=0; x<Xrange; x++){
+			if(x==0)
+				Bprint(&b, "\t");
+
+			if(x != 0 && (x%8)==0)
+				Bprint(&b, " ");
+
+			npr = 0;
+			if(x==labelpt.x && y==labelpt.y){
+				Bprint(&b, "*");
+				npr++;
+			}
+			if(npr == 0)
+				Bprint(&b, " ");
+
+			while(nb < bpp){
+				v &= (1<<nb)-1;
+				v |= (ulong)(*p++) << nb;
+				nb += 8;
+			}
+			nb -= bpp;
+//			print("bpp %d v %.8lux mask %.8lux nb %d\n", bpp, v, mask, nb);
+			fmt(&b, arg, (v>>nb)&mask);
+		}
+		Bprint(&b, "\n");
+	}
+	Bterm(&b);
+}
+
+/*
+ * Verify that the destination pixel has the specified value.
+ * The value is in the high bits of v, suitably masked, but must
+ * be extracted from the destination Memimage.
+ */
+void
+checkone(Point p, Point sp, Point mp)
+{
+	int delta;
+	uchar *dp, *sdp;
+
+	delta = (uchar*)byteaddr(dst, p)-(uchar*)dst->data->bdata;
+	dp = (uchar*)dst->data->bdata+delta;
+	sdp = (uchar*)savedstbits+delta;
+
+	if(memcmp(dp, sdp, (dst->depth+7)/8) != 0) {
+		fprint(2, "dtest: one bad pixel drawing at dst %P from source %P mask %P\n", p, sp, mp);
+		fprint(2, " %.2ux %.2ux %.2ux %.2ux should be %.2ux %.2ux %.2ux %.2ux\n",
+			dp[0], dp[1], dp[2], dp[3], sdp[0], sdp[1], sdp[2], sdp[3]);
+		fprint(2, "addresses dst %p src %p mask %p\n", dp, byteaddr(src, sp), byteaddr(mask, mp));
+		dumpimage("src", src, src->data->bdata, sp);
+		dumpimage("mask", mask, mask->data->bdata, mp);
+		dumpimage("origdst", dst, dstbits, p);
+		dumpimage("dst", dst, dst->data->bdata, p);
+		dumpimage("gooddst", dst, savedstbits, p);
+		abort();
+	}
+}
+
+/*
+ * Verify that the destination line has the same value as the saved line.
+ */
+#define RECTPTS(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
+void
+checkline(Rectangle r, Point sp, Point mp, int y, Memimage *stmp, Memimage *mtmp)
+{
+	ulong *dp;
+	int nb;
+	ulong *saved;
+
+	dp = wordaddr(dst, Pt(0, y));
+	saved = savedstbits + y*dst->width;
+	if(dst->depth < 8)
+		nb = Xrange/(8/dst->depth);
+	else
+		nb = Xrange*(dst->depth/8);
+	if(memcmp(dp, saved, nb) != 0){
+		fprint(2, "dtest: bad line at y=%d; saved %p dp %p\n", y, saved, dp);
+		fprint(2, "draw dst %R src %P mask %P\n", r, sp, mp);
+		dumpimage("src", src, src->data->bdata, sp);
+		if(stmp) dumpimage("stmp", stmp, stmp->data->bdata, sp);
+		dumpimage("mask", mask, mask->data->bdata, mp);
+		if(mtmp) dumpimage("mtmp", mtmp, mtmp->data->bdata, mp);
+		dumpimage("origdst", dst, dstbits, r.min);
+		dumpimage("dst", dst, dst->data->bdata, r.min);
+		dumpimage("gooddst", dst, savedstbits, r.min);
+		abort();
+	}
+}
+
+/*
+ * Fill the bits of an image with random data.
+ * The Memimage parameter is used only to make sure
+ * the data is well formatted: only ucbits is written.
+ */
+void
+fill(Memimage *img, uchar *ucbits)
+{
+	int i, x, y;
+	ushort *up;
+	uchar alpha, r, g, b;
+	void *data;
+
+	if((img->flags&Falpha) == 0){
+		up = (ushort*)ucbits;
+		for(i=0; i<nbytes/2; i++)
+			*up++ = lrand() >> 7;
+		if(i+i != nbytes)
+			*(uchar*)up = lrand() >> 7;
+	}else{
+		data = img->data->bdata;
+		img->data->bdata = ucbits;
+
+		for(x=img->r.min.x; x<img->r.max.x; x++)
+		for(y=img->r.min.y; y<img->r.max.y; y++){
+			alpha = rand() >> 4;
+			r = rand()%(alpha+1);
+			g = rand()%(alpha+1);
+			b = rand()%(alpha+1);
+			putpixel(img, Pt(x,y), rgbatopix(r,g,b,alpha));
+		}
+		img->data->bdata = data;
+	}
+		
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyonemask(void)
+{
+	Point dp, sp, mp;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+	memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
+	memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);
+
+	dp.x = nrand(Xrange);
+	dp.y = nrand(Yrange);
+
+	sp.x = nrand(Xrange);
+	sp.y = nrand(Yrange);
+
+	mp.x = nrand(Xrange);
+	mp.y = nrand(Yrange);
+
+	drawonepixel(dst, dp, src, sp, mask, mp);
+	memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);
+	memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);
+	
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+	memimagedraw(dst, Rect(dp.x, dp.y, dp.x+1, dp.y+1), src, sp, mask, mp, SoverD);
+	memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);
+
+	checkone(dp, sp, mp);
+}
+
+void
+verifyone(void)
+{
+	int i;
+
+	/* mask all zeros */
+	memset(maskbits, 0, nbytes);
+	for(i=0; i<niters; i++)
+		verifyonemask();
+
+	/* mask all ones */
+	memset(maskbits, 0xFF, nbytes);
+	for(i=0; i<niters; i++)
+		verifyonemask();
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifyonemask();
+	}
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifylinemask(void)
+{
+	Point sp, mp, tp, up;
+	Rectangle dr;
+	int x;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+	memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
+	memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);
+
+	dr.min.x = nrand(Xrange-1);
+	dr.min.y = nrand(Yrange-1);
+	dr.max.x = dr.min.x + 1 + nrand(Xrange-1-dr.min.x);
+	dr.max.y = dr.min.y + 1;
+
+	sp.x = nrand(Xrange);
+	sp.y = nrand(Yrange);
+
+	mp.x = nrand(Xrange);
+	mp.y = nrand(Yrange);
+
+	tp = sp;
+	up = mp;
+	for(x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
+		memimagedraw(dst, Rect(x, dr.min.y, x+1, dr.min.y+1), src, tp, mask, up, SoverD);
+	memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);
+
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+
+	memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
+	checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), dr.min.y, nil, nil);
+}
+
+void
+verifyline(void)
+{
+	int i;
+
+	/* mask all ones */
+	memset(maskbits, 0xFF, nbytes);
+	for(i=0; i<niters; i++)
+		verifylinemask();
+
+	/* mask all zeros */
+	memset(maskbits, 0, nbytes);
+	for(i=0; i<niters; i++)
+		verifylinemask();
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifylinemask();
+	}
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyrectmask(void)
+{
+	Point sp, mp, tp, up;
+	Rectangle dr;
+	int x, y;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+	memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
+	memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);
+
+	dr.min.x = nrand(Xrange-1);
+	dr.min.y = nrand(Yrange-1);
+	dr.max.x = dr.min.x + 1 + nrand(Xrange-1-dr.min.x);
+	dr.max.y = dr.min.y + 1 + nrand(Yrange-1-dr.min.y);
+
+	sp.x = nrand(Xrange);
+	sp.y = nrand(Yrange);
+
+	mp.x = nrand(Xrange);
+	mp.y = nrand(Yrange);
+
+	tp = sp;
+	up = mp;
+	for(y=dr.min.y; y<dr.max.y && tp.y<Yrange && up.y<Yrange; y++,tp.y++,up.y++){
+		for(x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
+			memimagedraw(dst, Rect(x, y, x+1, y+1), src, tp, mask, up, SoverD);
+		tp.x = sp.x;
+		up.x = mp.x;
+	}
+	memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);
+
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+
+	memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
+	for(y=0; y<Yrange; y++)
+		checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), y, nil, nil);
+}
+
+void
+verifyrect(void)
+{
+	int i;
+
+	/* mask all zeros */
+	memset(maskbits, 0, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmask();
+
+	/* mask all ones */
+	memset(maskbits, 0xFF, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmask();
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifyrectmask();
+	}
+}
+
+Rectangle
+randrect(void)
+{
+	Rectangle r;
+
+	r.min.x = nrand(Xrange-1);
+	r.min.y = nrand(Yrange-1);
+	r.max.x = r.min.x + 1 + nrand(Xrange-1-r.min.x);
+	r.max.y = r.min.y + 1 + nrand(Yrange-1-r.min.y);
+	return r;
+}
+
+/*
+ * Return coordinate corresponding to x withing range [minx, maxx)
+ */
+int
+tilexy(int minx, int maxx, int x)
+{
+	int sx;
+
+	sx = (x-minx) % (maxx-minx);
+	if(sx < 0)
+		sx += maxx-minx;
+	return sx+minx;
+}
+
+void
+replicate(Memimage *i, Memimage *tmp)
+{
+	Rectangle r, r1;
+	int x, y, nb;
+
+	/* choose the replication window (i->r) */
+	r.min.x = nrand(Xrange-1);
+	r.min.y = nrand(Yrange-1);
+	/* make it trivial more often than pure chance allows */
+	switch(lrand()&0){
+	case 1:
+		r.max.x = r.min.x + 2;
+		r.max.y = r.min.y + 2;
+		if(r.max.x < Xrange && r.max.y < Yrange)
+			break;
+		/* fall through */
+	case 0:
+		r.max.x = r.min.x + 1;
+		r.max.y = r.min.y + 1;
+		break;
+	default:
+		if(r.min.x+3 >= Xrange)
+			r.max.x = Xrange;
+		else
+			r.max.x = r.min.x+3 + nrand(Xrange-(r.min.x+3));
+
+		if(r.min.y+3 >= Yrange)
+			r.max.y = Yrange;
+		else
+			r.max.y = r.min.y+3 + nrand(Yrange-(r.min.y+3));
+	}
+	assert(r.min.x >= 0);	
+	assert(r.max.x <= Xrange);
+	assert(r.min.y >= 0);
+	assert(r.max.y <= Yrange);
+	/* copy from i to tmp so we have just the replicated bits */
+	nb = tmp->width*sizeof(ulong)*Yrange;
+	memset(tmp->data->bdata, 0, nb);
+	memimagedraw(tmp, r, i, r.min, ones, r.min, SoverD);
+	memmove(i->data->bdata, tmp->data->bdata, nb);
+	/* i is now a non-replicated instance of the replication */
+	/* replicate it by hand through tmp */
+	memset(tmp->data->bdata, 0, nb);
+	x = -(tilexy(r.min.x, r.max.x, 0)-r.min.x);
+	for(; x<Xrange; x+=Dx(r)){
+		y = -(tilexy(r.min.y, r.max.y, 0)-r.min.y);
+		for(; y<Yrange; y+=Dy(r)){
+			/* set r1 to instance of tile by translation */
+			r1.min.x = x;
+			r1.min.y = y;
+			r1.max.x = r1.min.x+Dx(r);
+			r1.max.y = r1.min.y+Dy(r);
+			memimagedraw(tmp, r1, i, r.min, ones, r.min, SoverD);
+		}
+	}
+	i->flags |= Frepl;
+	i->r = r;
+	i->clipr = randrect();
+//	fprint(2, "replicate [[%d %d] [%d %d]] [[%d %d][%d %d]]\n", r.min.x, r.min.y, r.max.x, r.max.y,
+//		i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
+	tmp->clipr = i->clipr;
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyrectmaskrepl(int srcrepl, int maskrepl)
+{
+	Point sp, mp, tp, up;
+	Rectangle dr;
+	int x, y;
+	Memimage *s, *m;
+
+//	print("verfrect %d %d\n", srcrepl, maskrepl);
+	src->flags &= ~Frepl;
+	src->r = Rect(0, 0, Xrange, Yrange);
+	src->clipr = src->r;
+	stmp->flags &= ~Frepl;
+	stmp->r = Rect(0, 0, Xrange, Yrange);
+	stmp->clipr = src->r;
+	mask->flags &= ~Frepl;
+	mask->r = Rect(0, 0, Xrange, Yrange);
+	mask->clipr = mask->r;
+	mtmp->flags &= ~Frepl;
+	mtmp->r = Rect(0, 0, Xrange, Yrange);
+	mtmp->clipr = mask->r;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+	memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
+	memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);
+
+	if(srcrepl){
+		replicate(src, stmp);
+		s = stmp;
+	}else
+		s = src;
+	if(maskrepl){
+		replicate(mask, mtmp);
+		m = mtmp;
+	}else
+		m = mask;
+
+	dr = randrect();
+
+	sp.x = nrand(Xrange);
+	sp.y = nrand(Yrange);
+
+	mp.x = nrand(Xrange);
+	mp.y = nrand(Yrange);
+
+DBG	print("smalldraws\n");
+	for(tp.y=sp.y,up.y=mp.y,y=dr.min.y; y<dr.max.y && tp.y<Yrange && up.y<Yrange; y++,tp.y++,up.y++)
+		for(tp.x=sp.x,up.x=mp.x,x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
+			memimagedraw(dst, Rect(x, y, x+1, y+1), s, tp, m, up, SoverD);
+	memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);
+
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
+
+DBG	print("bigdraw\n");
+	memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
+	for(y=0; y<Yrange; y++)
+		checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), y, srcrepl?stmp:nil, maskrepl?mtmp:nil);
+}
+
+void
+verifyrectrepl(int srcrepl, int maskrepl)
+{
+	int i;
+
+	/* mask all ones */
+	memset(maskbits, 0xFF, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmaskrepl(srcrepl, maskrepl);
+
+	/* mask all zeros */
+	memset(maskbits, 0, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmaskrepl(srcrepl, maskrepl);
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifyrectmaskrepl(srcrepl, maskrepl);
+	}
+}
+
+/*
+ * Trivial draw implementation.
+ * Color values are passed around as ulongs containing ααRRGGBB
+ */
+
+/*
+ * Convert v, which is nhave bits wide, into its nwant bits wide equivalent.
+ * Replicates to widen the value, truncates to narrow it.
+ */
+ulong
+replbits(ulong v, int nhave, int nwant)
+{
+	v &= (1<<nhave)-1;
+	for(; nhave<nwant; nhave*=2)
+		v |= v<<nhave;
+	v >>= (nhave-nwant);
+	return v & ((1<<nwant)-1);
+}
+
+/*
+ * Decode a pixel into the uchar* values.
+ */
+void
+pixtorgba(ulong v, uchar *r, uchar *g, uchar *b, uchar *a)
+{
+	*a = v>>24;
+	*r = v>>16;
+	*g = v>>8;
+	*b = v;
+}
+
+/*
+ * Convert uchar channels into ulong pixel.
+ */
+ulong
+rgbatopix(uchar r, uchar g, uchar b, uchar a)
+{
+	return (a<<24)|(r<<16)|(g<<8)|b;
+}
+
+/*
+ * Retrieve the pixel value at pt in the image.
+ */
+ulong
+getpixel(Memimage *img, Point pt)
+{
+	uchar r, g, b, a, *p;
+	int nbits, npack, bpp;
+	ulong v, c, rbits, bits;
+
+	r = g = b = 0;
+	a = ~0;	/* default alpha is full */
+
+	p = byteaddr(img, pt);
+	v = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
+	bpp = img->depth;
+	if(bpp<8){
+		/*
+		 * Sub-byte greyscale pixels.
+		 *
+		 * We want to throw away the top pt.x%npack pixels and then use the next bpp bits
+		 * in the bottom byte of v.  This madness is due to having big endian bits
+		 * but little endian bytes.
+		 */
+		npack = 8/bpp;
+		v >>= 8 - bpp*(pt.x%npack+1);
+		v &= (1<<bpp)-1;
+		r = g = b = replbits(v, bpp, 8);
+	}else{
+		/*
+		 * General case.  We need to parse the channel descriptor and do what it says.
+		 * In all channels but the color map, we replicate to 8 bits because that's the
+		 * precision that all calculations are done at.
+		 *
+		 * In the case of the color map, we leave the bits alone, in case a color map
+		 * with less than 8 bits of index is used.  This is currently disallowed, so it's
+		 * sort of silly.
+		 */
+
+		for(c=img->chan; c; c>>=8){
+			nbits = NBITS(c);
+			bits = v & ((1<<nbits)-1);
+			rbits = replbits(bits, nbits, 8);
+			v >>= nbits;
+			switch(TYPE(c)){
+			case CRed:
+				r = rbits;
+				break;
+			case CGreen:
+				g = rbits;
+				break;
+			case CBlue:
+				b = rbits;
+				break;
+			case CGrey:
+				r = g = b = rbits;
+				break;
+			case CAlpha:
+				a = rbits;
+				break;
+			case CMap:
+				p = img->cmap->cmap2rgb + 3*bits;
+				r = p[0];
+				g = p[1];
+				b = p[2];
+				break;
+			case CIgnore:
+				break;
+			default:
+				fprint(2, "unknown channel type %lud\n", TYPE(c));
+				abort();
+			}
+		}
+	}
+	return rgbatopix(r, g, b, a);
+}
+
+/*
+ * Return the greyscale equivalent of a pixel.
+ */
+uchar
+getgrey(Memimage *img, Point pt)
+{
+	uchar r, g, b, a;
+	pixtorgba(getpixel(img, pt), &r, &g, &b, &a);
+	return RGB2K(r, g, b);
+}
+
+/*
+ * Return the value at pt in image, if image is interpreted
+ * as a mask.  This means the alpha channel if present, else
+ * the greyscale or its computed equivalent.
+ */
+uchar
+getmask(Memimage *img, Point pt)
+{
+	if(img->flags&Falpha)
+		return getpixel(img, pt)>>24;
+	else
+		return getgrey(img, pt);
+}
+#undef DBG
+
+#define DBG if(0)
+/*
+ * Write a pixel to img at point pt.
+ * 
+ * We do this by reading a 32-bit little endian
+ * value from p and then writing it back
+ * after tweaking the appropriate bits.  Because
+ * the data is little endian, we don't have to worry
+ * about what the actual depth is, as long as it is
+ * less than 32 bits.
+ */
+void
+putpixel(Memimage *img, Point pt, ulong nv)
+{
+	uchar r, g, b, a, *p, *q;
+	ulong c, mask, bits, v;
+	int bpp, sh, npack, nbits;
+
+	pixtorgba(nv, &r, &g, &b, &a);
+
+	p = byteaddr(img, pt);
+	v = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
+	bpp = img->depth;
+DBG print("v %.8lux...", v);
+	if(bpp < 8){
+		/*
+		 * Sub-byte greyscale pixels.  We need to skip the leftmost pt.x%npack pixels,
+		 * which is equivalent to skipping the rightmost npack - pt.x%npack - 1 pixels.
+		 */	
+		npack = 8/bpp;
+		sh = bpp*(npack - pt.x%npack - 1);
+		bits = RGB2K(r,g,b);
+DBG print("repl %lux 8 %d = %lux...", bits, bpp, replbits(bits, 8, bpp));
+		bits = replbits(bits, 8, bpp);
+		mask = (1<<bpp)-1;
+DBG print("bits %lux mask %lux sh %d...", bits, mask, sh);
+		mask <<= sh;
+		bits <<= sh;
+DBG print("(%lux & %lux) | (%lux & %lux)", v, ~mask, bits, mask);
+		v = (v & ~mask) | (bits & mask);
+	} else {
+		/*
+		 * General case.  We need to parse the channel descriptor again.
+		 */
+		sh = 0;
+		for(c=img->chan; c; c>>=8){
+			nbits = NBITS(c);
+			switch(TYPE(c)){
+			case CRed:
+				bits = r;
+				break;
+			case CGreen:
+				bits = g;
+				break;
+			case CBlue:
+				bits = b;
+				break;
+			case CGrey:
+				bits = RGB2K(r, g, b);
+				break;
+			case CAlpha:
+				bits = a;
+				break;
+			case CIgnore:
+				bits = 0;
+				break;
+			case CMap:
+				q = img->cmap->rgb2cmap;
+				bits = q[(r>>4)*16*16+(g>>4)*16+(b>>4)];
+				break;
+			default:
+				SET(bits);
+				fprint(2, "unknown channel type %lud\n", TYPE(c));
+				abort();
+			}
+
+DBG print("repl %lux 8 %d = %lux...", bits, nbits, replbits(bits, 8, nbits));
+			if(TYPE(c) != CMap)
+				bits = replbits(bits, 8, nbits);
+			mask = (1<<nbits)-1;
+DBG print("bits %lux mask %lux sh %d...", bits, mask, sh);
+			bits <<= sh;
+			mask <<= sh;
+			v = (v & ~mask) | (bits & mask);
+			sh += nbits;
+		}
+	}
+DBG print("v %.8lux\n", v);
+	p[0] = v;
+	p[1] = v>>8;
+	p[2] = v>>16;
+	p[3] = v>>24;	
+}
+#undef DBG
+
+#define DBG if(0)
+void
+drawonepixel(Memimage *dst, Point dp, Memimage *src, Point sp, Memimage *mask, Point mp)
+{
+	uchar m, M, sr, sg, sb, sa, sk, dr, dg, db, da, dk;
+
+	pixtorgba(getpixel(dst, dp), &dr, &dg, &db, &da);
+	pixtorgba(getpixel(src, sp), &sr, &sg, &sb, &sa);
+	m = getmask(mask, mp);
+	M = 255-(sa*m)/255;
+
+DBG print("dst %x %x %x %x src %x %x %x %x m %x = ", dr,dg,db,da, sr,sg,sb,sa, m);
+	if(dst->flags&Fgrey){
+		/*
+		 * We need to do the conversion to grey before the alpha calculation
+		 * because the draw operator does this, and we need to be operating
+		 * at the same precision so we get exactly the same answers.
+		 */
+		sk = RGB2K(sr, sg, sb);
+		dk = RGB2K(dr, dg, db);
+		dk = (sk*m + dk*M)/255;
+		dr = dg = db = dk;
+		da = (sa*m + da*M)/255;
+	}else{
+		/*
+		 * True color alpha calculation treats all channels (including alpha)
+		 * the same.  It might have been nice to use an array, but oh well.
+		 */
+		dr = (sr*m + dr*M)/255;
+		dg = (sg*m + dg*M)/255;
+		db = (sb*m + db*M)/255;
+		da = (sa*m + da*M)/255;
+	}
+
+DBG print("%x %x %x %x\n", dr,dg,db,da);
+	putpixel(dst, dp, rgbatopix(dr, dg, db, da));
+}
--- /dev/null
+++ b/libmemdraw/ellipse.c
@@ -1,0 +1,248 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * ellipse(dst, c, a, b, t, src, sp)
+ *   draws an ellipse centered at c with semiaxes a,b>=0
+ *   and semithickness t>=0, or filled if t<0.  point sp
+ *   in src maps to c in dst
+ *
+ *   very thick skinny ellipses are brushed with circles (slow)
+ *   others are approximated by filling between 2 ellipses
+ *   criterion for very thick when b<a: t/b > 0.5*x/(1-x)
+ *   where x = b/a
+ */
+
+typedef struct Param	Param;
+typedef struct State	State;
+
+static	void	bellipse(int, State*, Param*);
+static	void	erect(int, int, int, int, Param*);
+static	void	eline(int, int, int, int, Param*);
+
+struct Param {
+	Memimage	*dst;
+	Memimage	*src;
+	Point			c;
+	int			t;
+	Point			sp;
+	Memimage	*disc;
+	int			op;
+};
+
+/*
+ * denote residual error by e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2
+ * e(x,y) = 0 on ellipse, e(x,y) < 0 inside, e(x,y) > 0 outside
+ */
+
+struct State {
+	int	a;
+	int	x;
+	vlong	a2;	/* a^2 */
+	vlong	b2;	/* b^2 */
+	vlong	b2x;	/* b^2 * x */
+	vlong	a2y;	/* a^2 * y */
+	vlong	c1;
+	vlong	c2;	/* test criteria */
+	vlong	ee;	/* ee = e(x+1/2,y-1/2) - (a^2+b^2)/4 */
+	vlong	dxe;
+	vlong	dye;
+	vlong	d2xe;
+	vlong	d2ye;
+};
+
+static
+State*
+newstate(State *s, int a, int b)
+{
+	s->x = 0;
+	s->a = a;
+	s->a2 = (vlong)(a*a);
+	s->b2 = (vlong)(b*b);
+	s->b2x = (vlong)0;
+	s->a2y = s->a2*(vlong)b;
+	s->c1 = -((s->a2>>2) + (vlong)(a&1) + s->b2);
+	s->c2 = -((s->b2>>2) + (vlong)(b&1));
+	s->ee = -s->a2y;
+	s->dxe = (vlong)0;
+	s->dye = s->ee<<1;
+	s->d2xe = s->b2<<1;
+	s->d2ye = s->a2<<1;
+	return s;
+}
+
+/*
+ * return x coord of rightmost pixel on next scan line
+ */
+static
+int
+step(State *s)
+{
+	while(s->x < s->a) {
+		if(s->ee+s->b2x <= s->c1 ||	/* e(x+1,y-1/2) <= 0 */
+		   s->ee+s->a2y <= s->c2) {	/* e(x+1/2,y) <= 0 (rare) */
+			s->dxe += s->d2xe;	  
+			s->ee += s->dxe;	  
+			s->b2x += s->b2;
+			s->x++;	  
+			continue;
+		}
+		s->dye += s->d2ye;	  
+		s->ee += s->dye;	  
+		s->a2y -= s->a2;
+		if(s->ee-s->a2y <= s->c2) {	/* e(x+1/2,y-1) <= 0 */
+			s->dxe += s->d2xe;	  
+			s->ee += s->dxe;	  
+			s->b2x += s->b2;
+			return s->x++;
+		}
+		break;
+	}
+	return s->x;	  
+}
+
+void
+memellipse(Memimage *dst, Point c, int a, int b, int t, Memimage *src, Point sp, int op)
+{
+	State in, out;
+	int y, inb, inx, outx, u;
+	Param p;
+
+	if(a < 0)
+		a = -a;
+	if(b < 0)
+		b = -b;
+	p.dst = dst;
+	p.src = src;
+	p.c = c;
+	p.t = t;
+	p.sp = subpt(sp, c);
+	p.disc = nil;
+	p.op = op;
+
+	u = (t<<1)*(a-b);
+	if(b<a && u>b*b || a<b && -u>a*a) {
+/*	if(b<a&&(t<<1)>b*b/a || a<b&&(t<<1)>a*a/b)	# very thick */
+		bellipse(b, newstate(&in, a, b), &p);
+		return;
+	}
+
+	if(t < 0) {
+		inb = -1;
+		newstate(&out, a, y = b);
+	} else {	
+		inb = b - t;
+		newstate(&out, a+t, y = b+t);
+	}
+	if(t > 0)
+		newstate(&in, a-t, inb);
+	inx = 0;
+	for( ; y>=0; y--) {
+		outx = step(&out);
+		if(y > inb) {
+			erect(-outx, y, outx, y, &p);
+			if(y != 0)
+				erect(-outx, -y, outx, -y, &p);
+			continue;
+		}
+		if(t > 0) {
+			inx = step(&in);
+			if(y == inb)
+				inx = 0;
+		} else if(inx > outx)
+			inx = outx;
+		erect(inx, y, outx, y, &p);
+		if(y != 0)
+			erect(inx, -y, outx, -y, &p);
+		erect(-outx, y, -inx, y, &p);
+		if(y != 0)
+			erect(-outx, -y, -inx, -y, &p);
+		inx = outx + 1;
+	}
+}
+
+static Point p00 = {0, 0};
+
+/*
+ * a brushed ellipse
+ */
+static
+void
+bellipse(int y, State *s, Param *p)
+{
+	int t, ox, oy, x, nx;
+
+	t = p->t;
+	p->disc = allocmemimage(Rect(-t,-t,t+1,t+1), GREY1);
+	if(p->disc == nil)
+		return;
+	memfillcolor(p->disc, DTransparent);
+	memellipse(p->disc, p00, t, t, -1, memopaque, p00, p->op);
+	oy = y;
+	ox = 0;
+	nx = x = step(s);
+	do {
+		while(nx==x && y-->0)
+			nx = step(s);
+		y++;
+		eline(-x,-oy,-ox, -y, p);
+		eline(ox,-oy,  x, -y, p);
+		eline(-x,  y,-ox, oy, p);
+		eline(ox,  y,  x, oy, p);
+		ox = x+1;
+		x = nx;
+		y--;
+		oy = y;
+	} while(oy > 0);
+}
+
+/*
+ * a rectangle with closed (not half-open) coordinates expressed
+ * relative to the center of the ellipse
+ */
+static
+void
+erect(int x0, int y0, int x1, int y1, Param *p)
+{
+	Rectangle r;
+
+/*	print("R %d,%d %d,%d\n", x0, y0, x1, y1); /**/
+	r = Rect(p->c.x+x0, p->c.y+y0, p->c.x+x1+1, p->c.y+y1+1);
+	memdraw(p->dst, r, p->src, addpt(p->sp, r.min), memopaque, p00, p->op);
+}
+
+/*
+ * a brushed point similarly specified
+ */
+static
+void
+epoint(int x, int y, Param *p)
+{
+	Point p0;
+	Rectangle r;
+
+/*	print("P%d %d,%d\n", p->t, x, y);	/**/
+	p0 = Pt(p->c.x+x, p->c.y+y);
+	r = Rpt(addpt(p0, p->disc->r.min), addpt(p0, p->disc->r.max));
+	memdraw(p->dst, r, p->src, addpt(p->sp, r.min), p->disc, p->disc->r.min, p->op);
+}
+
+/* 
+ * a brushed horizontal or vertical line similarly specified
+ */
+static
+void
+eline(int x0, int y0, int x1, int y1, Param *p)
+{
+/*	print("L%d %d,%d %d,%d\n", p->t, x0, y0, x1, y1); /**/
+	if(x1 > x0+1)
+		erect(x0+1, y0-p->t, x1-1, y1+p->t, p);
+	else if(y1 > y0+1)
+		erect(x0-p->t, y0+1, x1+p->t, y1-1, p);
+	epoint(x0, y0, p);
+	if(x1-x0 || y1-y0)
+		epoint(x1, y1, p);
+}
--- /dev/null
+++ b/libmemdraw/fillpoly.c
@@ -1,0 +1,523 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+typedef struct Seg	Seg;
+
+struct Seg
+{
+	Point	p0;
+	Point	p1;
+	long	num;
+	long	den;
+	long	dz;
+	long	dzrem;
+	long	z;
+	long	zerr;
+	long	d;
+};
+
+static	void	zsort(Seg **seg, Seg **ep);
+static	int	ycompare(void*, void*);
+static	int	xcompare(void*, void*);
+static	int	zcompare(void*, void*);
+static	void	xscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int, int, int, int);
+static	void	yscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int, int);
+
+static void
+fillcolor(Memimage *dst, int left, int right, int y, Memimage *src, Point p)
+{
+	int srcval;
+
+	USED(src);
+	srcval = p.x;
+	p.x = left;
+	p.y = y;
+	memset(byteaddr(dst, p), srcval, right-left);
+}
+
+static void
+fillline(Memimage *dst, int left, int right, int y, Memimage *src, Point p, int op)
+{
+	Rectangle r;
+
+	r.min.x = left;
+	r.min.y = y;
+	r.max.x = right;
+	r.max.y = y+1;
+	p.x += left;
+	p.y += y;
+	memdraw(dst, r, src, p, memopaque, p, op);
+}
+
+static void
+fillpoint(Memimage *dst, int x, int y, Memimage *src, Point p, int op)
+{
+	Rectangle r;
+
+	r.min.x = x;
+	r.min.y = y;
+	r.max.x = x+1;
+	r.max.y = y+1;
+	p.x += x;
+	p.y += y;
+	memdraw(dst, r, src, p, memopaque, p, op);
+}
+
+void
+memfillpoly(Memimage *dst, Point *vert, int nvert, int w, Memimage *src, Point sp, int op)
+{
+	_memfillpolysc(dst, vert, nvert, w, src, sp, 0, 0, 0, op);
+}
+
+void
+_memfillpolysc(Memimage *dst, Point *vert, int nvert, int w, Memimage *src, Point sp, int detail, int fixshift, int clipped, int op)
+{
+	Seg **seg, *segtab;
+	Point p0;
+	int i;
+
+	if(nvert == 0)
+		return;
+
+	seg = malloc((nvert+2)*sizeof(Seg*));
+	if(seg == nil)
+		return;
+	segtab = malloc((nvert+1)*sizeof(Seg));
+	if(segtab == nil) {
+		free(seg);
+		return;
+	}
+
+	sp.x = (sp.x - vert[0].x) >> fixshift;
+	sp.y = (sp.y - vert[0].y) >> fixshift;
+	p0 = vert[nvert-1];
+	if(!fixshift) {
+		p0.x <<= 1;
+		p0.y <<= 1;
+	}
+	for(i = 0; i < nvert; i++) {
+		segtab[i].p0 = p0;
+		p0 = vert[i];
+		if(!fixshift) {
+			p0.x <<= 1;
+			p0.y <<= 1;
+		}
+		segtab[i].p1 = p0;
+		segtab[i].d = 1;
+	}
+	if(!fixshift)
+		fixshift = 1;
+
+	xscan(dst, seg, segtab, nvert, w, src, sp, detail, fixshift, clipped, op);
+	if(detail)
+		yscan(dst, seg, segtab, nvert, w, src, sp, fixshift, op);
+
+	free(seg);
+	free(segtab);
+}
+
+static long
+mod(long x, long y)
+{
+	long z;
+
+	z = x%y;
+	if((long)(((ulong)z)^((ulong)y)) > 0 || z == 0)
+		return z;
+	return z + y;
+}
+
+static long
+sdiv(long x, long y)
+{
+	if((long)(((ulong)x)^((ulong)y)) >= 0 || x == 0)
+		return x/y;
+
+	return (x+((y>>30)|1))/y-1;
+}
+
+static long
+smuldivmod(long x, long y, long z, long *mod)
+{
+	vlong vx;
+
+	if(x == 0 || y == 0){
+		*mod = 0;
+		return 0;
+	}
+	vx = x;
+	vx *= y;
+	*mod = vx % z;
+	if(*mod < 0)
+		*mod += z;	/* z is always >0 */
+	if((vx < 0) == (z < 0))
+		return vx/z;
+	return -((-vx)/z);
+}
+
+static void
+xscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int detail, int fixshift, int clipped, int op)
+{
+	long y, maxy, x, x2, xerr, xden, onehalf;
+	Seg **ep, **next, **p, **q, *s;
+	long n, i, iy, cnt, ix, ix2, minx, maxx;
+	Point pt;
+	void	(*fill)(Memimage*, int, int, int, Memimage*, Point, int);
+
+	fill = fillline;
+/*
+ * This can only work on 8-bit destinations, since fillcolor is
+ * just using memset on sp.x.
+ *
+ * I'd rather not even enable it then, since then if the general
+ * code is too slow, someone will come up with a better improvement
+ * than this sleazy hack.	-rsc
+ *
+	if(clipped && (src->flags&Frepl) && src->depth==8 && Dx(src->r)==1 && Dy(src->r)==1) {
+		fill = fillcolor;
+		sp.x = membyteval(src);
+	}
+ *
+ */
+	USED(clipped);
+
+
+	for(i=0, s=segtab, p=seg; i<nseg; i++, s++) {
+		*p = s;
+		if(s->p0.y == s->p1.y)
+			continue;
+		if(s->p0.y > s->p1.y) {
+			pt = s->p0;
+			s->p0 = s->p1;
+			s->p1 = pt;
+			s->d = -s->d;
+		}
+		s->num = s->p1.x - s->p0.x;
+		s->den = s->p1.y - s->p0.y;
+		s->dz = sdiv(s->num, s->den) << fixshift;
+		s->dzrem = mod(s->num, s->den) << fixshift;
+		s->dz += sdiv(s->dzrem, s->den);
+		s->dzrem = mod(s->dzrem, s->den);
+		p++;
+	}
+	n = p-seg;
+	if(n == 0)
+		return;
+	*p = 0;
+	qsort(seg, p-seg , sizeof(Seg*), ycompare);
+
+	onehalf = 0;
+	if(fixshift)
+		onehalf = 1 << (fixshift-1);
+
+	minx = dst->clipr.min.x;
+	maxx = dst->clipr.max.x;
+
+	y = seg[0]->p0.y;
+	if(y < (dst->clipr.min.y << fixshift))
+		y = dst->clipr.min.y << fixshift;
+	iy = (y + onehalf) >> fixshift;
+	y = (iy << fixshift) + onehalf;
+	maxy = dst->clipr.max.y << fixshift;
+
+	ep = next = seg;
+
+	while(y<maxy) {
+		for(q = p = seg; p < ep; p++) {
+			s = *p;
+			if(s->p1.y < y)
+				continue;
+			s->z += s->dz;
+			s->zerr += s->dzrem;
+			if(s->zerr >= s->den) {
+				s->z++;
+				s->zerr -= s->den;
+				if(s->zerr < 0 || s->zerr >= s->den)
+					print("bad ratzerr1: %ld den %ld dzrem %ld\n", s->zerr, s->den, s->dzrem);
+			}
+			*q++ = s;
+		}
+
+		for(p = next; *p; p++) {
+			s = *p;
+			if(s->p0.y >= y)
+				break;
+			if(s->p1.y < y)
+				continue;
+			s->z = s->p0.x;
+			s->z += smuldivmod(y - s->p0.y, s->num, s->den, &s->zerr);
+			if(s->zerr < 0 || s->zerr >= s->den)
+				print("bad ratzerr2: %ld den %ld ratdzrem %ld\n", s->zerr, s->den, s->dzrem);
+			*q++ = s;
+		}
+		ep = q;
+		next = p;
+
+		if(ep == seg) {
+			if(*next == 0)
+				break;
+			iy = (next[0]->p0.y + onehalf) >> fixshift;
+			y = (iy << fixshift) + onehalf;
+			continue;
+		}
+
+		zsort(seg, ep);
+
+		for(p = seg; p < ep; p++) {
+			cnt = 0;
+			x = p[0]->z;
+			xerr = p[0]->zerr;
+			xden = p[0]->den;
+			ix = (x + onehalf) >> fixshift;
+			if(ix >= maxx)
+				break;
+			if(ix < minx)
+				ix = minx;
+			cnt += p[0]->d;
+			p++;
+			for(;;) {
+				if(p == ep) {
+					print("xscan: fill to infinity");
+					return;
+				}
+				cnt += p[0]->d;
+				if((cnt&wind) == 0)
+					break;
+				p++;
+			}
+			x2 = p[0]->z;
+			ix2 = (x2 + onehalf) >> fixshift;
+			if(ix2 <= minx)
+				continue;
+			if(ix2 > maxx)
+				ix2 = maxx;
+			if(ix == ix2 && detail) {
+				if(xerr*p[0]->den + p[0]->zerr*xden > p[0]->den*xden)
+					x++;
+				ix = (x + x2) >> (fixshift+1);
+				ix2 = ix+1;
+			}
+			(*fill)(dst, ix, ix2, iy, src, sp, op);
+		}
+		y += (1<<fixshift);
+		iy++;
+	}
+}
+
+static void
+yscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int fixshift, int op)
+{
+	long x, maxx, y, y2, yerr, yden, onehalf;
+	Seg **ep, **next, **p, **q, *s;
+	int n, i, ix, cnt, iy, iy2, miny, maxy;
+	Point pt;
+
+	for(i=0, s=segtab, p=seg; i<nseg; i++, s++) {
+		*p = s;
+		if(s->p0.x == s->p1.x)
+			continue;
+		if(s->p0.x > s->p1.x) {
+			pt = s->p0;
+			s->p0 = s->p1;
+			s->p1 = pt;
+			s->d = -s->d;
+		}
+		s->num = s->p1.y - s->p0.y;
+		s->den = s->p1.x - s->p0.x;
+		s->dz = sdiv(s->num, s->den) << fixshift;
+		s->dzrem = mod(s->num, s->den) << fixshift;
+		s->dz += sdiv(s->dzrem, s->den);
+		s->dzrem = mod(s->dzrem, s->den);
+		p++;
+	}
+	n = p-seg;
+	if(n == 0)
+		return;
+	*p = 0;
+	qsort(seg, n , sizeof(Seg*), xcompare);
+
+	onehalf = 0;
+	if(fixshift)
+		onehalf = 1 << (fixshift-1);
+
+	miny = dst->clipr.min.y;
+	maxy = dst->clipr.max.y;
+
+	x = seg[0]->p0.x;
+	if(x < (dst->clipr.min.x << fixshift))
+		x = dst->clipr.min.x << fixshift;
+	ix = (x + onehalf) >> fixshift;
+	x = (ix << fixshift) + onehalf;
+	maxx = dst->clipr.max.x << fixshift;
+
+	ep = next = seg;
+
+	while(x<maxx) {
+		for(q = p = seg; p < ep; p++) {
+			s = *p;
+			if(s->p1.x < x)
+				continue;
+			s->z += s->dz;
+			s->zerr += s->dzrem;
+			if(s->zerr >= s->den) {
+				s->z++;
+				s->zerr -= s->den;
+				if(s->zerr < 0 || s->zerr >= s->den)
+					print("bad ratzerr1: %ld den %ld ratdzrem %ld\n", s->zerr, s->den, s->dzrem);
+			}
+			*q++ = s;
+		}
+
+		for(p = next; *p; p++) {
+			s = *p;
+			if(s->p0.x >= x)
+				break;
+			if(s->p1.x < x)
+				continue;
+			s->z = s->p0.y;
+			s->z += smuldivmod(x - s->p0.x, s->num, s->den, &s->zerr);
+			if(s->zerr < 0 || s->zerr >= s->den)
+				print("bad ratzerr2: %ld den %ld ratdzrem %ld\n", s->zerr, s->den, s->dzrem);
+			*q++ = s;
+		}
+		ep = q;
+		next = p;
+
+		if(ep == seg) {
+			if(*next == 0)
+				break;
+			ix = (next[0]->p0.x + onehalf) >> fixshift;
+			x = (ix << fixshift) + onehalf;
+			continue;
+		}
+
+		zsort(seg, ep);
+
+		for(p = seg; p < ep; p++) {
+			cnt = 0;
+			y = p[0]->z;
+			yerr = p[0]->zerr;
+			yden = p[0]->den;
+			iy = (y + onehalf) >> fixshift;
+			if(iy >= maxy)
+				break;
+			if(iy < miny)
+				iy = miny;
+			cnt += p[0]->d;
+			p++;
+			for(;;) {
+				if(p == ep) {
+					print("yscan: fill to infinity");
+					return;
+				}
+				cnt += p[0]->d;
+				if((cnt&wind) == 0)
+					break;
+				p++;
+			}
+			y2 = p[0]->z;
+			iy2 = (y2 + onehalf) >> fixshift;
+			if(iy2 <= miny)
+				continue;
+			if(iy2 > maxy)
+				iy2 = maxy;
+			if(iy == iy2) {
+				if(yerr*p[0]->den + p[0]->zerr*yden > p[0]->den*yden)
+					y++;
+				iy = (y + y2) >> (fixshift+1);
+				fillpoint(dst, ix, iy, src, sp, op);
+			}
+		}
+		x += (1<<fixshift);
+		ix++;
+	}
+}
+
+static void
+zsort(Seg **seg, Seg **ep)
+{
+	int done;
+	Seg **q, **p, *s;
+
+	if(ep-seg < 20) {
+		/* bubble sort by z - they should be almost sorted already */
+		q = ep;
+		do {
+			done = 1;
+			q--;
+			for(p = seg; p < q; p++) {
+				if(p[0]->z > p[1]->z) {
+					s = p[0];
+					p[0] = p[1];
+					p[1] = s;
+					done = 0;
+				}
+			}
+		} while(!done);
+	} else {
+		q = ep-1;
+		for(p = seg; p < q; p++) {
+			if(p[0]->z > p[1]->z) {
+				qsort(seg, ep-seg, sizeof(Seg*), zcompare);
+				break;
+			}
+		}
+	}
+}
+
+static int
+ycompare(void *a, void *b)
+{
+	Seg **s0, **s1;
+	long y0, y1;
+
+	s0 = a;
+	s1 = b;
+	y0 = (*s0)->p0.y;
+	y1 = (*s1)->p0.y;
+
+	if(y0 < y1)
+		return -1;
+	if(y0 == y1)
+		return 0;
+	return 1;
+}
+
+static int
+xcompare(void *a, void *b)
+{
+	Seg **s0, **s1;
+	long x0, x1;
+
+	s0 = a;
+	s1 = b;
+	x0 = (*s0)->p0.x;
+	x1 = (*s1)->p0.x;
+
+	if(x0 < x1)
+		return -1;
+	if(x0 == x1)
+		return 0;
+	return 1;
+}
+
+static int
+zcompare(void *a, void *b)
+{
+	Seg **s0, **s1;
+	long z0, z1;
+
+	s0 = a;
+	s1 = b;
+	z0 = (*s0)->z;
+	z1 = (*s1)->z;
+
+	if(z0 < z1)
+		return -1;
+	if(z0 == z1)
+		return 0;
+	return 1;
+}
--- /dev/null
+++ b/libmemdraw/hwdraw.c
@@ -1,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+hwdraw(Memdrawparam *p)
+{
+	USED(p);
+	return 0;	/* could not satisfy request */
+}
+
--- /dev/null
+++ b/libmemdraw/iprint.c
@@ -1,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+iprint(char *fmt,...)
+{
+	USED(fmt);
+	return -1;
+}
+
--- /dev/null
+++ b/libmemdraw/line.c
@@ -1,0 +1,485 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+enum
+{
+	Arrow1 = 8,
+	Arrow2 = 10,
+	Arrow3 = 3,
+};
+
+static
+int
+lmin(int a, int b)
+{
+	if(a < b)
+		return a;
+	return b;
+}
+
+static
+int
+lmax(int a, int b)
+{
+	if(a > b)
+		return a;
+	return b;
+}
+
+#ifdef NOTUSED
+/*
+ * Rather than line clip, we run the Bresenham loop over the full line,
+ * and clip on each pixel.  This is more expensive but means that
+ * lines look the same regardless of how the windowing has tiled them.
+ * For speed, we check for clipping outside the loop and make the
+ * test easy when possible.
+ */
+
+static
+void
+horline1(Memimage *dst, Point p0, Point p1, int srcval, Rectangle clipr)
+{
+	int x, y, dy, deltay, deltax, maxx;
+	int dd, easy, e, bpp, m, m0;
+	uchar *d;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	dd = dst->width*sizeof(ulong);
+	dy = 1;
+	if(deltay < 0){
+		dd = -dd;
+		deltay = -deltay;
+		dy = -1;
+	}
+	maxx = lmin(p1.x, clipr.max.x-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	m = m0 >> (p0.x&(7/dst->depth))*bpp;
+	easy = ptinrect(p0, clipr) && ptinrect(p1, clipr);
+	e = 2*deltay - deltax;
+	y = p0.y;
+	d = byteaddr(dst, p0);
+	deltay *= 2;
+	deltax = deltay - 2*deltax;
+	for(x=p0.x; x<=maxx; x++){
+		if(easy || (clipr.min.x<=x && clipr.min.y<=y && y<clipr.max.y))
+			*d ^= (*d^srcval) & m;
+		if(e > 0){
+			y += dy;
+			d += dd;
+			e += deltax;
+		}else
+			e += deltay;
+		d++;
+		m >>= bpp;
+		if(m == 0)
+			m = m0;
+	}
+}
+
+static
+void
+verline1(Memimage *dst, Point p0, Point p1, int srcval, Rectangle clipr)
+{
+	int x, y, deltay, deltax, maxy;
+	int easy, e, bpp, m, m0, dd;
+	uchar *d;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	dd = 1;
+	if(deltax < 0){
+		dd = -1;
+		deltax = -deltax;
+	}
+	maxy = lmin(p1.y, clipr.max.y-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	m = m0 >> (p0.x&(7/dst->depth))*bpp;
+	easy = ptinrect(p0, clipr) && ptinrect(p1, clipr);
+	e = 2*deltax - deltay;
+	x = p0.x;
+	d = byteaddr(dst, p0);
+	deltax *= 2;
+	deltay = deltax - 2*deltay;
+	for(y=p0.y; y<=maxy; y++){
+		if(easy || (clipr.min.y<=y && clipr.min.x<=x && x<clipr.max.x))
+			*d ^= (*d^srcval) & m;
+		if(e > 0){
+			x += dd;
+			d += dd;
+			e += deltay;
+		}else
+			e += deltax;
+		d += dst->width*sizeof(ulong);
+		m >>= bpp;
+		if(m == 0)
+			m = m0;
+	}
+}
+
+static
+void
+horliner(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
+{
+	int x, y, sx, sy, deltay, deltax, minx, maxx;
+	int bpp, m, m0;
+	uchar *d, *s;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	sx = drawreplxy(src->r.min.x, src->r.max.x, p0.x+dsrc.x);
+	minx = lmax(p0.x, clipr.min.x);
+	maxx = lmin(p1.x, clipr.max.x-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	m = m0 >> (minx&(7/dst->depth))*bpp;
+	for(x=minx; x<=maxx; x++){
+		y = p0.y + (deltay*(x-p0.x)+deltax/2)/deltax;
+		if(clipr.min.y<=y && y<clipr.max.y){
+			d = byteaddr(dst, Pt(x, y));
+			sy = drawreplxy(src->r.min.y, src->r.max.y, y+dsrc.y);
+			s = byteaddr(src, Pt(sx, sy));
+			*d ^= (*d^*s) & m;
+		}
+		if(++sx >= src->r.max.x)
+			sx = src->r.min.x;
+		m >>= bpp;
+		if(m == 0)
+			m = m0;
+	}
+}
+
+static
+void
+verliner(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
+{
+	int x, y, sx, sy, deltay, deltax, miny, maxy;
+	int bpp, m, m0;
+	uchar *d, *s;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	sy = drawreplxy(src->r.min.y, src->r.max.y, p0.y+dsrc.y);
+	miny = lmax(p0.y, clipr.min.y);
+	maxy = lmin(p1.y, clipr.max.y-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	for(y=miny; y<=maxy; y++){
+		if(deltay == 0)	/* degenerate line */
+			x = p0.x;
+		else
+			x = p0.x + (deltax*(y-p0.y)+deltay/2)/deltay;
+		if(clipr.min.x<=x && x<clipr.max.x){
+			m = m0 >> (x&(7/dst->depth))*bpp;
+			d = byteaddr(dst, Pt(x, y));
+			sx = drawreplxy(src->r.min.x, src->r.max.x, x+dsrc.x);
+			s = byteaddr(src, Pt(sx, sy));
+			*d ^= (*d^*s) & m;
+		}
+		if(++sy >= src->r.max.y)
+			sy = src->r.min.y;
+	}
+}
+
+static
+void
+horline(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
+{
+	int x, y, deltay, deltax, minx, maxx;
+	int bpp, m, m0;
+	uchar *d, *s;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	minx = lmax(p0.x, clipr.min.x);
+	maxx = lmin(p1.x, clipr.max.x-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	m = m0 >> (minx&(7/dst->depth))*bpp;
+	for(x=minx; x<=maxx; x++){
+		y = p0.y + (deltay*(x-p0.x)+deltay/2)/deltax;
+		if(clipr.min.y<=y && y<clipr.max.y){
+			d = byteaddr(dst, Pt(x, y));
+			s = byteaddr(src, addpt(dsrc, Pt(x, y)));
+			*d ^= (*d^*s) & m;
+		}
+		m >>= bpp;
+		if(m == 0)
+			m = m0;
+	}
+}
+
+static
+void
+verline(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
+{
+	int x, y, deltay, deltax, miny, maxy;
+	int bpp, m, m0;
+	uchar *d, *s;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	miny = lmax(p0.y, clipr.min.y);
+	maxy = lmin(p1.y, clipr.max.y-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	for(y=miny; y<=maxy; y++){
+		if(deltay == 0)	/* degenerate line */
+			x = p0.x;
+		else
+			x = p0.x + deltax*(y-p0.y)/deltay;
+		if(clipr.min.x<=x && x<clipr.max.x){
+			m = m0 >> (x&(7/dst->depth))*bpp;
+			d = byteaddr(dst, Pt(x, y));
+			s = byteaddr(src, addpt(dsrc, Pt(x, y)));
+			*d ^= (*d^*s) & m;
+		}
+	}
+}
+#endif /* NOTUSED */
+
+static Memimage*
+membrush(int radius)
+{
+	static Memimage *brush;
+	static int brushradius;
+
+	if(brush==nil || brushradius!=radius){
+		freememimage(brush);
+		brush = allocmemimage(Rect(0, 0, 2*radius+1, 2*radius+1), memopaque->chan);
+		if(brush != nil){
+			memfillcolor(brush, DTransparent);	/* zeros */
+			memellipse(brush, Pt(radius, radius), radius, radius, -1, memopaque, Pt(radius, radius), S);
+		}
+		brushradius = radius;
+	}
+	return brush;
+}
+
+static
+void
+discend(Point p, int radius, Memimage *dst, Memimage *src, Point dsrc, int op)
+{
+	Memimage *disc;
+	Rectangle r;
+
+	disc = membrush(radius);
+	if(disc != nil){
+		r.min.x = p.x - radius;
+		r.min.y = p.y - radius;
+		r.max.x = p.x + radius+1;
+		r.max.y = p.y + radius+1;
+		memdraw(dst, r, src, addpt(r.min, dsrc), disc, Pt(0,0), op);
+	}
+}
+
+static
+void
+arrowend(Point tip, Point *pp, int end, int sin, int cos, int radius)
+{
+	int x1, x2, x3;
+
+	/* before rotation */
+	if(end == Endarrow){
+		x1 = Arrow1;
+		x2 = Arrow2;
+		x3 = Arrow3;
+	}else{
+		x1 = (end>>5) & 0x1FF;	/* distance along line from end of line to tip */
+		x2 = (end>>14) & 0x1FF;	/* distance along line from barb to tip */
+		x3 = (end>>23) & 0x1FF;	/* distance perpendicular from edge of line to barb */
+	}
+
+	/* comments follow track of right-facing arrowhead */
+	pp->x = tip.x+((2*radius+1)*sin/2-x1*cos);		/* upper side of shaft */
+	pp->y = tip.y-((2*radius+1)*cos/2+x1*sin);
+	pp++;
+	pp->x = tip.x+((2*radius+2*x3+1)*sin/2-x2*cos);		/* upper barb */
+	pp->y = tip.y-((2*radius+2*x3+1)*cos/2+x2*sin);
+	pp++;
+	pp->x = tip.x;
+	pp->y = tip.y;
+	pp++;
+	pp->x = tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos);	/* lower barb */
+	pp->y = tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin);
+	pp++;
+	pp->x = tip.x+(-(2*radius+1)*sin/2-x1*cos);		/* lower side of shaft */
+	pp->y = tip.y+((2*radius+1)*cos/2-x1*sin);
+}
+
+void
+_memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, Rectangle clipr, int op)
+{
+	/*
+	 * BUG: We should really really pick off purely horizontal and purely
+	 * vertical lines and handle them separately with calls to memimagedraw
+	 * on rectangles.
+	 */
+
+	int hor;
+	int sin, cos, dx, dy, t;
+	Rectangle oclipr, r;
+	Point q, pts[10], *pp, d;
+
+	if(radius < 0)
+		return;
+	if(rectclip(&clipr, dst->r) == 0)
+		return;
+	if(rectclip(&clipr, dst->clipr) == 0)
+		return;
+	d = subpt(sp, p0);
+	if(rectclip(&clipr, rectsubpt(src->clipr, d)) == 0)
+		return;
+	if((src->flags&Frepl)==0 && rectclip(&clipr, rectsubpt(src->r, d))==0)
+		return;
+	/* this means that only verline() handles degenerate lines (p0==p1) */
+	hor = (abs(p1.x-p0.x) > abs(p1.y-p0.y));
+	/*
+	 * Clipping is a little peculiar.  We can't use Sutherland-Cohen
+	 * clipping because lines are wide.  But this is probably just fine:
+	 * we do all math with the original p0 and p1, but clip when deciding
+	 * what pixels to draw.  This means the layer code can call this routine,
+	 * using clipr to define the region being written, and get the same set
+	 * of pixels regardless of the dicing.
+	 */
+	if((hor && p0.x>p1.x) || (!hor && p0.y>p1.y)){
+		q = p0;
+		p0 = p1;
+		p1 = q;
+		t = end0;
+		end0 = end1;
+		end1 = t;
+	}
+
+	if((p0.x == p1.x || p0.y == p1.y) && (end0&0x1F) == Endsquare && (end1&0x1F) == Endsquare){
+		r.min = p0;
+		r.max = p1;
+		if(p0.x == p1.x){
+			r.min.x -= radius;
+			r.max.x += radius+1;
+		}
+		else{
+			r.min.y -= radius;
+			r.max.y += radius+1;
+		}
+		oclipr = dst->clipr;
+		dst->clipr = clipr;
+		memimagedraw(dst, r, src, sp, memopaque, sp, op);
+		dst->clipr = oclipr;
+		return;
+	}
+
+/*    Hard: */
+	/* draw thick line using polygon fill */
+	icossin2(p1.x-p0.x, p1.y-p0.y, &cos, &sin);
+	dx = (sin*(2*radius+1))/2;
+	dy = (cos*(2*radius+1))/2;
+	pp = pts;
+	oclipr = dst->clipr;
+	dst->clipr = clipr;
+	q.x = ICOSSCALE*p0.x+ICOSSCALE/2-cos/2;
+	q.y = ICOSSCALE*p0.y+ICOSSCALE/2-sin/2;
+	switch(end0 & 0x1F){
+	case Enddisc:
+		discend(p0, radius, dst, src, d, op);
+		/* fall through */
+	case Endsquare:
+	default:
+		pp->x = q.x-dx;
+		pp->y = q.y+dy;
+		pp++;
+		pp->x = q.x+dx;
+		pp->y = q.y-dy;
+		pp++;
+		break;
+	case Endarrow:
+		arrowend(q, pp, end0, -sin, -cos, radius);
+		_memfillpolysc(dst, pts, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
+		pp[1] = pp[4];
+		pp += 2;
+	}
+	q.x = ICOSSCALE*p1.x+ICOSSCALE/2+cos/2;
+	q.y = ICOSSCALE*p1.y+ICOSSCALE/2+sin/2;
+	switch(end1 & 0x1F){
+	case Enddisc:
+		discend(p1, radius, dst, src, d, op);
+		/* fall through */
+	case Endsquare:
+	default:
+		pp->x = q.x+dx;
+		pp->y = q.y-dy;
+		pp++;
+		pp->x = q.x-dx;
+		pp->y = q.y+dy;
+		pp++;
+		break;
+	case Endarrow:
+		arrowend(q, pp, end1, sin, cos, radius);
+		_memfillpolysc(dst, pp, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
+		pp[1] = pp[4];
+		pp += 2;
+	}
+	_memfillpolysc(dst, pts, pp-pts, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 0, 10, 1, op);
+	dst->clipr = oclipr;
+	return;
+}
+
+void
+memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+	_memimageline(dst, p0, p1, end0, end1, radius, src, sp, dst->clipr, op);
+}
+
+/*
+ * Simple-minded conservative code to compute bounding box of line.
+ * Result is probably a little larger than it needs to be.
+ */
+static
+void
+addbbox(Rectangle *r, Point p)
+{
+	if(r->min.x > p.x)
+		r->min.x = p.x;
+	if(r->min.y > p.y)
+		r->min.y = p.y;
+	if(r->max.x < p.x+1)
+		r->max.x = p.x+1;
+	if(r->max.y < p.y+1)
+		r->max.y = p.y+1;
+}
+
+int
+memlineendsize(int end)
+{
+	int x3;
+
+	if((end&0x3F) != Endarrow)
+		return 0;
+	if(end == Endarrow)
+		x3 = Arrow3;
+	else
+		x3 = (end>>23) & 0x1FF;
+	return x3;
+}
+
+Rectangle
+memlinebbox(Point p0, Point p1, int end0, int end1, int radius)
+{
+	Rectangle r, r1;
+	int extra;
+
+	r.min.x = 10000000;
+	r.min.y = 10000000;
+	r.max.x = -10000000;
+	r.max.y = -10000000;
+	extra = lmax(memlineendsize(end0), memlineendsize(end1));
+	r1 = insetrect(canonrect(Rpt(p0, p1)), -(radius+extra));
+	addbbox(&r, r1.min);
+	addbbox(&r, r1.max);
+	return r;
+}
--- /dev/null
+++ b/libmemdraw/load.c
@@ -1,0 +1,72 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+_loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int y, l, lpart, rpart, mx, m, mr;
+	uchar *q;
+
+	if(!rectinrect(r, i->r))
+		return -1;
+	l = bytesperline(r, i->depth);
+	if(ndata < l*Dy(r))
+		return -1;
+	ndata = l*Dy(r);
+	q = byteaddr(i, r.min);
+	mx = 7/i->depth;
+	lpart = (r.min.x & mx) * i->depth;
+	rpart = (r.max.x & mx) * i->depth;
+	m = 0xFF >> lpart;
+	/* may need to do bit insertion on edges */
+	if(l == 1){	/* all in one byte */
+		if(rpart)
+			m ^= 0xFF >> rpart;
+		for(y=r.min.y; y<r.max.y; y++){
+			*q ^= (*data^*q) & m;
+			q += i->width*sizeof(ulong);
+			data++;
+		}
+		return ndata;
+	}
+	if(lpart==0 && rpart==0){	/* easy case */
+		for(y=r.min.y; y<r.max.y; y++){
+			memmove(q, data, l);
+			q += i->width*sizeof(ulong);
+			data += l;
+		}
+		return ndata;
+	}
+	mr = 0xFF ^ (0xFF >> rpart);
+	if(lpart!=0 && rpart==0){
+		for(y=r.min.y; y<r.max.y; y++){
+			*q ^= (*data^*q) & m;
+			if(l > 1)
+				memmove(q+1, data+1, l-1);
+			q += i->width*sizeof(ulong);
+			data += l;
+		}
+		return ndata;
+	}
+	if(lpart==0 && rpart!=0){
+		for(y=r.min.y; y<r.max.y; y++){
+			if(l > 1)
+				memmove(q, data, l-1);
+			q[l-1] ^= (data[l-1]^q[l-1]) & mr;
+			q += i->width*sizeof(ulong);
+			data += l;
+		}
+		return ndata;
+	}
+	for(y=r.min.y; y<r.max.y; y++){
+		*q ^= (*data^*q) & m;
+		if(l > 2)
+			memmove(q+1, data+1, l-2);
+		q[l-1] ^= (data[l-1]^q[l-1]) & mr;
+		q += i->width*sizeof(ulong);
+		data += l;
+	}
+	return ndata;
+}
--- /dev/null
+++ b/libmemdraw/mkcmap.c
@@ -1,0 +1,79 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+/*
+struct Memcmap
+{
+	uchar	cmap2rgb[3*256];
+	uchar	rgb2cmap[16*16*16];
+};
+*/
+
+static Memcmap*
+mkcmap(void)
+{
+	static Memcmap def;
+
+	int i, rgb, r, g, b;
+
+	for(i=0; i<256; i++){
+		rgb = cmap2rgb(i);
+		r = (rgb>>16)&0xff;
+		g = (rgb>>8)&0xff;
+		b = rgb&0xff;
+		def.cmap2rgb[3*i] = r;
+		def.cmap2rgb[3*i+1] = g;
+		def.cmap2rgb[3*i+2] = b;
+	}
+
+	for(r=0; r<16; r++)
+	for(g=0; g<16; g++)
+	for(b=0; b<16; b++)
+		def.rgb2cmap[r*16*16+g*16+b] = rgb2cmap(r*0x11, g*0x11, b*0x11);
+	return &def;
+}
+
+void
+main(int argc, char **argv)
+{
+	Memcmap *c;
+	int i, j, inferno;
+
+	inferno = 0;
+	ARGBEGIN{
+	case 'i':
+		inferno = 1;
+	}ARGEND
+
+	memimageinit();
+	c = mkcmap();
+	if(!inferno)
+		print("#include <u.h>\n#include <libc.h>\n");
+	else
+		print("#include \"lib9.h\"\n");
+	print("#include <draw.h>\n");
+	print("#include <memdraw.h>\n\n");
+	print("static Memcmap def = {\n");
+	print("/* cmap2rgb */ {\n");
+	for(i=0; i<sizeof(c->cmap2rgb); ){
+		print("\t");
+		for(j=0; j<16; j++, i++)
+			print("0x%2.2ux,", c->cmap2rgb[i]);
+		print("\n");
+	}
+	print("},\n");
+	print("/* rgb2cmap */ {\n");
+	for(i=0; i<sizeof(c->rgb2cmap);){
+		print("\t");
+		for(j=0; j<16; j++, i++)
+			print("0x%2.2ux,", c->rgb2cmap[i]);
+		print("\n");
+	}
+	print("}\n");
+	print("};\n");
+	print("Memcmap *memdefcmap = &def;\n");
+	print("void _memmkcmap(void){}\n");
+	exits(0);
+}
--- /dev/null
+++ b/libmemdraw/mkfile
@@ -1,0 +1,27 @@
+<$DSRC/mkfile-$CONF
+TARG=libmemdraw.$L
+
+OFILES=\
+	alloc.$O\
+	arc.$O\
+	cload.$O\
+	cmap.$O\
+	cread.$O\
+	defont.$O\
+	draw.$O\
+	ellipse.$O\
+	fillpoly.$O\
+	hwdraw.$O\
+	line.$O\
+	load.$O\
+	openmemsubfont.$O\
+	poly.$O\
+	read.$O\
+	string.$O\
+	subfont.$O\
+	unload.$O\
+	write.$O
+
+HFILE=\
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/libmemdraw/openmemsubfont.c
@@ -1,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memsubfont*
+openmemsubfont(char *name)
+{
+	Memsubfont *sf;
+	Memimage *i;
+	Fontchar *fc;
+	int fd, n;
+	char hdr[3*12+4+1];
+	uchar *p;
+
+	fd = open(name, OREAD);
+	if(fd < 0)
+		return nil;
+	p = nil;
+	i = readmemimage(fd);
+	if(i == nil)
+		goto Err;
+	if(read(fd, hdr, 3*12) != 3*12){
+		werrstr("openmemsubfont: header read error: %r");
+		goto Err;
+	}
+	n = atoi(hdr);
+	p = malloc(6*(n+1));
+	if(p == nil)
+		goto Err;
+	if(read(fd, p, 6*(n+1)) != 6*(n+1)){
+		werrstr("openmemsubfont: fontchar read error: %r");
+		goto Err;
+	}
+	fc = malloc(sizeof(Fontchar)*(n+1));
+	if(fc == nil)
+		goto Err;
+	_unpackinfo(fc, p, n);
+	sf = allocmemsubfont(name, n, atoi(hdr+12), atoi(hdr+24), fc, i);
+	if(sf == nil){
+		free(fc);
+		goto Err;
+	}
+	free(p);
+	return sf;
+Err:
+	close(fd);
+	if (i != nil)
+		freememimage(i);
+	if (p != nil)
+		free(p);
+	return nil;
+}
--- /dev/null
+++ b/libmemdraw/poly.c
@@ -1,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+void
+mempoly(Memimage *dst, Point *vert, int nvert, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+	int i, e0, e1;
+	Point d;
+
+	if(nvert < 2)
+		return;
+	d = subpt(sp, vert[0]);
+	for(i=1; i<nvert; i++){
+		e0 = e1 = Enddisc;
+		if(i == 1)
+			e0 = end0;
+		if(i == nvert-1)
+			e1 = end1;
+		memline(dst, vert[i-1], vert[i], e0, e1, radius, src, addpt(d, vert[i-1]), op);
+	}
+}
--- /dev/null
+++ b/libmemdraw/read.c
@@ -1,0 +1,111 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memimage*
+readmemimage(int fd)
+{
+	char hdr[5*12+1];
+	int dy;
+	ulong chan;
+	uint l, n;
+	int m, j;
+	int new, miny, maxy;
+	Rectangle r;
+	uchar *tmp;
+	int ldepth, chunk;
+	Memimage *i;
+
+	if(readn(fd, hdr, 11) != 11){
+		werrstr("readimage: short header");
+		return nil;
+	}
+	if(memcmp(hdr, "compressed\n", 11) == 0)
+		return creadmemimage(fd);
+	if(readn(fd, hdr+11, 5*12-11) != 5*12-11){
+		werrstr("readimage: short header (2)");
+		return nil;
+	}
+
+	/*
+	 * distinguish new channel descriptor from old ldepth.
+	 * channel descriptors have letters as well as numbers,
+	 * while ldepths are a single digit formatted as %-11d.
+	 */
+	new = 0;
+	for(m=0; m<10; m++){
+		if(hdr[m] != ' '){
+			new = 1;
+			break;
+		}
+	}
+	if(hdr[11] != ' '){
+		werrstr("readimage: bad format");
+		return nil;
+	}
+	if(new){
+		hdr[11] = '\0';
+		if((chan = strtochan(hdr)) == 0){
+			werrstr("readimage: bad channel string %s", hdr);
+			return nil;
+		}
+	}else{
+		ldepth = ((int)hdr[10])-'0';
+		if(ldepth<0 || ldepth>3){
+			werrstr("readimage: bad ldepth %d", ldepth);
+			return nil;
+		}
+		chan = drawld2chan[ldepth];
+	}
+
+	r.min.x = atoi(hdr+1*12);
+	r.min.y = atoi(hdr+2*12);
+	r.max.x = atoi(hdr+3*12);
+	r.max.y = atoi(hdr+4*12);
+	if(r.min.x>r.max.x || r.min.y>r.max.y){
+		werrstr("readimage: bad rectangle");
+		return nil;
+	}
+
+	miny = r.min.y;
+	maxy = r.max.y;
+
+	l = bytesperline(r, chantodepth(chan));
+	i = allocmemimage(r, chan);
+	if(i == nil)
+		return nil;
+	chunk = 32*1024;
+	if(chunk < l)
+		chunk = l;
+	tmp = malloc(chunk);
+	if(tmp == nil)
+		goto Err;
+	while(maxy > miny){
+		dy = maxy - miny;
+		if(dy*l > chunk)
+			dy = chunk/l;
+		if(dy <= 0){
+			werrstr("readmemimage: image too wide for buffer");
+			goto Err;
+		}
+		n = dy*l;
+		m = readn(fd, tmp, n);
+		if(m != n){
+			werrstr("readmemimage: read count %d not %d: %r", m, n);
+   Err:
+ 			freememimage(i);
+			free(tmp);
+			return nil;
+		}
+		if(!new)	/* an old image: must flip all the bits */
+			for(j=0; j<chunk; j++)
+				tmp[j] ^= 0xFF;
+
+		if(loadmemimage(i, Rect(r.min.x, miny, r.max.x, miny+dy), tmp, chunk) <= 0)
+			goto Err;
+		miny += dy;
+	}
+	free(tmp);
+	return i;
+}
--- /dev/null
+++ b/libmemdraw/string.c
@@ -1,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+Point
+memimagestring(Memimage *b, Point p, Memimage *color, Point cp, Memsubfont *f, char *cs)
+{
+	int w, width;
+	uchar *s;
+	Rune c;
+	Fontchar *i;
+
+	s = (uchar*)cs;
+	for(; c=*s; p.x+=width, cp.x+=width){
+		width = 0;
+		if(c < Runeself)
+			s++;
+		else{
+			w = chartorune(&c, (char*)s);
+			if(w == 0){
+				s++;
+				continue;
+			}
+			s += w;
+		}
+		if(c >= f->n)
+			continue;
+//		i = f->info+c;
+		i = &(f->info[c]);
+		width = i->width;
+		memdraw(b, Rect(p.x+i->left, p.y+i->top, p.x+i->left+(i[1].x-i[0].x), p.y+i->bottom),
+			color, cp, f->bits, Pt(i->x, i->top), SoverD);
+	}
+	return p;
+}
+
+Point
+memsubfontwidth(Memsubfont *f, char *cs)
+{
+	Rune c;
+	Point p;
+	uchar *s;
+	Fontchar *i;
+	int w, width;
+
+	p = Pt(0, f->height);
+	s = (uchar*)cs;
+	for(; c=*s; p.x+=width){
+		width = 0;
+		if(c < Runeself)
+			s++;
+		else{
+			w = chartorune(&c, (char*)s);
+			if(w == 0){
+				s++;
+				continue;
+			}
+			s += w;
+		}
+		if(c >= f->n)
+			continue;
+		i = f->info+c;
+		width = i->width;
+	}
+	return p;
+}
--- /dev/null
+++ b/libmemdraw/subfont.c
@@ -1,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memsubfont*
+allocmemsubfont(char *name, int n, int height, int ascent, Fontchar *info, Memimage *i)
+{
+	Memsubfont *f;
+
+	f = malloc(sizeof(Memsubfont));
+	if(f == 0)
+		return 0;
+	f->n = n;
+	f->height = height;
+	f->ascent = ascent;
+	f->info = info;
+	f->bits = i;
+	if(name)
+		f->name = strdup(name);
+	else
+		f->name = 0;
+	return f;
+}
+
+void
+freememsubfont(Memsubfont *f)
+{
+	if(f == 0)
+		return;
+	free(f->info);	/* note: f->info must have been malloc'ed! */
+	freememimage(f->bits);
+	free(f);
+}
--- /dev/null
+++ b/libmemdraw/times
@@ -1,0 +1,19 @@
+draw1:	6M for draw 0,0,100,100 no repl
+draw3:	4M for draw 0,0,100,100 no repl
+just read src, dst - 250k
+mask reading - 650k
+write dst - 100k
+alpha calculation - 3000k
+
+olddraw:	10M for draw 0, 0, 1000, 1000 no repl all ldepth 3
+		44M for draw 0, 0, 1000, 1000 src, mask ldepth 2 dst ldepth 3
+draw4:	160M for draw 0, 0, 1000, 1000 no repl all r8g8b8
+	null loop: 10k
+	src, dst reading: 13-15M each
+	mask reading: 30M
+	alpha calculation loop: 90M
+		null alpha loop: 2M
+		minimal loop control +20M
+		alpha calculation with divides +190M
+		alpha calculation wtih shifts +70M
+	writeback: 11M
--- /dev/null
+++ b/libmemdraw/unload.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+unloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int y, l;
+	uchar *q;
+
+	if(!rectinrect(r, i->r))
+		return -1;
+	l = bytesperline(r, i->depth);
+	if(ndata < l*Dy(r))
+		return -1;
+	ndata = l*Dy(r);
+	q = byteaddr(i, r.min);
+	for(y=r.min.y; y<r.max.y; y++){
+		memmove(data, q, l);
+		q += i->width*sizeof(ulong);
+		data += l;
+	}
+	return ndata;
+}
--- /dev/null
+++ b/libmemdraw/write.c
@@ -1,0 +1,183 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+#define	CHUNK	8000
+
+#define	HSHIFT	3	/* HSHIFT==5 runs slightly faster, but hash table is 64x bigger */
+#define	NHASH	(1<<(HSHIFT*NMATCH))
+#define	HMASK	(NHASH-1)
+#define	hupdate(h, c)	((((h)<<HSHIFT)^(c))&HMASK)
+typedef struct Hlist Hlist;
+struct Hlist{
+	uchar *s;
+	Hlist *next, *prev;
+};
+
+int
+writememimage(int fd, Memimage *i)
+{
+	uchar *outbuf, *outp, *eout;		/* encoded data, pointer, end */
+	uchar *loutp;				/* start of encoded line */
+	Hlist *hash;				/* heads of hash chains of past strings */
+	Hlist *chain, *hp;			/* hash chain members, pointer */
+	Hlist *cp;				/* next Hlist to fall out of window */
+	int h;					/* hash value */
+	uchar *line, *eline;			/* input line, end pointer */
+	uchar *data, *edata;			/* input buffer, end pointer */
+	ulong n;				/* length of input buffer */
+	ulong nb;				/* # of bytes returned by unloadimage */
+	int bpl;				/* input line length */
+	int offs, runlen;			/* offset, length of consumed data */
+	uchar dumpbuf[NDUMP];			/* dump accumulator */
+	int ndump;				/* length of dump accumulator */
+	int miny, dy;				/* y values while unloading input */
+	int ncblock;				/* size of compressed blocks */
+	Rectangle r;
+	uchar *p, *q, *s, *es, *t;
+	char hdr[11+5*12+1];
+	char cbuf[20];
+
+	r = i->r;
+	bpl = bytesperline(r, i->depth);
+	n = Dy(r)*bpl;
+	data = malloc(n);
+	ncblock = _compblocksize(r, i->depth);
+	outbuf = malloc(ncblock);
+	hash = malloc(NHASH*sizeof(Hlist));
+	chain = malloc(NMEM*sizeof(Hlist));
+	if(data == 0 || outbuf == 0 || hash == 0 || chain == 0){
+	ErrOut:
+		free(data);
+		free(outbuf);
+		free(hash);
+		free(chain);
+		return -1;
+	}
+	for(miny = r.min.y; miny != r.max.y; miny += dy){
+		dy = r.max.y-miny;
+		if(dy*bpl > CHUNK)
+			dy = CHUNK/bpl;
+		nb = unloadmemimage(i, Rect(r.min.x, miny, r.max.x, miny+dy),
+			data+(miny-r.min.y)*bpl, dy*bpl);
+		if(nb != dy*bpl)
+			goto ErrOut;
+	}
+	sprint(hdr, "compressed\n%11s %11d %11d %11d %11d ",
+		chantostr(cbuf, i->chan), r.min.x, r.min.y, r.max.x, r.max.y);
+	if(write(fd, hdr, 11+5*12) != 11+5*12)
+		goto ErrOut;
+	edata = data+n;
+	eout = outbuf+ncblock;
+	line = data;
+	r.max.y = r.min.y;
+	while(line != edata){
+		memset(hash, 0, NHASH*sizeof(Hlist));
+		memset(chain, 0, NMEM*sizeof(Hlist));
+		cp = chain;
+		h = 0;
+		outp = outbuf;
+		for(n = 0; n != NMATCH; n++)
+			h = hupdate(h, line[n]);
+		loutp = outbuf;
+		while(line != edata){
+			ndump = 0;
+			eline = line+bpl;
+			for(p = line; p != eline; ){
+				if(eline-p < NRUN)
+					es = eline;
+				else
+					es = p+NRUN;
+				q = 0;
+				runlen = 0;
+				for(hp = hash[h].next; hp; hp = hp->next){
+					s = p + runlen;
+					if(s >= es)
+						continue;
+					t = hp->s + runlen;
+					for(; s >= p; s--)
+						if(*s != *t--)
+							goto matchloop;
+					t += runlen+2;
+					s += runlen+2;
+					for(; s < es; s++)
+						if(*s != *t++)
+							break;
+					n = s-p;
+					if(n > runlen){
+						runlen = n;
+						q = hp->s;
+						if(n == NRUN)
+							break;
+					}
+			matchloop: ;
+				}
+				if(runlen < NMATCH){
+					if(ndump == NDUMP){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					dumpbuf[ndump++] = *p;
+					runlen = 1;
+				}
+				else{
+					if(ndump != 0){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					offs = p-q-1;
+					if(eout-outp < 2)
+						goto Bfull;
+					*outp++ = ((runlen-NMATCH)<<2) + (offs>>8);
+					*outp++ = offs&255;
+				}
+				for(q = p+runlen; p != q; p++){
+					if(cp->prev)
+						cp->prev->next = 0;
+					cp->next = hash[h].next;
+					cp->prev = &hash[h];
+					if(cp->next)
+						cp->next->prev = cp;
+					cp->prev->next = cp;
+					cp->s = p;
+					if(++cp == &chain[NMEM])
+						cp = chain;
+					if(edata-p > NMATCH)
+						h = hupdate(h, p[NMATCH]);
+				}
+			}
+			if(ndump != 0){
+				if(eout-outp < ndump+1)
+					goto Bfull;
+				*outp++ = ndump-1+128;
+				memmove(outp, dumpbuf, ndump);
+				outp += ndump;
+			}
+			line = eline;
+			loutp = outp;
+			r.max.y++;
+		}
+	Bfull:
+		if(loutp == outbuf)
+			goto ErrOut;
+		n = loutp-outbuf;
+		sprint(hdr, "%11d %11ld ", r.max.y, n);
+		write(fd, hdr, 2*12);
+		write(fd, outbuf, n);
+		r.min.y = r.max.y;
+	}
+	free(data);
+	free(outbuf);
+	free(hash);
+	free(chain);
+	return 0;
+}
--- /dev/null
+++ b/libmemlayer/Makefile
@@ -1,0 +1,26 @@
+LIB=libmemlayer.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	draw.$O\
+	lalloc.$O\
+	layerop.$O\
+	ldelete.$O\
+	lhide.$O\
+	line.$O\
+	load.$O\
+	lorigin.$O\
+	lsetrefresh.$O\
+	ltofront.$O\
+	ltorear.$O\
+	unload.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libmemlayer/draw.c
@@ -1,0 +1,192 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+struct Draw
+{
+	Point	deltas;
+	Point	deltam;
+	Memlayer		*dstlayer;
+	Memimage	*src;
+	Memimage	*mask;
+	int	op;
+};
+
+static
+void
+ldrawop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	struct Draw *d;
+	Point p0, p1;
+	Rectangle oclipr, srcr, r, mr;
+	int ok;
+
+	d = etc;
+	if(insave && d->dstlayer->save==nil)
+		return;
+
+	p0 = addpt(screenr.min, d->deltas);
+	p1 = addpt(screenr.min, d->deltam);
+
+	if(insave){
+		r = rectsubpt(screenr, d->dstlayer->delta);
+		clipr = rectsubpt(clipr, d->dstlayer->delta);
+	}else
+		r = screenr;
+
+	/* now in logical coordinates */
+
+	/* clipr may have narrowed what we should draw on, so clip if necessary */
+	if(!rectinrect(r, clipr)){
+		oclipr = dst->clipr;
+		dst->clipr = clipr;
+		ok = drawclip(dst, &r, d->src, &p0, d->mask, &p1, &srcr, &mr);
+		dst->clipr = oclipr;
+		if(!ok)
+			return;
+	}
+	memdraw(dst, r, d->src, p0, d->mask, p1, d->op);
+}
+
+void
+memdraw(Memimage *dst, Rectangle r, Memimage *src, Point p0, Memimage *mask, Point p1, int op)
+{
+	struct Draw d;
+	Rectangle srcr, tr, mr;
+	Memlayer *dl, *sl;
+
+	if(drawdebug)
+		iprint("memdraw %p %R %p %P %p %P\n", dst, r, src, p0, mask, p1);
+
+	if(mask == nil)
+		mask = memopaque;
+
+	if(mask->layer){
+if(drawdebug)	iprint("mask->layer != nil\n");
+		return;	/* too hard, at least for now */
+	}
+
+    Top:
+	if(dst->layer==nil && src->layer==nil){
+		memimagedraw(dst, r, src, p0, mask, p1, op);
+		return;
+	}
+
+	if(drawclip(dst, &r, src, &p0, mask, &p1, &srcr, &mr) == 0){
+if(drawdebug)	iprint("drawclip dstcr %R srccr %R maskcr %R\n", dst->clipr, src->clipr, mask->clipr);
+		return;
+	}
+
+	/*
+ 	 * Convert to screen coordinates.
+	 */
+	dl = dst->layer;
+	if(dl != nil){
+		r.min.x += dl->delta.x;
+		r.min.y += dl->delta.y;
+		r.max.x += dl->delta.x;
+		r.max.y += dl->delta.y;
+	}
+    Clearlayer:
+	if(dl!=nil && dl->clear){
+		if(src == dst){
+			p0.x += dl->delta.x;
+			p0.y += dl->delta.y;
+			src = dl->screen->image;
+		}
+		dst = dl->screen->image;
+		goto Top;
+	}
+
+	sl = src->layer;
+	if(sl != nil){
+		p0.x += sl->delta.x;
+		p0.y += sl->delta.y;
+		srcr.min.x += sl->delta.x;
+		srcr.min.y += sl->delta.y;
+		srcr.max.x += sl->delta.x;
+		srcr.max.y += sl->delta.y;
+	}
+
+	/*
+	 * Now everything is in screen coordinates.
+	 * mask is an image.  dst and src are images or obscured layers.
+	 */
+
+	/*
+	 * if dst and src are the same layer, just draw in save area and expose.
+	 */
+	if(dl!=nil && dst==src){
+		if(dl->save == nil)
+			return;	/* refresh function makes this case unworkable */
+		if(rectXrect(r, srcr)){
+			tr = r;
+			if(srcr.min.x < tr.min.x){
+				p1.x += tr.min.x - srcr.min.x;
+				tr.min.x = srcr.min.x;
+			}
+			if(srcr.min.y < tr.min.y){
+				p1.y += tr.min.x - srcr.min.x;
+				tr.min.y = srcr.min.y;
+			}
+			if(srcr.max.x > tr.max.x)
+				tr.max.x = srcr.max.x;
+			if(srcr.max.y > tr.max.y)
+				tr.max.y = srcr.max.y;
+			memlhide(dst, tr);
+		}else{
+			memlhide(dst, r);
+			memlhide(dst, srcr);
+		}
+		memdraw(dl->save, rectsubpt(r, dl->delta), dl->save,
+			subpt(srcr.min, src->layer->delta), mask, p1, op);
+		memlexpose(dst, r);
+		return;
+	}
+
+	if(sl){
+		if(sl->clear){
+			src = sl->screen->image;
+			if(dl != nil){
+				r.min.x -= dl->delta.x;
+				r.min.y -= dl->delta.y;
+				r.max.x -= dl->delta.x;
+				r.max.y -= dl->delta.y;
+			}
+			goto Top;
+		}
+		/* relatively rare case; use save area */
+		if(sl->save == nil)
+			return;	/* refresh function makes this case unworkable */
+		memlhide(src, srcr);
+		/* convert back to logical coordinates */
+		p0.x -= sl->delta.x;
+		p0.y -= sl->delta.y;
+		srcr.min.x -= sl->delta.x;
+		srcr.min.y -= sl->delta.y;
+		srcr.max.x -= sl->delta.x;
+		srcr.max.y -= sl->delta.y;
+		src = src->layer->save;
+	}
+
+	/*
+	 * src is now an image.  dst may be an image or a clear layer
+	 */
+	if(dst->layer==nil)
+		goto Top;
+	if(dst->layer->clear)
+		goto Clearlayer;
+
+	/*
+	 * dst is an obscured layer
+	 */
+	d.deltas = subpt(p0, r.min);
+	d.deltam = subpt(p1, r.min);
+	d.dstlayer = dl;
+	d.src = src;
+	d.op = op;
+	d.mask = mask;
+	_memlayerop(ldrawop, dst, r, r, &d);
+}
--- /dev/null
+++ b/libmemlayer/lalloc.c
@@ -1,0 +1,79 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+Memimage*
+memlalloc(Memscreen *s, Rectangle screenr, Refreshfn refreshfn, void *refreshptr, ulong val)
+{
+	Memlayer *l;
+	Memimage *n;
+	static Memimage *paint;
+
+	if(paint == nil){
+		paint = allocmemimage(Rect(0,0,1,1), RGBA32);
+		if(paint == nil)
+			return nil;
+		paint->flags |= Frepl;
+		paint->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+	}
+
+	n = allocmemimaged(screenr, s->image->chan, s->image->data, s->image->X);
+	if(n == nil)
+		return nil;
+	l = malloc(sizeof(Memlayer));
+	if(l == nil){
+		free(n);
+		return nil;
+	}
+
+	l->screen = s;
+	if(refreshfn)
+		l->save = nil;
+	else{
+		l->save = allocmemimage(screenr, s->image->chan);
+		if(l->save == nil){
+			free(l);
+			free(n);
+			return nil;
+		}
+		/* allocmemimage doesn't initialize memory; this paints save area */
+		if(val != DNofill)
+			memfillcolor(l->save, val);
+	}
+	l->refreshfn = refreshfn;
+	l->refreshptr = nil;	/* don't set it until we're done */
+	l->screenr = screenr;
+	l->delta = Pt(0,0);
+
+	n->data->ref++;
+	n->zero = s->image->zero;
+	n->width = s->image->width;
+	n->layer = l;
+
+	/* start with new window behind all existing ones */
+	l->front = s->rearmost;
+	l->rear = nil;
+	if(s->rearmost)
+		s->rearmost->layer->rear = n;
+	s->rearmost = n;
+	if(s->frontmost == nil)
+		s->frontmost = n;
+	l->clear = 0;
+
+	/* now pull new window to front */
+	_memltofrontfill(n, val != DNofill);
+	l->refreshptr = refreshptr;
+
+	/*
+	 * paint with requested color; previously exposed areas are already right
+	 * if this window has backing store, but just painting the whole thing is simplest.
+	 */
+	if(val != DNofill){
+		memsetchan(paint, n->chan);
+		memfillcolor(paint, val);
+		memdraw(n, n->r, paint, n->r.min, nil, n->r.min, S);
+	}
+	return n;
+}
--- /dev/null
+++ b/libmemlayer/layerop.c
@@ -1,0 +1,112 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+#define	RECUR(a,b,c,d)	_layerop(fn, i, Rect(a.x, b.y, c.x, d.y), clipr, etc, front->layer->rear);
+
+static void
+_layerop(
+	void (*fn)(Memimage*, Rectangle, Rectangle, void*, int),
+	Memimage *i,
+	Rectangle r,
+	Rectangle clipr,
+	void *etc,
+	Memimage *front)
+{
+	Rectangle fr;
+
+    Top:
+	if(front == i){
+		/* no one is in front of this part of window; use the screen */
+		fn(i->layer->screen->image, r, clipr, etc, 0);
+		return;
+	}
+	fr = front->layer->screenr;
+	if(rectXrect(r, fr) == 0){
+		/* r doesn't touch this window; continue on next rearmost */
+		// assert(front && front->layer && front->layer->screen && front->layer->rear);
+		front = front->layer->rear;
+		goto Top;
+	}
+	if(fr.max.y < r.max.y){
+		RECUR(r.min, fr.max, r.max, r.max);
+		r.max.y = fr.max.y;
+	}
+	if(r.min.y < fr.min.y){
+		RECUR(r.min, r.min, r.max, fr.min);
+		r.min.y = fr.min.y;
+	}
+	if(fr.max.x < r.max.x){
+		RECUR(fr.max, r.min, r.max, r.max);
+		r.max.x = fr.max.x;
+	}
+	if(r.min.x < fr.min.x){
+		RECUR(r.min, r.min, fr.min, r.max);
+		r.min.x = fr.min.x;
+	}
+	/* r is covered by front, so put in save area */
+	(*fn)(i->layer->save, r, clipr, etc, 1);
+}
+
+/*
+ * Assumes incoming rectangle has already been clipped to i's logical r and clipr
+ */
+void
+_memlayerop(
+	void (*fn)(Memimage*, Rectangle, Rectangle, void*, int),
+	Memimage *i,
+	Rectangle screenr,	/* clipped to window boundaries */
+	Rectangle clipr,		/* clipped also to clipping rectangles of hierarchy */
+	void *etc)
+{
+	Memlayer *l;
+	Rectangle r, scr;
+
+	l = i->layer;
+	if(!rectclip(&screenr, l->screenr))
+		return;
+	if(l->clear){
+		fn(l->screen->image, screenr, clipr, etc, 0);
+		return;
+	}
+	r = screenr;
+	scr = l->screen->image->clipr;
+
+	/*
+	 * Do the piece on the screen
+	 */
+	if(rectclip(&screenr, scr))
+		_layerop(fn, i, screenr, clipr, etc, l->screen->frontmost);
+	if(rectinrect(r, scr))
+		return;
+
+	/*
+	 * Do the piece off the screen
+	*/
+	if(!rectXrect(r, scr)){
+		/* completely offscreen; easy */
+		fn(l->save, r, clipr, etc, 1);
+		return;
+	}
+	if(r.min.y < scr.min.y){
+		/* above screen */
+		fn(l->save, Rect(r.min.x, r.min.y, r.max.x, scr.min.y), clipr, etc, 1);
+		r.min.y = scr.min.y;
+	}
+	if(r.max.y > scr.max.y){
+		/* below screen */
+		fn(l->save, Rect(r.min.x, scr.max.y, r.max.x, r.max.y), clipr, etc, 1);
+		r.max.y = scr.max.y;
+	}
+	if(r.min.x < scr.min.x){
+		/* left of screen */
+		fn(l->save, Rect(r.min.x, r.min.y, scr.min.x, r.max.y), clipr, etc, 1);
+		r.min.x = scr.min.x;
+	}
+	if(r.max.x > scr.max.x){
+		/* right of screen */
+		fn(l->save, Rect(scr.max.x, r.min.y, r.max.x, r.max.y), clipr, etc, 1);
+	}
+}
--- /dev/null
+++ b/libmemlayer/ldelete.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+void
+memldelete(Memimage *i)
+{
+	Memscreen *s;
+	Memlayer *l;
+
+	l = i->layer;
+	/* free backing store and disconnect refresh, to make pushback fast */
+	freememimage(l->save);
+	l->save = nil;
+	l->refreshptr = nil;
+	memltorear(i);
+
+	/* window is now the rearmost;  clean up screen structures and deallocate */
+	s = i->layer->screen;
+	if(s->fill){
+		i->clipr = i->r;
+		memdraw(i, i->r, s->fill, i->r.min, nil, i->r.min, S);
+	}
+	if(l->front){
+		l->front->layer->rear = nil;
+		s->rearmost = l->front;
+	}else{
+		s->frontmost = nil;
+		s->rearmost = nil;
+	}
+	free(l);
+	freememimage(i);
+}
+
+/*
+ * Just free the data structures, don't do graphics
+ */
+void
+memlfree(Memimage *i)
+{
+	Memlayer *l;
+
+	l = i->layer;
+	freememimage(l->save);
+	free(l);
+	freememimage(i);
+}
+
+void
+_memlsetclear(Memscreen *s)
+{
+	Memimage *i, *j;
+	Memlayer *l;
+
+	for(i=s->rearmost; i; i=i->layer->front){
+		l = i->layer;
+		l->clear = rectinrect(l->screenr, l->screen->image->clipr);
+		if(l->clear)
+			for(j=l->front; j; j=j->layer->front)
+				if(rectXrect(l->screenr, j->layer->screenr)){
+					l->clear = 0;
+					break;
+				}
+	}
+}
--- /dev/null
+++ b/libmemlayer/lhide.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * Hide puts that portion of screenr now on the screen into the window's save area.
+ * Expose puts that portion of screenr now in the save area onto the screen.
+ *
+ * Hide and Expose both require that the layer structures in the screen
+ * match the geometry they are being asked to update, that is, they update the
+ * save area (hide) or screen (expose) based on what those structures tell them.
+ * This means they must be called at the correct time during window shuffles.
+ */
+
+static
+void
+lhideop(Memimage *src, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	Rectangle r;
+	Memlayer *l;
+
+	USED(clipr.min.x);
+	USED(insave);
+	l = etc;
+	if(src != l->save){	/* do nothing if src is already in save area */
+		r = rectsubpt(screenr, l->delta);
+		memdraw(l->save, r, src, screenr.min, nil, screenr.min, S);
+	}
+}
+
+void
+memlhide(Memimage *i, Rectangle screenr)
+{
+	if(i->layer->save == nil)
+		return;
+	if(rectclip(&screenr, i->layer->screen->image->r) == 0)
+		return;
+	_memlayerop(lhideop, i, screenr, screenr, i->layer);
+}
+
+static
+void
+lexposeop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	Memlayer *l;
+	Rectangle r;
+
+	USED(clipr.min.x);
+	if(insave)	/* if dst is save area, don't bother */
+		return;
+	l = etc;
+	r = rectsubpt(screenr, l->delta);
+	if(l->save)
+		memdraw(dst, screenr, l->save, r.min, nil, r.min, S);
+	else
+		l->refreshfn(dst, r, l->refreshptr);
+}
+
+void
+memlexpose(Memimage *i, Rectangle screenr)
+{
+	if(rectclip(&screenr, i->layer->screen->image->r) == 0)
+		return;
+	_memlayerop(lexposeop, i, screenr, screenr, i->layer);
+}
--- /dev/null
+++ b/libmemlayer/line.c
@@ -1,0 +1,122 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+struct Lline
+{
+	Point			p0;
+	Point			p1;
+	Point			delta;
+	int			end0;
+	int			end1;
+	int			radius;
+	Point			sp;
+	Memlayer		*dstlayer;
+	Memimage	*src;
+	int			op;
+};
+
+static void llineop(Memimage*, Rectangle, Rectangle, void*, int);
+
+static
+void
+_memline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, Rectangle clipr, int op)
+{
+	Rectangle r;
+	struct Lline ll;
+	Point d;
+	int srcclipped;
+	Memlayer *dl;
+
+	if(radius < 0)
+		return;
+	if(src->layer)	/* can't draw line with layered source */
+		return;
+	srcclipped = 0;
+
+   Top:
+	dl = dst->layer;
+	if(dl == nil){
+		_memimageline(dst, p0, p1, end0, end1, radius, src, sp, clipr, op);
+		return;
+	}
+	if(!srcclipped){
+		d = subpt(sp, p0);
+		if(rectclip(&clipr, rectsubpt(src->clipr, d)) == 0)
+			return;
+		if((src->flags&Frepl)==0 && rectclip(&clipr, rectsubpt(src->r, d))==0)
+			return;
+		srcclipped = 1;
+	}
+
+	/* dst is known to be a layer */
+	p0.x += dl->delta.x;
+	p0.y += dl->delta.y;
+	p1.x += dl->delta.x;
+	p1.y += dl->delta.y;
+	clipr.min.x += dl->delta.x;
+	clipr.min.y += dl->delta.y;
+	clipr.max.x += dl->delta.x;
+	clipr.max.y += dl->delta.y;
+	if(dl->clear){
+		dst = dst->layer->screen->image;
+		goto Top;
+	}
+
+	/* XXX */
+	/* this is not the correct set of tests */
+//	if(log2[dst->depth] != log2[src->depth] || log2[dst->depth]!=3)
+//		return;
+
+	/* can't use sutherland-cohen clipping because lines are wide */
+	r = memlinebbox(p0, p1, end0, end1, radius);
+	/*
+	 * r is now a bounding box for the line;
+	 * use it as a clipping rectangle for subdivision
+	 */
+	if(rectclip(&r, clipr) == 0)
+		return;
+	ll.p0 = p0;
+	ll.p1 = p1;
+	ll.end0 = end0;
+	ll.end1 = end1;
+	ll.sp = sp;
+	ll.dstlayer = dst->layer;
+	ll.src = src;
+	ll.radius = radius;
+	ll.delta = dl->delta;
+	ll.op = op;
+	_memlayerop(llineop, dst, r, r, &ll);
+}
+
+static
+void
+llineop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	struct Lline *ll;
+	Point p0, p1;
+
+	USED(screenr.min.x);
+	ll = etc;
+	if(insave && ll->dstlayer->save==nil)
+		return;
+	if(!rectclip(&clipr, screenr))
+		return;
+	if(insave){
+		p0 = subpt(ll->p0, ll->delta);
+		p1 = subpt(ll->p1, ll->delta);
+		clipr = rectsubpt(clipr, ll->delta);
+	}else{
+		p0 = ll->p0;
+		p1 = ll->p1;
+	}
+	_memline(dst, p0, p1, ll->end0, ll->end1, ll->radius, ll->src, ll->sp, clipr, ll->op);
+}
+
+void
+memline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+	_memline(dst, p0, p1, end0, end1, radius, src, sp, dst->clipr, op);
+}
--- /dev/null
+++ b/libmemlayer/load.c
@@ -1,0 +1,55 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+int
+memload(Memimage *dst, Rectangle r, uchar *data, int n, int iscompressed)
+{
+	int (*loadfn)(Memimage*, Rectangle, uchar*, int);
+	Memimage *tmp;
+	Memlayer *dl;
+	Rectangle lr;
+	int dx;
+
+	loadfn = loadmemimage;
+	if(iscompressed)
+		loadfn = cloadmemimage;
+
+    Top:
+	dl = dst->layer;
+	if(dl == nil)
+		return loadfn(dst, r, data, n);
+
+	/*
+ 	 * Convert to screen coordinates.
+	 */
+	lr = r;
+	r.min.x += dl->delta.x;
+	r.min.y += dl->delta.y;
+	r.max.x += dl->delta.x;
+	r.max.y += dl->delta.y;
+	dx = dl->delta.x&(7/dst->depth);
+	if(dl->clear && dx==0){
+		dst = dl->screen->image;
+		goto Top;
+	}
+
+	/*
+	 * dst is an obscured layer or data is unaligned
+	 */
+	if(dl->save && dx==0){
+		n = loadfn(dl->save, lr, data, n);
+		if(n > 0)
+			memlexpose(dst, r);
+		return n;
+	}
+	tmp = allocmemimage(lr, dst->chan);
+	if(tmp == nil)
+		return -1;
+	n = loadfn(tmp, lr, data, n);
+	memdraw(dst, lr, tmp, lr.min, nil, lr.min, S);
+	freememimage(tmp);
+	return n;
+}
--- /dev/null
+++ b/libmemlayer/lorigin.c
@@ -1,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * Place i so i->r.min = log, i->layer->screenr.min == scr.
+*/
+int
+memlorigin(Memimage *i, Point log, Point scr)
+{
+	Memlayer *l;
+	Memscreen *s;
+	Memimage *t, *shad, *nsave;
+	Rectangle x, newr, oldr;
+	Point delta;
+	int overlap, eqlog, eqscr, wasclear;
+
+	l = i->layer;
+	s = l->screen;
+	oldr = l->screenr;
+	newr = Rect(scr.x, scr.y, scr.x+Dx(oldr), scr.y+Dy(oldr));
+	eqscr = eqpt(scr, oldr.min);
+	eqlog = eqpt(log, i->r.min);
+	if(eqscr && eqlog)
+		return 0;
+	nsave = nil;
+	if(eqlog==0 && l->save!=nil){
+		nsave = allocmemimage(Rect(log.x, log.y, log.x+Dx(oldr), log.y+Dy(oldr)), i->chan);
+		if(nsave == nil)
+			return -1;
+	}
+
+	/*
+	 * Bring it to front and move logical coordinate system.
+	 */
+	memltofront(i);
+	wasclear = l->clear;
+	if(nsave){
+		if(!wasclear)
+			memimagedraw(nsave, nsave->r, l->save, l->save->r.min, nil, Pt(0,0), S);
+		freememimage(l->save);
+		l->save = nsave;
+	}
+	delta = subpt(log, i->r.min);
+	i->r = rectaddpt(i->r, delta);
+	i->clipr = rectaddpt(i->clipr, delta);
+	l->delta = subpt(l->screenr.min, i->r.min);
+	if(eqscr)
+		return 0;
+
+	/*
+	 * To clean up old position, make a shadow window there, don't paint it,
+	 * push it behind this one, and (later) delete it.  Because the refresh function
+	 * for this fake window is a no-op, this will cause no graphics action except
+	 * to restore the background and expose the windows previously hidden.
+	 */
+	shad = memlalloc(s, oldr, memlnorefresh, nil, DNofill);
+	if(shad == nil)
+		return -1;
+	s->frontmost = i;
+	if(s->rearmost == i)
+		s->rearmost = shad;
+	else
+		l->rear->layer->front = shad;
+	shad->layer->front = i;
+	shad->layer->rear = l->rear;
+	l->rear = shad;
+	l->front = nil;
+	shad->layer->clear = 0;
+
+	/*
+	 * Shadow is now holding down the fort at the old position.
+	 * Move the window and hide things obscured by new position.
+	 */
+	for(t=l->rear->layer->rear; t!=nil; t=t->layer->rear){
+		x = newr;
+		overlap = rectclip(&x, t->layer->screenr);
+		if(overlap){
+			memlhide(t, x);
+			t->layer->clear = 0;
+		}
+	}
+	l->screenr = newr;
+	l->delta = subpt(scr, i->r.min);
+	l->clear = rectinrect(newr, l->screen->image->clipr);
+
+	/*
+	 * Everything's covered.  Copy to new position and delete shadow window.
+	 */
+	if(wasclear)
+		memdraw(s->image, newr, s->image, oldr.min, nil, Pt(0,0), S);
+	else
+		memlexpose(i, newr);
+	memldelete(shad);
+
+	return 1;
+}
+
+void
+memlnorefresh(Memimage *l, Rectangle r, void *v)
+{
+	USED(l);
+	USED(r.min.x);
+	USED(v);
+}
--- /dev/null
+++ b/libmemlayer/lsetrefresh.c
@@ -1,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+int
+memlsetrefresh(Memimage *i, Refreshfn fn, void *ptr)
+{
+	Memlayer *l;
+
+	l = i->layer;
+	if(l->refreshfn!=nil && fn!=nil){	/* just change functions */
+		l->refreshfn = fn;
+		l->refreshptr = ptr;
+		return 1;
+	}
+
+	if(l->refreshfn == nil){	/* is using backup image; just free it */
+		freememimage(l->save);
+		l->save = nil;
+		l->refreshfn = fn;
+		l->refreshptr = ptr;
+		return 1;
+	}
+
+	l->save = allocmemimage(i->r, i->chan);
+	if(l->save == nil)
+		return 0;
+	/* easiest way is just to update the entire save area */
+	l->refreshfn(i, i->r, l->refreshptr);
+	l->refreshfn = nil;
+	l->refreshptr = nil;
+	return 1;
+}
--- /dev/null
+++ b/libmemlayer/ltofront.c
@@ -1,0 +1,80 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * Pull i towards top of screen, just behind front
+*/
+static
+void
+_memltofront(Memimage *i, Memimage *front, int fill)
+{
+	Memlayer *l;
+	Memscreen *s;
+	Memimage *f, *ff, *rr;
+	Rectangle x;
+	int overlap;
+
+	l = i->layer;
+	s = l->screen;
+	while(l->front != front){
+		f = l->front;
+		x = l->screenr;
+		overlap = rectclip(&x, f->layer->screenr);
+		if(overlap){
+			memlhide(f, x);
+			f->layer->clear = 0;
+		}
+		/* swap l and f in screen's list */
+		ff = f->layer->front;
+		rr = l->rear;
+		if(ff == nil)
+			s->frontmost = i;
+		else
+			ff->layer->rear = i;
+		if(rr == nil)
+			s->rearmost = f;
+		else
+			rr->layer->front = f;
+		l->front = ff;
+		l->rear = f;
+		f->layer->front = i;
+		f->layer->rear = rr;
+		if(overlap && fill)
+			memlexpose(i, x);
+	}
+}
+
+void
+_memltofrontfill(Memimage *i, int fill)
+{
+	_memltofront(i, nil, fill);
+	_memlsetclear(i->layer->screen);
+}
+
+void
+memltofront(Memimage *i)
+{
+	_memltofront(i, nil, 1);
+	_memlsetclear(i->layer->screen);
+}
+
+void
+memltofrontn(Memimage **ip, int n)
+{
+	Memimage *i, *front;
+	Memscreen *s;
+
+	if(n == 0)
+		return;
+	front = nil;
+	while(--n >= 0){
+		i = *ip++;
+		_memltofront(i, front, 1);
+		front = i;
+	}
+	s = front->layer->screen;
+	_memlsetclear(s);
+}
--- /dev/null
+++ b/libmemlayer/ltorear.c
@@ -1,0 +1,69 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+void
+_memltorear(Memimage *i, Memimage *rear)
+{
+	Memlayer *l;
+	Memscreen *s;
+	Memimage *f, *r, *rr;
+	Rectangle x;
+	int overlap;
+
+	l = i->layer;
+	s = l->screen;
+	while(l->rear != rear){
+		r = l->rear;
+		x = l->screenr;
+		overlap = rectclip(&x, r->layer->screenr);
+		if(overlap){
+			memlhide(i, x);
+			l->clear = 0;
+		}
+		/* swap l and r in screen's list */
+		rr = r->layer->rear;
+		f = l->front;
+		if(rr == nil)
+			s->rearmost = i;
+		else
+			rr->layer->front = i;
+		if(f == nil)
+			s->frontmost = r;
+		else
+			f->layer->rear = r;
+		l->rear = rr;
+		l->front = r;
+		r->layer->rear = i;
+		r->layer->front = f;
+		if(overlap)
+			memlexpose(r, x);
+	}
+}
+
+void
+memltorear(Memimage *i)
+{
+	_memltorear(i, nil);
+	_memlsetclear(i->layer->screen);
+}
+
+void
+memltorearn(Memimage **ip, int n)
+{
+	Memimage *i, *rear;
+	Memscreen *s;
+
+	if(n == 0)
+		return;
+	rear = nil;
+	while(--n >= 0){
+		i = *ip++;
+		_memltorear(i, rear);
+		rear = i;
+	}
+	s = rear->layer->screen;
+	_memlsetclear(s);
+}
--- /dev/null
+++ b/libmemlayer/mkfile
@@ -1,0 +1,23 @@
+<$DSRC/mkfile-$CONF
+TARG=libmemlayer.$L
+
+OFILES=\
+	draw.$O\
+	lalloc.$O\
+	layerop.$O\
+	ldelete.$O\
+	lhide.$O\
+	line.$O\
+	load.$O\
+	lorigin.$O\
+	lsetrefresh.$O\
+	ltofront.$O\
+	ltorear.$O\
+	unload.$O\
+
+HFILES=\
+	../include/memlayer.h\
+	../include/memdraw.h\
+	../include/draw.h
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/libmemlayer/unload.c
@@ -1,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+int
+memunload(Memimage *src, Rectangle r, uchar *data, int n)
+{
+	Memimage *tmp;
+	Memlayer *dl;
+	Rectangle lr;
+	int dx;
+
+    Top:
+	dl = src->layer;
+	if(dl == nil)
+		return unloadmemimage(src, r, data, n);
+
+	/*
+ 	 * Convert to screen coordinates.
+	 */
+	lr = r;
+	r.min.x += dl->delta.x;
+	r.min.y += dl->delta.y;
+	r.max.x += dl->delta.x;
+	r.max.y += dl->delta.y;
+	dx = dl->delta.x&(7/src->depth);
+	if(dl->clear && dx==0){
+		src = dl->screen->image;
+		goto Top;
+	}
+
+	/*
+	 * src is an obscured layer or data is unaligned
+	 */
+	if(dl->save && dx==0){
+		if(dl->refreshfn != nil)
+			return -1;	/* can't unload window if it's not Refbackup */
+		if(n > 0)
+			memlhide(src, r);
+		n = unloadmemimage(dl->save, lr, data, n);
+		return n;
+	}
+	tmp = allocmemimage(lr, src->chan);
+	if(tmp == nil)
+		return -1;
+	memdraw(tmp, lr, src, lr.min, nil, lr.min, S);
+	n = unloadmemimage(tmp, lr, data, n);
+	freememimage(tmp);
+	return n;
+}
--- /dev/null
+++ b/libmp/Makefile
@@ -1,0 +1,46 @@
+# N.B.  This is used only for secstore.  It needn't be fast.
+
+LIB=libmp.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	betomp.$O\
+	crt.$O\
+	letomp.$O\
+	mpadd.$O\
+	mpaux.$O\
+	mpcmp.$O\
+	mpdigdiv.$O\
+	mpdiv.$O\
+	mpeuclid.$O\
+	mpexp.$O\
+	mpextendedgcd.$O\
+	mpfmt.$O\
+	mpinvert.$O\
+	mpleft.$O\
+	mpmod.$O\
+	mpmul.$O\
+	mprand.$O\
+	mpright.$O\
+	mpsub.$O\
+	mptobe.$O\
+	mptoi.$O\
+	mptole.$O\
+	mptoui.$O\
+	mptouv.$O\
+	mptov.$O\
+	mpvecadd.$O\
+	mpveccmp.$O\
+	mpvecdigmuladd.$O\
+	mpvecsub.$O\
+	strtomp.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libmp/betomp.c
@@ -1,0 +1,40 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// convert a big-endian byte array (most significant byte first) to an mpint
+mpint*
+betomp(uchar *p, uint n, mpint *b)
+{
+	int m, s;
+	mpdigit x;
+
+	if(b == nil)
+		b = mpnew(0);
+
+	// dump leading zeros
+	while(*p == 0 && n > 1){
+		p++;
+		n--;
+	}
+
+	// get the space
+	mpbits(b, n*8);
+	b->top = DIGITS(n*8);
+	m = b->top-1;
+
+	// first digit might not be Dbytes long
+	s = ((n-1)*8)%Dbits;
+	x = 0;
+	for(; n > 0; n--){
+		x |= ((mpdigit)(*p++)) << s;
+		s -= 8;
+		if(s < 0){
+			b->p[m--] = x;
+			s = Dbits-8;
+			x = 0;
+		}
+	}
+
+	return b;
+}
--- /dev/null
+++ b/libmp/crt.c
@@ -1,0 +1,122 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// chinese remainder theorem
+//
+// handbook of applied cryptography, menezes et al, 1997, pp 610 - 613
+
+struct CRTpre
+{
+	int	n;		// number of moduli
+	mpint	**m;		// pointer to moduli
+	mpint	**c;		// precomputed coefficients
+	mpint	**p;		// precomputed products
+	mpint	*a[1];		// local storage
+};
+
+// setup crt info, returns a newly created structure
+CRTpre*
+crtpre(int n, mpint **m)
+{
+	CRTpre *crt;
+	int i, j;
+	mpint *u;
+
+	crt = malloc(sizeof(CRTpre)+sizeof(mpint)*3*n);
+	if(crt == nil)
+		sysfatal("crtpre: %r");
+	crt->m = crt->a;
+	crt->c = crt->a+n;
+	crt->p = crt->c+n;
+	crt->n = n;
+
+	// make a copy of the moduli
+	for(i = 0; i < n; i++)
+		crt->m[i] = mpcopy(m[i]);
+
+	// precompute the products
+	u = mpcopy(mpone);
+	for(i = 0; i < n; i++){
+		mpmul(u, m[i], u);
+		crt->p[i] = mpcopy(u);
+	}
+
+	// precompute the coefficients
+	for(i = 1; i < n; i++){
+		crt->c[i] = mpcopy(mpone);
+		for(j = 0; j < i; j++){
+			mpinvert(m[j], m[i], u);
+			mpmul(u, crt->c[i], u);
+			mpmod(u, m[i], crt->c[i]);
+		}
+	}
+
+	mpfree(u);
+
+	return crt;		
+}
+
+void
+crtprefree(CRTpre *crt)
+{
+	int i;
+
+	for(i = 0; i < crt->n; i++){
+		if(i != 0)
+			mpfree(crt->c[i]);
+		mpfree(crt->p[i]);
+		mpfree(crt->m[i]);
+	}
+	free(crt);
+}
+
+// convert to residues, returns a newly created structure
+CRTres*
+crtin(CRTpre *crt, mpint *x)
+{
+	int i;
+	CRTres *res;
+
+	res = malloc(sizeof(CRTres)+sizeof(mpint)*crt->n);
+	if(res == nil)
+		sysfatal("crtin: %r");
+	res->n = crt->n;
+	for(i = 0; i < res->n; i++){
+		res->r[i] = mpnew(0);
+		mpmod(x, crt->m[i], res->r[i]);
+	}
+	return res;
+}
+
+// garners algorithm for converting residue form to linear
+void
+crtout(CRTpre *crt, CRTres *res, mpint *x)
+{
+	mpint *u;
+	int i;
+
+	u = mpnew(0);
+	mpassign(res->r[0], x);
+
+	for(i = 1; i < crt->n; i++){
+		mpsub(res->r[i], x, u);
+		mpmul(u, crt->c[i], u);
+		mpmod(u, crt->m[i], u);
+		mpmul(u, crt->p[i-1], u);
+		mpadd(x, u, x);
+	}
+
+	mpfree(u);
+}
+
+// free the residue
+void
+crtresfree(CRTres *res)
+{
+	int i;
+
+	for(i = 0; i < res->n; i++)
+		mpfree(res->r[i]);
+	free(res);
+}
--- /dev/null
+++ b/libmp/crttest.c
@@ -1,0 +1,54 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+void
+testcrt(mpint **p)
+{
+	CRTpre *crt;
+	CRTres *res;
+	mpint *m, *x, *y;
+	int i;
+
+	fmtinstall('B', mpconv);
+
+	// get a modulus and a test number
+	m = mpnew(1024+160);
+	mpmul(p[0], p[1], m);
+	x = mpnew(1024+160);
+	mpadd(m, mpone, x);
+
+	// do the precomputation for crt conversion
+	crt = crtpre(2, p);
+
+	// convert x to residues
+	res = crtin(crt, x);
+
+	// convert back
+	y = mpnew(1024+160);
+	crtout(crt, res, y);
+	print("x %B\ny %B\n", x, y);
+	mpfree(m);
+	mpfree(x);
+	mpfree(y);
+}
+
+void
+main(void)
+{
+	int i;
+	mpint *p[2];
+	long start;
+
+	start = time(0);
+	for(i = 0; i < 10; i++){
+		p[0] = mpnew(1024);
+		p[1] = mpnew(1024);
+		DSAprimes(p[0], p[1], nil);
+		testcrt(p);
+		mpfree(p[0]);
+		mpfree(p[1]);
+	}
+	print("%d secs with more\n", time(0)-start);
+	exits(0);
+}
--- /dev/null
+++ b/libmp/dat.h
@@ -1,0 +1,15 @@
+#define	mpdighi  (mpdigit)(1<<(Dbits-1))
+#define DIGITS(x) ((Dbits - 1 + (x))/Dbits)
+
+// for converting between int's and mpint's
+#define MAXUINT ((uint)-1)
+#define MAXINT (MAXUINT>>1)
+#define MININT (MAXINT+1)
+
+// for converting between vlongs's and mpint's
+// #define MAXUVLONG (~0ULL)
+// #define MAXVLONG (MAXUVLONG>>1)
+// #define MINVLONG (MAXVLONG+1ULL)
+#define MAXUVLONG ((uvlong) ~0)
+#define MAXVLONG (MAXUVLONG>>1)
+#define MINVLONG (MAXVLONG+((uvlong) 1))
--- /dev/null
+++ b/libmp/letomp.c
@@ -1,0 +1,29 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// convert a little endian byte array (least significant byte first) to an mpint
+mpint*
+letomp(uchar *s, uint n, mpint *b)
+{
+	int i=0, m = 0;
+	mpdigit x=0;
+
+	if(b == nil)
+		b = mpnew(0);
+	mpbits(b, 8*n);
+	b->top = DIGITS(8*n);
+	for(; n > 0; n--){
+		x |= ((mpdigit)(*s++)) << i;
+		i += 8;
+		if(i == Dbits){
+			b->p[m++] = x;
+			i = 0;
+			x = 0;
+		}
+	}
+	if(i > 0)
+		b->p[m++] = x;
+	b->top = m;
+	return b;
+}
--- /dev/null
+++ b/libmp/mkfile
@@ -1,0 +1,40 @@
+TARG=libmp.$L
+<$DSRC/mkfile-$CONF
+
+OFILES=\
+	betomp.$O\
+	crt.$O\
+	letomp.$O\
+	mpadd.$O\
+	mpaux.$O\
+	mpcmp.$O\
+	mpdigdiv.$O\
+	mpdiv.$O\
+	mpeuclid.$O\
+	mpexp.$O\
+	mpextendedgcd.$O\
+	mpfmt.$O\
+	mpinvert.$O\
+	mpleft.$O\
+	mpmod.$O\
+	mpmul.$O\
+	mprand.$O\
+	mpright.$O\
+	mpsub.$O\
+	mptobe.$O\
+	mptoi.$O\
+	mptole.$O\
+	mptoui.$O\
+	mptouv.$O\
+	mptov.$O\
+	mpvecadd.$O\
+	mpveccmp.$O\
+	mpvecdigmuladd.$O\
+	mpvecsub.$O\
+	strtomp.$O
+
+HFILE=\
+	dat.h\
+	os.h
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/libmp/mpadd.c
@@ -1,0 +1,54 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// sum = abs(b1) + abs(b2), i.e., add the magnitudes
+void
+mpmagadd(mpint *b1, mpint *b2, mpint *sum)
+{
+	int m, n;
+	mpint *t;
+
+	// get the sizes right
+	if(b2->top > b1->top){
+		t = b1;
+		b1 = b2;
+		b2 = t;
+	}
+	n = b1->top;
+	m = b2->top;
+	if(n == 0){
+		mpassign(mpzero, sum);
+		return;
+	}
+	if(m == 0){
+		mpassign(b1, sum);
+		return;
+	}
+	mpbits(sum, (n+1)*Dbits);
+	sum->top = n+1;
+
+	mpvecadd(b1->p, n, b2->p, m, sum->p);
+	sum->sign = 1;
+
+	mpnorm(sum);
+}
+
+// sum = b1 + b2
+void
+mpadd(mpint *b1, mpint *b2, mpint *sum)
+{
+	int sign;
+
+	if(b1->sign != b2->sign){
+		if(b1->sign < 0)
+			mpmagsub(b2, b1, sum);
+		else
+			mpmagsub(b1, b2, sum);
+	} else {
+		sign = b1->sign;
+		mpmagadd(b1, b2, sum);
+		if(sum->top != 0)
+			sum->sign = sign;
+	}
+}
--- /dev/null
+++ b/libmp/mpaux.c
@@ -1,0 +1,185 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+static mpdigit _mptwodata[1] = { 2 };
+static mpint _mptwo =
+{
+	1,
+	1,
+	1,
+	_mptwodata,
+	MPstatic
+};
+mpint *mptwo = &_mptwo;
+
+static mpdigit _mponedata[1] = { 1 };
+static mpint _mpone =
+{
+	1,
+	1,
+	1,
+	_mponedata,
+	MPstatic
+};
+mpint *mpone = &_mpone;
+
+static mpdigit _mpzerodata[1] = { 0 };
+static mpint _mpzero =
+{
+	1,
+	1,
+	0,
+	_mpzerodata,
+	MPstatic
+};
+mpint *mpzero = &_mpzero;
+
+static int mpmindigits = 33;
+
+// set minimum digit allocation
+void
+mpsetminbits(int n)
+{
+	if(n == 0)
+		n = 1;
+	mpmindigits = DIGITS(n);
+}
+
+// allocate an n bit 0'd number 
+mpint*
+mpnew(int n)
+{
+	mpint *b;
+
+	b = mallocz(sizeof(mpint), 1);
+	if(b == nil)
+		sysfatal("mpnew: %r");
+	n = DIGITS(n);
+	if(n < mpmindigits)
+		n = mpmindigits;
+	n = n;
+	b->p = (mpdigit*)mallocz(n*Dbytes, 1);
+	if(b->p == nil)
+		sysfatal("mpnew: %r");
+	b->size = n;
+	b->sign = 1;
+
+	return b;
+}
+
+// guarantee at least n significant bits
+void
+mpbits(mpint *b, int m)
+{
+	int n;
+
+	n = DIGITS(m);
+	if(b->size >= n){
+		if(b->top >= n)
+			return;
+		memset(&b->p[b->top], 0, Dbytes*(n - b->top));
+		b->top = n;
+		return;
+	}
+	b->p = (mpdigit*)realloc(b->p, n*Dbytes);
+	if(b->p == nil)
+		sysfatal("mpbits: %r");
+	memset(&b->p[b->top], 0, Dbytes*(n - b->top));
+	b->size = n;
+	b->top = n;
+}
+
+void
+mpfree(mpint *b)
+{
+	if(b == nil)
+		return;
+	if(b->flags & MPstatic)
+		sysfatal("freeing mp constant");
+	memset(b->p, 0, b->top*Dbytes);	// information hiding
+	free(b->p);
+	free(b);
+}
+
+void
+mpnorm(mpint *b)
+{
+	int i;
+
+	for(i = b->top-1; i >= 0; i--)
+		if(b->p[i] != 0)
+			break;
+	b->top = i+1;
+	if(b->top == 0)
+		b->sign = 1;
+}
+
+mpint*
+mpcopy(mpint *old)
+{
+	mpint *new;
+
+	new = mpnew(Dbits*old->size);
+	new->top = old->top;
+	new->sign = old->sign;
+	memmove(new->p, old->p, Dbytes*old->top);
+	return new;
+}
+
+void
+mpassign(mpint *old, mpint *new)
+{
+	mpbits(new, Dbits*old->top);
+	new->sign = old->sign;
+	new->top = old->top;
+	memmove(new->p, old->p, Dbytes*old->top);
+}
+
+// number of significant bits in mantissa
+int
+mpsignif(mpint *n)
+{
+	int i, j;
+	mpdigit d;
+
+	if(n->top == 0)
+		return 0;
+	for(i = n->top-1; i >= 0; i--){
+		d = n->p[i];
+		for(j = Dbits-1; j >= 0; j--){
+			if(d & (((mpdigit)1)<<j))
+				return i*Dbits + j + 1;
+		}
+	}
+	return 0;
+}
+
+// k, where n = 2**k * q for odd q
+int
+mplowbits0(mpint *n)
+{
+	int k, bit, digit;
+	mpdigit d;
+
+	if(n->top==0)
+		return 0;
+	k = 0;
+	bit = 0;
+	digit = 0;
+	d = n->p[0];
+	for(;;){
+		if(d & (1<<bit))
+			break;
+		k++;
+		bit++;
+		if(bit==Dbits){
+			if(++digit >= n->top)
+				return 0;
+			d = n->p[digit];
+			bit = 0;
+		}
+	}
+	return k;
+}
+
--- /dev/null
+++ b/libmp/mpcmp.c
@@ -1,0 +1,28 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// return 1, 0, -1 as abs(b1)-abs(b2) is neg, 0, pos
+int
+mpmagcmp(mpint *b1, mpint *b2)
+{
+	int i;
+
+	i = b1->top - b2->top;
+	if(i)
+		return i;
+
+	return mpveccmp(b1->p, b1->top, b2->p, b2->top);
+}
+
+// return neg, 0, pos as b1-b2 is neg, 0, pos
+int
+mpcmp(mpint *b1, mpint *b2)
+{
+	if(b1->sign != b2->sign)
+		return b1->sign - b2->sign;
+	if(b1->sign < 0)
+		return mpmagcmp(b2, b1);
+	else
+		return mpmagcmp(b1, b2);
+}
--- /dev/null
+++ b/libmp/mpdigdiv.c
@@ -1,0 +1,43 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+//
+//	divide two digits by one and return quotient
+//
+void
+mpdigdiv(mpdigit *dividend, mpdigit divisor, mpdigit *quotient)
+{
+	mpdigit hi, lo, q, x, y;
+	int i;
+
+	hi = dividend[1];
+	lo = dividend[0];
+
+	// return highest digit value if the result >= 2**32
+	if(hi >= divisor || divisor == 0){
+		divisor = 0;
+		*quotient = ~divisor;
+		return;
+	}
+
+	// at this point we know that hi < divisor
+	// just shift and subtract till we're done
+	q = 0;
+	x = divisor;
+	for(i = Dbits-1; hi > 0 && i >= 0; i--){
+		x >>= 1;
+		if(x > hi)
+			continue;
+		y = divisor<<i;
+		if(x == hi && y > lo)
+			continue;
+		if(y > lo)
+			hi--;
+		lo -= y;
+		hi -= x;
+		q |= 1<<i;
+	}
+	q += lo/divisor;
+	*quotient = q;
+}
--- /dev/null
+++ b/libmp/mpdiv.c
@@ -1,0 +1,112 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// division ala knuth, seminumerical algorithms, pp 237-238
+// the numbers are stored backwards to what knuth expects so j
+// counts down rather than up.
+
+void
+mpdiv(mpint *dividend, mpint *divisor, mpint *quotient, mpint *remainder)
+{
+	int j, s, vn, sign;
+	mpdigit qd, *up, *vp, *qp;
+	mpint *u, *v, *t;
+
+	// divide bv zero
+	if(divisor->top == 0)
+		abort();
+
+	// quick check
+	if(mpmagcmp(dividend, divisor) < 0){
+		if(remainder != nil)
+			mpassign(dividend, remainder);
+		if(quotient != nil)
+			mpassign(mpzero, quotient);
+		return;
+	}
+
+	// D1: shift until divisor, v, has hi bit set (needed to make trial
+	//     divisor accurate)
+	qd = divisor->p[divisor->top-1];
+	for(s = 0; (qd & mpdighi) == 0; s++)
+		qd <<= 1;
+	u = mpnew((dividend->top+2)*Dbits + s);
+	if(s == 0 && divisor != quotient && divisor != remainder) {
+		mpassign(dividend, u);
+		v = divisor;
+	} else {
+		mpleft(dividend, s, u);
+		v = mpnew(divisor->top*Dbits);
+		mpleft(divisor, s, v);
+	}
+	up = u->p+u->top-1;
+	vp = v->p+v->top-1;
+	vn = v->top;
+
+	// D1a: make sure high digit of dividend is less than high digit of divisor
+	if(*up >= *vp){
+		*++up = 0;
+		u->top++;
+	}
+
+	// storage for multiplies
+	t = mpnew(4*Dbits);
+
+	qp = nil;
+	if(quotient != nil){
+		mpbits(quotient, (u->top - v->top)*Dbits);
+		quotient->top = u->top - v->top;
+		qp = quotient->p+quotient->top-1;
+	}
+
+	// D2, D7: loop on length of dividend
+	for(j = u->top; j > vn; j--){
+
+		// D3: calculate trial divisor
+		mpdigdiv(up-1, *vp, &qd);
+
+		// D3a: rule out trial divisors 2 greater than real divisor
+		if(vn > 1) for(;;){
+			memset(t->p, 0, 3*Dbytes);	// mpvecdigmuladd adds to what's there
+			mpvecdigmuladd(vp-1, 2, qd, t->p);
+			if(mpveccmp(t->p, 3, up-2, 3) > 0)
+				qd--;
+			else
+				break;
+		}
+
+		// D4: u -= v*qd << j*Dbits
+		sign = mpvecdigmulsub(v->p, vn, qd, up-vn);
+		if(sign < 0){
+
+			// D6: trial divisor was too high, add back borrowed
+			//     value and decrease divisor
+			mpvecadd(up-vn, vn+1, v->p, vn, up-vn);
+			qd--;
+		}
+
+		// D5: save quotient digit
+		if(qp != nil)
+			*qp-- = qd;
+
+		// push top of u down one
+		u->top--;
+		*up-- = 0;
+	}
+	if(qp != nil){
+		mpnorm(quotient);
+		if(dividend->sign != divisor->sign)
+			quotient->sign = -1;
+	}
+
+	if(remainder != nil){
+		mpright(u, s, remainder);	// u is the remainder shifted
+		remainder->sign = dividend->sign;
+	}
+
+	mpfree(t);
+	mpfree(u);
+	if(v != divisor)
+		mpfree(v);
+}
--- /dev/null
+++ b/libmp/mpeuclid.c
@@ -1,0 +1,82 @@
+#include "os.h"
+#include <mp.h>
+
+// extended euclid
+//
+// For a and b it solves, d = gcd(a,b) and finds x and y s.t.
+// ax + by = d
+//
+// Handbook of Applied Cryptography, Menezes et al, 1997, pg 67
+
+void
+mpeuclid(mpint *a, mpint *b, mpint *d, mpint *x, mpint *y)
+{
+	mpint *tmp, *x0, *x1, *x2, *y0, *y1, *y2, *q, *r;
+
+	if(mpcmp(a, b) < 0){
+		tmp = a;
+		a = b;
+		b = tmp;
+		tmp = x;
+		x = y;
+		y = tmp;
+	}
+
+	if(b->top == 0){
+		mpassign(a, d);
+		mpassign(mpone, x);
+		mpassign(mpzero, y);
+		return;
+	}
+
+	a = mpcopy(a);
+	b = mpcopy(b);
+	x0 = mpnew(0);
+	x1 = mpcopy(mpzero);
+	x2 = mpcopy(mpone);
+	y0 = mpnew(0);
+	y1 = mpcopy(mpone);
+	y2 = mpcopy(mpzero);
+	q = mpnew(0);
+	r = mpnew(0);
+
+	while(b->top != 0 && b->sign > 0){
+		// q = a/b
+		// r = a mod b
+		mpdiv(a, b, q, r);
+		// x0 = x2 - qx1
+		mpmul(q, x1, x0);
+		mpsub(x2, x0, x0);
+		// y0 = y2 - qy1
+		mpmul(q, y1, y0);
+		mpsub(y2, y0, y0);
+		// rotate values
+		tmp = a;
+		a = b;
+		b = r;
+		r = tmp;
+		tmp = x2;
+		x2 = x1;
+		x1 = x0;
+		x0 = tmp;
+		tmp = y2;
+		y2 = y1;
+		y1 = y0;
+		y0 = tmp;
+	}
+
+	mpassign(a, d);
+	mpassign(x2, x);
+	mpassign(y2, y);
+
+	mpfree(x0);
+	mpfree(x1);
+	mpfree(x2);
+	mpfree(y0);
+	mpfree(y1);
+	mpfree(y2);
+	mpfree(q);
+	mpfree(r);
+	mpfree(a);
+	mpfree(b);
+}
--- /dev/null
+++ b/libmp/mpexp.c
@@ -1,0 +1,86 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// res = b**e
+//
+// knuth, vol 2, pp 398-400
+
+enum {
+	Freeb=	0x1,
+	Freee=	0x2,
+	Freem=	0x4,
+};
+
+//int expdebug;
+
+void
+mpexp(mpint *b, mpint *e, mpint *m, mpint *res)
+{
+	mpint *t[2];
+	int tofree;
+	mpdigit d, bit;
+	int i, j;
+
+	t[0] = mpcopy(b);
+	t[1] = res;
+
+	tofree = 0;
+	if(res == b){
+		b = mpcopy(b);
+		tofree |= Freeb;
+	}
+	if(res == e){
+		e = mpcopy(e);
+		tofree |= Freee;
+	}
+	if(res == m){
+		m = mpcopy(m);
+		tofree |= Freem;
+	}
+
+	// skip first bit
+	i = e->top-1;
+	d = e->p[i];
+	for(bit = mpdighi; (bit & d) == 0; bit >>= 1)
+		;
+	bit >>= 1;
+
+	j = 0;
+	for(;;){
+		for(; bit != 0; bit >>= 1){
+			mpmul(t[j], t[j], t[j^1]);
+			if(bit & d)
+				mpmul(t[j^1], b, t[j]);
+			else
+				j ^= 1;
+			if(m != nil && t[j]->top > m->top){
+				mpmod(t[j], m, t[j^1]);
+				j ^= 1;
+			}
+		}
+		if(--i < 0)
+			break;
+		bit = mpdighi;
+		d = e->p[i];
+	}
+	if(m != nil){
+		mpmod(t[j], m, t[j^1]);
+		j ^= 1;
+	}
+	if(t[j] == res){
+		mpfree(t[j^1]);
+	} else {
+		mpassign(t[j], res);
+		mpfree(t[j]);
+	}
+
+	if(tofree){
+		if(tofree & Freeb)
+			mpfree(b);
+		if(tofree & Freee)
+			mpfree(e);
+		if(tofree & Freem)
+			mpfree(m);
+	}
+}
--- /dev/null
+++ b/libmp/mpextendedgcd.c
@@ -1,0 +1,97 @@
+#include "os.h"
+#include <mp.h>
+
+#define iseven(a)	(((a)->p[0] & 1) == 0)
+
+// extended binary gcd
+//
+// For a anv b it solves, v = gcd(a,b) and finds x and y s.t.
+// ax + by = v
+//
+// Handbook of Applied Cryptography, Menezes et al, 1997, pg 608.  
+void
+mpextendedgcd(mpint *a, mpint *b, mpint *v, mpint *x, mpint *y)
+{
+	mpint *u, *A, *B, *C, *D;
+	int g;
+
+	if(a->top == 0){
+		mpassign(b, v);
+		mpassign(mpone, y);
+		mpassign(mpzero, x);
+		return;
+	}
+	if(b->top == 0){
+		mpassign(a, v);
+		mpassign(mpone, x);
+		mpassign(mpzero, y);
+		return;
+	}
+
+	g = 0;
+	a = mpcopy(a);
+	b = mpcopy(b);
+
+	while(iseven(a) && iseven(b)){
+		mpright(a, 1, a);
+		mpright(b, 1, b);
+		g++;
+	}
+
+	u = mpcopy(a);
+	mpassign(b, v);
+	A = mpcopy(mpone);
+	B = mpcopy(mpzero);
+	C = mpcopy(mpzero);
+	D = mpcopy(mpone);
+
+	for(;;) {
+//		print("%B %B %B %B %B %B\n", u, v, A, B, C, D);
+		while(iseven(u)){
+			mpright(u, 1, u);
+			if(!iseven(A) || !iseven(B)) {
+				mpadd(A, b, A);
+				mpsub(B, a, B);
+			}
+			mpright(A, 1, A);
+			mpright(B, 1, B);
+		}
+	
+//		print("%B %B %B %B %B %B\n", u, v, A, B, C, D);
+		while(iseven(v)){
+			mpright(v, 1, v);
+			if(!iseven(C) || !iseven(D)) {
+				mpadd(C, b, C);
+				mpsub(D, a, D);
+			}
+			mpright(C, 1, C);
+			mpright(D, 1, D);
+		}
+	
+//		print("%B %B %B %B %B %B\n", u, v, A, B, C, D);
+		if(mpcmp(u, v) >= 0){
+			mpsub(u, v, u);
+			mpsub(A, C, A);
+			mpsub(B, D, B);
+		} else {
+			mpsub(v, u, v);
+			mpsub(C, A, C);
+			mpsub(D, B, D);
+		}
+
+		if(u->top == 0)
+			break;
+
+	}
+	mpassign(C, x);
+	mpassign(D, y);
+	mpleft(v, g, v);
+
+	mpfree(A);
+	mpfree(B);
+	mpfree(C);
+	mpfree(D);
+	mpfree(u);
+	mpfree(a);
+	mpfree(b);
+}
--- /dev/null
+++ b/libmp/mpfmt.c
@@ -1,0 +1,193 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+#include "dat.h"
+
+static int
+to64(mpint *b, char *buf, int len)
+{
+	uchar *p;
+	int n, rv;
+
+	p = nil;
+	n = mptobe(b, nil, 0, &p);
+	if(n < 0)
+		return -1;
+	rv = enc64(buf, len, p, n);
+	free(p);
+	return rv;
+}
+
+static int
+to32(mpint *b, char *buf, int len)
+{
+	uchar *p;
+	int n, rv;
+
+	// leave room for a multiple of 5 buffer size
+	n = b->top*Dbytes + 5;
+	p = malloc(n);
+	if(p == nil)
+		return -1;
+	n = mptobe(b, p, n, nil);
+	if(n < 0)
+		return -1;
+
+	// round up buffer size, enc32 only accepts a multiple of 5
+	if(n%5)
+		n += 5 - (n%5);
+	rv = enc32(buf, len, p, n);
+	free(p);
+	return rv;
+}
+
+static char set16[] = "0123456789ABCDEF";
+
+static int
+to16(mpint *b, char *buf, int len)
+{
+	mpdigit *p, x;
+	int i, j;
+	char *out, *eout;
+
+	if(len < 1)
+		return -1;
+
+	out = buf;
+	eout = buf+len;
+	for(p = &b->p[b->top-1]; p >= b->p; p--){
+		x = *p;
+		for(i = Dbits-4; i >= 0; i -= 4){
+			j = 0xf & (x>>i);
+			if(j != 0 || out != buf){
+				if(out >= eout)
+					return -1;
+				*out++ = set16[j];
+			}
+		}
+	}
+	if(out == buf)
+		*out++ = '0';
+	if(out >= eout)
+		return -1;
+	*out = 0;
+	return 0;
+}
+
+static char*
+modbillion(int rem, ulong r, char *out, char *buf)
+{
+	ulong rr;
+	int i;
+
+	for(i = 0; i < 9; i++){
+		rr = r%10;
+		r /= 10;
+		if(out <= buf)
+			return nil;
+		*--out = '0' + rr;
+		if(rem == 0 && r == 0)
+			break;
+	}
+	return out;
+}
+
+static int
+to10(mpint *b, char *buf, int len)
+{
+	mpint *d, *r, *billion;
+	char *out;
+
+	if(len < 1)
+		return -1;
+
+	d = mpcopy(b);
+	r = mpnew(0);
+	billion = uitomp(1000000000, nil);
+	out = buf+len;
+	*--out = 0;
+	do {
+		mpdiv(d, billion, d, r);
+		out = modbillion(d->top, r->p[0], out, buf);
+		if(out == nil)
+			break;
+	} while(d->top != 0);
+	mpfree(d);
+	mpfree(r);
+	mpfree(billion);
+
+	if(out == nil)
+		return -1;
+	len -= out-buf;
+	if(out != buf)
+		memmove(buf, out, len);
+	return 0;
+}
+
+int
+mpfmt(Fmt *fmt)
+{
+	mpint *b;
+	char *p;
+
+	b = va_arg(fmt->args, mpint*);
+	if(b == nil)
+		return fmtstrcpy(fmt, "*");
+	
+	p = mptoa(b, fmt->prec, nil, 0);
+	fmt->flags &= ~FmtPrec;
+
+	if(p == nil)
+		return fmtstrcpy(fmt, "*");
+	else{
+		fmtstrcpy(fmt, p);
+		free(p);
+		return 0;
+	}
+}
+
+char*
+mptoa(mpint *b, int base, char *buf, int len)
+{
+	char *out;
+	int rv, alloced;
+
+	alloced = 0;
+	if(buf == nil){
+		len = ((b->top+1)*Dbits+2)/3 + 1;
+		buf = malloc(len);
+		if(buf == nil)
+			return nil;
+		alloced = 1;
+	}
+
+	if(len < 2)
+		return nil;
+
+	out = buf;
+	if(b->sign < 0){
+		*out++ = '-';
+		len--;
+	}
+	switch(base){
+	case 64:
+		rv = to64(b, out, len);
+		break;
+	case 32:
+		rv = to32(b, out, len);
+		break;
+	default:
+	case 16:
+		rv = to16(b, out, len);
+		break;
+	case 10:
+		rv = to10(b, out, len);
+		break;
+	}
+	if(rv < 0){
+		if(alloced)
+			free(buf);
+		return nil;
+	}
+	return buf;
+}
--- /dev/null
+++ b/libmp/mpinvert.c
@@ -1,0 +1,21 @@
+#include "os.h"
+#include <mp.h>
+
+#define iseven(a)	(((a)->p[0] & 1) == 0)
+
+// use extended gcd to find the multiplicative inverse
+// res = b**-1 mod m
+void
+mpinvert(mpint *b, mpint *m, mpint *res)
+{
+	mpint *dc1, *dc2;	// don't care
+
+	dc1 = mpnew(0);
+	dc2 = mpnew(0);
+	mpextendedgcd(b, m, dc1, res, dc2);
+	if(mpcmp(dc1, mpone) != 0)
+		abort();
+	mpmod(res, m, res);
+	mpfree(dc1);
+	mpfree(dc2);
+}
--- /dev/null
+++ b/libmp/mpleft.c
@@ -1,0 +1,46 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// res = b << shift
+void
+mpleft(mpint *b, int shift, mpint *res)
+{
+	int d, l, r, i, otop;
+	mpdigit this, last;
+
+	// a negative left shift is a right shift
+	if(shift < 0){
+		mpright(b, -shift, res);
+		return;
+	}
+
+	// b and res may be the same so remember the old top
+	otop = b->top;
+
+	// shift
+	mpbits(res, otop*Dbits + shift);	// overkill
+	res->top = DIGITS(otop*Dbits + shift);
+	d = shift/Dbits;
+	l = shift - d*Dbits;
+	r = Dbits - l;
+
+	if(l == 0){
+		for(i = otop-1; i >= 0; i--)
+			res->p[i+d] = b->p[i];
+	} else {
+		last = 0;
+		for(i = otop-1; i >= 0; i--) {
+			this = b->p[i];
+			res->p[i+d+1] = (last<<l) | (this>>r);
+			last = this;
+		}
+		res->p[d] = last<<l;
+	}
+	for(i = 0; i < d; i++)
+		res->p[i] = 0;
+
+	// normalize
+	while(res->top > 0 && res->p[res->top-1] == 0)
+		res->top--;
+}
--- /dev/null
+++ b/libmp/mpmod.c
@@ -1,0 +1,15 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// remainder = b mod m
+//
+// knuth, vol 2, pp 398-400
+
+void
+mpmod(mpint *b, mpint *m, mpint *remainder)
+{
+	mpdiv(b, m, nil, remainder);
+	if(remainder->sign < 0)
+		mpadd(m, remainder, remainder);
+}
--- /dev/null
+++ b/libmp/mpmul.c
@@ -1,0 +1,156 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+//
+//  from knuth's 1969 seminumberical algorithms, pp 233-235 and pp 258-260
+//
+//  mpvecmul is an assembly language routine that performs the inner
+//  loop.
+//
+//  the karatsuba trade off is set empiricly by measuring the algs on
+//  a 400 MHz Pentium II.
+//
+
+// karatsuba like (see knuth pg 258)
+// prereq: p is already zeroed
+static void
+mpkaratsuba(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *p)
+{
+	mpdigit *t, *u0, *u1, *v0, *v1, *u0v0, *u1v1, *res, *diffprod;
+	int u0len, u1len, v0len, v1len, reslen;
+	int sign, n;
+
+	// divide each piece in half
+	n = alen/2;
+	if(alen&1)
+		n++;
+	u0len = n;
+	u1len = alen-n;
+	if(blen > n){
+		v0len = n;
+		v1len = blen-n;
+	} else {
+		v0len = blen;
+		v1len = 0;
+	}
+	u0 = a;
+	u1 = a + u0len;
+	v0 = b;
+	v1 = b + v0len;
+
+	// room for the partial products
+	t = mallocz(Dbytes*5*(2*n+1), 1);
+	if(t == nil)
+		sysfatal("mpkaratsuba: %r");
+	u0v0 = t;
+	u1v1 = t + (2*n+1);
+	diffprod = t + 2*(2*n+1);
+	res = t + 3*(2*n+1);
+	reslen = 4*n+1;
+
+	// t[0] = (u1-u0)
+	sign = 1;
+	if(mpveccmp(u1, u1len, u0, u0len) < 0){
+		sign = -1;
+		mpvecsub(u0, u0len, u1, u1len, u0v0);
+	} else
+		mpvecsub(u1, u1len, u0, u1len, u0v0);
+
+	// t[1] = (v0-v1)
+	if(mpveccmp(v0, v0len, v1, v1len) < 0){
+		sign *= -1;
+		mpvecsub(v1, v1len, v0, v1len, u1v1);
+	} else
+		mpvecsub(v0, v0len, v1, v1len, u1v1);
+
+	// t[4:5] = (u1-u0)*(v0-v1)
+	mpvecmul(u0v0, u0len, u1v1, v0len, diffprod);
+
+	// t[0:1] = u1*v1
+	memset(t, 0, 2*(2*n+1)*Dbytes);
+	if(v1len > 0)
+		mpvecmul(u1, u1len, v1, v1len, u1v1);
+
+	// t[2:3] = u0v0
+	mpvecmul(u0, u0len, v0, v0len, u0v0);
+
+	// res = u0*v0<<n + u0*v0
+	mpvecadd(res, reslen, u0v0, u0len+v0len, res);
+	mpvecadd(res+n, reslen-n, u0v0, u0len+v0len, res+n);
+
+	// res += u1*v1<<n + u1*v1<<2*n
+	if(v1len > 0){
+		mpvecadd(res+n, reslen-n, u1v1, u1len+v1len, res+n);
+		mpvecadd(res+2*n, reslen-2*n, u1v1, u1len+v1len, res+2*n);
+	}
+
+	// res += (u1-u0)*(v0-v1)<<n
+	if(sign < 0)
+		mpvecsub(res+n, reslen-n, diffprod, u0len+v0len, res+n);
+	else
+		mpvecadd(res+n, reslen-n, diffprod, u0len+v0len, res+n);
+	memmove(p, res, (alen+blen)*Dbytes);
+
+	free(t);
+}
+
+#define KARATSUBAMIN 32
+
+void
+mpvecmul(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *p)
+{
+	int i;
+	mpdigit d;
+	mpdigit *t;
+
+	// both mpvecdigmuladd and karatsuba are fastest when a is the longer vector
+	if(alen < blen){
+		i = alen;
+		alen = blen;
+		blen = i;
+		t = a;
+		a = b;
+		b = t;
+	}
+	if(blen == 0){
+		memset(p, 0, Dbytes*(alen+blen));
+		return;
+	}
+
+	if(alen >= KARATSUBAMIN && blen > 1){
+		// O(n^1.585)
+		mpkaratsuba(a, alen, b, blen, p);
+	} else {
+		// O(n^2)
+		for(i = 0; i < blen; i++){
+			d = b[i];
+			if(d != 0)
+				mpvecdigmuladd(a, alen, d, &p[i]);
+		}
+	}
+}
+
+void
+mpmul(mpint *b1, mpint *b2, mpint *prod)
+{
+	mpint *oprod;
+
+	oprod = nil;
+	if(prod == b1 || prod == b2){
+		oprod = prod;
+		prod = mpnew(0);
+	}
+
+	prod->top = 0;
+	mpbits(prod, (b1->top+b2->top+1)*Dbits);
+	mpvecmul(b1->p, b1->top, b2->p, b2->top, prod->p);
+	prod->top = b1->top+b2->top+1;
+	prod->sign = b1->sign*b2->sign;
+	mpnorm(prod);
+
+	if(oprod != nil){
+		mpassign(prod, oprod);
+		mpfree(prod);
+	}
+}
--- /dev/null
+++ b/libmp/mprand.c
@@ -1,0 +1,42 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+#include "dat.h"
+
+mpint*
+mprand(int bits, void (*gen)(uchar*, int), mpint *b)
+{
+	int n, m;
+	mpdigit mask;
+	uchar *p;
+
+	n = DIGITS(bits);
+	if(b == nil)
+		b = mpnew(bits);
+	else
+		mpbits(b, bits);
+
+	p = malloc(n*Dbytes);
+	if(p == nil)
+		return nil;
+	(*gen)(p, n*Dbytes);
+	betomp(p, n*Dbytes, b);
+	free(p);
+
+	// make sure we don't give too many bits
+	m = bits%Dbits;
+	n--;
+	if(m > 0){
+		mask = 1;
+		mask <<= m;
+		mask--;
+		b->p[n] &= mask;
+	}
+
+	for(; n >= 0; n--)
+		if(b->p[n] != 0)
+			break;
+	b->top = n+1;
+	b->sign = 1;
+	return b;
+}
--- /dev/null
+++ b/libmp/mpright.c
@@ -1,0 +1,40 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// res = b >> shift
+void
+mpright(mpint *b, int shift, mpint *res)
+{
+	int d, l, r, i;
+	mpdigit this, last;
+
+	// a negative right shift is a left shift
+	if(shift < 0){
+		mpleft(b, -shift, res);
+		return;
+	}
+
+	if(res != b)
+		mpbits(res, b->top*Dbits - shift);
+	d = shift/Dbits;
+	r = shift - d*Dbits;
+	l = Dbits - r;
+
+	// special case digit shifts
+	if(r == 0){
+		for(i = 0; i < b->top-d; i++)
+			res->p[i] = b->p[i+d];
+	} else {
+		last = b->p[d];
+		for(i = 0; i < b->top-d-1; i++){
+			this = b->p[i+d+1];
+			res->p[i] = (this<<l) | (last>>r);
+			last = this;
+		}
+		res->p[i++] = last>>r;
+	}
+	while(i > 0 && res->p[i-1] == 0)
+		i--;
+	res->top = i;
+}
--- /dev/null
+++ b/libmp/mpsub.c
@@ -1,0 +1,52 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// diff = abs(b1) - abs(b2), i.e., subtract the magnitudes
+void
+mpmagsub(mpint *b1, mpint *b2, mpint *diff)
+{
+	int n, m, sign;
+	mpint *t;
+
+	// get the sizes right
+	if(mpmagcmp(b1, b2) < 0){
+		sign = -1;
+		t = b1;
+		b1 = b2;
+		b2 = t;
+	} else
+		sign = 1;
+	n = b1->top;
+	m = b2->top;
+	if(m == 0){
+		mpassign(b1, diff);
+		diff->sign = sign;
+		return;
+	}
+	mpbits(diff, n*Dbits);
+
+	mpvecsub(b1->p, n, b2->p, m, diff->p);
+	diff->sign = sign;
+	diff->top = n;
+	mpnorm(diff);
+}
+
+// diff = b1 - b2
+void
+mpsub(mpint *b1, mpint *b2, mpint *diff)
+{
+	int sign;
+
+	if(b1->sign != b2->sign){
+		sign = b1->sign;
+		mpmagadd(b1, b2, diff);
+		diff->sign = sign;
+		return;
+	}
+
+	sign = b1->sign;
+	mpmagsub(b1, b2, diff);
+	if(diff->top != 0)
+		diff->sign *= sign;
+}
--- /dev/null
+++ b/libmp/mptobe.c
@@ -1,0 +1,57 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// convert an mpint into a big endian byte array (most significant byte first)
+//   return number of bytes converted
+//   if p == nil, allocate and result array
+int
+mptobe(mpint *b, uchar *p, uint n, uchar **pp)
+{
+	int i, j, suppress;
+	mpdigit x;
+	uchar *e, *s, c;
+
+	if(p == nil){
+		n = (b->top+1)*Dbytes;
+		p = malloc(n);
+	}
+	if(p == nil)
+		return -1;
+	if(pp != nil)
+		*pp = p;
+	memset(p, 0, n);
+
+	// special case 0
+	if(b->top == 0){
+		if(n < 1)
+			return -1;
+		else
+			return 1;
+	}
+		
+	s = p;
+	e = s+n;
+	suppress = 1;
+	for(i = b->top-1; i >= 0; i--){
+		x = b->p[i];
+		for(j = Dbits-8; j >= 0; j -= 8){
+			c = x>>j;
+			if(c == 0 && suppress)
+				continue;
+			if(p >= e)
+				return -1;
+			*p++ = c;
+			suppress = 0;
+		}
+	}
+
+	// guarantee at least one byte
+	if(s == p){
+		if(p >= e)
+			return -1;
+		*p++ = 0;
+	}
+
+	return p - s;
+}
--- /dev/null
+++ b/libmp/mptoi.c
@@ -1,0 +1,44 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+/*
+ *  this code assumes that mpdigit is at least as
+ *  big as an int.
+ */
+
+mpint*
+itomp(int i, mpint *b)
+{
+	if(b == nil)
+		b = mpnew(0);
+	mpassign(mpzero, b);
+	if(i != 0)
+		b->top = 1;
+	if(i < 0){
+		b->sign = -1;
+		*b->p = -i;
+	} else
+		*b->p = i;
+	return b;
+}
+
+int
+mptoi(mpint *b)
+{
+	uint x;
+
+	x = *b->p;
+	if(b->sign > 0){
+		if(b->top > 1 || (x > MAXINT))
+			x = (int)MAXINT;
+		else
+			x = (int)x;
+	} else {
+		if(b->top > 1 || x > MAXINT+1)
+			x = (int)MININT;
+		else
+			x = -(int)x;
+	}
+	return x;
+}
--- /dev/null
+++ b/libmp/mptole.c
@@ -1,0 +1,54 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// convert an mpint into a little endian byte array (least significant byte first)
+
+//   return number of bytes converted
+//   if p == nil, allocate and result array
+int
+mptole(mpint *b, uchar *p, uint n, uchar **pp)
+{
+	int i, j;
+	mpdigit x;
+	uchar *e, *s;
+
+	if(p == nil){
+		n = (b->top+1)*Dbytes;
+		p = malloc(n);
+	}
+	if(pp != nil)
+		*pp = p;
+	if(p == nil)
+		return -1;
+	memset(p, 0, n);
+
+	// special case 0
+	if(b->top == 0){
+		if(n < 1)
+			return -1;
+		else
+			return 0;
+	}
+		
+	s = p;
+	e = s+n;
+	for(i = 0; i < b->top-1; i++){
+		x = b->p[i];
+		for(j = 0; j < Dbytes; j++){
+			if(p >= e)
+				return -1;
+			*p++ = x;
+			x >>= 8;
+		}
+	}
+	x = b->p[i];
+	while(x > 0){
+		if(p >= e)
+			return -1;
+		*p++ = x;
+		x >>= 8;
+	}
+
+	return p - s;
+}
--- /dev/null
+++ b/libmp/mptoui.c
@@ -1,0 +1,35 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+/*
+ *  this code assumes that mpdigit is at least as
+ *  big as an int.
+ */
+
+mpint*
+uitomp(uint i, mpint *b)
+{
+	if(b == nil)
+		b = mpnew(0);
+	mpassign(mpzero, b);
+	if(i != 0)
+		b->top = 1;
+	*b->p = i;
+	return b;
+}
+
+uint
+mptoui(mpint *b)
+{
+	uint x;
+
+	x = *b->p;
+	if(b->sign < 0){
+		x = 0;
+	} else {
+		if(b->top > 1 || x > MAXUINT)
+			x =  MAXUINT;
+	}
+	return x;
+}
--- /dev/null
+++ b/libmp/mptouv.c
@@ -1,0 +1,49 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+#define VLDIGITS (sizeof(vlong)/sizeof(mpdigit))
+
+/*
+ *  this code assumes that a vlong is an integral number of
+ *  mpdigits long.
+ */
+mpint*
+uvtomp(uvlong v, mpint *b)
+{
+	int s;
+
+	if(b == nil)
+		b = mpnew(VLDIGITS*sizeof(mpdigit));
+	else
+		mpbits(b, VLDIGITS*sizeof(mpdigit));
+	mpassign(mpzero, b);
+	if(v == 0)
+		return b;
+	for(s = 0; s < VLDIGITS && v != 0; s++){
+		b->p[s] = v;
+		v >>= sizeof(mpdigit)*8;
+	}
+	b->top = s;
+	return b;
+}
+
+uvlong
+mptouv(mpint *b)
+{
+	uvlong v;
+	int s;
+
+	if(b->top == 0)
+		return (vlong) 0;
+
+	mpnorm(b);
+	if(b->top > VLDIGITS)
+		return MAXVLONG;
+
+	v = (uvlong) 0;
+	for(s = 0; s < b->top; s++)
+		v |= b->p[s]<<(s*sizeof(mpdigit)*8);
+
+	return v;
+}
--- /dev/null
+++ b/libmp/mptov.c
@@ -1,0 +1,69 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+#define VLDIGITS (sizeof(vlong)/sizeof(mpdigit))
+
+/*
+ *  this code assumes that a vlong is an integral number of
+ *  mpdigits long.
+ */
+mpint*
+vtomp(vlong v, mpint *b)
+{
+	int s;
+	uvlong uv;
+
+	if(b == nil)
+		b = mpnew(VLDIGITS*sizeof(mpdigit));
+	else
+		mpbits(b, VLDIGITS*sizeof(mpdigit));
+	mpassign(mpzero, b);
+	if(v == 0)
+		return b;
+	if(v < 0){
+		b->sign = -1;
+		uv = -v;
+	} else
+		uv = v;
+	for(s = 0; s < VLDIGITS && uv != 0; s++){
+		b->p[s] = uv;
+		uv >>= sizeof(mpdigit)*8;
+	}
+	b->top = s;
+	return b;
+}
+
+vlong
+mptov(mpint *b)
+{
+	uvlong v;
+	int s;
+
+	if(b->top == 0)
+		return (vlong) 0;
+
+	mpnorm(b);
+	if(b->top > VLDIGITS){
+		if(b->sign > 0)
+			return (vlong)MAXVLONG;
+		else
+			return (vlong)MINVLONG;
+	}
+
+	v = (uvlong) 0;
+	for(s = 0; s < b->top; s++)
+		v |= b->p[s]<<(s*sizeof(mpdigit)*8);
+
+	if(b->sign > 0){
+		if(v > MAXVLONG)
+			v = MAXVLONG;
+	} else {
+		if(v > MINVLONG)
+			v = MINVLONG;
+		else
+			v = -(vlong)v;
+	}
+
+	return (vlong)v;
+}
--- /dev/null
+++ b/libmp/mpvecadd.c
@@ -1,0 +1,35 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// prereq: alen >= blen, sum has at least blen+1 digits
+void
+mpvecadd(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *sum)
+{
+	int i, carry;
+	mpdigit x, y;
+
+	carry = 0;
+	for(i = 0; i < blen; i++){
+		x = *a++;
+		y = *b++;
+		x += carry;
+		if(x < carry)
+			carry = 1;
+		else
+			carry = 0;
+		x += y;
+		if(x < y)
+			carry++;
+		*sum++ = x;
+	}
+	for(; i < alen; i++){
+		x = *a++ + carry;
+		if(x < carry)
+			carry = 1;
+		else
+			carry = 0;
+		*sum++ = x;
+	}
+	*sum = carry;
+}
--- /dev/null
+++ b/libmp/mpveccmp.c
@@ -1,0 +1,28 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// prereq: alen >= blen
+int
+mpveccmp(mpdigit *a, int alen, mpdigit *b, int blen)
+{
+	mpdigit x;
+
+	while(alen > blen)
+		if(a[--alen] != 0)
+			return 1;
+	while(blen > alen)
+		if(b[--blen] != 0)
+			return -1;
+	while(alen > 0){
+		--alen;
+		x = a[alen] - b[alen];
+		if(x == 0)
+			continue;
+		if(x > a[alen])
+			return -1;
+		else
+			return 1;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libmp/mpvecdigmuladd.c
@@ -1,0 +1,103 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+#define LO(x) ((x) & ((1<<(Dbits/2))-1))
+#define HI(x) ((x) >> (Dbits/2))
+
+static void
+mpdigmul(mpdigit a, mpdigit b, mpdigit *p)
+{
+	mpdigit x, ah, al, bh, bl, p1, p2, p3, p4;
+	int carry;
+
+	// half digits
+	ah = HI(a);
+	al = LO(a);
+	bh = HI(b);
+	bl = LO(b);
+
+	// partial products
+	p1 = ah*bl;
+	p2 = bh*al;
+	p3 = bl*al;
+	p4 = ah*bh;
+
+	// p = ((p1+p2)<<(Dbits/2)) + (p4<<Dbits) + p3
+	carry = 0;
+	x = p1<<(Dbits/2);
+	p3 += x;
+	if(p3 < x)
+		carry++;
+	x = p2<<(Dbits/2);
+	p3 += x;
+	if(p3 < x)
+		carry++;
+	p4 += carry + HI(p1) + HI(p2);	// can't carry out of the high digit
+	p[0] = p3;
+	p[1] = p4;
+}
+
+// prereq: p must have room for n+1 digits
+void
+mpvecdigmuladd(mpdigit *b, int n, mpdigit m, mpdigit *p)
+{
+	int i;
+	mpdigit carry, x, y, part[2];
+
+	carry = 0;
+	part[1] = 0;
+	for(i = 0; i < n; i++){
+		x = part[1] + carry;
+		if(x < carry)
+			carry = 1;
+		else
+			carry = 0;
+		y = *p;
+		mpdigmul(*b++, m, part);
+		x += part[0];
+		if(x < part[0])
+			carry++;
+		x += y;
+		if(x < y)
+			carry++;
+		*p++ = x;
+	}
+	*p = part[1] + carry;
+}
+
+// prereq: p must have room for n+1 digits
+int
+mpvecdigmulsub(mpdigit *b, int n, mpdigit m, mpdigit *p)
+{
+	int i;
+	mpdigit x, y, part[2], borrow;
+
+	borrow = 0;
+	part[1] = 0;
+	for(i = 0; i < n; i++){
+		x = *p;
+		y = x - borrow;
+		if(y > x)
+			borrow = 1;
+		else
+			borrow = 0;
+		x = part[1];
+		mpdigmul(*b++, m, part);
+		x += part[0];
+		if(x < part[0])
+			borrow++;
+		x = y - x;
+		if(x > y)
+			borrow++;
+		*p++ = x;
+	}
+
+	x = *p;
+	y = x - borrow - part[1];
+	*p = y;
+	if(y > x)
+		return -1;
+	else
+		return 1;
+}
--- /dev/null
+++ b/libmp/mpvecsub.c
@@ -1,0 +1,34 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// prereq: a >= b, alen >= blen, diff has at least alen digits
+void
+mpvecsub(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *diff)
+{
+	int i, borrow;
+	mpdigit x, y;
+
+	borrow = 0;
+	for(i = 0; i < blen; i++){
+		x = *a++;
+		y = *b++;
+		y += borrow;
+		if(y < borrow)
+			borrow = 1;
+		else
+			borrow = 0;
+		if(x < y)
+			borrow++;
+		*diff++ = x - y;
+	}
+	for(; i < alen; i++){
+		x = *a++;
+		y = x - borrow;
+		if(y > x)
+			borrow = 1;
+		else
+			borrow = 0;
+		*diff++ = y;
+	}
+}
--- /dev/null
+++ b/libmp/os.h
@@ -1,0 +1,3 @@
+#include <u.h>
+#include <libc.h>
+
--- /dev/null
+++ b/libmp/reduce
@@ -1,0 +1,16 @@
+O=$1
+shift
+objtype=$1
+shift
+
+ls -p ../$objtype/*.[cs] >[2]/dev/null | sed 's/..$//' > /tmp/reduce.$pid
+#
+#	if empty directory, just return the input files
+#
+if (! ~ $status '|') {
+	echo $*
+	rm /tmp/reduce.$pid
+	exit 0
+}
+echo $* | tr ' ' \012 | grep -v -f /tmp/reduce.$pid | tr \012 ' '
+rm /tmp/reduce.$pid
--- /dev/null
+++ b/libmp/strtomp.c
@@ -1,0 +1,205 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+#include "dat.h"
+
+static struct {
+	int	inited;
+
+	uchar	t64[256];
+	uchar	t32[256];
+	uchar	t16[256];
+	uchar	t10[256];
+} tab;
+
+enum {
+	INVAL=	255
+};
+
+static char set64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static char set32[] = "23456789abcdefghijkmnpqrstuvwxyz";
+static char set16[] = "0123456789ABCDEF0123456789abcdef";
+static char set10[] = "0123456789";
+
+static void
+init(void)
+{
+	char *p;
+
+	memset(tab.t64, INVAL, sizeof(tab.t64));
+	memset(tab.t32, INVAL, sizeof(tab.t32));
+	memset(tab.t16, INVAL, sizeof(tab.t16));
+	memset(tab.t10, INVAL, sizeof(tab.t10));
+
+	for(p = set64; *p; p++)
+		tab.t64[*p] = p-set64;
+	for(p = set32; *p; p++)
+		tab.t32[*p] = p-set32;
+	for(p = set16; *p; p++)
+		tab.t16[*p] = (p-set16)%16;
+	for(p = set10; *p; p++)
+		tab.t10[*p] = (p-set10);
+
+	tab.inited = 1;
+}
+
+static char*
+from16(char *a, mpint *b)
+{
+	char *p, *next;
+	int i;
+	mpdigit x;
+
+	b->top = 0;
+	for(p = a; *p; p++)
+		if(tab.t16[*p] == INVAL)
+			break;
+	mpbits(b, (p-a)*4);
+	b->top = 0;
+	next = p;
+	while(p > a){
+		x = 0;
+		for(i = 0; i < Dbits; i += 4){
+			if(p <= a)
+				break;
+			x |= tab.t16[*--p]<<i;
+		}
+		b->p[b->top++] = x;
+	}
+	return next;
+}
+
+static ulong mppow10[] = {
+	1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
+};
+
+static char*
+from10(char *a, mpint *b)
+{
+	ulong x, y;
+	mpint *pow, *r;
+	int i;
+
+	pow = mpnew(0);
+	r = mpnew(0);
+
+	b->top = 0;
+	for(;;){
+		// do a billion at a time in native arithmetic
+		x = 0;
+		for(i = 0; i < 9; i++){
+			y = tab.t10[*a];
+			if(y == INVAL)
+				break;
+			a++;
+			x *= 10;
+			x += y;
+		}
+		if(i == 0)
+			break;
+
+		// accumulate into mpint
+		uitomp(mppow10[i], pow);
+		uitomp(x, r);
+		mpmul(b, pow, b);
+		mpadd(b, r, b);
+		if(i != 9)
+			break;
+	}
+	mpfree(pow);
+	mpfree(r);
+	return a;
+}
+
+static char*
+from64(char *a, mpint *b)
+{
+	char *buf = a;
+	uchar *p;
+	int n, m;
+
+	for(; tab.t64[*a] != INVAL; a++)
+		;
+	n = a-buf;
+	mpbits(b, n*6);
+	p = malloc(n);
+	if(p == nil)
+		return a;
+	m = dec64(p, n, buf, n);
+	betomp(p, m, b);
+	free(p);
+	return a;
+}
+
+static char*
+from32(char *a, mpint *b)
+{
+	char *buf = a;
+	uchar *p;
+	int n, m;
+
+	for(; tab.t64[*a] != INVAL; a++)
+		;
+	n = a-buf;
+	mpbits(b, n*5);
+	p = malloc(n);
+	if(p == nil)
+		return a;
+	m = dec32(p, n, buf, n);
+	betomp(p, m, b);
+	free(p);
+	return a;
+}
+
+mpint*
+strtomp(char *a, char **pp, int base, mpint *b)
+{
+	int sign;
+	char *e;
+
+	if(b == nil)
+		b = mpnew(0);
+
+	if(tab.inited == 0)
+		init();
+
+	while(*a==' ' || *a=='\t')
+		a++;
+
+	sign = 1;
+	for(;; a++){
+		switch(*a){
+		case '-':
+			sign *= -1;
+			continue;
+		}
+		break;
+	}
+
+	switch(base){
+	case 10:
+		e = from10(a, b);
+		break;
+	default:
+	case 16:
+		e = from16(a, b);
+		break;
+	case 32:
+		e = from32(a, b);
+		break;
+	case 64:
+		e = from64(a, b);
+		break;
+	}
+
+	// if no characters parsed, there wasn't a number to convert
+	if(e == a)
+		return nil;
+
+	mpnorm(b);
+	b->sign = sign;
+	if(pp != nil)
+		*pp = e;
+
+	return b;
+}
--- /dev/null
+++ b/libsec/Makefile
@@ -1,0 +1,60 @@
+LIB=libsec.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	aes.$O\
+	blowfish.$O\
+	decodepem.$O\
+	des.$O\
+	des3CBC.$O\
+	des3ECB.$O\
+	desCBC.$O\
+	desECB.$O\
+	desmodes.$O\
+	dsaalloc.$O\
+	dsagen.$O\
+	dsaprimes.$O\
+	dsaprivtopub.$O\
+	dsasign.$O\
+	dsaverify.$O\
+	egalloc.$O\
+	egdecrypt.$O\
+	egencrypt.$O\
+	eggen.$O\
+	egprivtopub.$O\
+	egsign.$O\
+	egverify.$O\
+	fastrand.$O\
+	genprime.$O\
+	genrandom.$O\
+	gensafeprime.$O\
+	genstrongprime.$O\
+	hmac.$O\
+	md4.$O\
+	md5.$O\
+	md5block.$O\
+	md5pickle.$O\
+	nfastrand.$O\
+	prng.$O\
+	probably_prime.$O\
+	rc4.$O\
+	rsaalloc.$O\
+	rsadecrypt.$O\
+	rsaencrypt.$O\
+	rsafill.$O\
+	rsagen.$O\
+	rsaprivtopub.$O\
+	sha1.$O\
+	sha1block.$O\
+	sha1pickle.$O\
+	smallprimes.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libsec/aes.c
@@ -1,0 +1,1544 @@
+/*
+ * this code is derived from the following source,
+ * and modified to fit into the plan 9 libsec interface.
+ * most of the changes are confined to the top section,
+ * with the exception of converting Te4 and Td4 into u8 rather than u32 arrays.
+ *
+ * rijndael-alg-fst.c
+ *
+ * @version 3.0 (December 2000)
+ *
+ * Optimised ANSI C code for the Rijndael cipher (now AES)
+ *
+ * @author Vincent Rijmen <vincent.rijmen@esat.kuleuven.ac.be>
+ * @author Antoon Bosselaers <antoon.bosselaers@esat.kuleuven.ac.be>
+ * @author Paulo Barreto <paulo.barreto@terra.com.br>
+ *
+ * This code is hereby placed in the public domain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+
+typedef uchar	u8;
+typedef u32int	u32;
+#define FULL_UNROLL
+
+static const u32 Td0[256];
+static const u32 Td1[256];
+static const u32 Td2[256];
+static const u32 Td3[256];
+static const u8  Te4[256];
+
+static int rijndaelKeySetupEnc(u32 rk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits);
+static int rijndaelKeySetupDec(u32 rk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits);
+static int rijndaelKeySetup(u32 erk[/*4*(Nr + 1)*/], u32 drk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits);
+static void	rijndaelEncrypt(const u32int rk[], int Nr, const uchar pt[16], uchar ct[16]);
+static void	rijndaelDecrypt(const u32int rk[], int Nr, const uchar ct[16], uchar pt[16]);
+
+void
+setupAESstate(AESstate *s, uchar key[], int keybytes, uchar *ivec)
+{
+	memset(s, 0, sizeof(*s));
+	if(keybytes > AESmaxkey)
+		keybytes = AESmaxkey;
+	memmove(s->key, key, keybytes);
+	s->keybytes = keybytes;
+	s->rounds = rijndaelKeySetup(s->ekey, s->dkey, s->key, keybytes * 8);
+	if(ivec != nil)
+		memmove(s->ivec, ivec, AESbsize);
+	if(keybytes==16 || keybytes==24 || keybytes==32)
+		s->setup = 0xcafebabe;
+	// else rijndaelKeySetup was invalid
+}
+
+// Define by analogy with desCBCencrypt;  AES modes are not standardized yet.
+// Because of the way that non-multiple-of-16 buffers are handled,
+// the decryptor must be fed buffers of the same size as the encryptor.
+void
+aesCBCencrypt(uchar *p, int len, AESstate *s)
+{
+	uchar *p2, *ip, *eip;
+	uchar q[AESbsize];
+
+	for(; len >= AESbsize; len -= AESbsize){
+		p2 = p;
+		ip = s->ivec;
+		for(eip = ip+AESbsize; ip < eip; )
+			*p2++ ^= *ip++;
+		rijndaelEncrypt(s->ekey, s->rounds, p, q);
+		memmove(s->ivec, q, AESbsize);
+		memmove(p, q, AESbsize);
+		p += AESbsize;
+	}
+
+	if(len > 0){
+		ip = s->ivec;
+		rijndaelEncrypt(s->ekey, s->rounds, ip, q);
+		memmove(s->ivec, q, AESbsize);
+		for(eip = ip+len; ip < eip; )
+			*p++ ^= *ip++;
+	}
+}
+
+void
+aesCBCdecrypt(uchar *p, int len, AESstate *s)
+{
+	uchar *ip, *eip, *tp;
+	uchar tmp[AESbsize], q[AESbsize];
+
+	for(; len >= AESbsize; len -= AESbsize){
+		memmove(tmp, p, AESbsize);
+		rijndaelDecrypt(s->dkey, s->rounds, p, q);
+		memmove(p, q, AESbsize);
+		tp = tmp;
+		ip = s->ivec;
+		for(eip = ip+AESbsize; ip < eip; ){
+			*p++ ^= *ip;
+			*ip++ = *tp++;
+		}
+	}
+
+	if(len > 0){
+		ip = s->ivec;
+		rijndaelEncrypt(s->ekey, s->rounds, ip, q);
+		memmove(s->ivec, q, AESbsize);
+		for(eip = ip+len; ip < eip; )
+			*p++ ^= *ip++;
+	}
+}
+
+/*
+ * this function has been changed for plan 9.
+ * Expand the cipher key into the encryption and decryption key schedules.
+ *
+ * @return	the number of rounds for the given cipher key size.
+ */
+static int rijndaelKeySetup(u32 erk[/*4*(Nr + 1)*/], u32 drk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits) {
+	int Nr, i;
+
+	/* expand the cipher key: */
+	Nr = rijndaelKeySetupEnc(erk, cipherKey, keyBits);
+
+	/*
+	 * invert the order of the round keys and
+	 * apply the inverse MixColumn transform to all round keys but the first and the last
+	 */
+	drk[0       ] = erk[4*Nr    ]; 
+	drk[1       ] = erk[4*Nr + 1];
+	drk[2       ] = erk[4*Nr + 2]; 
+	drk[3       ] = erk[4*Nr + 3];
+	drk[4*Nr    ] = erk[0       ]; 
+	drk[4*Nr + 1] = erk[1       ];
+	drk[4*Nr + 2] = erk[2       ]; 
+	drk[4*Nr + 3] = erk[3       ];
+	erk += 4 * Nr;
+	for (i = 1; i < Nr; i++) {
+		drk += 4;
+		erk -= 4;
+		drk[0] =
+		    Td0[Te4[(erk[0] >> 24)       ]] ^
+		    Td1[Te4[(erk[0] >> 16) & 0xff]] ^
+		    Td2[Te4[(erk[0] >>  8) & 0xff]] ^
+		    Td3[Te4[(erk[0]      ) & 0xff]];
+		drk[1] =
+		    Td0[Te4[(erk[1] >> 24)       ]] ^
+		    Td1[Te4[(erk[1] >> 16) & 0xff]] ^
+		    Td2[Te4[(erk[1] >>  8) & 0xff]] ^
+		    Td3[Te4[(erk[1]      ) & 0xff]];
+		drk[2] =
+		    Td0[Te4[(erk[2] >> 24)       ]] ^
+		    Td1[Te4[(erk[2] >> 16) & 0xff]] ^
+		    Td2[Te4[(erk[2] >>  8) & 0xff]] ^
+		    Td3[Te4[(erk[2]      ) & 0xff]];
+		drk[3] =
+		    Td0[Te4[(erk[3] >> 24)       ]] ^
+		    Td1[Te4[(erk[3] >> 16) & 0xff]] ^
+		    Td2[Te4[(erk[3] >>  8) & 0xff]] ^
+		    Td3[Te4[(erk[3]      ) & 0xff]];
+	}
+	return Nr;
+}
+
+/*
+Te0[x] = S [x].[02, 01, 01, 03];
+Te1[x] = S [x].[03, 02, 01, 01];
+Te2[x] = S [x].[01, 03, 02, 01];
+Te3[x] = S [x].[01, 01, 03, 02];
+Te4[x] = S [x]
+
+Td0[x] = Si[x].[0e, 09, 0d, 0b];
+Td1[x] = Si[x].[0b, 0e, 09, 0d];
+Td2[x] = Si[x].[0d, 0b, 0e, 09];
+Td3[x] = Si[x].[09, 0d, 0b, 0e];
+Td4[x] = Si[x]
+*/
+
+static const u32 Te0[256] = {
+    0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
+    0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
+    0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
+    0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
+    0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
+    0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
+    0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
+    0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
+    0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
+    0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
+    0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
+    0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
+    0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
+    0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
+    0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
+    0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
+    0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
+    0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
+    0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
+    0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
+    0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
+    0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
+    0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
+    0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
+    0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
+    0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
+    0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
+    0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
+    0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
+    0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
+    0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
+    0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
+    0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
+    0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
+    0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
+    0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
+    0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
+    0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
+    0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
+    0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
+    0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
+    0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
+    0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
+    0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
+    0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
+    0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
+    0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
+    0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
+    0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
+    0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
+    0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
+    0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
+    0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
+    0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
+    0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
+    0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
+    0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
+    0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
+    0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
+    0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
+    0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
+    0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
+    0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
+    0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
+};
+static const u32 Te1[256] = {
+    0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
+    0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
+    0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
+    0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
+    0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
+    0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
+    0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
+    0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
+    0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
+    0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
+    0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
+    0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
+    0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
+    0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
+    0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
+    0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
+    0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
+    0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
+    0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
+    0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
+    0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
+    0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
+    0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
+    0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
+    0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
+    0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
+    0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
+    0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
+    0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
+    0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
+    0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
+    0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
+    0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
+    0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
+    0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
+    0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
+    0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
+    0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
+    0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
+    0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
+    0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
+    0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
+    0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
+    0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
+    0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
+    0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
+    0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
+    0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
+    0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
+    0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
+    0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
+    0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
+    0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
+    0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
+    0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
+    0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
+    0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
+    0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
+    0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
+    0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
+    0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
+    0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
+    0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
+    0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
+};
+static const u32 Te2[256] = {
+    0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
+    0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
+    0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
+    0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
+    0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
+    0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
+    0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
+    0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
+    0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
+    0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
+    0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
+    0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
+    0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
+    0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
+    0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
+    0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
+    0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
+    0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
+    0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
+    0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
+    0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
+    0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
+    0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
+    0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
+    0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
+    0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
+    0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
+    0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
+    0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
+    0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
+    0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
+    0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
+    0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
+    0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
+    0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
+    0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
+    0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
+    0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
+    0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
+    0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
+    0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
+    0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
+    0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
+    0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
+    0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
+    0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
+    0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
+    0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
+    0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
+    0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
+    0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
+    0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
+    0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
+    0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
+    0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
+    0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
+    0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
+    0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
+    0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
+    0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
+    0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
+    0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
+    0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
+    0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
+};
+static const u32 Te3[256] = {
+
+    0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
+    0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
+    0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
+    0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
+    0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
+    0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
+    0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
+    0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
+    0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
+    0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
+    0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
+    0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
+    0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
+    0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
+    0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
+    0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
+    0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
+    0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
+    0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
+    0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
+    0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
+    0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
+    0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
+    0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
+    0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
+    0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
+    0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
+    0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
+    0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
+    0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
+    0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
+    0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
+    0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
+    0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
+    0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
+    0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
+    0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
+    0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
+    0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
+    0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
+    0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
+    0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
+    0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
+    0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
+    0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
+    0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
+    0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
+    0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
+    0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
+    0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
+    0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
+    0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
+    0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
+    0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
+    0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
+    0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
+    0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
+    0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
+    0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
+    0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
+    0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
+    0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
+    0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
+    0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
+};
+static const u8 Te4[256] = {
+    0x63U, 0x7cU, 0x77U, 0x7bU,
+    0xf2U, 0x6bU, 0x6fU, 0xc5U,
+    0x30U, 0x01U, 0x67U, 0x2bU,
+    0xfeU, 0xd7U, 0xabU, 0x76U,
+    0xcaU, 0x82U, 0xc9U, 0x7dU,
+    0xfaU, 0x59U, 0x47U, 0xf0U,
+    0xadU, 0xd4U, 0xa2U, 0xafU,
+    0x9cU, 0xa4U, 0x72U, 0xc0U,
+    0xb7U, 0xfdU, 0x93U, 0x26U,
+    0x36U, 0x3fU, 0xf7U, 0xccU,
+    0x34U, 0xa5U, 0xe5U, 0xf1U,
+    0x71U, 0xd8U, 0x31U, 0x15U,
+    0x04U, 0xc7U, 0x23U, 0xc3U,
+    0x18U, 0x96U, 0x05U, 0x9aU,
+    0x07U, 0x12U, 0x80U, 0xe2U,
+    0xebU, 0x27U, 0xb2U, 0x75U,
+    0x09U, 0x83U, 0x2cU, 0x1aU,
+    0x1bU, 0x6eU, 0x5aU, 0xa0U,
+    0x52U, 0x3bU, 0xd6U, 0xb3U,
+    0x29U, 0xe3U, 0x2fU, 0x84U,
+    0x53U, 0xd1U, 0x00U, 0xedU,
+    0x20U, 0xfcU, 0xb1U, 0x5bU,
+    0x6aU, 0xcbU, 0xbeU, 0x39U,
+    0x4aU, 0x4cU, 0x58U, 0xcfU,
+    0xd0U, 0xefU, 0xaaU, 0xfbU,
+    0x43U, 0x4dU, 0x33U, 0x85U,
+    0x45U, 0xf9U, 0x02U, 0x7fU,
+    0x50U, 0x3cU, 0x9fU, 0xa8U,
+    0x51U, 0xa3U, 0x40U, 0x8fU,
+    0x92U, 0x9dU, 0x38U, 0xf5U,
+    0xbcU, 0xb6U, 0xdaU, 0x21U,
+    0x10U, 0xffU, 0xf3U, 0xd2U,
+    0xcdU, 0x0cU, 0x13U, 0xecU,
+    0x5fU, 0x97U, 0x44U, 0x17U,
+    0xc4U, 0xa7U, 0x7eU, 0x3dU,
+    0x64U, 0x5dU, 0x19U, 0x73U,
+    0x60U, 0x81U, 0x4fU, 0xdcU,
+    0x22U, 0x2aU, 0x90U, 0x88U,
+    0x46U, 0xeeU, 0xb8U, 0x14U,
+    0xdeU, 0x5eU, 0x0bU, 0xdbU,
+    0xe0U, 0x32U, 0x3aU, 0x0aU,
+    0x49U, 0x06U, 0x24U, 0x5cU,
+    0xc2U, 0xd3U, 0xacU, 0x62U,
+    0x91U, 0x95U, 0xe4U, 0x79U,
+    0xe7U, 0xc8U, 0x37U, 0x6dU,
+    0x8dU, 0xd5U, 0x4eU, 0xa9U,
+    0x6cU, 0x56U, 0xf4U, 0xeaU,
+    0x65U, 0x7aU, 0xaeU, 0x08U,
+    0xbaU, 0x78U, 0x25U, 0x2eU,
+    0x1cU, 0xa6U, 0xb4U, 0xc6U,
+    0xe8U, 0xddU, 0x74U, 0x1fU,
+    0x4bU, 0xbdU, 0x8bU, 0x8aU,
+    0x70U, 0x3eU, 0xb5U, 0x66U,
+    0x48U, 0x03U, 0xf6U, 0x0eU,
+    0x61U, 0x35U, 0x57U, 0xb9U,
+    0x86U, 0xc1U, 0x1dU, 0x9eU,
+    0xe1U, 0xf8U, 0x98U, 0x11U,
+    0x69U, 0xd9U, 0x8eU, 0x94U,
+    0x9bU, 0x1eU, 0x87U, 0xe9U,
+    0xceU, 0x55U, 0x28U, 0xdfU,
+    0x8cU, 0xa1U, 0x89U, 0x0dU,
+    0xbfU, 0xe6U, 0x42U, 0x68U,
+    0x41U, 0x99U, 0x2dU, 0x0fU,
+    0xb0U, 0x54U, 0xbbU, 0x16U,
+};
+static const u32 Td0[256] = {
+    0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U,
+    0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U,
+    0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U,
+    0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU,
+    0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U,
+    0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U,
+    0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU,
+    0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U,
+    0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU,
+    0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U,
+    0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U,
+    0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U,
+    0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U,
+    0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU,
+    0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U,
+    0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU,
+    0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U,
+    0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU,
+    0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U,
+    0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U,
+    0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U,
+    0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU,
+    0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U,
+    0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU,
+    0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U,
+    0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU,
+    0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U,
+    0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU,
+    0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU,
+    0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U,
+    0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU,
+    0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U,
+    0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU,
+    0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U,
+    0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U,
+    0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U,
+    0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU,
+    0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U,
+    0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U,
+    0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU,
+    0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U,
+    0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U,
+    0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U,
+    0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U,
+    0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U,
+    0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU,
+    0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U,
+    0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U,
+    0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U,
+    0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U,
+    0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U,
+    0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU,
+    0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU,
+    0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU,
+    0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU,
+    0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U,
+    0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U,
+    0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU,
+    0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU,
+    0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U,
+    0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU,
+    0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U,
+    0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U,
+    0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U,
+};
+static const u32 Td1[256] = {
+    0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU,
+    0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U,
+    0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU,
+    0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U,
+    0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U,
+    0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U,
+    0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U,
+    0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U,
+    0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U,
+    0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU,
+    0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU,
+    0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU,
+    0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U,
+    0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU,
+    0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U,
+    0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U,
+    0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U,
+    0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU,
+    0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU,
+    0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U,
+    0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU,
+    0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U,
+    0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU,
+    0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU,
+    0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U,
+    0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U,
+    0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U,
+    0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU,
+    0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U,
+    0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU,
+    0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U,
+    0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U,
+    0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U,
+    0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU,
+    0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U,
+    0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U,
+    0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U,
+    0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U,
+    0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U,
+    0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U,
+    0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU,
+    0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU,
+    0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U,
+    0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU,
+    0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U,
+    0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU,
+    0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU,
+    0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U,
+    0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU,
+    0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U,
+    0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U,
+    0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U,
+    0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U,
+    0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U,
+    0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U,
+    0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U,
+    0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU,
+    0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U,
+    0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U,
+    0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU,
+    0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U,
+    0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U,
+    0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U,
+    0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U,
+};
+static const u32 Td2[256] = {
+    0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U,
+    0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U,
+    0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U,
+    0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U,
+    0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU,
+    0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U,
+    0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U,
+    0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U,
+    0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U,
+    0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU,
+    0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U,
+    0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U,
+    0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU,
+    0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U,
+    0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U,
+    0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U,
+    0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U,
+    0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U,
+    0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U,
+    0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU,
+
+    0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U,
+    0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U,
+    0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U,
+    0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U,
+    0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U,
+    0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU,
+    0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU,
+    0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U,
+    0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU,
+    0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U,
+    0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU,
+    0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU,
+    0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU,
+    0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU,
+    0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U,
+    0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U,
+    0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U,
+    0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U,
+    0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U,
+    0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U,
+    0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U,
+    0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU,
+    0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU,
+    0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U,
+    0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U,
+    0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU,
+    0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU,
+    0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U,
+    0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U,
+    0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U,
+    0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U,
+    0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U,
+    0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U,
+    0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U,
+    0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU,
+    0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U,
+    0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U,
+    0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U,
+    0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U,
+    0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U,
+    0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U,
+    0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU,
+    0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U,
+    0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U,
+};
+static const u32 Td3[256] = {
+    0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU,
+    0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU,
+    0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U,
+    0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U,
+    0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU,
+    0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU,
+    0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U,
+    0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU,
+    0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U,
+    0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU,
+    0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U,
+    0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U,
+    0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U,
+    0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U,
+    0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U,
+    0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU,
+    0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU,
+    0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U,
+    0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U,
+    0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU,
+    0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU,
+    0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U,
+    0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U,
+    0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U,
+    0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U,
+    0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU,
+    0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U,
+    0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U,
+    0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU,
+    0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU,
+    0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U,
+    0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U,
+    0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U,
+    0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU,
+    0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U,
+    0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U,
+    0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U,
+    0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U,
+    0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U,
+    0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U,
+    0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U,
+    0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU,
+    0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U,
+    0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U,
+    0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU,
+    0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU,
+    0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U,
+    0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU,
+    0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U,
+    0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U,
+    0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U,
+    0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U,
+    0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U,
+    0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U,
+    0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU,
+    0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU,
+    0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU,
+    0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU,
+    0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U,
+    0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U,
+    0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U,
+    0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU,
+    0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U,
+    0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U,
+};
+static const u8 Td4[256] = {
+    0x52U, 0x09U, 0x6aU, 0xd5U,
+    0x30U, 0x36U, 0xa5U, 0x38U,
+    0xbfU, 0x40U, 0xa3U, 0x9eU,
+    0x81U, 0xf3U, 0xd7U, 0xfbU,
+    0x7cU, 0xe3U, 0x39U, 0x82U,
+    0x9bU, 0x2fU, 0xffU, 0x87U,
+    0x34U, 0x8eU, 0x43U, 0x44U,
+    0xc4U, 0xdeU, 0xe9U, 0xcbU,
+    0x54U, 0x7bU, 0x94U, 0x32U,
+    0xa6U, 0xc2U, 0x23U, 0x3dU,
+    0xeeU, 0x4cU, 0x95U, 0x0bU,
+    0x42U, 0xfaU, 0xc3U, 0x4eU,
+    0x08U, 0x2eU, 0xa1U, 0x66U,
+    0x28U, 0xd9U, 0x24U, 0xb2U,
+    0x76U, 0x5bU, 0xa2U, 0x49U,
+    0x6dU, 0x8bU, 0xd1U, 0x25U,
+    0x72U, 0xf8U, 0xf6U, 0x64U,
+    0x86U, 0x68U, 0x98U, 0x16U,
+    0xd4U, 0xa4U, 0x5cU, 0xccU,
+    0x5dU, 0x65U, 0xb6U, 0x92U,
+    0x6cU, 0x70U, 0x48U, 0x50U,
+    0xfdU, 0xedU, 0xb9U, 0xdaU,
+    0x5eU, 0x15U, 0x46U, 0x57U,
+    0xa7U, 0x8dU, 0x9dU, 0x84U,
+    0x90U, 0xd8U, 0xabU, 0x00U,
+    0x8cU, 0xbcU, 0xd3U, 0x0aU,
+    0xf7U, 0xe4U, 0x58U, 0x05U,
+    0xb8U, 0xb3U, 0x45U, 0x06U,
+    0xd0U, 0x2cU, 0x1eU, 0x8fU,
+    0xcaU, 0x3fU, 0x0fU, 0x02U,
+    0xc1U, 0xafU, 0xbdU, 0x03U,
+    0x01U, 0x13U, 0x8aU, 0x6bU,
+    0x3aU, 0x91U, 0x11U, 0x41U,
+    0x4fU, 0x67U, 0xdcU, 0xeaU,
+    0x97U, 0xf2U, 0xcfU, 0xceU,
+    0xf0U, 0xb4U, 0xe6U, 0x73U,
+    0x96U, 0xacU, 0x74U, 0x22U,
+    0xe7U, 0xadU, 0x35U, 0x85U,
+    0xe2U, 0xf9U, 0x37U, 0xe8U,
+    0x1cU, 0x75U, 0xdfU, 0x6eU,
+    0x47U, 0xf1U, 0x1aU, 0x71U,
+    0x1dU, 0x29U, 0xc5U, 0x89U,
+    0x6fU, 0xb7U, 0x62U, 0x0eU,
+    0xaaU, 0x18U, 0xbeU, 0x1bU,
+    0xfcU, 0x56U, 0x3eU, 0x4bU,
+    0xc6U, 0xd2U, 0x79U, 0x20U,
+    0x9aU, 0xdbU, 0xc0U, 0xfeU,
+    0x78U, 0xcdU, 0x5aU, 0xf4U,
+    0x1fU, 0xddU, 0xa8U, 0x33U,
+    0x88U, 0x07U, 0xc7U, 0x31U,
+    0xb1U, 0x12U, 0x10U, 0x59U,
+    0x27U, 0x80U, 0xecU, 0x5fU,
+    0x60U, 0x51U, 0x7fU, 0xa9U,
+    0x19U, 0xb5U, 0x4aU, 0x0dU,
+    0x2dU, 0xe5U, 0x7aU, 0x9fU,
+    0x93U, 0xc9U, 0x9cU, 0xefU,
+    0xa0U, 0xe0U, 0x3bU, 0x4dU,
+    0xaeU, 0x2aU, 0xf5U, 0xb0U,
+    0xc8U, 0xebU, 0xbbU, 0x3cU,
+    0x83U, 0x53U, 0x99U, 0x61U,
+    0x17U, 0x2bU, 0x04U, 0x7eU,
+    0xbaU, 0x77U, 0xd6U, 0x26U,
+    0xe1U, 0x69U, 0x14U, 0x63U,
+    0x55U, 0x21U, 0x0cU, 0x7dU,
+};
+static const u32 rcon[] = {
+	0x01000000, 0x02000000, 0x04000000, 0x08000000,
+	0x10000000, 0x20000000, 0x40000000, 0x80000000,
+	0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */
+};
+
+#define SWAP(x) (_lrotl(x, 8) & 0x00ff00ff | _lrotr(x, 8) & 0xff00ff00)
+
+#ifdef _MSC_VER
+#define GETU32(p) SWAP(*((u32 *)(p)))
+#define PUTU32(ct, st) { *((u32 *)(ct)) = SWAP((st)); }
+#else
+#define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] <<  8) ^ ((u32)(pt)[3]))
+#define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >>  8); (ct)[3] = (u8)(st); }
+#endif
+
+/**
+ * Expand the cipher key into the encryption key schedule.
+ *
+ * @return	the number of rounds for the given cipher key size.
+ */
+static int rijndaelKeySetupEnc(u32 rk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits) {
+   	int i = 0;
+	u32 temp;
+
+	rk[0] = GETU32(cipherKey     );
+	rk[1] = GETU32(cipherKey +  4);
+	rk[2] = GETU32(cipherKey +  8);
+	rk[3] = GETU32(cipherKey + 12);
+	if (keyBits == 128) {
+		for (;;) {
+			temp  = rk[3];
+			rk[4] = rk[0] ^
+				(Te4[(temp >> 16) & 0xff] << 24) ^
+				(Te4[(temp >>  8) & 0xff] << 16) ^
+				(Te4[(temp      ) & 0xff] <<  8) ^
+				(Te4[(temp >> 24)       ]      ) ^
+				rcon[i];
+			rk[5] = rk[1] ^ rk[4];
+			rk[6] = rk[2] ^ rk[5];
+			rk[7] = rk[3] ^ rk[6];
+			if (++i == 10) {
+				return 10;
+			}
+			rk += 4;
+		}
+	}
+	rk[4] = GETU32(cipherKey + 16);
+	rk[5] = GETU32(cipherKey + 20);
+	if (keyBits == 192) {
+		for (;;) {
+			temp = rk[ 5];
+			rk[ 6] = rk[ 0] ^
+				(Te4[(temp >> 16) & 0xff] << 24) ^
+				(Te4[(temp >>  8) & 0xff] << 16) ^
+				(Te4[(temp      ) & 0xff] <<  8) ^
+				(Te4[(temp >> 24)       ]      ) ^
+				rcon[i];
+			rk[ 7] = rk[ 1] ^ rk[ 6];
+			rk[ 8] = rk[ 2] ^ rk[ 7];
+			rk[ 9] = rk[ 3] ^ rk[ 8];
+			if (++i == 8) {
+				return 12;
+			}
+			rk[10] = rk[ 4] ^ rk[ 9];
+			rk[11] = rk[ 5] ^ rk[10];
+			rk += 6;
+		}
+	}
+	rk[6] = GETU32(cipherKey + 24);
+	rk[7] = GETU32(cipherKey + 28);
+	if (keyBits == 256) {
+        for (;;) {
+        	temp = rk[ 7];
+        	rk[ 8] = rk[ 0] ^
+        		(Te4[(temp >> 16) & 0xff] << 24) ^
+        		(Te4[(temp >>  8) & 0xff] << 16) ^
+        		(Te4[(temp      ) & 0xff] <<  8) ^
+        		(Te4[(temp >> 24)       ]      ) ^
+        		rcon[i];
+        	rk[ 9] = rk[ 1] ^ rk[ 8];
+        	rk[10] = rk[ 2] ^ rk[ 9];
+        	rk[11] = rk[ 3] ^ rk[10];
+			if (++i == 7) {
+				return 14;
+			}
+        	temp = rk[11];
+        	rk[12] = rk[ 4] ^
+        		(Te4[(temp >> 24)       ] << 24) ^
+        		(Te4[(temp >> 16) & 0xff] << 16) ^
+        		(Te4[(temp >>  8) & 0xff] <<  8) ^
+        		(Te4[(temp      ) & 0xff]      );
+        	rk[13] = rk[ 5] ^ rk[12];
+        	rk[14] = rk[ 6] ^ rk[13];
+        	rk[15] = rk[ 7] ^ rk[14];
+
+			rk += 8;
+        }
+	}
+	return 0;
+}
+
+/**
+ * Expand the cipher key into the decryption key schedule.
+ *
+ * @return	the number of rounds for the given cipher key size.
+ */
+static int rijndaelKeySetupDec(u32 rk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits) {
+	int Nr, i, j;
+	u32 temp;
+
+	/* expand the cipher key: */
+	Nr = rijndaelKeySetupEnc(rk, cipherKey, keyBits);
+	/* invert the order of the round keys: */
+	for (i = 0, j = 4*Nr; i < j; i += 4, j -= 4) {
+		temp = rk[i    ]; rk[i    ] = rk[j    ]; rk[j    ] = temp;
+		temp = rk[i + 1]; rk[i + 1] = rk[j + 1]; rk[j + 1] = temp;
+		temp = rk[i + 2]; rk[i + 2] = rk[j + 2]; rk[j + 2] = temp;
+		temp = rk[i + 3]; rk[i + 3] = rk[j + 3]; rk[j + 3] = temp;
+	}
+	/* apply the inverse MixColumn transform to all round keys but the first and the last: */
+	for (i = 1; i < Nr; i++) {
+		rk += 4;
+		rk[0] =
+			Td0[Te4[(rk[0] >> 24)       ]] ^
+			Td1[Te4[(rk[0] >> 16) & 0xff]] ^
+			Td2[Te4[(rk[0] >>  8) & 0xff]] ^
+			Td3[Te4[(rk[0]      ) & 0xff]];
+		rk[1] =
+			Td0[Te4[(rk[1] >> 24)       ]] ^
+			Td1[Te4[(rk[1] >> 16) & 0xff]] ^
+			Td2[Te4[(rk[1] >>  8) & 0xff]] ^
+			Td3[Te4[(rk[1]      ) & 0xff]];
+		rk[2] =
+			Td0[Te4[(rk[2] >> 24)       ]] ^
+			Td1[Te4[(rk[2] >> 16) & 0xff]] ^
+			Td2[Te4[(rk[2] >>  8) & 0xff]] ^
+			Td3[Te4[(rk[2]      ) & 0xff]];
+		rk[3] =
+			Td0[Te4[(rk[3] >> 24)       ]] ^
+			Td1[Te4[(rk[3] >> 16) & 0xff]] ^
+			Td2[Te4[(rk[3] >>  8) & 0xff]] ^
+			Td3[Te4[(rk[3]      ) & 0xff]];
+	}
+	return Nr;
+}
+
+static void rijndaelEncrypt(const u32 rk[/*4*(Nr + 1)*/], int Nr, const u8 pt[16], u8 ct[16]) {
+	u32 s0, s1, s2, s3, t0, t1, t2, t3;
+#ifndef FULL_UNROLL
+    int r;
+#endif /* ?FULL_UNROLL */
+
+    /*
+	 * map byte array block to cipher state
+	 * and add initial round key:
+	 */
+	s0 = GETU32(pt     ) ^ rk[0];
+	s1 = GETU32(pt +  4) ^ rk[1];
+	s2 = GETU32(pt +  8) ^ rk[2];
+	s3 = GETU32(pt + 12) ^ rk[3];
+#ifdef FULL_UNROLL
+    /* round 1: */
+   	t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4];
+   	t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5];
+   	t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6];
+   	t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7];
+   	/* round 2: */
+   	s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8];
+   	s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9];
+   	s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10];
+   	s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11];
+    /* round 3: */
+   	t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12];
+   	t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13];
+   	t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14];
+   	t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15];
+   	/* round 4: */
+   	s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16];
+   	s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17];
+   	s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18];
+   	s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19];
+    /* round 5: */
+   	t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20];
+   	t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21];
+   	t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22];
+   	t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23];
+   	/* round 6: */
+   	s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24];
+   	s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25];
+   	s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26];
+   	s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27];
+    /* round 7: */
+   	t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28];
+   	t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29];
+   	t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30];
+   	t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31];
+   	/* round 8: */
+   	s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32];
+   	s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33];
+   	s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34];
+   	s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35];
+    /* round 9: */
+   	t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36];
+   	t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37];
+   	t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38];
+   	t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39];
+    if (Nr > 10) {
+        /* round 10: */
+        s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[40];
+        s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[41];
+        s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[42];
+        s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[43];
+        /* round 11: */
+        t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[44];
+        t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[45];
+        t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[46];
+        t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[47];
+        if (Nr > 12) {
+            /* round 12: */
+            s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[48];
+            s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[49];
+            s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[50];
+            s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[51];
+            /* round 13: */
+            t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[52];
+            t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[53];
+            t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[54];
+            t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[55];
+        }
+    }
+    rk += Nr << 2;
+#else  /* !FULL_UNROLL */
+    /*
+	 * Nr - 1 full rounds:
+	 */
+    r = Nr >> 1;
+    for (;;) {
+        t0 =
+            Te0[(s0 >> 24)       ] ^
+            Te1[(s1 >> 16) & 0xff] ^
+            Te2[(s2 >>  8) & 0xff] ^
+            Te3[(s3      ) & 0xff] ^
+            rk[4];
+        t1 =
+            Te0[(s1 >> 24)       ] ^
+            Te1[(s2 >> 16) & 0xff] ^
+            Te2[(s3 >>  8) & 0xff] ^
+            Te3[(s0      ) & 0xff] ^
+            rk[5];
+        t2 =
+            Te0[(s2 >> 24)       ] ^
+            Te1[(s3 >> 16) & 0xff] ^
+            Te2[(s0 >>  8) & 0xff] ^
+            Te3[(s1      ) & 0xff] ^
+            rk[6];
+        t3 =
+            Te0[(s3 >> 24)       ] ^
+            Te1[(s0 >> 16) & 0xff] ^
+            Te2[(s1 >>  8) & 0xff] ^
+            Te3[(s2      ) & 0xff] ^
+            rk[7];
+
+        rk += 8;
+        if (--r == 0) {
+            break;
+        }
+
+        s0 =
+            Te0[(t0 >> 24)       ] ^
+            Te1[(t1 >> 16) & 0xff] ^
+            Te2[(t2 >>  8) & 0xff] ^
+            Te3[(t3      ) & 0xff] ^
+            rk[0];
+        s1 =
+            Te0[(t1 >> 24)       ] ^
+            Te1[(t2 >> 16) & 0xff] ^
+            Te2[(t3 >>  8) & 0xff] ^
+            Te3[(t0      ) & 0xff] ^
+            rk[1];
+        s2 =
+            Te0[(t2 >> 24)       ] ^
+            Te1[(t3 >> 16) & 0xff] ^
+            Te2[(t0 >>  8) & 0xff] ^
+            Te3[(t1      ) & 0xff] ^
+            rk[2];
+        s3 =
+            Te0[(t3 >> 24)       ] ^
+            Te1[(t0 >> 16) & 0xff] ^
+            Te2[(t1 >>  8) & 0xff] ^
+            Te3[(t2      ) & 0xff] ^
+            rk[3];
+    }
+#endif /* ?FULL_UNROLL */
+    /*
+	 * apply last round and
+	 * map cipher state to byte array block:
+	 */
+	s0 =
+		(Te4[(t0 >> 24)       ] << 24) ^
+		(Te4[(t1 >> 16) & 0xff] << 16) ^
+		(Te4[(t2 >>  8) & 0xff] <<  8) ^
+		(Te4[(t3      ) & 0xff]      ) ^
+		rk[0];
+	PUTU32(ct     , s0);
+	s1 =
+		(Te4[(t1 >> 24)       ] << 24) ^
+		(Te4[(t2 >> 16) & 0xff] << 16) ^
+		(Te4[(t3 >>  8) & 0xff] <<  8) ^
+		(Te4[(t0      ) & 0xff]      ) ^
+		rk[1];
+	PUTU32(ct +  4, s1);
+	s2 =
+		(Te4[(t2 >> 24)       ] << 24) ^
+		(Te4[(t3 >> 16) & 0xff] << 16) ^
+		(Te4[(t0 >>  8) & 0xff] <<  8) ^
+		(Te4[(t1      ) & 0xff]      ) ^
+		rk[2];
+	PUTU32(ct +  8, s2);
+	s3 =
+		(Te4[(t3 >> 24)       ] << 24) ^
+		(Te4[(t0 >> 16) & 0xff] << 16) ^
+		(Te4[(t1 >>  8) & 0xff] <<  8) ^
+		(Te4[(t2      ) & 0xff]      ) ^
+		rk[3];
+	PUTU32(ct + 12, s3);
+}
+
+static void rijndaelDecrypt(const u32 rk[/*4*(Nr + 1)*/], int Nr, const u8 ct[16], u8 pt[16]) {
+	u32 s0, s1, s2, s3, t0, t1, t2, t3;
+#ifndef FULL_UNROLL
+    int r;
+#endif /* ?FULL_UNROLL */
+
+    /*
+	 * map byte array block to cipher state
+	 * and add initial round key:
+	 */
+    s0 = GETU32(ct     ) ^ rk[0];
+    s1 = GETU32(ct +  4) ^ rk[1];
+    s2 = GETU32(ct +  8) ^ rk[2];
+    s3 = GETU32(ct + 12) ^ rk[3];
+#ifdef FULL_UNROLL
+    /* round 1: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[ 4];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[ 5];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[ 6];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[ 7];
+    /* round 2: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[ 8];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[ 9];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[10];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[11];
+    /* round 3: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[12];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[13];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[14];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[15];
+    /* round 4: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[16];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[17];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[18];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[19];
+    /* round 5: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[20];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[21];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[22];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[23];
+    /* round 6: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[24];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[25];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[26];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[27];
+    /* round 7: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[28];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[29];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[30];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[31];
+    /* round 8: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[32];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[33];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[34];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[35];
+    /* round 9: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[36];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[37];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[38];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[39];
+    if (Nr > 10) {
+        /* round 10: */
+        s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[40];
+        s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[41];
+        s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[42];
+        s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[43];
+        /* round 11: */
+        t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[44];
+        t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[45];
+        t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[46];
+        t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[47];
+        if (Nr > 12) {
+            /* round 12: */
+            s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[48];
+            s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[49];
+            s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[50];
+            s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[51];
+            /* round 13: */
+            t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[52];
+            t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[53];
+            t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[54];
+            t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[55];
+        }
+    }
+	rk += Nr << 2;
+#else  /* !FULL_UNROLL */
+    /*
+     * Nr - 1 full rounds:
+     */
+    r = Nr >> 1;
+    for (;;) {
+        t0 =
+            Td0[(s0 >> 24)       ] ^
+            Td1[(s3 >> 16) & 0xff] ^
+            Td2[(s2 >>  8) & 0xff] ^
+            Td3[(s1      ) & 0xff] ^
+            rk[4];
+        t1 =
+            Td0[(s1 >> 24)       ] ^
+            Td1[(s0 >> 16) & 0xff] ^
+            Td2[(s3 >>  8) & 0xff] ^
+            Td3[(s2      ) & 0xff] ^
+            rk[5];
+        t2 =
+            Td0[(s2 >> 24)       ] ^
+            Td1[(s1 >> 16) & 0xff] ^
+            Td2[(s0 >>  8) & 0xff] ^
+            Td3[(s3      ) & 0xff] ^
+            rk[6];
+        t3 =
+            Td0[(s3 >> 24)       ] ^
+            Td1[(s2 >> 16) & 0xff] ^
+            Td2[(s1 >>  8) & 0xff] ^
+            Td3[(s0      ) & 0xff] ^
+            rk[7];
+
+        rk += 8;
+        if (--r == 0) {
+            break;
+        }
+
+        s0 =
+            Td0[(t0 >> 24)       ] ^
+            Td1[(t3 >> 16) & 0xff] ^
+            Td2[(t2 >>  8) & 0xff] ^
+            Td3[(t1      ) & 0xff] ^
+            rk[0];
+        s1 =
+            Td0[(t1 >> 24)       ] ^
+            Td1[(t0 >> 16) & 0xff] ^
+            Td2[(t3 >>  8) & 0xff] ^
+            Td3[(t2      ) & 0xff] ^
+            rk[1];
+        s2 =
+            Td0[(t2 >> 24)       ] ^
+            Td1[(t1 >> 16) & 0xff] ^
+            Td2[(t0 >>  8) & 0xff] ^
+            Td3[(t3      ) & 0xff] ^
+            rk[2];
+        s3 =
+            Td0[(t3 >> 24)       ] ^
+            Td1[(t2 >> 16) & 0xff] ^
+            Td2[(t1 >>  8) & 0xff] ^
+            Td3[(t0      ) & 0xff] ^
+            rk[3];
+    }
+#endif /* ?FULL_UNROLL */
+    /*
+	 * apply last round and
+	 * map cipher state to byte array block:
+	 */
+   	s0 =
+   		(Td4[(t0 >> 24)       ] << 24) ^
+   		(Td4[(t3 >> 16) & 0xff] << 16) ^
+   		(Td4[(t2 >>  8) & 0xff] <<  8) ^
+   		(Td4[(t1      ) & 0xff]      ) ^
+   		rk[0];
+	PUTU32(pt     , s0);
+   	s1 =
+   		(Td4[(t1 >> 24)       ] << 24) ^
+   		(Td4[(t0 >> 16) & 0xff] << 16) ^
+   		(Td4[(t3 >>  8) & 0xff] <<  8) ^
+   		(Td4[(t2      ) & 0xff]      ) ^
+   		rk[1];
+	PUTU32(pt +  4, s1);
+   	s2 =
+   		(Td4[(t2 >> 24)       ] << 24) ^
+   		(Td4[(t1 >> 16) & 0xff] << 16) ^
+   		(Td4[(t0 >>  8) & 0xff] <<  8) ^
+   		(Td4[(t3      ) & 0xff]      ) ^
+   		rk[2];
+	PUTU32(pt +  8, s2);
+   	s3 =
+   		(Td4[(t3 >> 24)       ] << 24) ^
+   		(Td4[(t2 >> 16) & 0xff] << 16) ^
+   		(Td4[(t1 >>  8) & 0xff] <<  8) ^
+   		(Td4[(t0      ) & 0xff]      ) ^
+   		rk[3];
+	PUTU32(pt + 12, s3);
+}
+
+#ifdef INTERMEDIATE_VALUE_KAT
+
+static void rijndaelEncryptRound(const u32 rk[/*4*(Nr + 1)*/], int Nr, u8 block[16], int rounds) {
+	int r;
+	u32 s0, s1, s2, s3, t0, t1, t2, t3;
+
+    /*
+	 * map byte array block to cipher state
+	 * and add initial round key:
+	 */
+	s0 = GETU32(block     ) ^ rk[0];
+	s1 = GETU32(block +  4) ^ rk[1];
+	s2 = GETU32(block +  8) ^ rk[2];
+	s3 = GETU32(block + 12) ^ rk[3];
+    rk += 4;
+
+    /*
+	 * Nr - 1 full rounds:
+	 */
+	for (r = (rounds < Nr ? rounds : Nr - 1); r > 0; r--) {
+		t0 =
+			Te0[(s0 >> 24)       ] ^
+			Te1[(s1 >> 16) & 0xff] ^
+			Te2[(s2 >>  8) & 0xff] ^
+			Te3[(s3      ) & 0xff] ^
+			rk[0];
+		t1 =
+			Te0[(s1 >> 24)       ] ^
+			Te1[(s2 >> 16) & 0xff] ^
+			Te2[(s3 >>  8) & 0xff] ^
+			Te3[(s0      ) & 0xff] ^
+			rk[1];
+		t2 =
+			Te0[(s2 >> 24)       ] ^
+			Te1[(s3 >> 16) & 0xff] ^
+			Te2[(s0 >>  8) & 0xff] ^
+			Te3[(s1      ) & 0xff] ^
+			rk[2];
+		t3 =
+			Te0[(s3 >> 24)       ] ^
+			Te1[(s0 >> 16) & 0xff] ^
+			Te2[(s1 >>  8) & 0xff] ^
+			Te3[(s2      ) & 0xff] ^
+			rk[3];
+
+		s0 = t0;
+		s1 = t1;
+		s2 = t2;
+		s3 = t3;
+		rk += 4;
+
+    }
+
+    /*
+	 * apply last round and
+	 * map cipher state to byte array block:
+	 */
+	if (rounds == Nr) {
+    	t0 =
+    		(Te4[(s0 >> 24)       ] << 24) ^
+    		(Te4[(s1 >> 16) & 0xff] << 16) ^
+    		(Te4[(s2 >>  8) & 0xff] <<  8) ^
+    		(Te4[(s3      ) & 0xff]      ) ^
+    		rk[0];
+    	t1 =
+    		(Te4[(s1 >> 24)       ] << 24) ^
+    		(Te4[(s2 >> 16) & 0xff] << 16) ^
+    		(Te4[(s3 >>  8) & 0xff] <<  8) ^
+    		(Te4[(s0      ) & 0xff]      ) ^
+    		rk[1];
+    	t2 =
+    		(Te4[(s2 >> 24)       ] << 24) ^
+    		(Te4[(s3 >> 16) & 0xff] << 16) ^
+    		(Te4[(s0 >>  8) & 0xff] <<  8) ^
+    		(Te4[(s1      ) & 0xff]      ) ^
+    		rk[2];
+    	t3 =
+    		(Te4[(s3 >> 24)       ] << 24) ^
+    		(Te4[(s0 >> 16) & 0xff] << 16) ^
+    		(Te4[(s1 >>  8) & 0xff] <<  8) ^
+    		(Te4[(s2      ) & 0xff]      ) ^
+    		rk[3];
+		
+		s0 = t0;
+		s1 = t1;
+		s2 = t2;
+		s3 = t3;
+	}
+
+	PUTU32(block     , s0);
+	PUTU32(block +  4, s1);
+	PUTU32(block +  8, s2);
+	PUTU32(block + 12, s3);
+}
+
+static void rijndaelDecryptRound(const u32 rk[/*4*(Nr + 1)*/], int Nr, u8 block[16], int rounds) {
+	int r;
+	u32 s0, s1, s2, s3, t0, t1, t2, t3;
+
+    /*
+	 * map byte array block to cipher state
+	 * and add initial round key:
+	 */
+	s0 = GETU32(block     ) ^ rk[0];
+	s1 = GETU32(block +  4) ^ rk[1];
+	s2 = GETU32(block +  8) ^ rk[2];
+	s3 = GETU32(block + 12) ^ rk[3];
+    rk += 4;
+
+    /*
+	 * Nr - 1 full rounds:
+	 */
+	for (r = (rounds < Nr ? rounds : Nr) - 1; r > 0; r--) {
+		t0 =
+			Td0[(s0 >> 24)       ] ^
+			Td1[(s3 >> 16) & 0xff] ^
+			Td2[(s2 >>  8) & 0xff] ^
+			Td3[(s1      ) & 0xff] ^
+			rk[0];
+		t1 =
+			Td0[(s1 >> 24)       ] ^
+			Td1[(s0 >> 16) & 0xff] ^
+			Td2[(s3 >>  8) & 0xff] ^
+			Td3[(s2      ) & 0xff] ^
+			rk[1];
+		t2 =
+			Td0[(s2 >> 24)       ] ^
+			Td1[(s1 >> 16) & 0xff] ^
+			Td2[(s0 >>  8) & 0xff] ^
+			Td3[(s3      ) & 0xff] ^
+			rk[2];
+		t3 =
+			Td0[(s3 >> 24)       ] ^
+			Td1[(s2 >> 16) & 0xff] ^
+			Td2[(s1 >>  8) & 0xff] ^
+			Td3[(s0      ) & 0xff] ^
+			rk[3];
+
+		s0 = t0;
+		s1 = t1;
+		s2 = t2;
+		s3 = t3;
+		rk += 4;
+
+    }
+
+    /*
+	 * complete the last round and
+	 * map cipher state to byte array block:
+	 */
+	t0 =
+		(Td4[(s0 >> 24)       ] << 24) ^
+		(Td4[(s3 >> 16) & 0xff] << 16) ^
+		(Td4[(s2 >>  8) & 0xff] <<  8) ^
+		(Td4[(s1      ) & 0xff]      );
+	t1 =
+		(Td4[(s1 >> 24)       ] << 24) ^
+		(Td4[(s0 >> 16) & 0xff] << 16) ^
+		(Td4[(s3 >>  8) & 0xff] <<  8) ^
+		(Td4[(s2      ) & 0xff]      );
+	t2 =
+		(Td4[(s2 >> 24)       ] << 24) ^
+		(Td4[(s1 >> 16) & 0xff] << 16) ^
+		(Td4[(s0 >>  8) & 0xff] <<  8) ^
+		(Td4[(s3      ) & 0xff]      );
+	t3 =
+		(Td4[(s3 >> 24)       ] << 24) ^
+		(Td4[(s2 >> 16) & 0xff] << 16) ^
+		(Td4[(s1 >>  8) & 0xff] <<  8) ^
+		(Td4[(s0      ) & 0xff]      );
+
+	if (rounds == Nr) {
+	    t0 ^= rk[0];
+	    t1 ^= rk[1];
+	    t2 ^= rk[2];
+	    t3 ^= rk[3];
+	}
+
+	PUTU32(block     , t0);
+	PUTU32(block +  4, t1);
+	PUTU32(block +  8, t2);
+	PUTU32(block + 12, t3);
+}
+
+#endif /* INTERMEDIATE_VALUE_KAT */
--- /dev/null
+++ b/libsec/blowfish.c
@@ -1,0 +1,579 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// Blowfish block cipher.  See:
+// 	Lecture Notes in Computer Science 809
+// 	Fast Software Encryption
+// 	Cambridge Security Workshop, Cambridge, England (1993)
+
+static u32int sbox[1024];
+static u32int pbox[BFrounds+2];
+
+static void bfencrypt(u32int *, BFstate *);
+static void bfdecrypt(u32int *, BFstate *);
+
+void
+setupBFstate(BFstate *s, uchar key[], int keybytes, uchar *ivec)
+{
+	int i, j;
+	u32int n, buf[2];
+
+	memset(s, 0, sizeof(*s));
+	memset(buf, 0, sizeof buf);
+
+	if (keybytes > sizeof(s->key))
+		keybytes = sizeof(s->key);
+
+	memmove(s->key, key, keybytes);
+
+	if (ivec != nil)
+		memmove(s->ivec, ivec, sizeof(s->ivec));
+	else
+		memset(s->ivec, 0, sizeof(s->ivec));
+		
+	memmove(s->pbox, pbox, sizeof(pbox));
+	memmove(s->sbox, sbox, sizeof(sbox));
+
+	if (keybytes > 4*(BFrounds + 2))
+		keybytes = 4*(BFrounds + 2);
+
+	for(i=j=0; i < BFrounds+2; i++) {
+		n = key[j];
+		j = (j+1) % keybytes;
+
+		n <<= 8;
+		n |= key[j];
+		j = (j+1) % keybytes;
+
+		n <<= 8;
+		n |= key[j];
+		j = (j+1) % keybytes;
+
+		n <<= 8;
+		n |= key[j];
+		j = (j+1) % keybytes;
+
+		s->pbox[i] ^= n;
+	}
+
+	for(i=0; i < BFrounds+2; i += 2) {
+		bfencrypt(buf, s);
+		s->pbox[i] = buf[0];
+		s->pbox[i+1] = buf[1];
+	}
+
+	for(i=0; i < 1024; i += 2) {
+		bfencrypt(buf, s);
+		s->sbox[i] = buf[0];
+		s->sbox[i+1] = buf[1];
+	}
+
+	s->setup = 0xcafebabe;
+}
+
+void
+bfCBCencrypt(uchar *buf, int n, BFstate *s)
+{
+	int i;
+	uchar *p;
+	u32int bo[2], bi[2], b;
+
+	assert((n & 7) == 0);
+
+	bo[0] =  s->ivec[0] | ((u32int) s->ivec[1]<<8) | ((u32int)s->ivec[2]<<16) | ((u32int)s->ivec[3]<<24);
+	bo[1] =  s->ivec[4] | ((u32int) s->ivec[5]<<8) | ((u32int)s->ivec[6]<<16) | ((u32int)s->ivec[7]<<24);
+
+	for(i=0; i < n; i += 8, buf += 8) {
+		bi[0] =  buf[0] | ((u32int) buf[1]<<8) | ((u32int)buf[2]<<16) | ((u32int)buf[3]<<24);
+		bi[1] =  buf[4] | ((u32int) buf[5]<<8) | ((u32int)buf[6]<<16) | ((u32int)buf[7]<<24);
+
+		bi[0] ^= bo[0];
+		bi[1] ^= bo[1];
+
+		bfencrypt(bi, s);
+
+		bo[0] = bi[0];
+		bo[1] = bi[1];
+
+		p = buf;
+		b = bo[0];
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+
+		b = bo[1];
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p = b;
+	}
+
+	s->ivec[7] = bo[1] >> 24;
+	s->ivec[6] = bo[1] >> 16;
+	s->ivec[5] = bo[1] >> 8;
+	s->ivec[4] = bo[1];
+
+	s->ivec[3] = bo[0] >> 24;
+	s->ivec[2] = bo[0] >> 16;
+	s->ivec[1] = bo[0] >> 8;
+	s->ivec[0] = bo[0];
+
+	return;
+}
+
+void
+bfCBCdecrypt(uchar *buf, int n, BFstate *s)
+{
+	int i;
+	uchar *p;
+	u32int b, bo[2], bi[2], xr[2];
+
+	assert((n & 7) == 0);
+
+	bo[0] =  s->ivec[0] | ((u32int) s->ivec[1]<<8) | ((u32int)s->ivec[2]<<16) | ((u32int)s->ivec[3]<<24);
+	bo[1] =  s->ivec[4] | ((u32int) s->ivec[5]<<8) | ((u32int)s->ivec[6]<<16) | ((u32int)s->ivec[7]<<24);
+
+	for(i=0; i < n; i += 8, buf += 8) {
+		bi[0] =  buf[0] | ((u32int) buf[1]<<8) | ((u32int)buf[2]<<16) | ((u32int)buf[3]<<24);
+		bi[1] =  buf[4] | ((u32int) buf[5]<<8) | ((u32int)buf[6]<<16) | ((u32int)buf[7]<<24);
+
+		xr[0] = bi[0];
+		xr[1] = bi[1];
+
+		bfdecrypt(bi, s);
+
+		bo[0] ^= bi[0];
+		bo[1] ^= bi[1];
+
+		p = buf;
+		b = bo[0];
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+
+		b = bo[1];
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p++ = b;
+		b >>= 8;
+		*p = b;
+
+		bo[0] = xr[0];
+		bo[1] = xr[1];
+	}
+
+	s->ivec[7] = bo[1] >> 24;
+	s->ivec[6] = bo[1] >> 16;
+	s->ivec[5] = bo[1] >> 8;
+	s->ivec[4] = bo[1];
+
+	s->ivec[3] = bo[0] >> 24;
+	s->ivec[2] = bo[0] >> 16;
+	s->ivec[1] = bo[0] >> 8;
+	s->ivec[0] = bo[0];
+
+	return;
+}
+
+void
+bfECBencrypt(uchar *buf, int n, BFstate *s)
+{
+	int i;
+	u32int b[2];
+
+	for(i=0; i < n; i += 8, buf += 8) {
+		b[0] =  buf[0] | ((u32int) buf[1]<<8) | ((u32int)buf[2]<<16) | ((u32int)buf[3]<<24);
+		b[1] =  buf[4] | ((u32int) buf[5]<<8) | ((u32int)buf[6]<<16) | ((u32int)buf[7]<<24);
+
+		bfencrypt(b, s);
+
+		buf[7] = b[1] >> 24;
+		buf[6] = b[1] >> 16;
+		buf[5] = b[1] >> 8;
+		buf[4] = b[1];
+
+		buf[3] = b[0] >> 24;
+		buf[2] = b[0] >> 16;
+		buf[1] = b[0] >> 8;
+		buf[0] = b[0];
+	}
+
+	return;
+}
+
+void
+bfECBdecrypt(uchar *buf, int n, BFstate *s)
+{
+	int i;
+	u32int b[2];
+
+	for(i=0; i < n; i += 8, buf += 8) {
+		b[0] =  buf[0] | ((u32int) buf[1]<<8) | ((u32int)buf[2]<<16) | ((u32int)buf[3]<<24);
+		b[1] =  buf[4] | ((u32int) buf[5]<<8) | ((u32int)buf[6]<<16) | ((u32int)buf[7]<<24);
+
+		bfdecrypt(b, s);
+
+		buf[7] = b[1] >> 24;
+		buf[6] = b[1] >> 16;
+		buf[5] = b[1] >> 8;
+		buf[4] = b[1];
+
+		buf[3] = b[0] >> 24;
+		buf[2] = b[0] >> 16;
+		buf[1] = b[0] >> 8;
+		buf[0] = b[0];
+	}
+
+	return;		
+}
+
+static void
+bfencrypt(u32int *b, BFstate *s)
+{
+	int i;
+	u32int l, r;
+	u32int *pb, *sb;
+
+	l = b[0];
+	r = b[1];
+
+	pb = s->pbox;
+	sb = s->sbox;
+
+	l ^= pb[0];
+
+	for(i=1; i<16; i += 2) {
+		r ^= pb[i];
+		r ^= ( (sb[ (uchar) (l>>24)] + sb[256 + ((uchar) (l>>16))]) ^  
+			sb[512 + ((uchar) (l>>8))]) + sb[768 +((uchar) l)];
+
+		l ^= pb[i+1];
+		l ^= ( (sb[ (uchar) (r>>24)] + sb[256 + ((uchar) (r>>16))]) ^  
+			sb[512 + ((uchar) (r>>8))]) + sb[768 +((uchar) r)];
+	}
+
+	r ^= pb[BFrounds+1];
+
+	/* sic */
+	b[0] = r;
+	b[1] = l;
+
+	return;
+}
+
+static void
+bfdecrypt(u32int *b, BFstate *s)
+{
+	int i;
+	u32int l, r;
+	u32int *pb, *sb;
+
+	l = b[0];
+	r = b[1];
+
+	pb = s->pbox;
+	sb = s->sbox;
+
+	l ^= pb[BFrounds+1];
+
+	for(i=16; i > 0; i -= 2) {
+		r ^= pb[i];
+		r ^= ( (sb[ (uchar) (l>>24)] + sb[256 + ((uchar) (l>>16))]) ^  
+			sb[512 + ((uchar) (l>>8))]) + sb[768 +((uchar) l)];
+
+		l ^= pb[i-1];
+		l ^= ( (sb[ (uchar) (r>>24)] + sb[256 + ((uchar) (r>>16))]) ^  
+			sb[512 + ((uchar) (r>>8))]) + sb[768 +((uchar) r)];
+	}
+
+	r ^= pb[0];
+
+	/* sic */
+	b[0] = r;
+	b[1] = l;
+
+	return;
+}
+
+static u32int pbox[BFrounds+2] = {
+	0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 
+	0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 
+	0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 
+	0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 
+	0x9216d5d9, 0x8979fb1b
+};
+
+static u32int sbox[1024] = {
+	0xd1310ba6L, 0x98dfb5acL, 0x2ffd72dbL, 0xd01adfb7L, 
+	0xb8e1afedL, 0x6a267e96L, 0xba7c9045L, 0xf12c7f99L, 
+	0x24a19947L, 0xb3916cf7L, 0x0801f2e2L, 0x858efc16L, 
+	0x636920d8L, 0x71574e69L, 0xa458fea3L, 0xf4933d7eL, 
+	0x0d95748fL, 0x728eb658L, 0x718bcd58L, 0x82154aeeL, 
+	0x7b54a41dL, 0xc25a59b5L, 0x9c30d539L, 0x2af26013L, 
+	0xc5d1b023L, 0x286085f0L, 0xca417918L, 0xb8db38efL, 
+	0x8e79dcb0L, 0x603a180eL, 0x6c9e0e8bL, 0xb01e8a3eL, 
+	0xd71577c1L, 0xbd314b27L, 0x78af2fdaL, 0x55605c60L, 
+	0xe65525f3L, 0xaa55ab94L, 0x57489862L, 0x63e81440L, 
+	0x55ca396aL, 0x2aab10b6L, 0xb4cc5c34L, 0x1141e8ceL, 
+	0xa15486afL, 0x7c72e993L, 0xb3ee1411L, 0x636fbc2aL, 
+	0x2ba9c55dL, 0x741831f6L, 0xce5c3e16L, 0x9b87931eL, 
+	0xafd6ba33L, 0x6c24cf5cL, 0x7a325381L, 0x28958677L, 
+	0x3b8f4898L, 0x6b4bb9afL, 0xc4bfe81bL, 0x66282193L, 
+	0x61d809ccL, 0xfb21a991L, 0x487cac60L, 0x5dec8032L, 
+	0xef845d5dL, 0xe98575b1L, 0xdc262302L, 0xeb651b88L, 
+	0x23893e81L, 0xd396acc5L, 0x0f6d6ff3L, 0x83f44239L, 
+	0x2e0b4482L, 0xa4842004L, 0x69c8f04aL, 0x9e1f9b5eL, 
+	0x21c66842L, 0xf6e96c9aL, 0x670c9c61L, 0xabd388f0L, 
+	0x6a51a0d2L, 0xd8542f68L, 0x960fa728L, 0xab5133a3L, 
+	0x6eef0b6cL, 0x137a3be4L, 0xba3bf050L, 0x7efb2a98L, 
+	0xa1f1651dL, 0x39af0176L, 0x66ca593eL, 0x82430e88L, 
+	0x8cee8619L, 0x456f9fb4L, 0x7d84a5c3L, 0x3b8b5ebeL, 
+	0xe06f75d8L, 0x85c12073L, 0x401a449fL, 0x56c16aa6L, 
+	0x4ed3aa62L, 0x363f7706L, 0x1bfedf72L, 0x429b023dL, 
+	0x37d0d724L, 0xd00a1248L, 0xdb0fead3L, 0x49f1c09bL, 
+	0x075372c9L, 0x80991b7bL, 0x25d479d8L, 0xf6e8def7L, 
+	0xe3fe501aL, 0xb6794c3bL, 0x976ce0bdL, 0x04c006baL, 
+	0xc1a94fb6L, 0x409f60c4L, 0x5e5c9ec2L, 0x196a2463L, 
+	0x68fb6fafL, 0x3e6c53b5L, 0x1339b2ebL, 0x3b52ec6fL, 
+	0x6dfc511fL, 0x9b30952cL, 0xcc814544L, 0xaf5ebd09L, 
+	0xbee3d004L, 0xde334afdL, 0x660f2807L, 0x192e4bb3L, 
+	0xc0cba857L, 0x45c8740fL, 0xd20b5f39L, 0xb9d3fbdbL, 
+	0x5579c0bdL, 0x1a60320aL, 0xd6a100c6L, 0x402c7279L, 
+	0x679f25feL, 0xfb1fa3ccL, 0x8ea5e9f8L, 0xdb3222f8L, 
+	0x3c7516dfL, 0xfd616b15L, 0x2f501ec8L, 0xad0552abL, 
+	0x323db5faL, 0xfd238760L, 0x53317b48L, 0x3e00df82L, 
+	0x9e5c57bbL, 0xca6f8ca0L, 0x1a87562eL, 0xdf1769dbL, 
+	0xd542a8f6L, 0x287effc3L, 0xac6732c6L, 0x8c4f5573L, 
+	0x695b27b0L, 0xbbca58c8L, 0xe1ffa35dL, 0xb8f011a0L, 
+	0x10fa3d98L, 0xfd2183b8L, 0x4afcb56cL, 0x2dd1d35bL, 
+	0x9a53e479L, 0xb6f84565L, 0xd28e49bcL, 0x4bfb9790L, 
+	0xe1ddf2daL, 0xa4cb7e33L, 0x62fb1341L, 0xcee4c6e8L, 
+	0xef20cadaL, 0x36774c01L, 0xd07e9efeL, 0x2bf11fb4L, 
+	0x95dbda4dL, 0xae909198L, 0xeaad8e71L, 0x6b93d5a0L, 
+	0xd08ed1d0L, 0xafc725e0L, 0x8e3c5b2fL, 0x8e7594b7L, 
+	0x8ff6e2fbL, 0xf2122b64L, 0x8888b812L, 0x900df01cL, 
+	0x4fad5ea0L, 0x688fc31cL, 0xd1cff191L, 0xb3a8c1adL, 
+	0x2f2f2218L, 0xbe0e1777L, 0xea752dfeL, 0x8b021fa1L, 
+	0xe5a0cc0fL, 0xb56f74e8L, 0x18acf3d6L, 0xce89e299L, 
+	0xb4a84fe0L, 0xfd13e0b7L, 0x7cc43b81L, 0xd2ada8d9L, 
+	0x165fa266L, 0x80957705L, 0x93cc7314L, 0x211a1477L, 
+	0xe6ad2065L, 0x77b5fa86L, 0xc75442f5L, 0xfb9d35cfL, 
+	0xebcdaf0cL, 0x7b3e89a0L, 0xd6411bd3L, 0xae1e7e49L, 
+	0x00250e2dL, 0x2071b35eL, 0x226800bbL, 0x57b8e0afL, 
+	0x2464369bL, 0xf009b91eL, 0x5563911dL, 0x59dfa6aaL, 
+	0x78c14389L, 0xd95a537fL, 0x207d5ba2L, 0x02e5b9c5L, 
+	0x83260376L, 0x6295cfa9L, 0x11c81968L, 0x4e734a41L, 
+	0xb3472dcaL, 0x7b14a94aL, 0x1b510052L, 0x9a532915L, 
+	0xd60f573fL, 0xbc9bc6e4L, 0x2b60a476L, 0x81e67400L, 
+	0x08ba6fb5L, 0x571be91fL, 0xf296ec6bL, 0x2a0dd915L, 
+	0xb6636521L, 0xe7b9f9b6L, 0xff34052eL, 0xc5855664L, 
+	0x53b02d5dL, 0xa99f8fa1L, 0x08ba4799L, 0x6e85076aL, 
+	0x4b7a70e9L, 0xb5b32944L, 0xdb75092eL, 0xc4192623L, 
+	0xad6ea6b0L, 0x49a7df7dL, 0x9cee60b8L, 0x8fedb266L, 
+	0xecaa8c71L, 0x699a17ffL, 0x5664526cL, 0xc2b19ee1L, 
+	0x193602a5L, 0x75094c29L, 0xa0591340L, 0xe4183a3eL, 
+	0x3f54989aL, 0x5b429d65L, 0x6b8fe4d6L, 0x99f73fd6L, 
+	0xa1d29c07L, 0xefe830f5L, 0x4d2d38e6L, 0xf0255dc1L, 
+	0x4cdd2086L, 0x8470eb26L, 0x6382e9c6L, 0x021ecc5eL, 
+	0x09686b3fL, 0x3ebaefc9L, 0x3c971814L, 0x6b6a70a1L, 
+	0x687f3584L, 0x52a0e286L, 0xb79c5305L, 0xaa500737L, 
+	0x3e07841cL, 0x7fdeae5cL, 0x8e7d44ecL, 0x5716f2b8L, 
+	0xb03ada37L, 0xf0500c0dL, 0xf01c1f04L, 0x0200b3ffL, 
+	0xae0cf51aL, 0x3cb574b2L, 0x25837a58L, 0xdc0921bdL, 
+	0xd19113f9L, 0x7ca92ff6L, 0x94324773L, 0x22f54701L, 
+	0x3ae5e581L, 0x37c2dadcL, 0xc8b57634L, 0x9af3dda7L, 
+	0xa9446146L, 0x0fd0030eL, 0xecc8c73eL, 0xa4751e41L, 
+	0xe238cd99L, 0x3bea0e2fL, 0x3280bba1L, 0x183eb331L, 
+	0x4e548b38L, 0x4f6db908L, 0x6f420d03L, 0xf60a04bfL, 
+	0x2cb81290L, 0x24977c79L, 0x5679b072L, 0xbcaf89afL, 
+	0xde9a771fL, 0xd9930810L, 0xb38bae12L, 0xdccf3f2eL, 
+	0x5512721fL, 0x2e6b7124L, 0x501adde6L, 0x9f84cd87L, 
+	0x7a584718L, 0x7408da17L, 0xbc9f9abcL, 0xe94b7d8cL, 
+	0xec7aec3aL, 0xdb851dfaL, 0x63094366L, 0xc464c3d2L, 
+	0xef1c1847L, 0x3215d908L, 0xdd433b37L, 0x24c2ba16L, 
+	0x12a14d43L, 0x2a65c451L, 0x50940002L, 0x133ae4ddL, 
+	0x71dff89eL, 0x10314e55L, 0x81ac77d6L, 0x5f11199bL, 
+	0x043556f1L, 0xd7a3c76bL, 0x3c11183bL, 0x5924a509L, 
+	0xf28fe6edL, 0x97f1fbfaL, 0x9ebabf2cL, 0x1e153c6eL, 
+	0x86e34570L, 0xeae96fb1L, 0x860e5e0aL, 0x5a3e2ab3L, 
+	0x771fe71cL, 0x4e3d06faL, 0x2965dcb9L, 0x99e71d0fL, 
+	0x803e89d6L, 0x5266c825L, 0x2e4cc978L, 0x9c10b36aL, 
+	0xc6150ebaL, 0x94e2ea78L, 0xa5fc3c53L, 0x1e0a2df4L, 
+	0xf2f74ea7L, 0x361d2b3dL, 0x1939260fL, 0x19c27960L, 
+	0x5223a708L, 0xf71312b6L, 0xebadfe6eL, 0xeac31f66L, 
+	0xe3bc4595L, 0xa67bc883L, 0xb17f37d1L, 0x018cff28L, 
+	0xc332ddefL, 0xbe6c5aa5L, 0x65582185L, 0x68ab9802L, 
+	0xeecea50fL, 0xdb2f953bL, 0x2aef7dadL, 0x5b6e2f84L, 
+	0x1521b628L, 0x29076170L, 0xecdd4775L, 0x619f1510L, 
+	0x13cca830L, 0xeb61bd96L, 0x0334fe1eL, 0xaa0363cfL, 
+	0xb5735c90L, 0x4c70a239L, 0xd59e9e0bL, 0xcbaade14L, 
+	0xeecc86bcL, 0x60622ca7L, 0x9cab5cabL, 0xb2f3846eL, 
+	0x648b1eafL, 0x19bdf0caL, 0xa02369b9L, 0x655abb50L, 
+	0x40685a32L, 0x3c2ab4b3L, 0x319ee9d5L, 0xc021b8f7L, 
+	0x9b540b19L, 0x875fa099L, 0x95f7997eL, 0x623d7da8L, 
+	0xf837889aL, 0x97e32d77L, 0x11ed935fL, 0x16681281L, 
+	0x0e358829L, 0xc7e61fd6L, 0x96dedfa1L, 0x7858ba99L, 
+	0x57f584a5L, 0x1b227263L, 0x9b83c3ffL, 0x1ac24696L, 
+	0xcdb30aebL, 0x532e3054L, 0x8fd948e4L, 0x6dbc3128L, 
+	0x58ebf2efL, 0x34c6ffeaL, 0xfe28ed61L, 0xee7c3c73L, 
+	0x5d4a14d9L, 0xe864b7e3L, 0x42105d14L, 0x203e13e0L, 
+	0x45eee2b6L, 0xa3aaabeaL, 0xdb6c4f15L, 0xfacb4fd0L, 
+	0xc742f442L, 0xef6abbb5L, 0x654f3b1dL, 0x41cd2105L, 
+	0xd81e799eL, 0x86854dc7L, 0xe44b476aL, 0x3d816250L, 
+	0xcf62a1f2L, 0x5b8d2646L, 0xfc8883a0L, 0xc1c7b6a3L, 
+	0x7f1524c3L, 0x69cb7492L, 0x47848a0bL, 0x5692b285L, 
+	0x095bbf00L, 0xad19489dL, 0x1462b174L, 0x23820e00L, 
+	0x58428d2aL, 0x0c55f5eaL, 0x1dadf43eL, 0x233f7061L, 
+	0x3372f092L, 0x8d937e41L, 0xd65fecf1L, 0x6c223bdbL, 
+	0x7cde3759L, 0xcbee7460L, 0x4085f2a7L, 0xce77326eL, 
+	0xa6078084L, 0x19f8509eL, 0xe8efd855L, 0x61d99735L, 
+	0xa969a7aaL, 0xc50c06c2L, 0x5a04abfcL, 0x800bcadcL, 
+	0x9e447a2eL, 0xc3453484L, 0xfdd56705L, 0x0e1e9ec9L, 
+	0xdb73dbd3L, 0x105588cdL, 0x675fda79L, 0xe3674340L, 
+	0xc5c43465L, 0x713e38d8L, 0x3d28f89eL, 0xf16dff20L, 
+	0x153e21e7L, 0x8fb03d4aL, 0xe6e39f2bL, 0xdb83adf7L, 
+	0xe93d5a68L, 0x948140f7L, 0xf64c261cL, 0x94692934L, 
+	0x411520f7L, 0x7602d4f7L, 0xbcf46b2eL, 0xd4a20068L, 
+	0xd4082471L, 0x3320f46aL, 0x43b7d4b7L, 0x500061afL, 
+	0x1e39f62eL, 0x97244546L, 0x14214f74L, 0xbf8b8840L, 
+	0x4d95fc1dL, 0x96b591afL, 0x70f4ddd3L, 0x66a02f45L, 
+	0xbfbc09ecL, 0x03bd9785L, 0x7fac6dd0L, 0x31cb8504L, 
+	0x96eb27b3L, 0x55fd3941L, 0xda2547e6L, 0xabca0a9aL, 
+	0x28507825L, 0x530429f4L, 0x0a2c86daL, 0xe9b66dfbL, 
+	0x68dc1462L, 0xd7486900L, 0x680ec0a4L, 0x27a18deeL, 
+	0x4f3ffea2L, 0xe887ad8cL, 0xb58ce006L, 0x7af4d6b6L, 
+	0xaace1e7cL, 0xd3375fecL, 0xce78a399L, 0x406b2a42L, 
+	0x20fe9e35L, 0xd9f385b9L, 0xee39d7abL, 0x3b124e8bL, 
+	0x1dc9faf7L, 0x4b6d1856L, 0x26a36631L, 0xeae397b2L, 
+	0x3a6efa74L, 0xdd5b4332L, 0x6841e7f7L, 0xca7820fbL, 
+	0xfb0af54eL, 0xd8feb397L, 0x454056acL, 0xba489527L, 
+	0x55533a3aL, 0x20838d87L, 0xfe6ba9b7L, 0xd096954bL, 
+	0x55a867bcL, 0xa1159a58L, 0xcca92963L, 0x99e1db33L, 
+	0xa62a4a56L, 0x3f3125f9L, 0x5ef47e1cL, 0x9029317cL, 
+	0xfdf8e802L, 0x04272f70L, 0x80bb155cL, 0x05282ce3L, 
+	0x95c11548L, 0xe4c66d22L, 0x48c1133fL, 0xc70f86dcL, 
+	0x07f9c9eeL, 0x41041f0fL, 0x404779a4L, 0x5d886e17L, 
+	0x325f51ebL, 0xd59bc0d1L, 0xf2bcc18fL, 0x41113564L, 
+	0x257b7834L, 0x602a9c60L, 0xdff8e8a3L, 0x1f636c1bL, 
+	0x0e12b4c2L, 0x02e1329eL, 0xaf664fd1L, 0xcad18115L, 
+	0x6b2395e0L, 0x333e92e1L, 0x3b240b62L, 0xeebeb922L, 
+	0x85b2a20eL, 0xe6ba0d99L, 0xde720c8cL, 0x2da2f728L, 
+	0xd0127845L, 0x95b794fdL, 0x647d0862L, 0xe7ccf5f0L, 
+	0x5449a36fL, 0x877d48faL, 0xc39dfd27L, 0xf33e8d1eL, 
+	0x0a476341L, 0x992eff74L, 0x3a6f6eabL, 0xf4f8fd37L, 
+	0xa812dc60L, 0xa1ebddf8L, 0x991be14cL, 0xdb6e6b0dL, 
+	0xc67b5510L, 0x6d672c37L, 0x2765d43bL, 0xdcd0e804L, 
+	0xf1290dc7L, 0xcc00ffa3L, 0xb5390f92L, 0x690fed0bL, 
+	0x667b9ffbL, 0xcedb7d9cL, 0xa091cf0bL, 0xd9155ea3L, 
+	0xbb132f88L, 0x515bad24L, 0x7b9479bfL, 0x763bd6ebL, 
+	0x37392eb3L, 0xcc115979L, 0x8026e297L, 0xf42e312dL, 
+	0x6842ada7L, 0xc66a2b3bL, 0x12754cccL, 0x782ef11cL, 
+	0x6a124237L, 0xb79251e7L, 0x06a1bbe6L, 0x4bfb6350L, 
+	0x1a6b1018L, 0x11caedfaL, 0x3d25bdd8L, 0xe2e1c3c9L, 
+	0x44421659L, 0x0a121386L, 0xd90cec6eL, 0xd5abea2aL, 
+	0x64af674eL, 0xda86a85fL, 0xbebfe988L, 0x64e4c3feL, 
+	0x9dbc8057L, 0xf0f7c086L, 0x60787bf8L, 0x6003604dL, 
+	0xd1fd8346L, 0xf6381fb0L, 0x7745ae04L, 0xd736fcccL, 
+	0x83426b33L, 0xf01eab71L, 0xb0804187L, 0x3c005e5fL, 
+	0x77a057beL, 0xbde8ae24L, 0x55464299L, 0xbf582e61L, 
+	0x4e58f48fL, 0xf2ddfda2L, 0xf474ef38L, 0x8789bdc2L, 
+	0x5366f9c3L, 0xc8b38e74L, 0xb475f255L, 0x46fcd9b9L, 
+	0x7aeb2661L, 0x8b1ddf84L, 0x846a0e79L, 0x915f95e2L, 
+	0x466e598eL, 0x20b45770L, 0x8cd55591L, 0xc902de4cL, 
+	0xb90bace1L, 0xbb8205d0L, 0x11a86248L, 0x7574a99eL, 
+	0xb77f19b6L, 0xe0a9dc09L, 0x662d09a1L, 0xc4324633L, 
+	0xe85a1f02L, 0x09f0be8cL, 0x4a99a025L, 0x1d6efe10L, 
+	0x1ab93d1dL, 0x0ba5a4dfL, 0xa186f20fL, 0x2868f169L, 
+	0xdcb7da83L, 0x573906feL, 0xa1e2ce9bL, 0x4fcd7f52L, 
+	0x50115e01L, 0xa70683faL, 0xa002b5c4L, 0x0de6d027L, 
+	0x9af88c27L, 0x773f8641L, 0xc3604c06L, 0x61a806b5L, 
+	0xf0177a28L, 0xc0f586e0L, 0x006058aaL, 0x30dc7d62L, 
+	0x11e69ed7L, 0x2338ea63L, 0x53c2dd94L, 0xc2c21634L, 
+	0xbbcbee56L, 0x90bcb6deL, 0xebfc7da1L, 0xce591d76L, 
+	0x6f05e409L, 0x4b7c0188L, 0x39720a3dL, 0x7c927c24L, 
+	0x86e3725fL, 0x724d9db9L, 0x1ac15bb4L, 0xd39eb8fcL, 
+	0xed545578L, 0x08fca5b5L, 0xd83d7cd3L, 0x4dad0fc4L, 
+	0x1e50ef5eL, 0xb161e6f8L, 0xa28514d9L, 0x6c51133cL, 
+	0x6fd5c7e7L, 0x56e14ec4L, 0x362abfceL, 0xddc6c837L, 
+	0xd79a3234L, 0x92638212L, 0x670efa8eL, 0x406000e0L, 
+	0x3a39ce37L, 0xd3faf5cfL, 0xabc27737L, 0x5ac52d1bL, 
+	0x5cb0679eL, 0x4fa33742L, 0xd3822740L, 0x99bc9bbeL, 
+	0xd5118e9dL, 0xbf0f7315L, 0xd62d1c7eL, 0xc700c47bL, 
+	0xb78c1b6bL, 0x21a19045L, 0xb26eb1beL, 0x6a366eb4L, 
+	0x5748ab2fL, 0xbc946e79L, 0xc6a376d2L, 0x6549c2c8L, 
+	0x530ff8eeL, 0x468dde7dL, 0xd5730a1dL, 0x4cd04dc6L, 
+	0x2939bbdbL, 0xa9ba4650L, 0xac9526e8L, 0xbe5ee304L, 
+	0xa1fad5f0L, 0x6a2d519aL, 0x63ef8ce2L, 0x9a86ee22L, 
+	0xc089c2b8L, 0x43242ef6L, 0xa51e03aaL, 0x9cf2d0a4L, 
+	0x83c061baL, 0x9be96a4dL, 0x8fe51550L, 0xba645bd6L, 
+	0x2826a2f9L, 0xa73a3ae1L, 0x4ba99586L, 0xef5562e9L, 
+	0xc72fefd3L, 0xf752f7daL, 0x3f046f69L, 0x77fa0a59L, 
+	0x80e4a915L, 0x87b08601L, 0x9b09e6adL, 0x3b3ee593L, 
+	0xe990fd5aL, 0x9e34d797L, 0x2cf0b7d9L, 0x022b8b51L, 
+	0x96d5ac3aL, 0x017da67dL, 0xd1cf3ed6L, 0x7c7d2d28L, 
+	0x1f9f25cfL, 0xadf2b89bL, 0x5ad6b472L, 0x5a88f54cL, 
+	0xe029ac71L, 0xe019a5e6L, 0x47b0acfdL, 0xed93fa9bL, 
+	0xe8d3c48dL, 0x283b57ccL, 0xf8d56629L, 0x79132e28L, 
+	0x785f0191L, 0xed756055L, 0xf7960e44L, 0xe3d35e8cL, 
+	0x15056dd4L, 0x88f46dbaL, 0x03a16125L, 0x0564f0bdL, 
+	0xc3eb9e15L, 0x3c9057a2L, 0x97271aecL, 0xa93a072aL, 
+	0x1b3f6d9bL, 0x1e6321f5L, 0xf59c66fbL, 0x26dcf319L, 
+	0x7533d928L, 0xb155fdf5L, 0x03563482L, 0x8aba3cbbL, 
+	0x28517711L, 0xc20ad9f8L, 0xabcc5167L, 0xccad925fL, 
+	0x4de81751L, 0x3830dc8eL, 0x379d5862L, 0x9320f991L, 
+	0xea7a90c2L, 0xfb3e7bceL, 0x5121ce64L, 0x774fbe32L, 
+	0xa8b6e37eL, 0xc3293d46L, 0x48de5369L, 0x6413e680L, 
+	0xa2ae0810L, 0xdd6db224L, 0x69852dfdL, 0x09072166L, 
+	0xb39a460aL, 0x6445c0ddL, 0x586cdecfL, 0x1c20c8aeL, 
+	0x5bbef7ddL, 0x1b588d40L, 0xccd2017fL, 0x6bb4e3bbL, 
+	0xdda26a7eL, 0x3a59ff45L, 0x3e350a44L, 0xbcb4cdd5L, 
+	0x72eacea8L, 0xfa6484bbL, 0x8d6612aeL, 0xbf3c6f47L, 
+	0xd29be463L, 0x542f5d9eL, 0xaec2771bL, 0xf64e6370L, 
+	0x740e0d8dL, 0xe75b1357L, 0xf8721671L, 0xaf537d5dL, 
+	0x4040cb08L, 0x4eb4e2ccL, 0x34d2466aL, 0x0115af84L, 
+	0xe1b00428L, 0x95983a1dL, 0x06b89fb4L, 0xce6ea048L, 
+	0x6f3f3b82L, 0x3520ab82L, 0x011a1d4bL, 0x277227f8L, 
+	0x611560b1L, 0xe7933fdcL, 0xbb3a792bL, 0x344525bdL, 
+	0xa08839e1L, 0x51ce794bL, 0x2f32c9b7L, 0xa01fbac9L, 
+	0xe01cc87eL, 0xbcc7d1f6L, 0xcf0111c3L, 0xa1e8aac7L, 
+	0x1a908749L, 0xd44fbd9aL, 0xd0dadecbL, 0xd50ada38L, 
+	0x0339c32aL, 0xc6913667L, 0x8df9317cL, 0xe0b12b4fL, 
+	0xf79e59b7L, 0x43f5bb3aL, 0xf2d519ffL, 0x27d9459cL, 
+	0xbf97222cL, 0x15e6fc2aL, 0x0f91fc71L, 0x9b941525L, 
+	0xfae59361L, 0xceb69cebL, 0xc2a86459L, 0x12baa8d1L, 
+	0xb6c1075eL, 0xe3056a0cL, 0x10d25065L, 0xcb03a442L, 
+	0xe0ec6e0eL, 0x1698db3bL, 0x4c98a0beL, 0x3278e964L, 
+	0x9f1f9532L, 0xe0d392dfL, 0xd3a0342bL, 0x8971f21eL, 
+	0x1b0a7441L, 0x4ba3348cL, 0xc5be7120L, 0xc37632d8L, 
+	0xdf359f8dL, 0x9b992f2eL, 0xe60b6f47L, 0x0fe3f11dL, 
+	0xe54cda54L, 0x1edad891L, 0xce6279cfL, 0xcd3e7e6fL, 
+	0x1618b166L, 0xfd2c1d05L, 0x848fd2c5L, 0xf6fb2299L, 
+	0xf523f357L, 0xa6327623L, 0x93a83531L, 0x56cccd02L, 
+	0xacf08162L, 0x5a75ebb5L, 0x6e163697L, 0x88d273ccL, 
+	0xde966292L, 0x81b949d0L, 0x4c50901bL, 0x71c65614L, 
+	0xe6c6c7bdL, 0x327a140aL, 0x45e1d006L, 0xc3f27b9aL, 
+	0xc9aa53fdL, 0x62a80f00L, 0xbb25bfe2L, 0x35bdd2f6L, 
+	0x71126905L, 0xb2040222L, 0xb6cbcf7cL, 0xcd769c2bL, 
+	0x53113ec0L, 0x1640e3d3L, 0x38abbd60L, 0x2547adf0L, 
+	0xba38209cL, 0xf746ce76L, 0x77afa1c5L, 0x20756060L, 
+	0x85cbfe4eL, 0x8ae88dd8L, 0x7aaaf9b0L, 0x4cf9aa7eL, 
+	0x1948c25cL, 0x02fb8a8cL, 0x01c36ae4L, 0xd6ebe1f9L, 
+	0x90d4f869L, 0xa65cdea0L, 0x3f09252dL, 0xc208e69fL, 
+	0xb74e6132L, 0xce77e25bL, 0x578fdfe3L, 0x3ac372e6L, 
+};
+
+
--- /dev/null
+++ b/libsec/decodepem.c
@@ -1,0 +1,59 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+
+#define STRLEN(s)	(sizeof(s)-1)
+
+uchar*
+decodepem(char *s, char *type, int *len)
+{
+	uchar *d;
+	char *t, *e, *tt;
+	int n;
+
+	/*
+	 * find the correct section of the file, stripping garbage at the beginning and end.
+	 * the data is delimited by -----BEGIN <type>-----\n and -----END <type>-----\n
+	 */
+	n = strlen(type);
+	e = strchr(s, '\0');
+	for(t = s; t != nil && t < e; ){
+		tt = t;
+		t = strchr(tt, '\n');
+		if(t != nil)
+			t++;
+		if(strncmp(tt, "-----BEGIN ", STRLEN("-----BEGIN ")) == 0
+		&& strncmp(&tt[STRLEN("-----BEGIN ")], type, n) == 0
+		&& strncmp(&tt[STRLEN("-----BEGIN ")+n], "-----\n", STRLEN("-----\n")) == 0)
+			break;
+	}
+	for(tt = t; tt != nil && tt < e; tt++){
+		if(strncmp(tt, "-----END ", STRLEN("-----END ")) == 0
+		&& strncmp(&tt[STRLEN("-----END ")], type, n) == 0
+		&& strncmp(&tt[STRLEN("-----END ")+n], "-----\n", STRLEN("-----\n")) == 0)
+			break;
+		tt = strchr(tt, '\n');
+		if(tt == nil)
+			break;
+	}
+	if(tt == nil || tt == e){
+		werrstr("incorrect .pem file format: bad header or trailer");
+		return nil;
+	}
+
+	n = ((tt - t) * 6 + 7) / 8;
+	d = malloc(n);
+	if(d == nil){
+		werrstr("out of memory");
+		return nil;
+	}
+	n = dec64(d, n, t, tt - t);
+	if(n < 0){
+		free(d);
+		werrstr("incorrect .pem file format: bad base64 encoded data");
+		return nil;
+	}
+	*len = n;
+	return d;
+}
--- /dev/null
+++ b/libsec/des.c
@@ -1,0 +1,480 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+ * integrated sbox & p perm
+ */
+static u32int spbox[] = {
+
+0x00808200,0x00000000,0x00008000,0x00808202,0x00808002,0x00008202,0x00000002,0x00008000,
+0x00000200,0x00808200,0x00808202,0x00000200,0x00800202,0x00808002,0x00800000,0x00000002,
+0x00000202,0x00800200,0x00800200,0x00008200,0x00008200,0x00808000,0x00808000,0x00800202,
+0x00008002,0x00800002,0x00800002,0x00008002,0x00000000,0x00000202,0x00008202,0x00800000,
+0x00008000,0x00808202,0x00000002,0x00808000,0x00808200,0x00800000,0x00800000,0x00000200,
+0x00808002,0x00008000,0x00008200,0x00800002,0x00000200,0x00000002,0x00800202,0x00008202,
+0x00808202,0x00008002,0x00808000,0x00800202,0x00800002,0x00000202,0x00008202,0x00808200,
+0x00000202,0x00800200,0x00800200,0x00000000,0x00008002,0x00008200,0x00000000,0x00808002,
+
+0x40084010,0x40004000,0x00004000,0x00084010,0x00080000,0x00000010,0x40080010,0x40004010,
+0x40000010,0x40084010,0x40084000,0x40000000,0x40004000,0x00080000,0x00000010,0x40080010,
+0x00084000,0x00080010,0x40004010,0x00000000,0x40000000,0x00004000,0x00084010,0x40080000,
+0x00080010,0x40000010,0x00000000,0x00084000,0x00004010,0x40084000,0x40080000,0x00004010,
+0x00000000,0x00084010,0x40080010,0x00080000,0x40004010,0x40080000,0x40084000,0x00004000,
+0x40080000,0x40004000,0x00000010,0x40084010,0x00084010,0x00000010,0x00004000,0x40000000,
+0x00004010,0x40084000,0x00080000,0x40000010,0x00080010,0x40004010,0x40000010,0x00080010,
+0x00084000,0x00000000,0x40004000,0x00004010,0x40000000,0x40080010,0x40084010,0x00084000,
+
+0x00000104,0x04010100,0x00000000,0x04010004,0x04000100,0x00000000,0x00010104,0x04000100,
+0x00010004,0x04000004,0x04000004,0x00010000,0x04010104,0x00010004,0x04010000,0x00000104,
+0x04000000,0x00000004,0x04010100,0x00000100,0x00010100,0x04010000,0x04010004,0x00010104,
+0x04000104,0x00010100,0x00010000,0x04000104,0x00000004,0x04010104,0x00000100,0x04000000,
+0x04010100,0x04000000,0x00010004,0x00000104,0x00010000,0x04010100,0x04000100,0x00000000,
+0x00000100,0x00010004,0x04010104,0x04000100,0x04000004,0x00000100,0x00000000,0x04010004,
+0x04000104,0x00010000,0x04000000,0x04010104,0x00000004,0x00010104,0x00010100,0x04000004,
+0x04010000,0x04000104,0x00000104,0x04010000,0x00010104,0x00000004,0x04010004,0x00010100,
+
+0x80401000,0x80001040,0x80001040,0x00000040,0x00401040,0x80400040,0x80400000,0x80001000,
+0x00000000,0x00401000,0x00401000,0x80401040,0x80000040,0x00000000,0x00400040,0x80400000,
+0x80000000,0x00001000,0x00400000,0x80401000,0x00000040,0x00400000,0x80001000,0x00001040,
+0x80400040,0x80000000,0x00001040,0x00400040,0x00001000,0x00401040,0x80401040,0x80000040,
+0x00400040,0x80400000,0x00401000,0x80401040,0x80000040,0x00000000,0x00000000,0x00401000,
+0x00001040,0x00400040,0x80400040,0x80000000,0x80401000,0x80001040,0x80001040,0x00000040,
+0x80401040,0x80000040,0x80000000,0x00001000,0x80400000,0x80001000,0x00401040,0x80400040,
+0x80001000,0x00001040,0x00400000,0x80401000,0x00000040,0x00400000,0x00001000,0x00401040,
+
+0x00000080,0x01040080,0x01040000,0x21000080,0x00040000,0x00000080,0x20000000,0x01040000,
+0x20040080,0x00040000,0x01000080,0x20040080,0x21000080,0x21040000,0x00040080,0x20000000,
+0x01000000,0x20040000,0x20040000,0x00000000,0x20000080,0x21040080,0x21040080,0x01000080,
+0x21040000,0x20000080,0x00000000,0x21000000,0x01040080,0x01000000,0x21000000,0x00040080,
+0x00040000,0x21000080,0x00000080,0x01000000,0x20000000,0x01040000,0x21000080,0x20040080,
+0x01000080,0x20000000,0x21040000,0x01040080,0x20040080,0x00000080,0x01000000,0x21040000,
+0x21040080,0x00040080,0x21000000,0x21040080,0x01040000,0x00000000,0x20040000,0x21000000,
+0x00040080,0x01000080,0x20000080,0x00040000,0x00000000,0x20040000,0x01040080,0x20000080,
+
+0x10000008,0x10200000,0x00002000,0x10202008,0x10200000,0x00000008,0x10202008,0x00200000,
+0x10002000,0x00202008,0x00200000,0x10000008,0x00200008,0x10002000,0x10000000,0x00002008,
+0x00000000,0x00200008,0x10002008,0x00002000,0x00202000,0x10002008,0x00000008,0x10200008,
+0x10200008,0x00000000,0x00202008,0x10202000,0x00002008,0x00202000,0x10202000,0x10000000,
+0x10002000,0x00000008,0x10200008,0x00202000,0x10202008,0x00200000,0x00002008,0x10000008,
+0x00200000,0x10002000,0x10000000,0x00002008,0x10000008,0x10202008,0x00202000,0x10200000,
+0x00202008,0x10202000,0x00000000,0x10200008,0x00000008,0x00002000,0x10200000,0x00202008,
+0x00002000,0x00200008,0x10002008,0x00000000,0x10202000,0x10000000,0x00200008,0x10002008,
+
+0x00100000,0x02100001,0x02000401,0x00000000,0x00000400,0x02000401,0x00100401,0x02100400,
+0x02100401,0x00100000,0x00000000,0x02000001,0x00000001,0x02000000,0x02100001,0x00000401,
+0x02000400,0x00100401,0x00100001,0x02000400,0x02000001,0x02100000,0x02100400,0x00100001,
+0x02100000,0x00000400,0x00000401,0x02100401,0x00100400,0x00000001,0x02000000,0x00100400,
+0x02000000,0x00100400,0x00100000,0x02000401,0x02000401,0x02100001,0x02100001,0x00000001,
+0x00100001,0x02000000,0x02000400,0x00100000,0x02100400,0x00000401,0x00100401,0x02100400,
+0x00000401,0x02000001,0x02100401,0x02100000,0x00100400,0x00000000,0x00000001,0x02100401,
+0x00000000,0x00100401,0x02100000,0x00000400,0x02000001,0x02000400,0x00000400,0x00100001,
+
+0x08000820,0x00000800,0x00020000,0x08020820,0x08000000,0x08000820,0x00000020,0x08000000,
+0x00020020,0x08020000,0x08020820,0x00020800,0x08020800,0x00020820,0x00000800,0x00000020,
+0x08020000,0x08000020,0x08000800,0x00000820,0x00020800,0x00020020,0x08020020,0x08020800,
+0x00000820,0x00000000,0x00000000,0x08020020,0x08000020,0x08000800,0x00020820,0x00020000,
+0x00020820,0x00020000,0x08020800,0x00000800,0x00000020,0x08020020,0x00000800,0x00020820,
+0x08000800,0x00000020,0x08000020,0x08020000,0x08020020,0x08000000,0x00020000,0x08000820,
+0x00000000,0x08020820,0x00020020,0x08000020,0x08020000,0x08000800,0x08000820,0x00000000,
+0x08020820,0x00020800,0x00020800,0x00000820,0x00000820,0x00020020,0x08000000,0x08020800,
+};
+
+/*
+ * for manual index calculation
+ * #define fetch(box, i, sh) (*((u32int*)((uchar*)spbox + (box << 8) + ((i >> (sh)) & 0xfc))))
+ */
+#define fetch(box, i, sh) ((spbox+(box << 6))[((i >> (sh + 2)) & 0x3f)])
+
+/*
+ * DES electronic codebook encryption of one block
+ */
+void
+block_cipher(ulong key[32], uchar text[8], int decrypting)
+{
+	u32int right, left, v0, v1;
+	int i, keystep;
+
+	/*
+	 * initial permutation
+	 */
+	v0 = text[0] | ((u32int)text[2]<<8) | ((u32int)text[4]<<16) | ((u32int)text[6]<<24);
+	left = text[1] | ((u32int)text[3]<<8) | ((u32int)text[5]<<16) | ((u32int)text[7]<<24);
+	right = (left & 0xaaaaaaaa) | ((v0 >> 1) & 0x55555555);
+	left = ((left << 1) & 0xaaaaaaaa) | (v0 & 0x55555555);
+	left = ((left << 6) & 0x33003300)
+		| (left & 0xcc33cc33)
+		| ((left >> 6) & 0x00cc00cc);
+	left = ((left << 12) & 0x0f0f0000)
+		| (left & 0xf0f00f0f)
+		| ((left >> 12) & 0x0000f0f0);
+	right = ((right << 6) & 0x33003300)
+		| (right & 0xcc33cc33)
+		| ((right >> 6) & 0x00cc00cc);
+	right = ((right << 12) & 0x0f0f0000)
+		| (right & 0xf0f00f0f)
+		| ((right >> 12) & 0x0000f0f0);
+
+	if (decrypting) {
+		keystep = -2;
+		key = key + 32 - 2;
+	} else
+		keystep = 2;
+	for (i = 0; i < 8; i++) {
+		v0 = key[0];
+		v0 ^= (right >> 1) | (right << 31);
+		left ^= fetch(0, v0, 24)
+			^ fetch(2, v0, 16)
+			^ fetch(4, v0, 8)
+			^ fetch(6, v0, 0);
+		v1 = key[1];
+		v1 ^= (right << 3) | (right >> 29);
+		left ^= fetch(1, v1, 24)
+			^ fetch(3, v1, 16)
+			^ fetch(5, v1, 8)
+			^ fetch(7, v1, 0);
+		key += keystep;
+		
+		v0 = key[0];
+		v0 ^= (left >> 1) | (left << 31);
+		right ^= fetch(0, v0, 24)
+			^ fetch(2, v0, 16)
+			^ fetch(4, v0, 8)
+			^ fetch(6, v0, 0);
+		v1 = key[1];
+		v1 ^= (left << 3) | (left >> 29);
+		right ^= fetch(1, v1, 24)
+			^ fetch(3, v1, 16)
+			^ fetch(5, v1, 8)
+			^ fetch(7, v1, 0);
+		key += keystep;
+	}
+
+	/*
+	 * final permutation, inverse initial permutation
+	 */
+	v0 = ((left << 1) & 0xaaaaaaaa) | (right & 0x55555555);
+	v1 = (left & 0xaaaaaaaa) | ((right >> 1) & 0x55555555);
+	v1 = ((v1 << 6) & 0x33003300)
+		| (v1 & 0xcc33cc33)
+		| ((v1 >> 6) & 0x00cc00cc);
+	v1 = ((v1 << 12) & 0x0f0f0000)
+		| (v1 & 0xf0f00f0f)
+		| ((v1 >> 12) & 0x0000f0f0);
+	v0 = ((v0 << 6) & 0x33003300)
+		| (v0 & 0xcc33cc33)
+		| ((v0 >> 6) & 0x00cc00cc);
+	v0 = ((v0 << 12) & 0x0f0f0000)
+		| (v0 & 0xf0f00f0f)
+		| ((v0 >> 12) & 0x0000f0f0);
+	text[0] = v0;
+	text[2] = v0 >> 8;
+	text[4] = v0 >> 16;
+	text[6] = v0 >> 24;
+	text[1] = v1;
+	text[3] = v1 >> 8;
+	text[5] = v1 >> 16;
+	text[7] = v1 >> 24;
+}
+
+/*
+ * triple DES electronic codebook encryption of one block
+ */
+void
+triple_block_cipher(ulong expanded_key[3][32], uchar text[8], int ende)
+{
+	ulong *key;
+	u32int right, left, v0, v1;
+	int i, j, keystep;
+
+	/*
+	 * initial permutation
+	 */
+	v0 = text[0] | ((u32int)text[2]<<8) | ((u32int)text[4]<<16) | ((u32int)text[6]<<24);
+	left = text[1] | ((u32int)text[3]<<8) | ((u32int)text[5]<<16) | ((u32int)text[7]<<24);
+	right = (left & 0xaaaaaaaa) | ((v0 >> 1) & 0x55555555);
+	left = ((left << 1) & 0xaaaaaaaa) | (v0 & 0x55555555);
+	left = ((left << 6) & 0x33003300)
+		| (left & 0xcc33cc33)
+		| ((left >> 6) & 0x00cc00cc);
+	left = ((left << 12) & 0x0f0f0000)
+		| (left & 0xf0f00f0f)
+		| ((left >> 12) & 0x0000f0f0);
+	right = ((right << 6) & 0x33003300)
+		| (right & 0xcc33cc33)
+		| ((right >> 6) & 0x00cc00cc);
+	right = ((right << 12) & 0x0f0f0000)
+		| (right & 0xf0f00f0f)
+		| ((right >> 12) & 0x0000f0f0);
+
+	for(j = 0; j < 3; j++){
+		if((ende & 1) == DES3D) {
+			key = &expanded_key[2-j][32-2];
+			keystep = -2;
+		} else {
+			key = &expanded_key[j][0];
+			keystep = 2;
+		}
+		ende >>= 1;
+		for (i = 0; i < 8; i++) {
+			v0 = key[0];
+			v0 ^= (right >> 1) | (right << 31);
+			left ^= fetch(0, v0, 24)
+				^ fetch(2, v0, 16)
+				^ fetch(4, v0, 8)
+				^ fetch(6, v0, 0);
+			v1 = key[1];
+			v1 ^= (right << 3) | (right >> 29);
+			left ^= fetch(1, v1, 24)
+				^ fetch(3, v1, 16)
+				^ fetch(5, v1, 8)
+				^ fetch(7, v1, 0);
+			key += keystep;
+			
+			v0 = key[0];
+			v0 ^= (left >> 1) | (left << 31);
+			right ^= fetch(0, v0, 24)
+				^ fetch(2, v0, 16)
+				^ fetch(4, v0, 8)
+				^ fetch(6, v0, 0);
+			v1 = key[1];
+			v1 ^= (left << 3) | (left >> 29);
+			right ^= fetch(1, v1, 24)
+				^ fetch(3, v1, 16)
+				^ fetch(5, v1, 8)
+				^ fetch(7, v1, 0);
+			key += keystep;
+		}
+
+		v0 = left;
+		left = right;
+		right = v0;
+	}
+
+	/*
+	 * final permutation, inverse initial permutation
+	 * left and right are swapped here
+	 */
+	v0 = ((right << 1) & 0xaaaaaaaa) | (left & 0x55555555);
+	v1 = (right & 0xaaaaaaaa) | ((left >> 1) & 0x55555555);
+	v1 = ((v1 << 6) & 0x33003300)
+		| (v1 & 0xcc33cc33)
+		| ((v1 >> 6) & 0x00cc00cc);
+	v1 = ((v1 << 12) & 0x0f0f0000)
+		| (v1 & 0xf0f00f0f)
+		| ((v1 >> 12) & 0x0000f0f0);
+	v0 = ((v0 << 6) & 0x33003300)
+		| (v0 & 0xcc33cc33)
+		| ((v0 >> 6) & 0x00cc00cc);
+	v0 = ((v0 << 12) & 0x0f0f0000)
+		| (v0 & 0xf0f00f0f)
+		| ((v0 >> 12) & 0x0000f0f0);
+	text[0] = v0;
+	text[2] = v0 >> 8;
+	text[4] = v0 >> 16;
+	text[6] = v0 >> 24;
+	text[1] = v1;
+	text[3] = v1 >> 8;
+	text[5] = v1 >> 16;
+	text[7] = v1 >> 24;
+}
+
+/*
+ * key compression permutation, 4 bits at a time
+ */
+static u32int comptab[] = {
+
+0x000000,0x010000,0x000008,0x010008,0x000080,0x010080,0x000088,0x010088,
+0x000000,0x010000,0x000008,0x010008,0x000080,0x010080,0x000088,0x010088,
+
+0x000000,0x100000,0x000800,0x100800,0x000000,0x100000,0x000800,0x100800,
+0x002000,0x102000,0x002800,0x102800,0x002000,0x102000,0x002800,0x102800,
+
+0x000000,0x000004,0x000400,0x000404,0x000000,0x000004,0x000400,0x000404,
+0x400000,0x400004,0x400400,0x400404,0x400000,0x400004,0x400400,0x400404,
+
+0x000000,0x000020,0x008000,0x008020,0x800000,0x800020,0x808000,0x808020,
+0x000002,0x000022,0x008002,0x008022,0x800002,0x800022,0x808002,0x808022,
+
+0x000000,0x000200,0x200000,0x200200,0x001000,0x001200,0x201000,0x201200,
+0x000000,0x000200,0x200000,0x200200,0x001000,0x001200,0x201000,0x201200,
+
+0x000000,0x000040,0x000010,0x000050,0x004000,0x004040,0x004010,0x004050,
+0x040000,0x040040,0x040010,0x040050,0x044000,0x044040,0x044010,0x044050,
+
+0x000000,0x000100,0x020000,0x020100,0x000001,0x000101,0x020001,0x020101,
+0x080000,0x080100,0x0a0000,0x0a0100,0x080001,0x080101,0x0a0001,0x0a0101,
+
+0x000000,0x000100,0x040000,0x040100,0x000000,0x000100,0x040000,0x040100,
+0x000040,0x000140,0x040040,0x040140,0x000040,0x000140,0x040040,0x040140,
+
+0x000000,0x400000,0x008000,0x408000,0x000008,0x400008,0x008008,0x408008,
+0x000400,0x400400,0x008400,0x408400,0x000408,0x400408,0x008408,0x408408,
+
+0x000000,0x001000,0x080000,0x081000,0x000020,0x001020,0x080020,0x081020,
+0x004000,0x005000,0x084000,0x085000,0x004020,0x005020,0x084020,0x085020,
+
+0x000000,0x000800,0x000000,0x000800,0x000010,0x000810,0x000010,0x000810,
+0x800000,0x800800,0x800000,0x800800,0x800010,0x800810,0x800010,0x800810,
+
+0x000000,0x010000,0x000200,0x010200,0x000000,0x010000,0x000200,0x010200,
+0x100000,0x110000,0x100200,0x110200,0x100000,0x110000,0x100200,0x110200,
+
+0x000000,0x000004,0x000000,0x000004,0x000080,0x000084,0x000080,0x000084,
+0x002000,0x002004,0x002000,0x002004,0x002080,0x002084,0x002080,0x002084,
+
+0x000000,0x000001,0x200000,0x200001,0x020000,0x020001,0x220000,0x220001,
+0x000002,0x000003,0x200002,0x200003,0x020002,0x020003,0x220002,0x220003,
+};
+
+static int keysh[] =
+{
+	1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1,
+};
+
+static void
+keycompperm(u32int left, u32int right, ulong *ek)
+{
+	u32int v0, v1;
+	int i;
+
+	for(i = 0; i < 16; i++){
+		left = (left << keysh[i]) | (left >> (28 - keysh[i]));
+		left &= 0xfffffff0;
+		right = (right << keysh[i]) | (right >> (28 - keysh[i]));
+		right &= 0xfffffff0;
+		v0 = comptab[6 * (1 << 4) + ((left >> (32-4)) & 0xf)]
+			| comptab[5 * (1 << 4) + ((left >> (32-8)) & 0xf)]
+			| comptab[4 * (1 << 4) + ((left >> (32-12)) & 0xf)]
+			| comptab[3 * (1 << 4) + ((left >> (32-16)) & 0xf)]
+			| comptab[2 * (1 << 4) + ((left >> (32-20)) & 0xf)]
+			| comptab[1 * (1 << 4) + ((left >> (32-24)) & 0xf)]
+			| comptab[0 * (1 << 4) + ((left >> (32-28)) & 0xf)];
+		v1 = comptab[13 * (1 << 4) + ((right >> (32-4)) & 0xf)]
+			| comptab[12 * (1 << 4) + ((right >> (32-8)) & 0xf)]
+			| comptab[11 * (1 << 4) + ((right >> (32-12)) & 0xf)]
+			| comptab[10 * (1 << 4) + ((right >> (32-16)) & 0xf)]
+			| comptab[9 * (1 << 4) + ((right >> (32-20)) & 0xf)]
+			| comptab[8 * (1 << 4) + ((right >> (32-24)) & 0xf)]
+			| comptab[7 * (1 << 4) + ((right >> (32-28)) & 0xf)];
+		ek[0] = (((v0 >> (24-6)) & 0x3f) << 26)
+			| (((v0 >> (24-18)) & 0x3f) << 18)
+			| (((v1 >> (24-6)) & 0x3f) << 10)
+			| (((v1 >> (24-18)) & 0x3f) << 2);
+		ek[1] = (((v0 >> (24-12)) & 0x3f) << 26)
+			| (((v0 >> (24-24)) & 0x3f) << 18)
+			| (((v1 >> (24-12)) & 0x3f) << 10)
+			| (((v1 >> (24-24)) & 0x3f) << 2);
+		ek += 2;
+	}
+}
+
+void
+des_key_setup(uchar key[8], ulong *ek)
+{
+	u32int left, right, v0, v1;
+
+	v0 = key[0] | ((u32int)key[2] << 8) | ((u32int)key[4] << 16) | ((u32int)key[6] << 24);
+	v1 = key[1] | ((u32int)key[3] << 8) | ((u32int)key[5] << 16) | ((u32int)key[7] << 24);
+	left = ((v0 >> 1) & 0x40404040)
+		| ((v0 >> 2) & 0x10101010)
+		| ((v0 >> 3) & 0x04040404)
+		| ((v0 >> 4) & 0x01010101)
+		| ((v1 >> 0) & 0x80808080)
+		| ((v1 >> 1) & 0x20202020)
+		| ((v1 >> 2) & 0x08080808)
+		| ((v1 >> 3) & 0x02020202);
+	right = ((v0 >> 1) & 0x04040404)
+		| ((v0 << 2) & 0x10101010)
+		| ((v0 << 5) & 0x40404040)
+		| ((v1 << 0) & 0x08080808)
+		| ((v1 << 3) & 0x20202020)
+		| ((v1 << 6) & 0x80808080);
+	left = ((left << 6) & 0x33003300)
+		| (left & 0xcc33cc33)
+		| ((left >> 6) & 0x00cc00cc);
+	v0 = ((left << 12) & 0x0f0f0000)
+		| (left & 0xf0f00f0f)
+		| ((left >> 12) & 0x0000f0f0);
+	right = ((right << 6) & 0x33003300)
+		| (right & 0xcc33cc33)
+		| ((right >> 6) & 0x00cc00cc);
+	v1 = ((right << 12) & 0x0f0f0000)
+		| (right & 0xf0f00f0f)
+		| ((right >> 12) & 0x0000f0f0);
+	left = v0 & 0xfffffff0;
+	right = (v1 & 0xffffff00) | ((v0 << 4) & 0xf0);
+
+	keycompperm(left, right, ek);
+}
+
+static uchar parity[128] =
+{
+	0x01, 0x02, 0x04, 0x07, 0x08, 0x0b, 0x0d, 0x0e, 
+	0x10, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c, 0x1f, 
+	0x20, 0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x2f, 
+	0x31, 0x32, 0x34, 0x37, 0x38, 0x3b, 0x3d, 0x3e, 
+	0x40, 0x43, 0x45, 0x46, 0x49, 0x4a, 0x4c, 0x4f, 
+	0x51, 0x52, 0x54, 0x57, 0x58, 0x5b, 0x5d, 0x5e, 
+	0x61, 0x62, 0x64, 0x67, 0x68, 0x6b, 0x6d, 0x6e, 
+	0x70, 0x73, 0x75, 0x76, 0x79, 0x7a, 0x7c, 0x7f, 
+	0x80, 0x83, 0x85, 0x86, 0x89, 0x8a, 0x8c, 0x8f, 
+	0x91, 0x92, 0x94, 0x97, 0x98, 0x9b, 0x9d, 0x9e, 
+	0xa1, 0xa2, 0xa4, 0xa7, 0xa8, 0xab, 0xad, 0xae, 
+	0xb0, 0xb3, 0xb5, 0xb6, 0xb9, 0xba, 0xbc, 0xbf, 
+	0xc1, 0xc2, 0xc4, 0xc7, 0xc8, 0xcb, 0xcd, 0xce, 
+	0xd0, 0xd3, 0xd5, 0xd6, 0xd9, 0xda, 0xdc, 0xdf, 
+	0xe0, 0xe3, 0xe5, 0xe6, 0xe9, 0xea, 0xec, 0xef, 
+	0xf1, 0xf2, 0xf4, 0xf7, 0xf8, 0xfb, 0xfd, 0xfe,
+};
+
+/*
+ *  convert a 7 byte key to an 8 byte one
+ */
+void
+des56to64(uchar *k56, uchar *k64)
+{
+	u32int hi, lo;
+
+	hi = ((u32int)k56[0]<<24)|((u32int)k56[1]<<16)|((u32int)k56[2]<<8)|k56[3];
+	lo = ((u32int)k56[4]<<24)|((u32int)k56[5]<<16)|((u32int)k56[6]<<8);
+
+	k64[0] = parity[(hi>>25)&0x7f];
+	k64[1] = parity[(hi>>18)&0x7f];
+	k64[2] = parity[(hi>>11)&0x7f];
+	k64[3] = parity[(hi>>4)&0x7f];
+	k64[4] = parity[((hi<<3)|(lo>>29))&0x7f];
+	k64[5] = parity[(lo>>22)&0x7f];
+	k64[6] = parity[(lo>>15)&0x7f];
+	k64[7] = parity[(lo>>8)&0x7f];
+}
+
+/*
+ *  convert an 8 byte key to a 7 byte one
+ */
+void
+des64to56(uchar *k64, uchar *k56)
+{
+	u32int hi, lo;
+
+	hi = (((u32int)k64[0]&0xfe)<<24)|(((u32int)k64[1]&0xfe)<<17)|(((u32int)k64[2]&0xfe)<<10)
+		|((k64[3]&0xfe)<<3)|(k64[4]>>4);
+	lo = (((u32int)k64[4]&0xfe)<<28)|(((u32int)k64[5]&0xfe)<<21)|(((u32int)k64[6]&0xfe)<<14)
+		|(((u32int)k64[7]&0xfe)<<7);
+
+	k56[0] = hi>>24;
+	k56[1] = hi>>16;
+	k56[2] = hi>>8;
+	k56[3] = hi>>0;
+	k56[4] = lo>>24;
+	k56[5] = lo>>16;
+	k56[6] = lo>>8;
+}
+
+void
+key_setup(uchar key[7], ulong *ek)
+{
+	uchar k64[8];
+
+	des56to64(key, k64);
+	des_key_setup(k64, ek);	
+}
--- /dev/null
+++ b/libsec/des3CBC.c
@@ -1,0 +1,59 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// Because of the way that non multiple of 8
+// buffers are handled, the decryptor must
+// be fed buffers of the same size as the
+// encryptor
+
+
+// If the length is not a multiple of 8, I encrypt
+// the overflow to be compatible with lacy's cryptlib
+void
+des3CBCencrypt(uchar *p, int len, DES3state *s)
+{
+	uchar *p2, *ip, *eip;
+
+	for(; len >= 8; len -= 8){
+		p2 = p;
+		ip = s->ivec;
+		for(eip = ip+8; ip < eip; )
+			*p2++ ^= *ip++;
+		triple_block_cipher(s->expanded, p, DES3EDE);
+		memmove(s->ivec, p, 8);
+		p += 8;
+	}
+
+	if(len > 0){
+		ip = s->ivec;
+		triple_block_cipher(s->expanded, ip, DES3EDE);
+		for(eip = ip+len; ip < eip; )
+			*p++ ^= *ip++;
+	}
+}
+
+void
+des3CBCdecrypt(uchar *p, int len, DES3state *s)
+{
+	uchar *ip, *eip, *tp;
+	uchar tmp[8];
+
+	for(; len >= 8; len -= 8){
+		memmove(tmp, p, 8);
+		triple_block_cipher(s->expanded, p, DES3DED);
+		tp = tmp;
+		ip = s->ivec;
+		for(eip = ip+8; ip < eip; ){
+			*p++ ^= *ip;
+			*ip++ = *tp++;
+		}
+	}
+
+	if(len > 0){
+		ip = s->ivec;
+		triple_block_cipher(s->expanded, ip, DES3EDE);
+		for(eip = ip+len; ip < eip; )
+			*p++ ^= *ip++;
+	}
+}
--- /dev/null
+++ b/libsec/des3ECB.c
@@ -1,0 +1,48 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// I wasn't sure what to do when the buffer was not
+// a multiple of 8.  I did what lacy's cryptolib did
+// to be compatible, but it looks dangerous to me
+// since its encrypting plain text with the key. -- presotto
+
+void
+des3ECBencrypt(uchar *p, int len, DES3state *s)
+{
+	int i;
+	uchar tmp[8];
+
+	for(; len >= 8; len -= 8){
+		triple_block_cipher(s->expanded, p, DES3EDE);
+		p += 8;
+	}
+	
+	if(len > 0){
+		for (i=0; i<8; i++)
+			tmp[i] = i;
+		triple_block_cipher(s->expanded, tmp, DES3EDE);
+		for (i = 0; i < len; i++)
+			p[i] ^= tmp[i];
+	}
+}
+
+void
+des3ECBdecrypt(uchar *p, int len, DES3state *s)
+{
+	int i;
+	uchar tmp[8];
+
+	for(; len >= 8; len -= 8){
+		triple_block_cipher(s->expanded, p, DES3DED);
+		p += 8;
+	}
+	
+	if(len > 0){
+		for (i=0; i<8; i++)
+			tmp[i] = i;
+		triple_block_cipher(s->expanded, tmp, DES3EDE);
+		for (i = 0; i < len; i++)
+			p[i] ^= tmp[i];
+	}
+}
--- /dev/null
+++ b/libsec/desCBC.c
@@ -1,0 +1,59 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// Because of the way that non multiple of 8
+// buffers are handled, the decryptor must
+// be fed buffers of the same size as the
+// encryptor
+
+
+// If the length is not a multiple of 8, I encrypt
+// the overflow to be compatible with lacy's cryptlib
+void
+desCBCencrypt(uchar *p, int len, DESstate *s)
+{
+	uchar *p2, *ip, *eip;
+
+	for(; len >= 8; len -= 8){
+		p2 = p;
+		ip = s->ivec;
+		for(eip = ip+8; ip < eip; )
+			*p2++ ^= *ip++;
+		block_cipher(s->expanded, p, 0);
+		memmove(s->ivec, p, 8);
+		p += 8;
+	}
+
+	if(len > 0){
+		ip = s->ivec;
+		block_cipher(s->expanded, ip, 0);
+		for(eip = ip+len; ip < eip; )
+			*p++ ^= *ip++;
+	}
+}
+
+void
+desCBCdecrypt(uchar *p, int len, DESstate *s)
+{
+	uchar *ip, *eip, *tp;
+	uchar tmp[8];
+
+	for(; len >= 8; len -= 8){
+		memmove(tmp, p, 8);
+		block_cipher(s->expanded, p, 1);
+		tp = tmp;
+		ip = s->ivec;
+		for(eip = ip+8; ip < eip; ){
+			*p++ ^= *ip;
+			*ip++ = *tp++;
+		}
+	}
+
+	if(len > 0){
+		ip = s->ivec;
+		block_cipher(s->expanded, ip, 0);
+		for(eip = ip+len; ip < eip; )
+			*p++ ^= *ip++;
+	}
+}
--- /dev/null
+++ b/libsec/desECB.c
@@ -1,0 +1,48 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// I wasn't sure what to do when the buffer was not
+// a multiple of 8.  I did what lacy's cryptolib did
+// to be compatible, but it looks dangerous to me
+// since its encrypting plain text with the key. -- presotto
+
+void
+desECBencrypt(uchar *p, int len, DESstate *s)
+{
+	int i;
+	uchar tmp[8];
+
+	for(; len >= 8; len -= 8){
+		block_cipher(s->expanded, p, 0);
+		p += 8;
+	}
+	
+	if(len > 0){
+		for (i=0; i<8; i++)
+			tmp[i] = i;
+		block_cipher(s->expanded, tmp, 0);
+		for (i = 0; i < len; i++)
+			p[i] ^= tmp[i];
+	}
+}
+
+void
+desECBdecrypt(uchar *p, int len, DESstate *s)
+{
+	int i;
+	uchar tmp[8];
+
+	for(; len >= 8; len -= 8){
+		block_cipher(s->expanded, p, 1);
+		p += 8;
+	}
+	
+	if(len > 0){
+		for (i=0; i<8; i++)
+			tmp[i] = i;
+		block_cipher(s->expanded, tmp, 0);
+		for (i = 0; i < len; i++)
+			p[i] ^= tmp[i];
+	}
+}
--- /dev/null
+++ b/libsec/desmodes.c
@@ -1,0 +1,31 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+ *  these routines use the 64bit format for
+ *  DES keys.
+ */
+
+void
+setupDESstate(DESstate *s, uchar key[8], uchar *ivec)
+{
+	memset(s, 0, sizeof(*s));
+	memmove(s->key, key, sizeof(s->key));
+	des_key_setup(key, s->expanded);
+	if(ivec)
+		memmove(s->ivec, ivec, 8);
+	s->setup = 0xdeadbeef;
+}
+
+void
+setupDES3state(DES3state *s, uchar key[3][8], uchar *ivec)
+{
+	memset(s, 0, sizeof(*s));
+	memmove(s->key, key, sizeof(s->key));
+	des_key_setup(key[0], s->expanded[0]);
+	des_key_setup(key[1], s->expanded[1]);
+	des_key_setup(key[2], s->expanded[2]);
+	if(ivec)
+		memmove(s->ivec, ivec, 8);
+	s->setup = 0xdeadbeef;
+}
--- /dev/null
+++ b/libsec/dsaalloc.c
@@ -1,0 +1,69 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+DSApub*
+dsapuballoc(void)
+{
+	DSApub *dsa;
+
+	dsa = mallocz(sizeof(*dsa), 1);
+	if(dsa == nil)
+		sysfatal("dsapuballoc");
+	return dsa;
+}
+
+void
+dsapubfree(DSApub *dsa)
+{
+	if(dsa == nil)
+		return;
+	mpfree(dsa->p);
+	mpfree(dsa->q);
+	mpfree(dsa->alpha);
+	mpfree(dsa->key);
+}
+
+
+DSApriv*
+dsaprivalloc(void)
+{
+	DSApriv *dsa;
+
+	dsa = mallocz(sizeof(*dsa), 1);
+	if(dsa == nil)
+		sysfatal("dsaprivalloc");
+	return dsa;
+}
+
+void
+dsaprivfree(DSApriv *dsa)
+{
+	if(dsa == nil)
+		return;
+	mpfree(dsa->pub.p);
+	mpfree(dsa->pub.q);
+	mpfree(dsa->pub.alpha);
+	mpfree(dsa->pub.key);
+	mpfree(dsa->secret);
+}
+
+DSAsig*
+dsasigalloc(void)
+{
+	DSAsig *dsa;
+
+	dsa = mallocz(sizeof(*dsa), 1);
+	if(dsa == nil)
+		sysfatal("dsasigalloc");
+	return dsa;
+}
+
+void
+dsasigfree(DSAsig *dsa)
+{
+	if(dsa == nil)
+		return;
+	mpfree(dsa->r);
+	mpfree(dsa->s);
+}
--- /dev/null
+++ b/libsec/dsagen.c
@@ -1,0 +1,61 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+DSApriv*
+dsagen(DSApub *opub)
+{
+	DSApub *pub;
+	DSApriv *priv;
+	mpint *exp;
+	mpint *g;
+	mpint *r;
+	int bits;
+
+	priv = dsaprivalloc();
+	pub = &priv->pub;
+
+	if(opub != nil){
+		pub->p = mpcopy(opub->p);
+		pub->q = mpcopy(opub->q);
+	} else {
+		pub->p = mpnew(0);
+		pub->q = mpnew(0);
+		DSAprimes(pub->q, pub->p, nil);
+	}
+	bits = Dbits*pub->p->top;
+
+	pub->alpha = mpnew(0);
+	pub->key = mpnew(0);
+	priv->secret = mpnew(0);
+
+	// find a generator alpha of the multiplicative
+	// group Z*p, i.e., of order n = p-1.  We use the
+	// fact that q divides p-1 to reduce the exponent.
+	//
+	// This isn't very efficient.  If anyone has a better
+	// idea, mail presotto@closedmind.org
+	exp = mpnew(0);
+	g = mpnew(0);
+	r = mpnew(0);
+	mpsub(pub->p, mpone, exp);
+	mpdiv(exp, pub->q, exp, r);
+	if(mpcmp(r, mpzero) != 0)
+		sysfatal("dsagen foul up");
+	while(1){
+		mprand(bits, genrandom, g);
+		mpmod(g, pub->p, g);
+		mpexp(g, exp, pub->p, pub->alpha);
+		if(mpcmp(pub->alpha, mpone) != 0)
+			break;
+	}
+	mpfree(g);
+	mpfree(exp);
+
+	// create the secret key
+	mprand(bits, genrandom, priv->secret);
+	mpmod(priv->secret, pub->p, priv->secret);
+	mpexp(pub->alpha, priv->secret, pub->p, pub->key);
+
+	return priv;
+}
--- /dev/null
+++ b/libsec/dsaprimes.c
@@ -1,0 +1,97 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// NIST algorithm for generating DSA primes
+// Menezes et al (1997) Handbook of Applied Cryptography, p.151
+// q is a 160-bit prime;  p is a 1024-bit prime;  q divides p-1
+
+// arithmetic on unsigned ints mod 2**160, represented
+//    as 20-byte, little-endian uchar array
+
+static void
+Hrand(uchar *s)
+{
+	ulong *u = (ulong*)s;
+	*u++ = fastrand();
+	*u++ = fastrand();
+	*u++ = fastrand();
+	*u++ = fastrand();
+	*u = fastrand();
+}
+
+static void
+Hincr(uchar *s)
+{
+	int i;
+	for(i=0; i<20; i++)
+		if(++s[i]!=0)
+			break;
+}
+
+// this can run for quite a while;  be patient
+void
+DSAprimes(mpint *q, mpint *p, uchar seed[SHA1dlen])
+{
+	int i, j, k, n = 6, b = 63;
+	uchar s[SHA1dlen], Hs[SHA1dlen], Hs1[SHA1dlen], sj[SHA1dlen], sjk[SHA1dlen];
+	mpint *two1023, *mb, *Vk, *W, *X, *q2;
+
+	two1023 = mpnew(1024);
+	mpleft(mpone, 1023, two1023);
+	mb = mpnew(0);
+	mpleft(mpone, b, mb);
+	W = mpnew(1024);
+	Vk = mpnew(1024);
+	X = mpnew(0);
+	q2 = mpnew(0);
+forever:
+	do{
+		Hrand(s);
+		memcpy(sj, s, 20);
+		sha1(s, 20, Hs, 0);
+		Hincr(sj);
+		sha1(sj, 20, Hs1, 0);
+		for(i=0; i<20; i++)
+			Hs[i] ^= Hs1[i];
+		Hs[0] |= 1;
+		Hs[19] |= 0x80;
+		letomp(Hs, 20, q);
+	}while(!probably_prime(q, 18));
+	if(seed != nil)	// allow skeptics to confirm computation
+		memmove(seed, s, SHA1dlen);
+	i = 0;
+	j = 2;
+	Hincr(sj);
+	mpleft(q, 1, q2);
+	while(i<4096){
+		memcpy(sjk, sj, 20);
+		for(k=0; k <= n; k++){
+			sha1(sjk, 20, Hs, 0);
+			letomp(Hs, 20, Vk);
+			if(k == n)
+				mpmod(Vk, mb, Vk);
+			mpleft(Vk, 160*k, Vk);
+			mpadd(W, Vk, W);
+			Hincr(sjk);
+		}
+		mpadd(W, two1023, X);
+		mpmod(X, q2, W);
+		mpsub(W, mpone, W);
+		mpsub(X, W, p);
+		if(mpcmp(p, two1023)>=0 && probably_prime(p, 5))
+			goto done;
+		i += 1;
+		j += n+1;
+		for(k=0; k<n+1; k++)
+			Hincr(sj);
+	}
+	goto forever;
+done:
+	mpfree(q2);
+	mpfree(X);
+	mpfree(Vk);
+	mpfree(W);
+	mpfree(mb);
+	mpfree(two1023);
+}
--- /dev/null
+++ b/libsec/dsaprivtopub.c
@@ -1,0 +1,16 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+DSApub*
+dsaprivtopub(DSApriv *priv)
+{
+	DSApub *pub;
+
+	pub = dsapuballoc();
+	pub->p = mpcopy(priv->pub.p);
+	pub->q = mpcopy(priv->pub.q);
+	pub->alpha = mpcopy(priv->pub.alpha);
+	pub->key = mpcopy(priv->pub.key);
+	return pub;
+}
--- /dev/null
+++ b/libsec/dsasign.c
@@ -1,0 +1,52 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+DSAsig*
+dsasign(DSApriv *priv, mpint *m)
+{
+	DSApub *pub = &priv->pub;
+	DSAsig *sig;
+	mpint *qm1, *k, *kinv, *r, *s;
+	mpint *q = pub->q, *p = pub->p, *alpha = pub->alpha;
+	int qlen = mpsignif(q);
+
+	qm1 = mpnew(0);
+	kinv = mpnew(0);
+	r = mpnew(0);
+	s = mpnew(0);
+	k = mpnew(0);
+	mpsub(pub->q, mpone, qm1);
+
+	// find a k that has an inverse mod q
+	while(1){
+		mprand(qlen, genrandom, k);
+		if((mpcmp(mpone, k) > 0) || (mpcmp(k, qm1) >= 0))
+			continue;
+		mpextendedgcd(k, q, r, kinv, s);
+		if(mpcmp(r, mpone) != 0)
+			continue;
+		break;
+	}
+
+  	// make kinv positive
+	mpmod(kinv, qm1, kinv);
+
+	// r = ((alpha**k) mod p) mod q
+	mpexp(alpha, k, p, r);
+	mpmod(r, q, r);
+
+	// s = (kinv*(m + ar)) mod q
+	mpmul(r, priv->secret, s);
+	mpadd(s, m, s);
+	mpmul(s, kinv, s);
+	mpmod(s, q, s);
+
+	sig = dsasigalloc();
+	sig->r = r;
+	sig->s = s;
+	mpfree(qm1);
+	mpfree(k);
+	mpfree(kinv);
+	return sig;
+}
--- /dev/null
+++ b/libsec/dsaverify.c
@@ -1,0 +1,46 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+int
+dsaverify(DSApub *pub, DSAsig *sig, mpint *m)
+{
+	int rv = -1;
+	mpint *u1, *u2, *v, *sinv;
+
+	if(sig->r->sign < 0 || mpcmp(sig->r, pub->q) >= 0)
+		return rv;
+	if(sig->s->sign < 0 || mpcmp(sig->s, pub->q) >= 0)
+		return rv;
+	u1 = mpnew(0);
+	u2 = mpnew(0);
+	v = mpnew(0);
+	sinv = mpnew(0);
+
+	// find (s**-1) mod q, make sure it exists
+	mpextendedgcd(sig->s, pub->q, u1, sinv, v);
+	if(mpcmp(u1, mpone) != 0)
+		goto out;
+
+	// u1 = (sinv * m) mod q, u2 = (r * sinv) mod q
+	mpmul(sinv, m, u1);
+	mpmod(u1, pub->q, u1);
+	mpmul(sig->r, sinv, u2);
+	mpmod(u2, pub->q, u2);
+
+	// v = (((alpha**u1)*(key**u2)) mod p) mod q
+	mpexp(pub->alpha, u1, pub->p, sinv);
+	mpexp(pub->key, u2, pub->p, v);
+	mpmul(sinv, v, v);
+	mpmod(v, pub->p, v);
+	mpmod(v, pub->q, v);
+
+	if(mpcmp(v, sig->r) == 0)
+		rv = 0;
+out:
+	mpfree(v);
+	mpfree(u1);
+	mpfree(u2);
+	mpfree(sinv);
+	return rv;
+}
--- /dev/null
+++ b/libsec/egalloc.c
@@ -1,0 +1,67 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+EGpub*
+egpuballoc(void)
+{
+	EGpub *eg;
+
+	eg = mallocz(sizeof(*eg), 1);
+	if(eg == nil)
+		sysfatal("egpuballoc");
+	return eg;
+}
+
+void
+egpubfree(EGpub *eg)
+{
+	if(eg == nil)
+		return;
+	mpfree(eg->p);
+	mpfree(eg->alpha);
+	mpfree(eg->key);
+}
+
+
+EGpriv*
+egprivalloc(void)
+{
+	EGpriv *eg;
+
+	eg = mallocz(sizeof(*eg), 1);
+	if(eg == nil)
+		sysfatal("egprivalloc");
+	return eg;
+}
+
+void
+egprivfree(EGpriv *eg)
+{
+	if(eg == nil)
+		return;
+	mpfree(eg->pub.p);
+	mpfree(eg->pub.alpha);
+	mpfree(eg->pub.key);
+	mpfree(eg->secret);
+}
+
+EGsig*
+egsigalloc(void)
+{
+	EGsig *eg;
+
+	eg = mallocz(sizeof(*eg), 1);
+	if(eg == nil)
+		sysfatal("egsigalloc");
+	return eg;
+}
+
+void
+egsigfree(EGsig *eg)
+{
+	if(eg == nil)
+		return;
+	mpfree(eg->r);
+	mpfree(eg->s);
+}
--- /dev/null
+++ b/libsec/egdecrypt.c
@@ -1,0 +1,28 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+mpint*
+egdecrypt(EGpriv *priv, mpint *in, mpint *out)
+{
+	EGpub *pub = &priv->pub;
+	mpint *gamma, *delta;
+	mpint *p = pub->p;
+	int plen = mpsignif(p)+1;
+	int shift = ((plen+Dbits-1)/Dbits)*Dbits;
+
+	if(out == nil)
+		out = mpnew(0);
+	gamma = mpnew(0);
+	delta = mpnew(0);
+	mpright(in, shift, gamma);
+	mpleft(gamma, shift, delta);
+	mpsub(in, delta, delta);	
+	mpexp(gamma, priv->secret, p, out);
+	mpinvert(out, p, gamma);
+	mpmul(gamma, delta, out);
+	mpmod(out, p, out);
+	mpfree(gamma);
+	mpfree(delta);
+	return out;
+}
--- /dev/null
+++ b/libsec/egencrypt.c
@@ -1,0 +1,38 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+mpint*
+egencrypt(EGpub *pub, mpint *in, mpint *out)
+{
+	mpint *m, *k, *gamma, *delta, *pm1;
+	mpint *p = pub->p, *alpha = pub->alpha;
+	int plen = mpsignif(p);
+	int shift = ((plen+Dbits)/Dbits)*Dbits;
+	// in libcrypt version, (int)(LENGTH(pub->p)*sizeof(NumType)*CHARBITS);
+
+	if(out == nil)
+		out = mpnew(0);
+	pm1 = mpnew(0);
+	m = mpnew(0);
+	gamma = mpnew(0);
+	delta = mpnew(0);
+	mpmod(in, p, m);
+	while(1){
+		k = mprand(plen, genrandom, nil);
+		if((mpcmp(mpone, k) <= 0) && (mpcmp(k, pm1) < 0))
+			break;
+	}
+	mpexp(alpha, k, p, gamma);
+	mpexp(pub->key, k, p, delta);
+	mpmul(m, delta, delta);
+	mpmod(delta, p, delta);
+	mpleft(gamma, shift, out);
+	mpadd(delta, out, out);
+	mpfree(pm1);
+	mpfree(m);
+	mpfree(k);
+	mpfree(gamma);
+	mpfree(delta);
+	return out;
+}
--- /dev/null
+++ b/libsec/eggen.c
@@ -1,0 +1,21 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+EGpriv*
+eggen(int nlen, int rounds)
+{
+	EGpub *pub;
+	EGpriv *priv;
+
+	priv = egprivalloc();
+	pub = &priv->pub;
+	pub->p = mpnew(0);
+	pub->alpha = mpnew(0);
+	pub->key = mpnew(0);
+	priv->secret = mpnew(0);
+	gensafeprime(pub->p, pub->alpha, nlen, rounds);
+	mprand(nlen-1, genrandom, priv->secret);
+	mpexp(pub->alpha, priv->secret, pub->p, pub->key);
+	return priv;
+}
--- /dev/null
+++ b/libsec/egprivtopub.c
@@ -1,0 +1,17 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+EGpub*
+egprivtopub(EGpriv *priv)
+{
+	EGpub *pub;
+
+	pub = egpuballoc();
+	if(pub == nil)
+		return nil;
+	pub->p = mpcopy(priv->pub.p);
+	pub->alpha = mpcopy(priv->pub.alpha);
+	pub->key = mpcopy(priv->pub.key);
+	return pub;
+}
--- /dev/null
+++ b/libsec/egsign.c
@@ -1,0 +1,43 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+EGsig*
+egsign(EGpriv *priv, mpint *m)
+{
+	EGpub *pub = &priv->pub;
+	EGsig *sig;
+	mpint *pm1, *k, *kinv, *r, *s;
+	mpint *p = pub->p, *alpha = pub->alpha;
+	int plen = mpsignif(p);
+
+	pm1 = mpnew(0);
+	kinv = mpnew(0);
+	r = mpnew(0);
+	s = mpnew(0);
+	k = mpnew(0);
+	mpsub(p, mpone, pm1);
+	while(1){
+		mprand(plen, genrandom, k);
+		if((mpcmp(mpone, k) > 0) || (mpcmp(k, pm1) >= 0))
+			continue;
+		mpextendedgcd(k, pm1, r, kinv, s);
+		if(mpcmp(r, mpone) != 0)
+			continue;
+		break;
+	}
+	mpmod(kinv, pm1, kinv);  // make kinv positive
+	mpexp(alpha, k, p, r);
+	mpmul(priv->secret, r, s);
+	mpmod(s, pm1, s);
+	mpsub(m, s, s);
+	mpmul(kinv, s, s);
+	mpmod(s, pm1, s);
+	sig = egsigalloc();
+	sig->r = r;
+	sig->s = s;
+	mpfree(pm1);
+	mpfree(k);
+	mpfree(kinv);
+	return sig;
+}
--- /dev/null
+++ b/libsec/egtest.c
@@ -1,0 +1,34 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+void
+main(void)
+{
+	EGpriv *sk;
+	mpint *m, *gamma, *delta, *in, *out;
+	int plen, shift;
+
+	fmtinstall('B', mpconv);
+
+	sk = egprivalloc();
+	sk->pub.p = uitomp(2357, nil);
+	sk->pub.alpha = uitomp(2, nil);
+	sk->pub.key = uitomp(1185, nil);
+	sk->secret = uitomp(1751, nil);
+
+	m = uitomp(2035, nil);
+
+	plen = mpsignif(sk->pub.p)+1;
+	shift = ((plen+Dbits-1)/Dbits)*Dbits;
+	gamma = uitomp(1430, nil);
+	delta = uitomp(697, nil);
+	out = mpnew(0);
+	in = mpnew(0);
+	mpleft(gamma, shift, in);
+	mpadd(delta, in, in);
+	egdecrypt(sk, in, out);
+
+	if(mpcmp(m, out) != 0)
+		print("decrypt failed to recover message\n");
+}
--- /dev/null
+++ b/libsec/egverify.c
@@ -1,0 +1,29 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+int
+egverify(EGpub *pub, EGsig *sig, mpint *m)
+{
+	mpint *p = pub->p, *alpha = pub->alpha;
+	mpint *r = sig->r, *s = sig->s;
+	mpint *v1, *v2, *rs;
+	int rv = -1;
+
+	if(mpcmp(r, mpone) < 0 || mpcmp(r, p) >= 0)
+		return rv;
+	v1 = mpnew(0);
+	rs = mpnew(0);
+	v2 = mpnew(0);
+	mpexp(pub->key, r, p, v1);
+	mpexp(r, s, p, rs);
+	mpmul(v1, rs, v1);
+	mpmod(v1, p, v1);
+	mpexp(alpha, m, p, v2);
+	if(mpcmp(v1, v2) == 0)
+		rv = 0;
+	mpfree(v1);
+	mpfree(rs);
+	mpfree(v2);
+	return rv;
+}
--- /dev/null
+++ b/libsec/fastrand.c
@@ -1,0 +1,16 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<libsec.h>
+
+/* 
+ *  use the X917 random number generator to create random
+ *  numbers (faster than truerand() but not as random).
+ */
+ulong
+fastrand(void)
+{
+	ulong x;
+	
+	genrandom((uchar*)&x, sizeof x);
+	return x;
+}
--- /dev/null
+++ b/libsec/genprime.c
@@ -1,0 +1,27 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+//  generate a probable prime.  accuracy is the miller-rabin interations
+void
+genprime(mpint *p, int n, int accuracy)
+{
+	mpdigit x;
+
+	// generate n random bits with high and low bits set
+	mpbits(p, n);
+	genrandom((uchar*)p->p, (n+7)/8);
+	p->top = (n+Dbits-1)/Dbits;
+	x = 1;
+	x <<= ((n-1)%Dbits);
+	p->p[p->top-1] &= (x-1);
+	p->p[p->top-1] |= x;
+	p->p[0] |= 1;
+
+	// keep icrementing till it looks prime
+	for(;;){
+		if(probably_prime(p, accuracy))
+			break;
+		mpadd(p, mptwo, p);
+	}
+}
--- /dev/null
+++ b/libsec/genrandom.c
@@ -1,0 +1,62 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+typedef struct State{
+	QLock		lock;
+	int		seeded;
+	uvlong		seed;
+	DES3state	des3;
+} State;
+static State x917state;
+
+static void
+X917(uchar *rand, int nrand)
+{
+	int i, m, n8;
+	uvlong I, x;
+
+	/* 1. Compute intermediate value I = Ek(time). */
+	I = nsec();
+	triple_block_cipher(x917state.des3.expanded, (uchar*)&I, 0); /* two-key EDE */
+
+	/* 2. x[i] = Ek(I^seed);  seed = Ek(x[i]^I); */
+	m = (nrand+7)/8;
+	for(i=0; i<m; i++){
+		x = I ^ x917state.seed;
+		triple_block_cipher(x917state.des3.expanded, (uchar*)&x, 0);
+		n8 = (nrand>8) ? 8 : nrand;
+		memcpy(rand, (uchar*)&x, n8);
+		rand += 8;
+		nrand -= 8;
+		x ^= I;
+		triple_block_cipher(x917state.des3.expanded, (uchar*)&x, 0);
+		x917state.seed = x;
+	}
+}
+
+static void
+X917init(void)
+{
+	int n;
+	uchar mix[128];
+	uchar key3[3][8];
+	ulong *ulp;
+
+	ulp = (ulong*)key3;
+	for(n = 0; n < sizeof(key3)/sizeof(ulong); n++)
+		ulp[n] = truerand();
+	setupDES3state(&x917state.des3, key3, nil);
+	X917(mix, sizeof mix);
+	x917state.seeded = 1;
+}
+
+void
+genrandom(uchar *p, int n)
+{
+	qlock(&x917state.lock);
+	if(x917state.seeded == 0)
+		X917init();
+	X917(p, n);
+	qunlock(&x917state.lock);
+}
--- /dev/null
+++ b/libsec/gensafeprime.c
@@ -1,0 +1,36 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// find a prime p of length n and a generator alpha of Z^*_p
+// Alg 4.86 Menezes et al () Handbook, p.164
+void
+gensafeprime(mpint *p, mpint *alpha, int n, int accuracy)
+{
+	mpint *q, *b;
+
+	q = mpnew(n-1);
+	while(1){
+		genprime(q, n-1, accuracy);
+		mpleft(q, 1, p);
+		mpadd(p, mpone, p); // p = 2*q+1
+		if(probably_prime(p, accuracy))
+			break;
+	}
+	// now find a generator alpha of the multiplicative
+	// group Z*_p of order p-1=2q
+	b = mpnew(0);
+	while(1){
+		mprand(n, genrandom, alpha);
+		mpmod(alpha, p, alpha);
+		mpmul(alpha, alpha, b);
+		mpmod(b, p, b);
+		if(mpcmp(b, mpone) == 0)
+			continue;
+		mpexp(alpha, q, p, b);
+		if(mpcmp(b, mpone) != 0)
+			break;
+	}
+	mpfree(b);
+	mpfree(q);
+}
--- /dev/null
+++ b/libsec/genstrongprime.c
@@ -1,0 +1,57 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// Gordon's algorithm for generating a strong prime
+//	Menezes et al () Handbook, p.150
+void
+genstrongprime(mpint *p, int n, int accuracy)
+{
+	mpint *s, *t, *r, *i;
+
+	if(n < 64)
+		n = 64;
+
+	s = mpnew(n/2);
+	genprime(s, (n/2)-16, accuracy);
+	t = mpnew(n/2);
+	genprime(t, n-mpsignif(s)-32, accuracy);
+
+	// first r = 2it + 1 that's prime
+	i = mpnew(16);
+	r = mpnew(0);
+	itomp(0x8000, i);
+	mpleft(t, 1, t);	// 2t
+	mpmul(i, t, r);		// 2it
+	mpadd(r, mpone, r);	// 2it + 1
+	for(;;){
+		if(probably_prime(r, 18))
+			break;
+		mpadd(r, t, r);	// r += 2t
+	}
+
+	// p0 = 2(s**(r-2) mod r)s - 1
+	itomp(2, p);
+	mpsub(r, p, p);
+	mpexp(s, p, r, p);
+	mpmul(s, p, p);
+	mpleft(p, 1, p);
+	mpsub(p, mpone, p);
+
+	// first p = p0 + 2irs that's prime
+	itomp(0x8000, i);
+	mpleft(r, 1, r);	// 2r
+	mpmul(r, s, r);		// 2rs
+	mpmul(r, i, i);		// 2irs
+	mpadd(p, i, p);		// p0 + 2irs
+	for(;;){
+		if(probably_prime(p, accuracy))
+			break;
+		mpadd(p, r, p); // p += 2rs
+	}
+
+	mpfree(i);
+	mpfree(s);
+	mpfree(r);
+	mpfree(t);
+}
--- /dev/null
+++ b/libsec/hmac.c
@@ -1,0 +1,56 @@
+#include "os.h"
+#include <libsec.h>
+
+/* rfc2104 */
+static DigestState*
+hmac_x(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s,
+	DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen)
+{
+	int i;
+	uchar pad[65], innerdigest[256];
+
+	if(xlen > sizeof(innerdigest))
+		return nil;
+
+	if(klen>64)
+		return nil;
+
+	/* first time through */
+	if(s == nil){
+		for(i=0; i<64; i++)
+			pad[i] = 0x36;
+		pad[64] = 0;
+		for(i=0; i<klen; i++)
+			pad[i] ^= key[i];
+		s = (*x)(pad, 64, nil, nil);
+		if(s == nil)
+			return nil;
+	}
+
+	s = (*x)(p, len, nil, s);
+	if(digest == nil)
+		return s;
+
+	/* last time through */
+	for(i=0; i<64; i++)
+		pad[i] = 0x5c;
+	pad[64] = 0;
+	for(i=0; i<klen; i++)
+		pad[i] ^= key[i];
+	(*x)(nil, 0, innerdigest, s);
+	s = (*x)(pad, 64, nil, nil);
+	(*x)(innerdigest, xlen, digest, s);
+	return nil;
+}
+
+DigestState*
+hmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+	return hmac_x(p, len, key, klen, digest, s, sha1, SHA1dlen);
+}
+
+DigestState*
+hmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+	return hmac_x(p, len, key, klen, digest, s, md5, MD5dlen);
+}
--- /dev/null
+++ b/libsec/hmactest.c
@@ -1,0 +1,19 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+uchar key[] = "Jefe";
+uchar data[] = "what do ya want for nothing?";
+
+void
+main(void)
+{
+	int i;
+	uchar hash[MD5dlen];
+
+	hmac_md5(data, strlen((char*)data), key, 4, hash, nil);
+	for(i=0; i<MD5dlen; i++)
+		print("%2.2x", hash[i]);
+	print("\n");
+	print("750c783e6ab0b503eaa86e310a5db738\n");
+}
--- /dev/null
+++ b/libsec/md4.c
@@ -1,0 +1,271 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+ *  This MD4 is implemented from the description in Stinson's Cryptography,
+ *  theory and practice. -- presotto
+ */
+
+/*
+ *	Rotate ammounts used in the algorithm
+ */
+enum
+{
+	S11=	3,
+	S12=	7,
+	S13=	11,
+	S14=	19,
+
+	S21=	3,
+	S22=	5,
+	S23=	9,
+	S24=	13,
+
+	S31=	3,
+	S32=	9,
+	S33=	11,
+	S34=	15,
+};
+
+typedef struct MD4Table MD4Table;
+struct MD4Table
+{
+	uchar	x;	/* index into data block */
+	uchar	rot;	/* amount to rotate left by */
+};
+
+static MD4Table tab[] =
+{
+	/* round 1 */
+/*[0]*/	{ 0,	S11},	
+	{ 1,	S12},	
+	{ 2,	S13},	
+	{ 3,	S14},	
+	{ 4,	S11},	
+	{ 5,	S12},	
+	{ 6,	S13},	
+	{ 7,	S14},	
+	{ 8,	S11},	
+	{ 9,	S12},	
+	{ 10,	S13},	
+	{ 11,	S14},	
+	{ 12,	S11},	
+	{ 13,	S12},	
+	{ 14,	S13},	
+	{ 15,	S14},
+
+	/* round 2 */
+/*[16]*/{ 0,	S21},	
+	{ 4,	S22},	
+	{ 8,	S23},	
+	{ 12,	S24},	
+	{ 1,	S21},	
+	{ 5,	S22},	
+	{ 9,	S23},	
+	{ 13,	S24},	
+	{ 2,	S21},	
+	{ 6,	S22},	
+	{ 10,	S23},	
+	{ 14,	S24},	
+	{ 3,	S21},	
+	{ 7,	S22},	
+	{ 11,	S23},	
+	{ 15,	S24},
+
+	/* round 3 */
+/*[32]*/{ 0,	S31},	
+	{ 8,	S32},	
+	{ 4,	S33},	
+	{ 12,	S34},	
+	{ 2,	S31},	
+	{ 10,	S32},	
+	{ 6,	S33},	
+	{ 14,	S34},	
+	{ 1,	S31},	
+	{ 9,	S32},	
+	{ 5,	S33},	
+	{ 13,	S34},	
+	{ 3,	S31},	
+	{ 11,	S32},	
+	{ 7,	S33},	
+	{ 15,	S34},	
+};
+
+static void encode(uchar*, u32int*, ulong);
+static void decode(u32int*, uchar*, ulong);
+
+static void
+md4block(uchar *p, ulong len, MD4state *s)
+{
+	int i;
+	u32int a, b, c, d, tmp;
+	MD4Table *t;
+	uchar *end;
+	u32int x[16];
+
+	for(end = p+len; p < end; p += 64){
+		a = s->state[0];
+		b = s->state[1];
+		c = s->state[2];
+		d = s->state[3];
+
+		decode(x, p, 64);
+	
+		for(i = 0; i < 48; i++){
+			t = tab + i;
+			switch(i>>4){
+			case 0:
+				a += (b & c) | (~b & d);
+				break;
+			case 1:
+				a += ((b & c) | (b & d) | (c & d)) + 0x5A827999;
+				break;
+			case 2:
+				a += (b ^ c ^ d) + 0x6ED9EBA1;
+				break;
+			}
+			a += x[t->x];
+			a = (a << t->rot) | (a >> (32 - t->rot));
+	
+			/* rotate variables */
+			tmp = d;
+			d = c;
+			c = b;
+			b = a;
+			a = tmp;
+		}
+
+		s->state[0] += a;
+		s->state[1] += b;
+		s->state[2] += c;
+		s->state[3] += d;
+
+		s->len += 64;
+	}
+}
+
+MD4state*
+md4(uchar *p, ulong len, uchar *digest, MD4state *s)
+{
+	u32int x[16];
+	uchar buf[128];
+	int i;
+	uchar *e;
+
+	if(s == nil){
+		s = malloc(sizeof(*s));
+		if(s == nil)
+			return nil;
+		memset(s, 0, sizeof(*s));
+		s->malloced = 1;
+	}
+
+	if(s->seeded == 0){
+		/* seed the state, these constants would look nicer big-endian */
+		s->state[0] = 0x67452301;
+		s->state[1] = 0xefcdab89;
+		s->state[2] = 0x98badcfe;
+		s->state[3] = 0x10325476;
+		s->seeded = 1;
+	}
+
+	/* fill out the partial 64 byte block from previous calls */
+	if(s->blen){
+		i = 64 - s->blen;
+		if(len < i)
+			i = len;
+		memmove(s->buf + s->blen, p, i);
+		len -= i;
+		s->blen += i;
+		p += i;
+		if(s->blen == 64){
+			md4block(s->buf, s->blen, s);
+			s->blen = 0;
+		}
+	}
+
+	/* do 64 byte blocks */
+	i = len & ~0x3f;
+	if(i){
+		md4block(p, i, s);
+		len -= i;
+		p += i;
+	}
+
+	/* save the left overs if not last call */
+	if(digest == 0){
+		if(len){
+			memmove(s->buf, p, len);
+			s->blen += len;
+		}
+		return s;
+	}
+
+	/*
+	 *  this is the last time through, pad what's left with 0x80,
+	 *  0's, and the input count to create a multiple of 64 bytes
+	 */
+	if(s->blen){
+		p = s->buf;
+		len = s->blen;
+	} else {
+		memmove(buf, p, len);
+		p = buf;
+	}
+	s->len += len;
+	e = p + len;
+	if(len < 56)
+		i = 56 - len;
+	else
+		i = 120 - len;
+	memset(e, 0, i);
+	*e = 0x80;
+	len += i;
+
+	/* append the count */
+	x[0] = s->len<<3;
+	x[1] = s->len>>29;
+	encode(p+len, x, 8);
+
+	/* digest the last part */
+	md4block(p, len+8, s);
+
+	/* return result and free state */
+	encode(digest, s->state, MD4dlen);
+	if(s->malloced == 1)
+		free(s);
+	return nil;
+}
+
+/*
+ *	encodes input (u32int) into output (uchar). Assumes len is
+ *	a multiple of 4.
+ */
+static void
+encode(uchar *output, u32int *input, ulong len)
+{
+	u32int x;
+	uchar *e;
+
+	for(e = output + len; output < e;) {
+		x = *input++;
+		*output++ = x;
+		*output++ = x >> 8;
+		*output++ = x >> 16;
+		*output++ = x >> 24;
+	}
+}
+
+/*
+ *	decodes input (uchar) into output (u32int). Assumes len is
+ *	a multiple of 4.
+ */
+static void
+decode(u32int *output, uchar *input, ulong len)
+{
+	uchar *e;
+
+	for(e = input+len; input < e; input += 4)
+		*output++ = input[0] | (input[1] << 8) |
+			(input[2] << 16) | (input[3] << 24);
+}
--- /dev/null
+++ b/libsec/md4test.c
@@ -1,0 +1,31 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+char *tests[] = {
+	"",
+	"a",
+	"abc",
+	"message digest",
+	"abcdefghijklmnopqrstuvwxyz",
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+	"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+	0
+};
+
+void
+main(void)
+{
+	char **pp;
+	uchar *p;
+	int i;
+	uchar digest[MD5dlen];
+
+	for(pp = tests; *pp; pp++){
+		p = (uchar*)*pp;
+		md4(p, strlen(*pp), digest, 0);
+		for(i = 0; i < MD5dlen; i++)
+			print("%2.2ux", digest[i]);
+		print("\n");
+	}
+}
--- /dev/null
+++ b/libsec/md5.c
@@ -1,0 +1,148 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+ *  rfc1321 requires that I include this.  The code is new.  The constants
+ *  all come from the rfc (hence the copyright).  We trade a table for the
+ *  macros in rfc.  The total size is a lot less. -- presotto
+ *
+ *	Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ *	rights reserved.
+ *
+ *	License to copy and use this software is granted provided that it
+ *	is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ *	Algorithm" in all material mentioning or referencing this software
+ *	or this function.
+ *
+ *	License is also granted to make and use derivative works provided
+ *	that such works are identified as "derived from the RSA Data
+ *	Security, Inc. MD5 Message-Digest Algorithm" in all material
+ *	mentioning or referencing the derived work.
+ *
+ *	RSA Data Security, Inc. makes no representations concerning either
+ *	the merchantability of this software or the suitability of this
+ *	software forany particular purpose. It is provided "as is"
+ *	without express or implied warranty of any kind.
+ *	These notices must be retained in any copies of any part of this
+ *	documentation and/or software.
+ */
+
+static void encode(uchar*, u32int*, ulong);
+static void decode(u32int*, uchar*, ulong);
+
+extern void _md5block(uchar*, ulong, u32int*);
+
+MD5state*
+md5(uchar *p, ulong len, uchar *digest, MD5state *s)
+{
+	u32int x[16];
+	uchar buf[128];
+	int i;
+	uchar *e;
+
+	if(s == nil){
+		s = malloc(sizeof(*s));
+		if(s == nil)
+			return nil;
+		memset(s, 0, sizeof(*s));
+		s->malloced = 1;
+	}
+
+	if(s->seeded == 0){
+		/* seed the state, these constants would look nicer big-endian */
+		s->state[0] = 0x67452301;
+		s->state[1] = 0xefcdab89;
+		s->state[2] = 0x98badcfe;
+		s->state[3] = 0x10325476;
+		s->seeded = 1;
+	}
+
+	/* fill out the partial 64 byte block from previous calls */
+	if(s->blen){
+		i = 64 - s->blen;
+		if(len < i)
+			i = len;
+		memmove(s->buf + s->blen, p, i);
+		len -= i;
+		s->blen += i;
+		p += i;
+		if(s->blen == 64){
+			_md5block(s->buf, s->blen, s->state);
+			s->len += s->blen;
+			s->blen = 0;
+		}
+	}
+
+	/* do 64 byte blocks */
+	i = len & ~0x3f;
+	if(i){
+		_md5block(p, i, s->state);
+		s->len += i;
+		len -= i;
+		p += i;
+	}
+
+	/* save the left overs if not last call */
+	if(digest == 0){
+		if(len){
+			memmove(s->buf, p, len);
+			s->blen += len;
+		}
+		return s;
+	}
+
+	/*
+	 *  this is the last time through, pad what's left with 0x80,
+	 *  0's, and the input count to create a multiple of 64 bytes
+	 */
+	if(s->blen){
+		p = s->buf;
+		len = s->blen;
+	} else {
+		memmove(buf, p, len);
+		p = buf;
+	}
+	s->len += len;
+	e = p + len;
+	if(len < 56)
+		i = 56 - len;
+	else
+		i = 120 - len;
+	memset(e, 0, i);
+	*e = 0x80;
+	len += i;
+
+	/* append the count */
+	x[0] = s->len<<3;
+	x[1] = s->len>>29;
+	encode(p+len, x, 8);
+
+	/* digest the last part */
+	_md5block(p, len+8, s->state);
+	s->len += len;
+
+	/* return result and free state */
+	encode(digest, s->state, MD5dlen);
+	if(s->malloced == 1)
+		free(s);
+	return nil;
+}
+
+/*
+ *	encodes input (u32int) into output (uchar). Assumes len is
+ *	a multiple of 4.
+ */
+static void
+encode(uchar *output, u32int *input, ulong len)
+{
+	u32int x;
+	uchar *e;
+
+	for(e = output + len; output < e;) {
+		x = *input++;
+		*output++ = x;
+		*output++ = x >> 8;
+		*output++ = x >> 16;
+		*output++ = x >> 24;
+	}
+}
--- /dev/null
+++ b/libsec/md5block.c
@@ -1,0 +1,267 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+ *  rfc1321 requires that I include this.  The code is new.  The constants
+ *  all come from the rfc (hence the copyright).  We trade a table for the
+ *  macros in rfc.  The total size is a lot less. -- presotto
+ *
+ *	Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ *	rights reserved.
+ *
+ *	License to copy and use this software is granted provided that it
+ *	is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ *	Algorithm" in all material mentioning or referencing this software
+ *	or this function.
+ *
+ *	License is also granted to make and use derivative works provided
+ *	that such works are identified as "derived from the RSA Data
+ *	Security, Inc. MD5 Message-Digest Algorithm" in all material
+ *	mentioning or referencing the derived work.
+ *
+ *	RSA Data Security, Inc. makes no representations concerning either
+ *	the merchantability of this software or the suitability of this
+ *	software forany particular purpose. It is provided "as is"
+ *	without express or implied warranty of any kind.
+ *	These notices must be retained in any copies of any part of this
+ *	documentation and/or software.
+ */
+
+/*
+ *	Rotate ammounts used in the algorithm
+ */
+enum
+{
+	S11=	7,
+	S12=	12,
+	S13=	17,
+	S14=	22,
+
+	S21=	5,
+	S22=	9,
+	S23=	14,
+	S24=	20,
+
+	S31=	4,
+	S32=	11,
+	S33=	16,
+	S34=	23,
+
+	S41=	6,
+	S42=	10,
+	S43=	15,
+	S44=	21,
+};
+
+static u32int md5tab[] =
+{
+	/* round 1 */
+/*[0]*/	0xd76aa478,	
+	0xe8c7b756,	
+	0x242070db,	
+	0xc1bdceee,	
+	0xf57c0faf,	
+	0x4787c62a,	
+	0xa8304613,	
+	0xfd469501,	
+	0x698098d8,	
+	0x8b44f7af,	
+	0xffff5bb1,	
+	0x895cd7be,	
+	0x6b901122,	
+	0xfd987193,	
+	0xa679438e,	
+	0x49b40821,
+
+	/* round 2 */
+/*[16]*/0xf61e2562,	
+	0xc040b340,	
+	0x265e5a51,	
+	0xe9b6c7aa,	
+	0xd62f105d,	
+	 0x2441453,	
+	0xd8a1e681,	
+	0xe7d3fbc8,	
+	0x21e1cde6,	
+	0xc33707d6,	
+	0xf4d50d87,	
+	0x455a14ed,	
+	0xa9e3e905,	
+	0xfcefa3f8,	
+	0x676f02d9,	
+	0x8d2a4c8a,
+
+	/* round 3 */
+/*[32]*/0xfffa3942,	
+	0x8771f681,	
+	0x6d9d6122,	
+	0xfde5380c,	
+	0xa4beea44,	
+	0x4bdecfa9,	
+	0xf6bb4b60,	
+	0xbebfbc70,	
+	0x289b7ec6,	
+	0xeaa127fa,	
+	0xd4ef3085,	
+	 0x4881d05,	
+	0xd9d4d039,	
+	0xe6db99e5,	
+	0x1fa27cf8,	
+	0xc4ac5665,	
+
+	/* round 4 */
+/*[48]*/0xf4292244,	
+	0x432aff97,	
+	0xab9423a7,	
+	0xfc93a039,	
+	0x655b59c3,	
+	0x8f0ccc92,	
+	0xffeff47d,	
+	0x85845dd1,	
+	0x6fa87e4f,	
+	0xfe2ce6e0,	
+	0xa3014314,	
+	0x4e0811a1,	
+	0xf7537e82,	
+	0xbd3af235,	
+	0x2ad7d2bb,	
+	0xeb86d391,	
+};
+
+static void decode(u32int*, uchar*, ulong);
+extern void _md5block(uchar *p, ulong len, u32int *s);
+
+void
+_md5block(uchar *p, ulong len, u32int *s)
+{
+	u32int a, b, c, d, sh;
+	u32int *t;
+	uchar *end;
+	u32int x[16];
+
+	for(end = p+len; p < end; p += 64){
+		a = s[0];
+		b = s[1];
+		c = s[2];
+		d = s[3];
+
+		decode(x, p, 64);
+	
+		t = md5tab;
+		sh = 0;
+		for(; sh != 16; t += 4){
+			a += ((c ^ d) & b) ^ d;
+			a += x[sh] + t[0];
+			a = (a << S11) | (a >> (32 - S11));
+			a += b;
+
+			d += ((b ^ c) & a) ^ c;
+			d += x[sh + 1] + t[1];
+			d = (d << S12) | (d >> (32 - S12));
+			d += a;
+
+			c += ((a ^ b) & d) ^ b;
+			c += x[sh + 2] + t[2];
+			c = (c << S13) | (c >> (32 - S13));
+			c += d;
+
+			b += ((d ^ a) & c) ^ a;
+			b += x[sh + 3] + t[3];
+			b = (b << S14) | (b >> (32 - S14));
+			b += c;
+
+			sh += 4;
+		}
+		sh = 1;
+		for(; sh != 1+20*4; t += 4){
+			a += ((b ^ c) & d) ^ c;
+			a += x[sh & 0xf] + t[0];
+			a = (a << S21) | (a >> (32 - S21));
+			a += b;
+
+			d += ((a ^ b) & c) ^ b;
+			d += x[(sh + 5) & 0xf] + t[1];
+			d = (d << S22) | (d >> (32 - S22));
+			d += a;
+
+			c += ((d ^ a) & b) ^ a;
+			c += x[(sh + 10) & 0xf] + t[2];
+			c = (c << S23) | (c >> (32 - S23));
+			c += d;
+
+			b += ((c ^ d) & a) ^ d;
+			b += x[(sh + 15) & 0xf] + t[3];
+			b = (b << S24) | (b >> (32 - S24));
+			b += c;
+
+			sh += 20;
+		}
+		sh = 5;
+		for(; sh != 5+12*4; t += 4){
+			a += b ^ c ^ d;
+			a += x[sh & 0xf] + t[0];
+			a = (a << S31) | (a >> (32 - S31));
+			a += b;
+
+			d += a ^ b ^ c;
+			d += x[(sh + 3) & 0xf] + t[1];
+			d = (d << S32) | (d >> (32 - S32));
+			d += a;
+
+			c += d ^ a ^ b;
+			c += x[(sh + 6) & 0xf] + t[2];
+			c = (c << S33) | (c >> (32 - S33));
+			c += d;
+
+			b += c ^ d ^ a;
+			b += x[(sh + 9) & 0xf] + t[3];
+			b = (b << S34) | (b >> (32 - S34));
+			b += c;
+
+			sh += 12;
+		}
+		sh = 0;
+		for(; sh != 28*4; t += 4){
+			a += c ^ (b | ~d);
+			a += x[sh & 0xf] + t[0];
+			a = (a << S41) | (a >> (32 - S41));
+			a += b;
+
+			d += b ^ (a | ~c);
+			d += x[(sh + 7) & 0xf] + t[1];
+			d = (d << S42) | (d >> (32 - S42));
+			d += a;
+
+			c += a ^ (d | ~b);
+			c += x[(sh + 14) & 0xf] + t[2];
+			c = (c << S43) | (c >> (32 - S43));
+			c += d;
+
+			b += d ^ (c | ~a);
+			b += x[(sh + 21) & 0xf] + t[3];
+			b = (b << S44) | (b >> (32 - S44));
+			b += c;
+
+			sh += 28;
+		}
+
+		s[0] += a;
+		s[1] += b;
+		s[2] += c;
+		s[3] += d;
+	}
+}
+
+/*
+ *	decodes input (uchar) into output (u32int). Assumes len is
+ *	a multiple of 4.
+ */
+static void
+decode(u32int *output, uchar *input, ulong len)
+{
+	uchar *e;
+
+	for(e = input+len; input < e; input += 4)
+		*output++ = input[0] | (input[1] << 8) |
+			(input[2] << 16) | (input[3] << 24);
+}
--- /dev/null
+++ b/libsec/md5pickle.c
@@ -1,0 +1,37 @@
+#include "os.h"
+#include <libsec.h>
+
+char*
+md5pickle(MD5state *s)
+{
+	char *p;
+	int m, n;
+
+	m = 4*9+4*((s->blen+3)/3);
+	p = malloc(m);
+	if(p == nil)
+		return p;
+	n = sprint(p, "%8.8ux %8.8ux %8.8ux %8.8ux ",
+		s->state[0], s->state[1], s->state[2],
+		s->state[3]);
+	enc64(p+n, m-n, s->buf, s->blen);
+	return p;
+}
+
+MD5state*
+md5unpickle(char *p)
+{
+	MD5state *s;
+
+	s = malloc(sizeof(*s));
+	if(s == nil)
+		return nil;
+	s->state[0] = strtoul(p, &p, 16);
+	s->state[1] = strtoul(p, &p, 16);
+	s->state[2] = strtoul(p, &p, 16);
+	s->state[3] = strtoul(p, &p, 16);
+	s->blen = dec64(s->buf, sizeof(s->buf), p, strlen(p));
+	s->malloced = 1;
+	s->seeded = 1;
+	return s;
+}
--- /dev/null
+++ b/libsec/mkfile
@@ -1,0 +1,55 @@
+<$DSRC/mkfile-$CONF
+TARG=libsec.$L
+
+OFILES=\
+	aes.$O\
+	blowfish.$O\
+	decodepem.$O\
+	des.$O\
+	des3CBC.$O\
+	des3ECB.$O\
+	desCBC.$O\
+	desECB.$O\
+	desmodes.$O\
+	dsaalloc.$O\
+	dsagen.$O\
+	dsaprimes.$O\
+	dsaprivtopub.$O\
+	dsasign.$O\
+	dsaverify.$O\
+	egalloc.$O\
+	egdecrypt.$O\
+	egencrypt.$O\
+	eggen.$O\
+	egprivtopub.$O\
+	egsign.$O\
+	egverify.$O\
+	fastrand.$O\
+	genprime.$O\
+	genrandom.$O\
+	gensafeprime.$O\
+	genstrongprime.$O\
+	hmac.$O\
+	md4.$O\
+	md5.$O\
+	md5block.$O\
+	md5pickle.$O\
+	nfastrand.$O\
+	prng.$O\
+	probably_prime.$O\
+	rc4.$O\
+	rsaalloc.$O\
+	rsadecrypt.$O\
+	rsaencrypt.$O\
+	rsafill.$O\
+	rsagen.$O\
+	rsaprivtopub.$O\
+	sha1.$O\
+	sha1block.$O\
+	sha1pickle.$O\
+	smallprimes.$O
+
+HFILE=\
+	os.h
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/libsec/nfastrand.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+
+#define Maxrand	((1UL<<31)-1)
+
+ulong
+nfastrand(ulong n)
+{
+	ulong m, r;
+	
+	/*
+	 * set m to the maximum multiple of n <= 2^31-1
+	 * so we want a random number < m.
+	 */
+	if(n > Maxrand)
+		abort();
+
+	m = Maxrand - Maxrand % n;
+	while((r = fastrand()) >= m)
+		;
+	return r%n;
+}
--- /dev/null
+++ b/libsec/os.h
@@ -1,0 +1,2 @@
+#include <u.h>
+#include <libc.h>
--- /dev/null
+++ b/libsec/primetest.c
@@ -1,0 +1,41 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+void
+main(void)
+{
+	mpint *z = mpnew(0);
+	mpint *p = mpnew(0);
+	mpint *q = mpnew(0);
+	mpint *nine = mpnew(0);
+
+	fmtinstall('B', mpconv);
+	strtomp("2492491", nil, 16, z);	// 38347921 = x*y = (2**28-9)/7, 
+				//    an example of 3**(n-1)=1 mod n
+	strtomp("15662C00E811", nil, 16, p);// 23528569104401, a prime
+	uitomp(9, nine);
+
+	if(probably_prime(z, 5) == 1)
+		fprint(2, "tricked primality test\n");
+	if(probably_prime(nine, 5) == 1)
+		fprint(2, "9 passed primality test!\n");
+	if(probably_prime(p, 25) == 1)
+		fprint(2, "ok\n");
+
+	DSAprimes(q, p, nil);
+	print("q=%B\np=%B\n", q, p);
+
+	exits(0);
+}
+
+// example output, checked with Maple:
+// seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E
+// q=E0F0EF284E10796C5A2A511E94748BA03C795C13
+//  = 1284186945063585093695748280224501481698995297299
+// p=C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBBDB12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A863A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D93D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D
+//  = 137715385439333164327584575331308277462546592976152006175830654712456008630139443747529133857837818585400418619916530061955288983751958831927807888408309879880101870216437711393638413509484569804814373511469405934988856674935304074081350525593807908358867354528898618574659752879015380013845760006721861915693
+// r=DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241CEF2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E887D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D21C4656848614D888A4
+//  = 107239359478548771267308764204625458348785444483302647285245969203446101233421655396874997253111222983406676955642093641709149748793954493558324738441197139556917622937892491175016280660608595599724194374948056515856812347094848443460715881455884639869144172708
+// g=2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D2327173444ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD410E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734E3E2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1
+//  = 33081848392740465806285326014906437543653045153885419334085917570615301913274531387168723847139029827598735376746057461417880810924280288611116213062512408829164220104555543445909528701551198146080221790002337033997295756585193926863581671466708482411159477816144226847280417522524922667065714073338662508017
--- /dev/null
+++ b/libsec/prng.c
@@ -1,0 +1,15 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+//
+//  just use the libc prng to fill a buffer
+//
+void
+prng(uchar *p, int n)
+{
+	uchar *e;
+
+	for(e = p+n; p < e; p++)
+		*p = rand();
+}
--- /dev/null
+++ b/libsec/probably_prime.c
@@ -1,0 +1,84 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// Miller-Rabin probabilistic primality testing
+//	Knuth (1981) Seminumerical Algorithms, p.379
+//	Menezes et al () Handbook, p.39
+// 0 if composite; 1 if almost surely prime, Pr(err)<1/4**nrep
+int
+probably_prime(mpint *n, int nrep)
+{
+	int j, k, rep, nbits, isprime = 1;
+	mpint *nm1, *q, *x, *y, *r;
+
+	if(n->sign < 0)
+		sysfatal("negative prime candidate");
+
+	if(nrep <= 0)
+		nrep = 18;
+
+	k = mptoi(n);
+	if(k == 2)		// 2 is prime
+		return 1;
+	if(k < 2)		// 1 is not prime
+		return 0;
+	if((n->p[0] & 1) == 0)	// even is not prime
+		return 0;
+
+	// test against small prime numbers
+	if(smallprimetest(n) < 0)
+		return 0;
+
+	// fermat test, 2^n mod n == 2 if p is prime
+	x = uitomp(2, nil);
+	y = mpnew(0);
+	mpexp(x, n, n, y);
+	k = mptoi(y);
+	if(k != 2){
+		mpfree(x);
+		mpfree(y);
+		return 0;
+	}
+
+	nbits = mpsignif(n);
+	nm1 = mpnew(nbits);
+	mpsub(n, mpone, nm1);	// nm1 = n - 1 */
+	k = mplowbits0(nm1);
+	q = mpnew(0);
+	mpright(nm1, k, q);	// q = (n-1)/2**k
+
+	for(rep = 0; rep < nrep; rep++){
+		
+		// x = random in [2, n-2]
+		r = mprand(nbits, prng, nil);
+		mpmod(r, nm1, x);
+		mpfree(r);
+		if(mpcmp(x, mpone) <= 0)
+			continue;
+
+		// y = x**q mod n
+		mpexp(x, q, n, y);
+
+		if(mpcmp(y, mpone) == 0 || mpcmp(y, nm1) == 0)
+			goto done;
+
+		for(j = 1; j < k; j++){
+			mpmul(y, y, x);
+			mpmod(x, n, y);	// y = y*y mod n
+			if(mpcmp(y, nm1) == 0)
+				goto done;
+			if(mpcmp(y, mpone) == 0){
+				isprime = 0;
+				goto done;
+			}
+		}
+		isprime = 0;
+	}
+done:
+	mpfree(y);
+	mpfree(x);
+	mpfree(q);
+	mpfree(nm1);
+	return isprime;
+}
binary files /dev/null b/libsec/ranlib.core differ
--- /dev/null
+++ b/libsec/rc4.c
@@ -1,0 +1,104 @@
+#include "os.h"
+#include <libsec.h>
+
+void
+setupRC4state(RC4state *key, uchar *start, int n)
+{
+	int t;
+	int index2;
+	uchar *state;
+	uchar *p, *e, *sp, *se;
+
+	state = key->state;
+	se = &state[256];
+	for(sp = state; sp < se; sp++)
+		*sp = sp - state;
+
+	key->x = 0;
+	key->y = 0;
+	index2 = 0;
+	e = start + n;
+	p = start;
+	for(sp = state; sp < se; sp++)
+	{
+		t = *sp;
+		index2 = (*p + t + index2) & 255;
+		*sp = state[index2];
+		state[index2] = t;
+		if(++p >= e)
+			p = start;
+	}
+}
+
+void
+rc4(RC4state *key, uchar *p, int len)
+{
+	int tx, ty;
+	int x, y;
+	uchar *state;
+	uchar *e;
+
+	x = key->x;
+	y = key->y;
+	state = &key->state[0];
+	for(e = p + len; p < e; p++)
+	{
+		x = (x+1)&255;
+		tx = state[x];
+		y = (y+tx)&255;
+		ty = state[y];
+		state[x] = ty;
+		state[y] = tx;
+		*p ^= state[(tx+ty)&255];
+	}
+	key->x = x;
+	key->y = y;
+}
+
+void
+rc4skip(RC4state *key, int len)
+{
+	int tx, ty;
+	int x, y;
+	uchar *state;
+	int i;
+
+	x = key->x;
+	y = key->y;
+	state = &key->state[0];
+	for(i=0; i<len; i++)
+	{
+		x = (x+1)&255;
+		tx = state[x];
+		y = (y+tx)&255;
+		ty = state[y];
+		state[x] = ty;
+		state[y] = tx;
+	}
+	key->x = x;
+	key->y = y;
+}
+
+void
+rc4back(RC4state *key, int len)
+{
+	int tx, ty;
+	int x, y;
+	uchar *state;
+	int i;
+
+	x = key->x;
+	y = key->y;
+	state = &key->state[0];
+	for(i=0; i<len; i++)
+	{
+		ty = state[x];
+		tx = state[y];
+		state[y] = ty;
+		state[x] = tx;
+		y = (y-tx)&255;
+		x = (x-1)&255;
+	}
+	key->x = x;
+	key->y = y;
+}
--- /dev/null
+++ b/libsec/readcert.c
@@ -1,0 +1,50 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+
+static char*
+readfile(char *name)
+{
+	int fd;
+	char *s;
+	Dir *d;
+
+	fd = open(name, OREAD);
+	if(fd < 0)
+		return nil;
+	if((d = dirfstat(fd)) == nil)
+		return nil;
+	s = malloc(d->length + 1);
+	if(s == nil || readn(fd, s, d->length) != d->length){
+		free(s);
+		free(d);
+		close(fd);
+		return nil;
+	}
+	close(fd);
+	s[d->length] = '\0';
+	free(d);
+	return s;
+}
+
+uchar*
+readcert(char *filename, int *pcertlen)
+{
+	char *pem;
+	uchar *binary;
+
+	pem = readfile(filename);
+	if(pem == nil){
+		werrstr("can't read %s", filename);
+		return nil;
+	}
+	binary = decodepem(pem, "CERTIFICATE", pcertlen);
+	free(pem);
+	if(binary == nil){
+		werrstr("can't parse %s", filename);
+		return nil;
+	}
+	return binary;
+}
+
--- /dev/null
+++ b/libsec/rsaalloc.c
@@ -1,0 +1,52 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+RSApub*
+rsapuballoc(void)
+{
+	RSApub *rsa;
+
+	rsa = mallocz(sizeof(*rsa), 1);
+	if(rsa == nil)
+		sysfatal("rsapuballoc");
+	return rsa;
+}
+
+void
+rsapubfree(RSApub *rsa)
+{
+	if(rsa == nil)
+		return;
+	mpfree(rsa->ek);
+	mpfree(rsa->n);
+	free(rsa);
+}
+
+
+RSApriv*
+rsaprivalloc(void)
+{
+	RSApriv *rsa;
+
+	rsa = mallocz(sizeof(*rsa), 1);
+	if(rsa == nil)
+		sysfatal("rsaprivalloc");
+	return rsa;
+}
+
+void
+rsaprivfree(RSApriv *rsa)
+{
+	if(rsa == nil)
+		return;
+	mpfree(rsa->pub.ek);
+	mpfree(rsa->pub.n);
+	mpfree(rsa->dk);
+	mpfree(rsa->p);
+	mpfree(rsa->q);
+	mpfree(rsa->kp);
+	mpfree(rsa->kq);
+	mpfree(rsa->c2);
+	free(rsa);
+}
--- /dev/null
+++ b/libsec/rsadecrypt.c
@@ -1,0 +1,37 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+// decrypt rsa using garner's algorithm for the chinese remainder theorem
+//	seminumerical algorithms, knuth, pp 253-254
+//	applied cryptography, menezes et al, pg 612
+mpint*
+rsadecrypt(RSApriv *rsa, mpint *in, mpint *out)
+{
+	mpint *v1, *v2;
+
+	if(out == nil)
+		out = mpnew(0);
+
+	// convert in to modular representation
+	v1 = mpnew(0);
+	mpmod(in, rsa->p, v1);
+	v2 = mpnew(0);
+	mpmod(in, rsa->q, v2);
+
+	// exponentiate the modular rep
+	mpexp(v1, rsa->kp, rsa->p, v1);
+	mpexp(v2, rsa->kq, rsa->q, v2);
+	
+	// out = v1 + p*((v2-v1)*c2 mod q)
+	mpsub(v2, v1, v2);
+	mpmul(v2, rsa->c2, v2);
+	mpmod(v2, rsa->q, v2);
+	mpmul(v2, rsa->p, out);
+	mpadd(v1, out, out);
+
+	mpfree(v1);
+	mpfree(v2);
+
+	return out;
+}
--- /dev/null
+++ b/libsec/rsaencrypt.c
@@ -1,0 +1,12 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+mpint*
+rsaencrypt(RSApub *rsa, mpint *in, mpint *out)
+{
+	if(out == nil)
+		out = mpnew(0);
+	mpexp(in, rsa->ek, rsa->n, out);
+	return out;
+}
--- /dev/null
+++ b/libsec/rsafill.c
@@ -1,0 +1,61 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+RSApriv*
+rsafill(mpint *n, mpint *e, mpint *d, mpint *p, mpint *q)
+{
+	mpint *c2, *kq, *kp, *x;
+	RSApriv *rsa;
+
+	// make sure we're not being hoodwinked
+	if(!probably_prime(p, 10) || !probably_prime(q, 10)){
+		werrstr("rsafill: p or q not prime");
+		return nil;
+	}
+	x = mpnew(0);
+	mpmul(p, q, x);
+	if(mpcmp(n, x) != 0){
+		werrstr("rsafill: n != p*q");
+		mpfree(x);
+		return nil;
+	}
+	c2 = mpnew(0);
+	mpsub(p, mpone, c2);
+	mpsub(q, mpone, x);
+	mpmul(c2, x, x);
+	mpmul(e, d, c2);
+	mpmod(c2, x, x);
+	if(mpcmp(x, mpone) != 0){
+		werrstr("rsafill: e*d != 1 mod (p-1)*(q-1)");
+		mpfree(x);
+		mpfree(c2);
+		return nil;
+	}
+
+	// compute chinese remainder coefficient
+	mpinvert(p, q, c2);
+
+	// for crt a**k mod p == (a**(k mod p-1)) mod p
+	kq = mpnew(0);
+	kp = mpnew(0);
+	mpsub(p, mpone, x);
+	mpmod(d, x, kp);
+	mpsub(q, mpone, x);
+	mpmod(d, x, kq);
+
+	rsa = rsaprivalloc();
+	rsa->pub.ek = mpcopy(e);
+	rsa->pub.n = mpcopy(n);
+	rsa->dk = mpcopy(d);
+	rsa->kp = kp;
+	rsa->kq = kq;
+	rsa->p = mpcopy(p);
+	rsa->q = mpcopy(q);
+	rsa->c2 = c2;
+
+	mpfree(x);
+
+	return rsa;
+}
+
--- /dev/null
+++ b/libsec/rsagen.c
@@ -1,0 +1,82 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+static void
+genrand(mpint *p, int n)
+{
+	mpdigit x;
+
+	// generate n random bits with high set
+	mpbits(p, n);
+	genrandom((uchar*)p->p, (n+7)/8);
+	p->top = (n+Dbits-1)/Dbits;
+	x = 1;
+	x <<= ((n-1)%Dbits);
+	p->p[p->top-1] &= (x-1);
+	p->p[p->top-1] |= x;
+}
+
+RSApriv*
+rsagen(int nlen, int elen, int rounds)
+{
+	mpint *p, *q, *e, *d, *phi, *n, *t1, *t2, *kp, *kq, *c2;
+	RSApriv *rsa;
+
+	p = mpnew(nlen/2);
+	q = mpnew(nlen/2);
+	n = mpnew(nlen);
+	e = mpnew(elen);
+	d = mpnew(0);
+	phi = mpnew(nlen);
+
+	// create the prime factors and euclid's function
+	genstrongprime(p, nlen/2, rounds);
+	genstrongprime(q, nlen - mpsignif(p) + 1, rounds);
+	mpmul(p, q, n);
+	mpsub(p, mpone, e);
+	mpsub(q, mpone, d);
+	mpmul(e, d, phi);
+
+	// find an e relatively prime to phi
+	t1 = mpnew(0);
+	t2 = mpnew(0);
+	genrand(e, elen);
+	for(;;){
+		mpextendedgcd(e, phi, d, t1, t2);
+		if(mpcmp(d, mpone) == 0)
+			break;
+		mpadd(mpone, e, e);
+	}
+	mpfree(t1);
+	mpfree(t2);
+
+	// d = e**-1 mod phi
+	mpinvert(e, phi, d);
+
+	// compute chinese remainder coefficient
+	c2 = mpnew(0);
+	mpinvert(p, q, c2);
+
+	// for crt a**k mod p == (a**(k mod p-1)) mod p
+	kq = mpnew(0);
+	kp = mpnew(0);
+	mpsub(p, mpone, phi);
+	mpmod(d, phi, kp);
+	mpsub(q, mpone, phi);
+	mpmod(d, phi, kq);
+
+	rsa = rsaprivalloc();
+	rsa->pub.ek = e;
+	rsa->pub.n = n;
+	rsa->dk = d;
+	rsa->kp = kp;
+	rsa->kq = kq;
+	rsa->p = p;
+	rsa->q = q;
+	rsa->c2 = c2;
+
+	mpfree(phi);
+
+	return rsa;
+}
--- /dev/null
+++ b/libsec/rsaprivtopub.c
@@ -1,0 +1,16 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+RSApub*
+rsaprivtopub(RSApriv *priv)
+{
+	RSApub *pub;
+
+	pub = rsapuballoc();
+	if(pub == nil)
+		return nil;
+	pub->n = mpcopy(priv->pub.n);
+	pub->ek = mpcopy(priv->pub.ek);
+	return pub;
+}
--- /dev/null
+++ b/libsec/rsatest.c
@@ -1,0 +1,57 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+#include <bio.h>
+
+void
+main(void)
+{
+	RSApriv *rsa;
+	Biobuf b;
+	char *p;
+	int n;
+	mpint *clr, *enc, *clr2;
+	uchar buf[4096];
+	uchar *e;
+	vlong start;
+
+	fmtinstall('B', mpconv);
+
+	rsa = rsagen(1024, 16, 0);
+	if(rsa == nil)
+		sysfatal("rsagen");
+	Binit(&b, 0, OREAD);
+	clr = mpnew(0);
+	clr2 = mpnew(0);
+	enc = mpnew(0);
+
+	strtomp("123456789abcdef123456789abcdef123456789abcdef123456789abcdef", nil, 16, clr);
+	rsaencrypt(&rsa->pub, clr, enc);
+	
+	start = nsec();
+	for(n = 0; n < 10; n++)
+		rsadecrypt(rsa, enc, clr);
+	print("%lld\n", nsec()-start);
+
+	start = nsec();
+	for(n = 0; n < 10; n++)
+		mpexp(enc, rsa->dk, rsa->pub.n, clr2);
+	print("%lld\n", nsec()-start);
+
+	if(mpcmp(clr, clr2) != 0)
+		print("%B != %B\n", clr, clr2);
+	
+	print("> ");
+	while(p = Brdline(&b, '\n')){
+		n = Blinelen(&b);
+		letomp((uchar*)p, n, clr);
+		print("clr %B\n", clr);
+		rsaencrypt(&rsa->pub, clr, enc);
+		print("enc %B\n", enc);
+		rsadecrypt(rsa, enc, clr);
+		print("clr %B\n", clr);
+		n = mptole(clr, buf, sizeof(buf), nil);
+		write(1, buf, n);
+		print("> ");
+	}
+}
--- /dev/null
+++ b/libsec/sha1.c
@@ -1,0 +1,127 @@
+#include "os.h"
+#include <libsec.h>
+
+static void encode(uchar*, u32int*, ulong);
+
+extern void _sha1block(uchar*, ulong, u32int*);
+
+/*
+ *  we require len to be a multiple of 64 for all but
+ *  the last call.  There must be room in the input buffer
+ *  to pad.
+ */
+SHA1state*
+sha1(uchar *p, ulong len, uchar *digest, SHA1state *s)
+{
+	uchar buf[128];
+	u32int x[16];
+	int i;
+	uchar *e;
+
+	if(s == nil){
+		s = malloc(sizeof(*s));
+		if(s == nil)
+			return nil;
+		memset(s, 0, sizeof(*s));
+		s->malloced = 1;
+	}
+
+	if(s->seeded == 0){
+		/* seed the state, these constants would look nicer big-endian */
+		s->state[0] = 0x67452301;
+		s->state[1] = 0xefcdab89;
+		s->state[2] = 0x98badcfe;
+		s->state[3] = 0x10325476;
+		s->state[4] = 0xc3d2e1f0;
+		s->seeded = 1;
+	}
+
+	/* fill out the partial 64 byte block from previous calls */
+	if(s->blen){
+		i = 64 - s->blen;
+		if(len < i)
+			i = len;
+		memmove(s->buf + s->blen, p, i);
+		len -= i;
+		s->blen += i;
+		p += i;
+		if(s->blen == 64){
+			_sha1block(s->buf, s->blen, s->state);
+			s->len += s->blen;
+			s->blen = 0;
+		}
+	}
+
+	/* do 64 byte blocks */
+	i = len & ~0x3f;
+	if(i){
+		_sha1block(p, i, s->state);
+		s->len += i;
+		len -= i;
+		p += i;
+	}
+
+	/* save the left overs if not last call */
+	if(digest == 0){
+		if(len){
+			memmove(s->buf, p, len);
+			s->blen += len;
+		}
+		return s;
+	}
+
+	/*
+	 *  this is the last time through, pad what's left with 0x80,
+	 *  0's, and the input count to create a multiple of 64 bytes
+	 */
+	if(s->blen){
+		p = s->buf;
+		len = s->blen;
+	} else {
+		memmove(buf, p, len);
+		p = buf;
+	}
+	s->len += len;
+	e = p + len;
+	if(len < 56)
+		i = 56 - len;
+	else
+		i = 120 - len;
+	memset(e, 0, i);
+	*e = 0x80;
+	len += i;
+
+	/* append the count */
+	x[0] = s->len>>29;
+	x[1] = s->len<<3;
+	encode(p+len, x, 8);
+
+	/* digest the last part */
+	_sha1block(p, len+8, s->state);
+	s->len += len+8;
+
+	/* return result and free state */
+	encode(digest, s->state, SHA1dlen);
+	if(s->malloced == 1)
+		free(s);
+	return nil;
+}
+
+/*
+ *	encodes input (ulong) into output (uchar). Assumes len is
+ *	a multiple of 4.
+ */
+static void
+encode(uchar *output, u32int *input, ulong len)
+{
+	u32int x;
+	uchar *e;
+
+	for(e = output + len; output < e;) {
+		x = *input++;
+		*output++ = x >> 24;
+		*output++ = x >> 16;
+		*output++ = x >> 8;
+		*output++ = x;
+	}
+}
--- /dev/null
+++ b/libsec/sha1block.c
@@ -1,0 +1,187 @@
+#include "os.h"
+
+void
+_sha1block(uchar *p, ulong len, u32int *s)
+{
+	u32int a, b, c, d, e, x;
+	uchar *end;
+	u32int *wp, *wend;
+	u32int w[80];
+
+	/* at this point, we have a multiple of 64 bytes */
+	for(end = p+len; p < end;){
+		a = s[0];
+		b = s[1];
+		c = s[2];
+		d = s[3];
+		e = s[4];
+
+		wend = w + 15;
+		for(wp = w; wp < wend; wp += 5){
+			wp[0] = (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
+			e += ((a<<5) | (a>>27)) + wp[0];
+			e += 0x5a827999 + (((c^d)&b)^d);
+			b = (b<<30)|(b>>2);
+
+			wp[1] = (p[4]<<24) | (p[5]<<16) | (p[6]<<8) | p[7];
+			d += ((e<<5) | (e>>27)) + wp[1];
+			d += 0x5a827999 + (((b^c)&a)^c);
+			a = (a<<30)|(a>>2);
+
+			wp[2] = (p[8]<<24) | (p[9]<<16) | (p[10]<<8) | p[11];
+			c += ((d<<5) | (d>>27)) + wp[2];
+			c += 0x5a827999 + (((a^b)&e)^b);
+			e = (e<<30)|(e>>2);
+
+			wp[3] = (p[12]<<24) | (p[13]<<16) | (p[14]<<8) | p[15];
+			b += ((c<<5) | (c>>27)) + wp[3];
+			b += 0x5a827999 + (((e^a)&d)^a);
+			d = (d<<30)|(d>>2);
+
+			wp[4] = (p[16]<<24) | (p[17]<<16) | (p[18]<<8) | p[19];
+			a += ((b<<5) | (b>>27)) + wp[4];
+			a += 0x5a827999 + (((d^e)&c)^e);
+			c = (c<<30)|(c>>2);
+			
+			p += 20;
+		}
+
+		wp[0] = (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
+		e += ((a<<5) | (a>>27)) + wp[0];
+		e += 0x5a827999 + (((c^d)&b)^d);
+		b = (b<<30)|(b>>2);
+
+		x = wp[-2] ^ wp[-7] ^ wp[-13] ^ wp[-15];
+		wp[1] = (x<<1) | (x>>31);
+		d += ((e<<5) | (e>>27)) + wp[1];
+		d += 0x5a827999 + (((b^c)&a)^c);
+		a = (a<<30)|(a>>2);
+
+		x = wp[-1] ^ wp[-6] ^ wp[-12] ^ wp[-14];
+		wp[2] = (x<<1) | (x>>31);
+		c += ((d<<5) | (d>>27)) + wp[2];
+		c += 0x5a827999 + (((a^b)&e)^b);
+		e = (e<<30)|(e>>2);
+
+		x = wp[0] ^ wp[-5] ^ wp[-11] ^ wp[-13];
+		wp[3] = (x<<1) | (x>>31);
+		b += ((c<<5) | (c>>27)) + wp[3];
+		b += 0x5a827999 + (((e^a)&d)^a);
+		d = (d<<30)|(d>>2);
+
+		x = wp[1] ^ wp[-4] ^ wp[-10] ^ wp[-12];
+		wp[4] = (x<<1) | (x>>31);
+		a += ((b<<5) | (b>>27)) + wp[4];
+		a += 0x5a827999 + (((d^e)&c)^e);
+		c = (c<<30)|(c>>2);
+
+		wp += 5;
+		p += 4;
+
+		wend = w + 40;
+		for(; wp < wend; wp += 5){
+			x = wp[-3] ^ wp[-8] ^ wp[-14] ^ wp[-16];
+			wp[0] = (x<<1) | (x>>31);
+			e += ((a<<5) | (a>>27)) + wp[0];
+			e += 0x6ed9eba1 + (b^c^d);
+			b = (b<<30)|(b>>2);
+
+			x = wp[-2] ^ wp[-7] ^ wp[-13] ^ wp[-15];
+			wp[1] = (x<<1) | (x>>31);
+			d += ((e<<5) | (e>>27)) + wp[1];
+			d += 0x6ed9eba1 + (a^b^c);
+			a = (a<<30)|(a>>2);
+
+			x = wp[-1] ^ wp[-6] ^ wp[-12] ^ wp[-14];
+			wp[2] = (x<<1) | (x>>31);
+			c += ((d<<5) | (d>>27)) + wp[2];
+			c += 0x6ed9eba1 + (e^a^b);
+			e = (e<<30)|(e>>2);
+
+			x = wp[0] ^ wp[-5] ^ wp[-11] ^ wp[-13];
+			wp[3] = (x<<1) | (x>>31);
+			b += ((c<<5) | (c>>27)) + wp[3];
+			b += 0x6ed9eba1 + (d^e^a);
+			d = (d<<30)|(d>>2);
+
+			x = wp[1] ^ wp[-4] ^ wp[-10] ^ wp[-12];
+			wp[4] = (x<<1) | (x>>31);
+			a += ((b<<5) | (b>>27)) + wp[4];
+			a += 0x6ed9eba1 + (c^d^e);
+			c = (c<<30)|(c>>2);
+		}
+
+		wend = w + 60;
+		for(; wp < wend; wp += 5){
+			x = wp[-3] ^ wp[-8] ^ wp[-14] ^ wp[-16];
+			wp[0] = (x<<1) | (x>>31);
+			e += ((a<<5) | (a>>27)) + wp[0];
+			e += 0x8f1bbcdc + ((b&c)|((b|c)&d));
+			b = (b<<30)|(b>>2);
+
+			x = wp[-2] ^ wp[-7] ^ wp[-13] ^ wp[-15];
+			wp[1] = (x<<1) | (x>>31);
+			d += ((e<<5) | (e>>27)) + wp[1];
+			d += 0x8f1bbcdc + ((a&b)|((a|b)&c));
+			a = (a<<30)|(a>>2);
+
+			x = wp[-1] ^ wp[-6] ^ wp[-12] ^ wp[-14];
+			wp[2] = (x<<1) | (x>>31);
+			c += ((d<<5) | (d>>27)) + wp[2];
+			c += 0x8f1bbcdc + ((e&a)|((e|a)&b));
+			e = (e<<30)|(e>>2);
+
+			x = wp[0] ^ wp[-5] ^ wp[-11] ^ wp[-13];
+			wp[3] = (x<<1) | (x>>31);
+			b += ((c<<5) | (c>>27)) + wp[3];
+			b += 0x8f1bbcdc + ((d&e)|((d|e)&a));
+			d = (d<<30)|(d>>2);
+
+			x = wp[1] ^ wp[-4] ^ wp[-10] ^ wp[-12];
+			wp[4] = (x<<1) | (x>>31);
+			a += ((b<<5) | (b>>27)) + wp[4];
+			a += 0x8f1bbcdc + ((c&d)|((c|d)&e));
+			c = (c<<30)|(c>>2);
+		}
+
+		wend = w + 80;
+		for(; wp < wend; wp += 5){
+			x = wp[-3] ^ wp[-8] ^ wp[-14] ^ wp[-16];
+			wp[0] = (x<<1) | (x>>31);
+			e += ((a<<5) | (a>>27)) + wp[0];
+			e += 0xca62c1d6 + (b^c^d);
+			b = (b<<30)|(b>>2);
+
+			x = wp[-2] ^ wp[-7] ^ wp[-13] ^ wp[-15];
+			wp[1] = (x<<1) | (x>>31);
+			d += ((e<<5) | (e>>27)) + wp[1];
+			d += 0xca62c1d6 + (a^b^c);
+			a = (a<<30)|(a>>2);
+
+			x = wp[-1] ^ wp[-6] ^ wp[-12] ^ wp[-14];
+			wp[2] = (x<<1) | (x>>31);
+			c += ((d<<5) | (d>>27)) + wp[2];
+			c += 0xca62c1d6 + (e^a^b);
+			e = (e<<30)|(e>>2);
+
+			x = wp[0] ^ wp[-5] ^ wp[-11] ^ wp[-13];
+			wp[3] = (x<<1) | (x>>31);
+			b += ((c<<5) | (c>>27)) + wp[3];
+			b += 0xca62c1d6 + (d^e^a);
+			d = (d<<30)|(d>>2);
+
+			x = wp[1] ^ wp[-4] ^ wp[-10] ^ wp[-12];
+			wp[4] = (x<<1) | (x>>31);
+			a += ((b<<5) | (b>>27)) + wp[4];
+			a += 0xca62c1d6 + (c^d^e);
+			c = (c<<30)|(c>>2);
+		}
+
+		/* save state */
+		s[0] += a;
+		s[1] += b;
+		s[2] += c;
+		s[3] += d;
+		s[4] += e;
+	}
+}
--- /dev/null
+++ b/libsec/sha1pickle.c
@@ -1,0 +1,38 @@
+#include "os.h"
+#include <libsec.h>
+
+char*
+sha1pickle(SHA1state *s)
+{
+	char *p;
+	int m, n;
+
+	m = 5*9+4*((s->blen+3)/3);
+	p = malloc(m);
+	if(p == nil)
+		return p;
+	n = sprint(p, "%8.8ux %8.8ux %8.8ux %8.8ux %8.8ux ",
+		s->state[0], s->state[1], s->state[2],
+		s->state[3], s->state[4]);
+	enc64(p+n, m-n, s->buf, s->blen);
+	return p;
+}
+
+SHA1state*
+sha1unpickle(char *p)
+{
+	SHA1state *s;
+
+	s = malloc(sizeof(*s));
+	if(s == nil)
+		return nil;
+	s->state[0] = strtoul(p, &p, 16);
+	s->state[1] = strtoul(p, &p, 16);
+	s->state[2] = strtoul(p, &p, 16);
+	s->state[3] = strtoul(p, &p, 16);
+	s->state[4] = strtoul(p, &p, 16);
+	s->blen = dec64(s->buf, sizeof(s->buf), p, strlen(p));
+	s->malloced = 1;
+	s->seeded = 1;
+	return s;
+}
--- /dev/null
+++ b/libsec/smallprimes.c
@@ -1,0 +1,1004 @@
+#include "os.h"
+
+ulong smallprimes[1000] = {
+	2,
+	3,
+	5,
+	7,
+	11,
+	13,
+	17,
+	19,
+	23,
+	29,
+	31,
+	37,
+	41,
+	43,
+	47,
+	53,
+	59,
+	61,
+	67,
+	71,
+	73,
+	79,
+	83,
+	89,
+	97,
+	101,
+	103,
+	107,
+	109,
+	113,
+	127,
+	131,
+	137,
+	139,
+	149,
+	151,
+	157,
+	163,
+	167,
+	173,
+	179,
+	181,
+	191,
+	193,
+	197,
+	199,
+	211,
+	223,
+	227,
+	229,
+	233,
+	239,
+	241,
+	251,
+	257,
+	263,
+	269,
+	271,
+	277,
+	281,
+	283,
+	293,
+	307,
+	311,
+	313,
+	317,
+	331,
+	337,
+	347,
+	349,
+	353,
+	359,
+	367,
+	373,
+	379,
+	383,
+	389,
+	397,
+	401,
+	409,
+	419,
+	421,
+	431,
+	433,
+	439,
+	443,
+	449,
+	457,
+	461,
+	463,
+	467,
+	479,
+	487,
+	491,
+	499,
+	503,
+	509,
+	521,
+	523,
+	541,
+	547,
+	557,
+	563,
+	569,
+	571,
+	577,
+	587,
+	593,
+	599,
+	601,
+	607,
+	613,
+	617,
+	619,
+	631,
+	641,
+	643,
+	647,
+	653,
+	659,
+	661,
+	673,
+	677,
+	683,
+	691,
+	701,
+	709,
+	719,
+	727,
+	733,
+	739,
+	743,
+	751,
+	757,
+	761,
+	769,
+	773,
+	787,
+	797,
+	809,
+	811,
+	821,
+	823,
+	827,
+	829,
+	839,
+	853,
+	857,
+	859,
+	863,
+	877,
+	881,
+	883,
+	887,
+	907,
+	911,
+	919,
+	929,
+	937,
+	941,
+	947,
+	953,
+	967,
+	971,
+	977,
+	983,
+	991,
+	997,
+	1009,
+	1013,
+	1019,
+	1021,
+	1031,
+	1033,
+	1039,
+	1049,
+	1051,
+	1061,
+	1063,
+	1069,
+	1087,
+	1091,
+	1093,
+	1097,
+	1103,
+	1109,
+	1117,
+	1123,
+	1129,
+	1151,
+	1153,
+	1163,
+	1171,
+	1181,
+	1187,
+	1193,
+	1201,
+	1213,
+	1217,
+	1223,
+	1229,
+	1231,
+	1237,
+	1249,
+	1259,
+	1277,
+	1279,
+	1283,
+	1289,
+	1291,
+	1297,
+	1301,
+	1303,
+	1307,
+	1319,
+	1321,
+	1327,
+	1361,
+	1367,
+	1373,
+	1381,
+	1399,
+	1409,
+	1423,
+	1427,
+	1429,
+	1433,
+	1439,
+	1447,
+	1451,
+	1453,
+	1459,
+	1471,
+	1481,
+	1483,
+	1487,
+	1489,
+	1493,
+	1499,
+	1511,
+	1523,
+	1531,
+	1543,
+	1549,
+	1553,
+	1559,
+	1567,
+	1571,
+	1579,
+	1583,
+	1597,
+	1601,
+	1607,
+	1609,
+	1613,
+	1619,
+	1621,
+	1627,
+	1637,
+	1657,
+	1663,
+	1667,
+	1669,
+	1693,
+	1697,
+	1699,
+	1709,
+	1721,
+	1723,
+	1733,
+	1741,
+	1747,
+	1753,
+	1759,
+	1777,
+	1783,
+	1787,
+	1789,
+	1801,
+	1811,
+	1823,
+	1831,
+	1847,
+	1861,
+	1867,
+	1871,
+	1873,
+	1877,
+	1879,
+	1889,
+	1901,
+	1907,
+	1913,
+	1931,
+	1933,
+	1949,
+	1951,
+	1973,
+	1979,
+	1987,
+	1993,
+	1997,
+	1999,
+	2003,
+	2011,
+	2017,
+	2027,
+	2029,
+	2039,
+	2053,
+	2063,
+	2069,
+	2081,
+	2083,
+	2087,
+	2089,
+	2099,
+	2111,
+	2113,
+	2129,
+	2131,
+	2137,
+	2141,
+	2143,
+	2153,
+	2161,
+	2179,
+	2203,
+	2207,
+	2213,
+	2221,
+	2237,
+	2239,
+	2243,
+	2251,
+	2267,
+	2269,
+	2273,
+	2281,
+	2287,
+	2293,
+	2297,
+	2309,
+	2311,
+	2333,
+	2339,
+	2341,
+	2347,
+	2351,
+	2357,
+	2371,
+	2377,
+	2381,
+	2383,
+	2389,
+	2393,
+	2399,
+	2411,
+	2417,
+	2423,
+	2437,
+	2441,
+	2447,
+	2459,
+	2467,
+	2473,
+	2477,
+	2503,
+	2521,
+	2531,
+	2539,
+	2543,
+	2549,
+	2551,
+	2557,
+	2579,
+	2591,
+	2593,
+	2609,
+	2617,
+	2621,
+	2633,
+	2647,
+	2657,
+	2659,
+	2663,
+	2671,
+	2677,
+	2683,
+	2687,
+	2689,
+	2693,
+	2699,
+	2707,
+	2711,
+	2713,
+	2719,
+	2729,
+	2731,
+	2741,
+	2749,
+	2753,
+	2767,
+	2777,
+	2789,
+	2791,
+	2797,
+	2801,
+	2803,
+	2819,
+	2833,
+	2837,
+	2843,
+	2851,
+	2857,
+	2861,
+	2879,
+	2887,
+	2897,
+	2903,
+	2909,
+	2917,
+	2927,
+	2939,
+	2953,
+	2957,
+	2963,
+	2969,
+	2971,
+	2999,
+	3001,
+	3011,
+	3019,
+	3023,
+	3037,
+	3041,
+	3049,
+	3061,
+	3067,
+	3079,
+	3083,
+	3089,
+	3109,
+	3119,
+	3121,
+	3137,
+	3163,
+	3167,
+	3169,
+	3181,
+	3187,
+	3191,
+	3203,
+	3209,
+	3217,
+	3221,
+	3229,
+	3251,
+	3253,
+	3257,
+	3259,
+	3271,
+	3299,
+	3301,
+	3307,
+	3313,
+	3319,
+	3323,
+	3329,
+	3331,
+	3343,
+	3347,
+	3359,
+	3361,
+	3371,
+	3373,
+	3389,
+	3391,
+	3407,
+	3413,
+	3433,
+	3449,
+	3457,
+	3461,
+	3463,
+	3467,
+	3469,
+	3491,
+	3499,
+	3511,
+	3517,
+	3527,
+	3529,
+	3533,
+	3539,
+	3541,
+	3547,
+	3557,
+	3559,
+	3571,
+	3581,
+	3583,
+	3593,
+	3607,
+	3613,
+	3617,
+	3623,
+	3631,
+	3637,
+	3643,
+	3659,
+	3671,
+	3673,
+	3677,
+	3691,
+	3697,
+	3701,
+	3709,
+	3719,
+	3727,
+	3733,
+	3739,
+	3761,
+	3767,
+	3769,
+	3779,
+	3793,
+	3797,
+	3803,
+	3821,
+	3823,
+	3833,
+	3847,
+	3851,
+	3853,
+	3863,
+	3877,
+	3881,
+	3889,
+	3907,
+	3911,
+	3917,
+	3919,
+	3923,
+	3929,
+	3931,
+	3943,
+	3947,
+	3967,
+	3989,
+	4001,
+	4003,
+	4007,
+	4013,
+	4019,
+	4021,
+	4027,
+	4049,
+	4051,
+	4057,
+	4073,
+	4079,
+	4091,
+	4093,
+	4099,
+	4111,
+	4127,
+	4129,
+	4133,
+	4139,
+	4153,
+	4157,
+	4159,
+	4177,
+	4201,
+	4211,
+	4217,
+	4219,
+	4229,
+	4231,
+	4241,
+	4243,
+	4253,
+	4259,
+	4261,
+	4271,
+	4273,
+	4283,
+	4289,
+	4297,
+	4327,
+	4337,
+	4339,
+	4349,
+	4357,
+	4363,
+	4373,
+	4391,
+	4397,
+	4409,
+	4421,
+	4423,
+	4441,
+	4447,
+	4451,
+	4457,
+	4463,
+	4481,
+	4483,
+	4493,
+	4507,
+	4513,
+	4517,
+	4519,
+	4523,
+	4547,
+	4549,
+	4561,
+	4567,
+	4583,
+	4591,
+	4597,
+	4603,
+	4621,
+	4637,
+	4639,
+	4643,
+	4649,
+	4651,
+	4657,
+	4663,
+	4673,
+	4679,
+	4691,
+	4703,
+	4721,
+	4723,
+	4729,
+	4733,
+	4751,
+	4759,
+	4783,
+	4787,
+	4789,
+	4793,
+	4799,
+	4801,
+	4813,
+	4817,
+	4831,
+	4861,
+	4871,
+	4877,
+	4889,
+	4903,
+	4909,
+	4919,
+	4931,
+	4933,
+	4937,
+	4943,
+	4951,
+	4957,
+	4967,
+	4969,
+	4973,
+	4987,
+	4993,
+	4999,
+	5003,
+	5009,
+	5011,
+	5021,
+	5023,
+	5039,
+	5051,
+	5059,
+	5077,
+	5081,
+	5087,
+	5099,
+	5101,
+	5107,
+	5113,
+	5119,
+	5147,
+	5153,
+	5167,
+	5171,
+	5179,
+	5189,
+	5197,
+	5209,
+	5227,
+	5231,
+	5233,
+	5237,
+	5261,
+	5273,
+	5279,
+	5281,
+	5297,
+	5303,
+	5309,
+	5323,
+	5333,
+	5347,
+	5351,
+	5381,
+	5387,
+	5393,
+	5399,
+	5407,
+	5413,
+	5417,
+	5419,
+	5431,
+	5437,
+	5441,
+	5443,
+	5449,
+	5471,
+	5477,
+	5479,
+	5483,
+	5501,
+	5503,
+	5507,
+	5519,
+	5521,
+	5527,
+	5531,
+	5557,
+	5563,
+	5569,
+	5573,
+	5581,
+	5591,
+	5623,
+	5639,
+	5641,
+	5647,
+	5651,
+	5653,
+	5657,
+	5659,
+	5669,
+	5683,
+	5689,
+	5693,
+	5701,
+	5711,
+	5717,
+	5737,
+	5741,
+	5743,
+	5749,
+	5779,
+	5783,
+	5791,
+	5801,
+	5807,
+	5813,
+	5821,
+	5827,
+	5839,
+	5843,
+	5849,
+	5851,
+	5857,
+	5861,
+	5867,
+	5869,
+	5879,
+	5881,
+	5897,
+	5903,
+	5923,
+	5927,
+	5939,
+	5953,
+	5981,
+	5987,
+	6007,
+	6011,
+	6029,
+	6037,
+	6043,
+	6047,
+	6053,
+	6067,
+	6073,
+	6079,
+	6089,
+	6091,
+	6101,
+	6113,
+	6121,
+	6131,
+	6133,
+	6143,
+	6151,
+	6163,
+	6173,
+	6197,
+	6199,
+	6203,
+	6211,
+	6217,
+	6221,
+	6229,
+	6247,
+	6257,
+	6263,
+	6269,
+	6271,
+	6277,
+	6287,
+	6299,
+	6301,
+	6311,
+	6317,
+	6323,
+	6329,
+	6337,
+	6343,
+	6353,
+	6359,
+	6361,
+	6367,
+	6373,
+	6379,
+	6389,
+	6397,
+	6421,
+	6427,
+	6449,
+	6451,
+	6469,
+	6473,
+	6481,
+	6491,
+	6521,
+	6529,
+	6547,
+	6551,
+	6553,
+	6563,
+	6569,
+	6571,
+	6577,
+	6581,
+	6599,
+	6607,
+	6619,
+	6637,
+	6653,
+	6659,
+	6661,
+	6673,
+	6679,
+	6689,
+	6691,
+	6701,
+	6703,
+	6709,
+	6719,
+	6733,
+	6737,
+	6761,
+	6763,
+	6779,
+	6781,
+	6791,
+	6793,
+	6803,
+	6823,
+	6827,
+	6829,
+	6833,
+	6841,
+	6857,
+	6863,
+	6869,
+	6871,
+	6883,
+	6899,
+	6907,
+	6911,
+	6917,
+	6947,
+	6949,
+	6959,
+	6961,
+	6967,
+	6971,
+	6977,
+	6983,
+	6991,
+	6997,
+	7001,
+	7013,
+	7019,
+	7027,
+	7039,
+	7043,
+	7057,
+	7069,
+	7079,
+	7103,
+	7109,
+	7121,
+	7127,
+	7129,
+	7151,
+	7159,
+	7177,
+	7187,
+	7193,
+	7207,
+	7211,
+	7213,
+	7219,
+	7229,
+	7237,
+	7243,
+	7247,
+	7253,
+	7283,
+	7297,
+	7307,
+	7309,
+	7321,
+	7331,
+	7333,
+	7349,
+	7351,
+	7369,
+	7393,
+	7411,
+	7417,
+	7433,
+	7451,
+	7457,
+	7459,
+	7477,
+	7481,
+	7487,
+	7489,
+	7499,
+	7507,
+	7517,
+	7523,
+	7529,
+	7537,
+	7541,
+	7547,
+	7549,
+	7559,
+	7561,
+	7573,
+	7577,
+	7583,
+	7589,
+	7591,
+	7603,
+	7607,
+	7621,
+	7639,
+	7643,
+	7649,
+	7669,
+	7673,
+	7681,
+	7687,
+	7691,
+	7699,
+	7703,
+	7717,
+	7723,
+	7727,
+	7741,
+	7753,
+	7757,
+	7759,
+	7789,
+	7793,
+	7817,
+	7823,
+	7829,
+	7841,
+	7853,
+	7867,
+	7873,
+	7877,
+	7879,
+	7883,
+	7901,
+	7907,
+	7919,
+};
--- /dev/null
+++ b/libsec/smallprimetest.c
@@ -1,0 +1,1039 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+static ulong smallprimes[] = {
+	2,	3,	5,	7,	11,	13,	17,	19,	23,	29,
+	31,	37,	41,	43,	47,	53,	59,	61,	67,	71,
+	73,	79,	83,	89,	97,	101,	103,	107,	109,	113,
+	127,	131,	137,	139,	149,	151,	157,	163,	167,	173,
+	179,	181,	191,	193,	197,	199,	211,	223,	227,	229,
+	233,	239,	241,	251,	257,	263,	269,	271,	277,	281,
+	283,	293,	307,	311,	313,	317,	331,	337,	347,	349,
+	353,	359,	367,	373,	379,	383,	389,	397,	401,	409,
+	419,	421,	431,	433,	439,	443,	449,	457,	461,	463,
+	467,	479,	487,	491,	499,	503,	509,	521,	523,	541,
+	547,	557,	563,	569,	571,	577,	587,	593,	599,	601,
+	607,	613,	617,	619,	631,	641,	643,	647,	653,	659,
+	661,	673,	677,	683,	691,	701,	709,	719,	727,	733,
+	739,	743,	751,	757,	761,	769,	773,	787,	797,	809,
+	811,	821,	823,	827,	829,	839,	853,	857,	859,	863,
+	877,	881,	883,	887,	907,	911,	919,	929,	937,	941,
+	947,	953,	967,	971,	977,	983,	991,	997,	1009,	1013,
+	1019,	1021,	1031,	1033,	1039,	1049,	1051,	1061,	1063,	1069,
+	1087,	1091,	1093,	1097,	1103,	1109,	1117,	1123,	1129,	1151,
+	1153,	1163,	1171,	1181,	1187,	1193,	1201,	1213,	1217,	1223,
+	1229,	1231,	1237,	1249,	1259,	1277,	1279,	1283,	1289,	1291,
+	1297,	1301,	1303,	1307,	1319,	1321,	1327,	1361,	1367,	1373,
+	1381,	1399,	1409,	1423,	1427,	1429,	1433,	1439,	1447,	1451,
+	1453,	1459,	1471,	1481,	1483,	1487,	1489,	1493,	1499,	1511,
+	1523,	1531,	1543,	1549,	1553,	1559,	1567,	1571,	1579,	1583,
+	1597,	1601,	1607,	1609,	1613,	1619,	1621,	1627,	1637,	1657,
+	1663,	1667,	1669,	1693,	1697,	1699,	1709,	1721,	1723,	1733,
+	1741,	1747,	1753,	1759,	1777,	1783,	1787,	1789,	1801,	1811,
+	1823,	1831,	1847,	1861,	1867,	1871,	1873,	1877,	1879,	1889,
+	1901,	1907,	1913,	1931,	1933,	1949,	1951,	1973,	1979,	1987,
+	1993,	1997,	1999,	2003,	2011,	2017,	2027,	2029,	2039,	2053,
+	2063,	2069,	2081,	2083,	2087,	2089,	2099,	2111,	2113,	2129,
+	2131,	2137,	2141,	2143,	2153,	2161,	2179,	2203,	2207,	2213,
+	2221,	2237,	2239,	2243,	2251,	2267,	2269,	2273,	2281,	2287,
+	2293,	2297,	2309,	2311,	2333,	2339,	2341,	2347,	2351,	2357,
+	2371,	2377,	2381,	2383,	2389,	2393,	2399,	2411,	2417,	2423,
+	2437,	2441,	2447,	2459,	2467,	2473,	2477,	2503,	2521,	2531,
+	2539,	2543,	2549,	2551,	2557,	2579,	2591,	2593,	2609,	2617,
+	2621,	2633,	2647,	2657,	2659,	2663,	2671,	2677,	2683,	2687,
+	2689,	2693,	2699,	2707,	2711,	2713,	2719,	2729,	2731,	2741,
+	2749,	2753,	2767,	2777,	2789,	2791,	2797,	2801,	2803,	2819,
+	2833,	2837,	2843,	2851,	2857,	2861,	2879,	2887,	2897,	2903,
+	2909,	2917,	2927,	2939,	2953,	2957,	2963,	2969,	2971,	2999,
+	3001,	3011,	3019,	3023,	3037,	3041,	3049,	3061,	3067,	3079,
+	3083,	3089,	3109,	3119,	3121,	3137,	3163,	3167,	3169,	3181,
+	3187,	3191,	3203,	3209,	3217,	3221,	3229,	3251,	3253,	3257,
+	3259,	3271,	3299,	3301,	3307,	3313,	3319,	3323,	3329,	3331,
+	3343,	3347,	3359,	3361,	3371,	3373,	3389,	3391,	3407,	3413,
+	3433,	3449,	3457,	3461,	3463,	3467,	3469,	3491,	3499,	3511,
+	3517,	3527,	3529,	3533,	3539,	3541,	3547,	3557,	3559,	3571,
+	3581,	3583,	3593,	3607,	3613,	3617,	3623,	3631,	3637,	3643,
+	3659,	3671,	3673,	3677,	3691,	3697,	3701,	3709,	3719,	3727,
+	3733,	3739,	3761,	3767,	3769,	3779,	3793,	3797,	3803,	3821,
+	3823,	3833,	3847,	3851,	3853,	3863,	3877,	3881,	3889,	3907,
+	3911,	3917,	3919,	3923,	3929,	3931,	3943,	3947,	3967,	3989,
+	4001,	4003,	4007,	4013,	4019,	4021,	4027,	4049,	4051,	4057,
+	4073,	4079,	4091,	4093,	4099,	4111,	4127,	4129,	4133,	4139,
+	4153,	4157,	4159,	4177,	4201,	4211,	4217,	4219,	4229,	4231,
+	4241,	4243,	4253,	4259,	4261,	4271,	4273,	4283,	4289,	4297,
+	4327,	4337,	4339,	4349,	4357,	4363,	4373,	4391,	4397,	4409,
+	4421,	4423,	4441,	4447,	4451,	4457,	4463,	4481,	4483,	4493,
+	4507,	4513,	4517,	4519,	4523,	4547,	4549,	4561,	4567,	4583,
+	4591,	4597,	4603,	4621,	4637,	4639,	4643,	4649,	4651,	4657,
+	4663,	4673,	4679,	4691,	4703,	4721,	4723,	4729,	4733,	4751,
+	4759,	4783,	4787,	4789,	4793,	4799,	4801,	4813,	4817,	4831,
+	4861,	4871,	4877,	4889,	4903,	4909,	4919,	4931,	4933,	4937,
+	4943,	4951,	4957,	4967,	4969,	4973,	4987,	4993,	4999,	5003,
+	5009,	5011,	5021,	5023,	5039,	5051,	5059,	5077,	5081,	5087,
+	5099,	5101,	5107,	5113,	5119,	5147,	5153,	5167,	5171,	5179,
+	5189,	5197,	5209,	5227,	5231,	5233,	5237,	5261,	5273,	5279,
+	5281,	5297,	5303,	5309,	5323,	5333,	5347,	5351,	5381,	5387,
+	5393,	5399,	5407,	5413,	5417,	5419,	5431,	5437,	5441,	5443,
+	5449,	5471,	5477,	5479,	5483,	5501,	5503,	5507,	5519,	5521,
+	5527,	5531,	5557,	5563,	5569,	5573,	5581,	5591,	5623,	5639,
+	5641,	5647,	5651,	5653,	5657,	5659,	5669,	5683,	5689,	5693,
+	5701,	5711,	5717,	5737,	5741,	5743,	5749,	5779,	5783,	5791,
+	5801,	5807,	5813,	5821,	5827,	5839,	5843,	5849,	5851,	5857,
+	5861,	5867,	5869,	5879,	5881,	5897,	5903,	5923,	5927,	5939,
+	5953,	5981,	5987,	6007,	6011,	6029,	6037,	6043,	6047,	6053,
+	6067,	6073,	6079,	6089,	6091,	6101,	6113,	6121,	6131,	6133,
+	6143,	6151,	6163,	6173,	6197,	6199,	6203,	6211,	6217,	6221,
+	6229,	6247,	6257,	6263,	6269,	6271,	6277,	6287,	6299,	6301,
+	6311,	6317,	6323,	6329,	6337,	6343,	6353,	6359,	6361,	6367,
+	6373,	6379,	6389,	6397,	6421,	6427,	6449,	6451,	6469,	6473,
+	6481,	6491,	6521,	6529,	6547,	6551,	6553,	6563,	6569,	6571,
+	6577,	6581,	6599,	6607,	6619,	6637,	6653,	6659,	6661,	6673,
+	6679,	6689,	6691,	6701,	6703,	6709,	6719,	6733,	6737,	6761,
+	6763,	6779,	6781,	6791,	6793,	6803,	6823,	6827,	6829,	6833,
+	6841,	6857,	6863,	6869,	6871,	6883,	6899,	6907,	6911,	6917,
+	6947,	6949,	6959,	6961,	6967,	6971,	6977,	6983,	6991,	6997,
+	7001,	7013,	7019,	7027,	7039,	7043,	7057,	7069,	7079,	7103,
+	7109,	7121,	7127,	7129,	7151,	7159,	7177,	7187,	7193,	7207,
+	7211,	7213,	7219,	7229,	7237,	7243,	7247,	7253,	7283,	7297,
+	7307,	7309,	7321,	7331,	7333,	7349,	7351,	7369,	7393,	7411,
+	7417,	7433,	7451,	7457,	7459,	7477,	7481,	7487,	7489,	7499,
+	7507,	7517,	7523,	7529,	7537,	7541,	7547,	7549,	7559,	7561,
+	7573,	7577,	7583,	7589,	7591,	7603,	7607,	7621,	7639,	7643,
+	7649,	7669,	7673,	7681,	7687,	7691,	7699,	7703,	7717,	7723,
+	7727,	7741,	7753,	7757,	7759,	7789,	7793,	7817,	7823,	7829,
+	7841,	7853,	7867,	7873,	7877,	7879,	7883,	7901,	7907,	7919,
+	7927,	7933,	7937,	7949,	7951,	7963,	7993,	8009,	8011,	8017,
+	8039,	8053,	8059,	8069,	8081,	8087,	8089,	8093,	8101,	8111,
+	8117,	8123,	8147,	8161,	8167,	8171,	8179,	8191,	8209,	8219,
+	8221,	8231,	8233,	8237,	8243,	8263,	8269,	8273,	8287,	8291,
+	8293,	8297,	8311,	8317,	8329,	8353,	8363,	8369,	8377,	8387,
+	8389,	8419,	8423,	8429,	8431,	8443,	8447,	8461,	8467,	8501,
+	8513,	8521,	8527,	8537,	8539,	8543,	8563,	8573,	8581,	8597,
+	8599,	8609,	8623,	8627,	8629,	8641,	8647,	8663,	8669,	8677,
+	8681,	8689,	8693,	8699,	8707,	8713,	8719,	8731,	8737,	8741,
+	8747,	8753,	8761,	8779,	8783,	8803,	8807,	8819,	8821,	8831,
+	8837,	8839,	8849,	8861,	8863,	8867,	8887,	8893,	8923,	8929,
+	8933,	8941,	8951,	8963,	8969,	8971,	8999,	9001,	9007,	9011,
+	9013,	9029,	9041,	9043,	9049,	9059,	9067,	9091,	9103,	9109,
+	9127,	9133,	9137,	9151,	9157,	9161,	9173,	9181,	9187,	9199,
+	9203,	9209,	9221,	9227,	9239,	9241,	9257,	9277,	9281,	9283,
+	9293,	9311,	9319,	9323,	9337,	9341,	9343,	9349,	9371,	9377,
+	9391,	9397,	9403,	9413,	9419,	9421,	9431,	9433,	9437,	9439,
+	9461,	9463,	9467,	9473,	9479,	9491,	9497,	9511,	9521,	9533,
+	9539,	9547,	9551,	9587,	9601,	9613,	9619,	9623,	9629,	9631,
+	9643,	9649,	9661,	9677,	9679,	9689,	9697,	9719,	9721,	9733,
+	9739,	9743,	9749,	9767,	9769,	9781,	9787,	9791,	9803,	9811,
+	9817,	9829,	9833,	9839,	9851,	9857,	9859,	9871,	9883,	9887,
+	9901,	9907,	9923,	9929,	9931,	9941,	9949,	9967,	9973,	10007,
+	10009,	10037,	10039,	10061,	10067,	10069,	10079,	10091,	10093,	10099,
+	10103,	10111,	10133,	10139,	10141,	10151,	10159,	10163,	10169,	10177,
+	10181,	10193,	10211,	10223,	10243,	10247,	10253,	10259,	10267,	10271,
+	10273,	10289,	10301,	10303,	10313,	10321,	10331,	10333,	10337,	10343,
+	10357,	10369,	10391,	10399,	10427,	10429,	10433,	10453,	10457,	10459,
+	10463,	10477,	10487,	10499,	10501,	10513,	10529,	10531,	10559,	10567,
+	10589,	10597,	10601,	10607,	10613,	10627,	10631,	10639,	10651,	10657,
+	10663,	10667,	10687,	10691,	10709,	10711,	10723,	10729,	10733,	10739,
+	10753,	10771,	10781,	10789,	10799,	10831,	10837,	10847,	10853,	10859,
+	10861,	10867,	10883,	10889,	10891,	10903,	10909,	10937,	10939,	10949,
+	10957,	10973,	10979,	10987,	10993,	11003,	11027,	11047,	11057,	11059,
+	11069,	11071,	11083,	11087,	11093,	11113,	11117,	11119,	11131,	11149,
+	11159,	11161,	11171,	11173,	11177,	11197,	11213,	11239,	11243,	11251,
+	11257,	11261,	11273,	11279,	11287,	11299,	11311,	11317,	11321,	11329,
+	11351,	11353,	11369,	11383,	11393,	11399,	11411,	11423,	11437,	11443,
+	11447,	11467,	11471,	11483,	11489,	11491,	11497,	11503,	11519,	11527,
+	11549,	11551,	11579,	11587,	11593,	11597,	11617,	11621,	11633,	11657,
+	11677,	11681,	11689,	11699,	11701,	11717,	11719,	11731,	11743,	11777,
+	11779,	11783,	11789,	11801,	11807,	11813,	11821,	11827,	11831,	11833,
+	11839,	11863,	11867,	11887,	11897,	11903,	11909,	11923,	11927,	11933,
+	11939,	11941,	11953,	11959,	11969,	11971,	11981,	11987,	12007,	12011,
+	12037,	12041,	12043,	12049,	12071,	12073,	12097,	12101,	12107,	12109,
+	12113,	12119,	12143,	12149,	12157,	12161,	12163,	12197,	12203,	12211,
+	12227,	12239,	12241,	12251,	12253,	12263,	12269,	12277,	12281,	12289,
+	12301,	12323,	12329,	12343,	12347,	12373,	12377,	12379,	12391,	12401,
+	12409,	12413,	12421,	12433,	12437,	12451,	12457,	12473,	12479,	12487,
+	12491,	12497,	12503,	12511,	12517,	12527,	12539,	12541,	12547,	12553,
+	12569,	12577,	12583,	12589,	12601,	12611,	12613,	12619,	12637,	12641,
+	12647,	12653,	12659,	12671,	12689,	12697,	12703,	12713,	12721,	12739,
+	12743,	12757,	12763,	12781,	12791,	12799,	12809,	12821,	12823,	12829,
+	12841,	12853,	12889,	12893,	12899,	12907,	12911,	12917,	12919,	12923,
+	12941,	12953,	12959,	12967,	12973,	12979,	12983,	13001,	13003,	13007,
+	13009,	13033,	13037,	13043,	13049,	13063,	13093,	13099,	13103,	13109,
+	13121,	13127,	13147,	13151,	13159,	13163,	13171,	13177,	13183,	13187,
+	13217,	13219,	13229,	13241,	13249,	13259,	13267,	13291,	13297,	13309,
+	13313,	13327,	13331,	13337,	13339,	13367,	13381,	13397,	13399,	13411,
+	13417,	13421,	13441,	13451,	13457,	13463,	13469,	13477,	13487,	13499,
+	13513,	13523,	13537,	13553,	13567,	13577,	13591,	13597,	13613,	13619,
+	13627,	13633,	13649,	13669,	13679,	13681,	13687,	13691,	13693,	13697,
+	13709,	13711,	13721,	13723,	13729,	13751,	13757,	13759,	13763,	13781,
+	13789,	13799,	13807,	13829,	13831,	13841,	13859,	13873,	13877,	13879,
+	13883,	13901,	13903,	13907,	13913,	13921,	13931,	13933,	13963,	13967,
+	13997,	13999,	14009,	14011,	14029,	14033,	14051,	14057,	14071,	14081,
+	14083,	14087,	14107,	14143,	14149,	14153,	14159,	14173,	14177,	14197,
+	14207,	14221,	14243,	14249,	14251,	14281,	14293,	14303,	14321,	14323,
+	14327,	14341,	14347,	14369,	14387,	14389,	14401,	14407,	14411,	14419,
+	14423,	14431,	14437,	14447,	14449,	14461,	14479,	14489,	14503,	14519,
+	14533,	14537,	14543,	14549,	14551,	14557,	14561,	14563,	14591,	14593,
+	14621,	14627,	14629,	14633,	14639,	14653,	14657,	14669,	14683,	14699,
+	14713,	14717,	14723,	14731,	14737,	14741,	14747,	14753,	14759,	14767,
+	14771,	14779,	14783,	14797,	14813,	14821,	14827,	14831,	14843,	14851,
+	14867,	14869,	14879,	14887,	14891,	14897,	14923,	14929,	14939,	14947,
+	14951,	14957,	14969,	14983,	15013,	15017,	15031,	15053,	15061,	15073,
+	15077,	15083,	15091,	15101,	15107,	15121,	15131,	15137,	15139,	15149,
+	15161,	15173,	15187,	15193,	15199,	15217,	15227,	15233,	15241,	15259,
+	15263,	15269,	15271,	15277,	15287,	15289,	15299,	15307,	15313,	15319,
+	15329,	15331,	15349,	15359,	15361,	15373,	15377,	15383,	15391,	15401,
+	15413,	15427,	15439,	15443,	15451,	15461,	15467,	15473,	15493,	15497,
+	15511,	15527,	15541,	15551,	15559,	15569,	15581,	15583,	15601,	15607,
+	15619,	15629,	15641,	15643,	15647,	15649,	15661,	15667,	15671,	15679,
+	15683,	15727,	15731,	15733,	15737,	15739,	15749,	15761,	15767,	15773,
+	15787,	15791,	15797,	15803,	15809,	15817,	15823,	15859,	15877,	15881,
+	15887,	15889,	15901,	15907,	15913,	15919,	15923,	15937,	15959,	15971,
+	15973,	15991,	16001,	16007,	16033,	16057,	16061,	16063,	16067,	16069,
+	16073,	16087,	16091,	16097,	16103,	16111,	16127,	16139,	16141,	16183,
+	16187,	16189,	16193,	16217,	16223,	16229,	16231,	16249,	16253,	16267,
+	16273,	16301,	16319,	16333,	16339,	16349,	16361,	16363,	16369,	16381,
+	16411,	16417,	16421,	16427,	16433,	16447,	16451,	16453,	16477,	16481,
+	16487,	16493,	16519,	16529,	16547,	16553,	16561,	16567,	16573,	16603,
+	16607,	16619,	16631,	16633,	16649,	16651,	16657,	16661,	16673,	16691,
+	16693,	16699,	16703,	16729,	16741,	16747,	16759,	16763,	16787,	16811,
+	16823,	16829,	16831,	16843,	16871,	16879,	16883,	16889,	16901,	16903,
+	16921,	16927,	16931,	16937,	16943,	16963,	16979,	16981,	16987,	16993,
+	17011,	17021,	17027,	17029,	17033,	17041,	17047,	17053,	17077,	17093,
+	17099,	17107,	17117,	17123,	17137,	17159,	17167,	17183,	17189,	17191,
+	17203,	17207,	17209,	17231,	17239,	17257,	17291,	17293,	17299,	17317,
+	17321,	17327,	17333,	17341,	17351,	17359,	17377,	17383,	17387,	17389,
+	17393,	17401,	17417,	17419,	17431,	17443,	17449,	17467,	17471,	17477,
+	17483,	17489,	17491,	17497,	17509,	17519,	17539,	17551,	17569,	17573,
+	17579,	17581,	17597,	17599,	17609,	17623,	17627,	17657,	17659,	17669,
+	17681,	17683,	17707,	17713,	17729,	17737,	17747,	17749,	17761,	17783,
+	17789,	17791,	17807,	17827,	17837,	17839,	17851,	17863,	17881,	17891,
+	17903,	17909,	17911,	17921,	17923,	17929,	17939,	17957,	17959,	17971,
+	17977,	17981,	17987,	17989,	18013,	18041,	18043,	18047,	18049,	18059,
+	18061,	18077,	18089,	18097,	18119,	18121,	18127,	18131,	18133,	18143,
+	18149,	18169,	18181,	18191,	18199,	18211,	18217,	18223,	18229,	18233,
+	18251,	18253,	18257,	18269,	18287,	18289,	18301,	18307,	18311,	18313,
+	18329,	18341,	18353,	18367,	18371,	18379,	18397,	18401,	18413,	18427,
+	18433,	18439,	18443,	18451,	18457,	18461,	18481,	18493,	18503,	18517,
+	18521,	18523,	18539,	18541,	18553,	18583,	18587,	18593,	18617,	18637,
+	18661,	18671,	18679,	18691,	18701,	18713,	18719,	18731,	18743,	18749,
+	18757,	18773,	18787,	18793,	18797,	18803,	18839,	18859,	18869,	18899,
+	18911,	18913,	18917,	18919,	18947,	18959,	18973,	18979,	19001,	19009,
+	19013,	19031,	19037,	19051,	19069,	19073,	19079,	19081,	19087,	19121,
+	19139,	19141,	19157,	19163,	19181,	19183,	19207,	19211,	19213,	19219,
+	19231,	19237,	19249,	19259,	19267,	19273,	19289,	19301,	19309,	19319,
+	19333,	19373,	19379,	19381,	19387,	19391,	19403,	19417,	19421,	19423,
+	19427,	19429,	19433,	19441,	19447,	19457,	19463,	19469,	19471,	19477,
+	19483,	19489,	19501,	19507,	19531,	19541,	19543,	19553,	19559,	19571,
+	19577,	19583,	19597,	19603,	19609,	19661,	19681,	19687,	19697,	19699,
+	19709,	19717,	19727,	19739,	19751,	19753,	19759,	19763,	19777,	19793,
+	19801,	19813,	19819,	19841,	19843,	19853,	19861,	19867,	19889,	19891,
+	19913,	19919,	19927,	19937,	19949,	19961,	19963,	19973,	19979,	19991,
+	19993,	19997,	20011,	20021,	20023,	20029,	20047,	20051,	20063,	20071,
+	20089,	20101,	20107,	20113,	20117,	20123,	20129,	20143,	20147,	20149,
+	20161,	20173,	20177,	20183,	20201,	20219,	20231,	20233,	20249,	20261,
+	20269,	20287,	20297,	20323,	20327,	20333,	20341,	20347,	20353,	20357,
+	20359,	20369,	20389,	20393,	20399,	20407,	20411,	20431,	20441,	20443,
+	20477,	20479,	20483,	20507,	20509,	20521,	20533,	20543,	20549,	20551,
+	20563,	20593,	20599,	20611,	20627,	20639,	20641,	20663,	20681,	20693,
+	20707,	20717,	20719,	20731,	20743,	20747,	20749,	20753,	20759,	20771,
+	20773,	20789,	20807,	20809,	20849,	20857,	20873,	20879,	20887,	20897,
+	20899,	20903,	20921,	20929,	20939,	20947,	20959,	20963,	20981,	20983,
+	21001,	21011,	21013,	21017,	21019,	21023,	21031,	21059,	21061,	21067,
+	21089,	21101,	21107,	21121,	21139,	21143,	21149,	21157,	21163,	21169,
+	21179,	21187,	21191,	21193,	21211,	21221,	21227,	21247,	21269,	21277,
+	21283,	21313,	21317,	21319,	21323,	21341,	21347,	21377,	21379,	21383,
+	21391,	21397,	21401,	21407,	21419,	21433,	21467,	21481,	21487,	21491,
+	21493,	21499,	21503,	21517,	21521,	21523,	21529,	21557,	21559,	21563,
+	21569,	21577,	21587,	21589,	21599,	21601,	21611,	21613,	21617,	21647,
+	21649,	21661,	21673,	21683,	21701,	21713,	21727,	21737,	21739,	21751,
+	21757,	21767,	21773,	21787,	21799,	21803,	21817,	21821,	21839,	21841,
+	21851,	21859,	21863,	21871,	21881,	21893,	21911,	21929,	21937,	21943,
+	21961,	21977,	21991,	21997,	22003,	22013,	22027,	22031,	22037,	22039,
+	22051,	22063,	22067,	22073,	22079,	22091,	22093,	22109,	22111,	22123,
+	22129,	22133,	22147,	22153,	22157,	22159,	22171,	22189,	22193,	22229,
+	22247,	22259,	22271,	22273,	22277,	22279,	22283,	22291,	22303,	22307,
+	22343,	22349,	22367,	22369,	22381,	22391,	22397,	22409,	22433,	22441,
+	22447,	22453,	22469,	22481,	22483,	22501,	22511,	22531,	22541,	22543,
+	22549,	22567,	22571,	22573,	22613,	22619,	22621,	22637,	22639,	22643,
+	22651,	22669,	22679,	22691,	22697,	22699,	22709,	22717,	22721,	22727,
+	22739,	22741,	22751,	22769,	22777,	22783,	22787,	22807,	22811,	22817,
+	22853,	22859,	22861,	22871,	22877,	22901,	22907,	22921,	22937,	22943,
+	22961,	22963,	22973,	22993,	23003,	23011,	23017,	23021,	23027,	23029,
+	23039,	23041,	23053,	23057,	23059,	23063,	23071,	23081,	23087,	23099,
+	23117,	23131,	23143,	23159,	23167,	23173,	23189,	23197,	23201,	23203,
+	23209,	23227,	23251,	23269,	23279,	23291,	23293,	23297,	23311,	23321,
+	23327,	23333,	23339,	23357,	23369,	23371,	23399,	23417,	23431,	23447,
+	23459,	23473,	23497,	23509,	23531,	23537,	23539,	23549,	23557,	23561,
+	23563,	23567,	23581,	23593,	23599,	23603,	23609,	23623,	23627,	23629,
+	23633,	23663,	23669,	23671,	23677,	23687,	23689,	23719,	23741,	23743,
+	23747,	23753,	23761,	23767,	23773,	23789,	23801,	23813,	23819,	23827,
+	23831,	23833,	23857,	23869,	23873,	23879,	23887,	23893,	23899,	23909,
+	23911,	23917,	23929,	23957,	23971,	23977,	23981,	23993,	24001,	24007,
+	24019,	24023,	24029,	24043,	24049,	24061,	24071,	24077,	24083,	24091,
+	24097,	24103,	24107,	24109,	24113,	24121,	24133,	24137,	24151,	24169,
+	24179,	24181,	24197,	24203,	24223,	24229,	24239,	24247,	24251,	24281,
+	24317,	24329,	24337,	24359,	24371,	24373,	24379,	24391,	24407,	24413,
+	24419,	24421,	24439,	24443,	24469,	24473,	24481,	24499,	24509,	24517,
+	24527,	24533,	24547,	24551,	24571,	24593,	24611,	24623,	24631,	24659,
+	24671,	24677,	24683,	24691,	24697,	24709,	24733,	24749,	24763,	24767,
+	24781,	24793,	24799,	24809,	24821,	24841,	24847,	24851,	24859,	24877,
+	24889,	24907,	24917,	24919,	24923,	24943,	24953,	24967,	24971,	24977,
+	24979,	24989,	25013,	25031,	25033,	25037,	25057,	25073,	25087,	25097,
+	25111,	25117,	25121,	25127,	25147,	25153,	25163,	25169,	25171,	25183,
+	25189,	25219,	25229,	25237,	25243,	25247,	25253,	25261,	25301,	25303,
+	25307,	25309,	25321,	25339,	25343,	25349,	25357,	25367,	25373,	25391,
+	25409,	25411,	25423,	25439,	25447,	25453,	25457,	25463,	25469,	25471,
+	25523,	25537,	25541,	25561,	25577,	25579,	25583,	25589,	25601,	25603,
+	25609,	25621,	25633,	25639,	25643,	25657,	25667,	25673,	25679,	25693,
+	25703,	25717,	25733,	25741,	25747,	25759,	25763,	25771,	25793,	25799,
+	25801,	25819,	25841,	25847,	25849,	25867,	25873,	25889,	25903,	25913,
+	25919,	25931,	25933,	25939,	25943,	25951,	25969,	25981,	25997,	25999,
+	26003,	26017,	26021,	26029,	26041,	26053,	26083,	26099,	26107,	26111,
+	26113,	26119,	26141,	26153,	26161,	26171,	26177,	26183,	26189,	26203,
+	26209,	26227,	26237,	26249,	26251,	26261,	26263,	26267,	26293,	26297,
+	26309,	26317,	26321,	26339,	26347,	26357,	26371,	26387,	26393,	26399,
+	26407,	26417,	26423,	26431,	26437,	26449,	26459,	26479,	26489,	26497,
+	26501,	26513,	26539,	26557,	26561,	26573,	26591,	26597,	26627,	26633,
+	26641,	26647,	26669,	26681,	26683,	26687,	26693,	26699,	26701,	26711,
+	26713,	26717,	26723,	26729,	26731,	26737,	26759,	26777,	26783,	26801,
+	26813,	26821,	26833,	26839,	26849,	26861,	26863,	26879,	26881,	26891,
+	26893,	26903,	26921,	26927,	26947,	26951,	26953,	26959,	26981,	26987,
+	26993,	27011,	27017,	27031,	27043,	27059,	27061,	27067,	27073,	27077,
+	27091,	27103,	27107,	27109,	27127,	27143,	27179,	27191,	27197,	27211,
+	27239,	27241,	27253,	27259,	27271,	27277,	27281,	27283,	27299,	27329,
+	27337,	27361,	27367,	27397,	27407,	27409,	27427,	27431,	27437,	27449,
+	27457,	27479,	27481,	27487,	27509,	27527,	27529,	27539,	27541,	27551,
+	27581,	27583,	27611,	27617,	27631,	27647,	27653,	27673,	27689,	27691,
+	27697,	27701,	27733,	27737,	27739,	27743,	27749,	27751,	27763,	27767,
+	27773,	27779,	27791,	27793,	27799,	27803,	27809,	27817,	27823,	27827,
+	27847,	27851,	27883,	27893,	27901,	27917,	27919,	27941,	27943,	27947,
+	27953,	27961,	27967,	27983,	27997,	28001,	28019,	28027,	28031,	28051,
+	28057,	28069,	28081,	28087,	28097,	28099,	28109,	28111,	28123,	28151,
+	28163,	28181,	28183,	28201,	28211,	28219,	28229,	28277,	28279,	28283,
+	28289,	28297,	28307,	28309,	28319,	28349,	28351,	28387,	28393,	28403,
+	28409,	28411,	28429,	28433,	28439,	28447,	28463,	28477,	28493,	28499,
+	28513,	28517,	28537,	28541,	28547,	28549,	28559,	28571,	28573,	28579,
+	28591,	28597,	28603,	28607,	28619,	28621,	28627,	28631,	28643,	28649,
+	28657,	28661,	28663,	28669,	28687,	28697,	28703,	28711,	28723,	28729,
+	28751,	28753,	28759,	28771,	28789,	28793,	28807,	28813,	28817,	28837,
+	28843,	28859,	28867,	28871,	28879,	28901,	28909,	28921,	28927,	28933,
+	28949,	28961,	28979,	29009,	29017,	29021,	29023,	29027,	29033,	29059,
+	29063,	29077,	29101,	29123,	29129,	29131,	29137,	29147,	29153,	29167,
+	29173,	29179,	29191,	29201,	29207,	29209,	29221,	29231,	29243,	29251,
+	29269,	29287,	29297,	29303,	29311,	29327,	29333,	29339,	29347,	29363,
+	29383,	29387,	29389,	29399,	29401,	29411,	29423,	29429,	29437,	29443,
+	29453,	29473,	29483,	29501,	29527,	29531,	29537,	29567,	29569,	29573,
+	29581,	29587,	29599,	29611,	29629,	29633,	29641,	29663,	29669,	29671,
+	29683,	29717,	29723,	29741,	29753,	29759,	29761,	29789,	29803,	29819,
+	29833,	29837,	29851,	29863,	29867,	29873,	29879,	29881,	29917,	29921,
+	29927,	29947,	29959,	29983,	29989,	30011,	30013,	30029,	30047,	30059,
+	30071,	30089,	30091,	30097,	30103,	30109,	30113,	30119,	30133,	30137,
+	30139,	30161,	30169,	30181,	30187,	30197,	30203,	30211,	30223,	30241,
+	30253,	30259,	30269,	30271,	30293,	30307,	30313,	30319,	30323,	30341,
+	30347,	30367,	30389,	30391,	30403,	30427,	30431,	30449,	30467,	30469,
+	30491,	30493,	30497,	30509,	30517,	30529,	30539,	30553,	30557,	30559,
+	30577,	30593,	30631,	30637,	30643,	30649,	30661,	30671,	30677,	30689,
+	30697,	30703,	30707,	30713,	30727,	30757,	30763,	30773,	30781,	30803,
+	30809,	30817,	30829,	30839,	30841,	30851,	30853,	30859,	30869,	30871,
+	30881,	30893,	30911,	30931,	30937,	30941,	30949,	30971,	30977,	30983,
+	31013,	31019,	31033,	31039,	31051,	31063,	31069,	31079,	31081,	31091,
+	31121,	31123,	31139,	31147,	31151,	31153,	31159,	31177,	31181,	31183,
+	31189,	31193,	31219,	31223,	31231,	31237,	31247,	31249,	31253,	31259,
+	31267,	31271,	31277,	31307,	31319,	31321,	31327,	31333,	31337,	31357,
+	31379,	31387,	31391,	31393,	31397,	31469,	31477,	31481,	31489,	31511,
+	31513,	31517,	31531,	31541,	31543,	31547,	31567,	31573,	31583,	31601,
+	31607,	31627,	31643,	31649,	31657,	31663,	31667,	31687,	31699,	31721,
+	31723,	31727,	31729,	31741,	31751,	31769,	31771,	31793,	31799,	31817,
+	31847,	31849,	31859,	31873,	31883,	31891,	31907,	31957,	31963,	31973,
+	31981,	31991,	32003,	32009,	32027,	32029,	32051,	32057,	32059,	32063,
+	32069,	32077,	32083,	32089,	32099,	32117,	32119,	32141,	32143,	32159,
+	32173,	32183,	32189,	32191,	32203,	32213,	32233,	32237,	32251,	32257,
+	32261,	32297,	32299,	32303,	32309,	32321,	32323,	32327,	32341,	32353,
+	32359,	32363,	32369,	32371,	32377,	32381,	32401,	32411,	32413,	32423,
+	32429,	32441,	32443,	32467,	32479,	32491,	32497,	32503,	32507,	32531,
+	32533,	32537,	32561,	32563,	32569,	32573,	32579,	32587,	32603,	32609,
+	32611,	32621,	32633,	32647,	32653,	32687,	32693,	32707,	32713,	32717,
+	32719,	32749,	32771,	32779,	32783,	32789,	32797,	32801,	32803,	32831,
+	32833,	32839,	32843,	32869,	32887,	32909,	32911,	32917,	32933,	32939,
+	32941,	32957,	32969,	32971,	32983,	32987,	32993,	32999,	33013,	33023,
+	33029,	33037,	33049,	33053,	33071,	33073,	33083,	33091,	33107,	33113,
+	33119,	33149,	33151,	33161,	33179,	33181,	33191,	33199,	33203,	33211,
+	33223,	33247,	33287,	33289,	33301,	33311,	33317,	33329,	33331,	33343,
+	33347,	33349,	33353,	33359,	33377,	33391,	33403,	33409,	33413,	33427,
+	33457,	33461,	33469,	33479,	33487,	33493,	33503,	33521,	33529,	33533,
+	33547,	33563,	33569,	33577,	33581,	33587,	33589,	33599,	33601,	33613,
+	33617,	33619,	33623,	33629,	33637,	33641,	33647,	33679,	33703,	33713,
+	33721,	33739,	33749,	33751,	33757,	33767,	33769,	33773,	33791,	33797,
+	33809,	33811,	33827,	33829,	33851,	33857,	33863,	33871,	33889,	33893,
+	33911,	33923,	33931,	33937,	33941,	33961,	33967,	33997,	34019,	34031,
+	34033,	34039,	34057,	34061,	34123,	34127,	34129,	34141,	34147,	34157,
+	34159,	34171,	34183,	34211,	34213,	34217,	34231,	34253,	34259,	34261,
+	34267,	34273,	34283,	34297,	34301,	34303,	34313,	34319,	34327,	34337,
+	34351,	34361,	34367,	34369,	34381,	34403,	34421,	34429,	34439,	34457,
+	34469,	34471,	34483,	34487,	34499,	34501,	34511,	34513,	34519,	34537,
+	34543,	34549,	34583,	34589,	34591,	34603,	34607,	34613,	34631,	34649,
+	34651,	34667,	34673,	34679,	34687,	34693,	34703,	34721,	34729,	34739,
+	34747,	34757,	34759,	34763,	34781,	34807,	34819,	34841,	34843,	34847,
+	34849,	34871,	34877,	34883,	34897,	34913,	34919,	34939,	34949,	34961,
+	34963,	34981,	35023,	35027,	35051,	35053,	35059,	35069,	35081,	35083,
+	35089,	35099,	35107,	35111,	35117,	35129,	35141,	35149,	35153,	35159,
+	35171,	35201,	35221,	35227,	35251,	35257,	35267,	35279,	35281,	35291,
+	35311,	35317,	35323,	35327,	35339,	35353,	35363,	35381,	35393,	35401,
+	35407,	35419,	35423,	35437,	35447,	35449,	35461,	35491,	35507,	35509,
+	35521,	35527,	35531,	35533,	35537,	35543,	35569,	35573,	35591,	35593,
+	35597,	35603,	35617,	35671,	35677,	35729,	35731,	35747,	35753,	35759,
+	35771,	35797,	35801,	35803,	35809,	35831,	35837,	35839,	35851,	35863,
+	35869,	35879,	35897,	35899,	35911,	35923,	35933,	35951,	35963,	35969,
+	35977,	35983,	35993,	35999,	36007,	36011,	36013,	36017,	36037,	36061,
+	36067,	36073,	36083,	36097,	36107,	36109,	36131,	36137,	36151,	36161,
+	36187,	36191,	36209,	36217,	36229,	36241,	36251,	36263,	36269,	36277,
+	36293,	36299,	36307,	36313,	36319,	36341,	36343,	36353,	36373,	36383,
+	36389,	36433,	36451,	36457,	36467,	36469,	36473,	36479,	36493,	36497,
+	36523,	36527,	36529,	36541,	36551,	36559,	36563,	36571,	36583,	36587,
+	36599,	36607,	36629,	36637,	36643,	36653,	36671,	36677,	36683,	36691,
+	36697,	36709,	36713,	36721,	36739,	36749,	36761,	36767,	36779,	36781,
+	36787,	36791,	36793,	36809,	36821,	36833,	36847,	36857,	36871,	36877,
+	36887,	36899,	36901,	36913,	36919,	36923,	36929,	36931,	36943,	36947,
+	36973,	36979,	36997,	37003,	37013,	37019,	37021,	37039,	37049,	37057,
+	37061,	37087,	37097,	37117,	37123,	37139,	37159,	37171,	37181,	37189,
+	37199,	37201,	37217,	37223,	37243,	37253,	37273,	37277,	37307,	37309,
+	37313,	37321,	37337,	37339,	37357,	37361,	37363,	37369,	37379,	37397,
+	37409,	37423,	37441,	37447,	37463,	37483,	37489,	37493,	37501,	37507,
+	37511,	37517,	37529,	37537,	37547,	37549,	37561,	37567,	37571,	37573,
+	37579,	37589,	37591,	37607,	37619,	37633,	37643,	37649,	37657,	37663,
+	37691,	37693,	37699,	37717,	37747,	37781,	37783,	37799,	37811,	37813,
+	37831,	37847,	37853,	37861,	37871,	37879,	37889,	37897,	37907,	37951,
+	37957,	37963,	37967,	37987,	37991,	37993,	37997,	38011,	38039,	38047,
+	38053,	38069,	38083,	38113,	38119,	38149,	38153,	38167,	38177,	38183,
+	38189,	38197,	38201,	38219,	38231,	38237,	38239,	38261,	38273,	38281,
+	38287,	38299,	38303,	38317,	38321,	38327,	38329,	38333,	38351,	38371,
+	38377,	38393,	38431,	38447,	38449,	38453,	38459,	38461,	38501,	38543,
+	38557,	38561,	38567,	38569,	38593,	38603,	38609,	38611,	38629,	38639,
+	38651,	38653,	38669,	38671,	38677,	38693,	38699,	38707,	38711,	38713,
+	38723,	38729,	38737,	38747,	38749,	38767,	38783,	38791,	38803,	38821,
+	38833,	38839,	38851,	38861,	38867,	38873,	38891,	38903,	38917,	38921,
+	38923,	38933,	38953,	38959,	38971,	38977,	38993,	39019,	39023,	39041,
+	39043,	39047,	39079,	39089,	39097,	39103,	39107,	39113,	39119,	39133,
+	39139,	39157,	39161,	39163,	39181,	39191,	39199,	39209,	39217,	39227,
+	39229,	39233,	39239,	39241,	39251,	39293,	39301,	39313,	39317,	39323,
+	39341,	39343,	39359,	39367,	39371,	39373,	39383,	39397,	39409,	39419,
+	39439,	39443,	39451,	39461,	39499,	39503,	39509,	39511,	39521,	39541,
+	39551,	39563,	39569,	39581,	39607,	39619,	39623,	39631,	39659,	39667,
+	39671,	39679,	39703,	39709,	39719,	39727,	39733,	39749,	39761,	39769,
+	39779,	39791,	39799,	39821,	39827,	39829,	39839,	39841,	39847,	39857,
+	39863,	39869,	39877,	39883,	39887,	39901,	39929,	39937,	39953,	39971,
+	39979,	39983,	39989,	40009,	40013,	40031,	40037,	40039,	40063,	40087,
+	40093,	40099,	40111,	40123,	40127,	40129,	40151,	40153,	40163,	40169,
+	40177,	40189,	40193,	40213,	40231,	40237,	40241,	40253,	40277,	40283,
+	40289,	40343,	40351,	40357,	40361,	40387,	40423,	40427,	40429,	40433,
+	40459,	40471,	40483,	40487,	40493,	40499,	40507,	40519,	40529,	40531,
+	40543,	40559,	40577,	40583,	40591,	40597,	40609,	40627,	40637,	40639,
+	40693,	40697,	40699,	40709,	40739,	40751,	40759,	40763,	40771,	40787,
+	40801,	40813,	40819,	40823,	40829,	40841,	40847,	40849,	40853,	40867,
+	40879,	40883,	40897,	40903,	40927,	40933,	40939,	40949,	40961,	40973,
+	40993,	41011,	41017,	41023,	41039,	41047,	41051,	41057,	41077,	41081,
+	41113,	41117,	41131,	41141,	41143,	41149,	41161,	41177,	41179,	41183,
+	41189,	41201,	41203,	41213,	41221,	41227,	41231,	41233,	41243,	41257,
+	41263,	41269,	41281,	41299,	41333,	41341,	41351,	41357,	41381,	41387,
+	41389,	41399,	41411,	41413,	41443,	41453,	41467,	41479,	41491,	41507,
+	41513,	41519,	41521,	41539,	41543,	41549,	41579,	41593,	41597,	41603,
+	41609,	41611,	41617,	41621,	41627,	41641,	41647,	41651,	41659,	41669,
+	41681,	41687,	41719,	41729,	41737,	41759,	41761,	41771,	41777,	41801,
+	41809,	41813,	41843,	41849,	41851,	41863,	41879,	41887,	41893,	41897,
+	41903,	41911,	41927,	41941,	41947,	41953,	41957,	41959,	41969,	41981,
+	41983,	41999,	42013,	42017,	42019,	42023,	42043,	42061,	42071,	42073,
+	42083,	42089,	42101,	42131,	42139,	42157,	42169,	42179,	42181,	42187,
+	42193,	42197,	42209,	42221,	42223,	42227,	42239,	42257,	42281,	42283,
+	42293,	42299,	42307,	42323,	42331,	42337,	42349,	42359,	42373,	42379,
+	42391,	42397,	42403,	42407,	42409,	42433,	42437,	42443,	42451,	42457,
+	42461,	42463,	42467,	42473,	42487,	42491,	42499,	42509,	42533,	42557,
+	42569,	42571,	42577,	42589,	42611,	42641,	42643,	42649,	42667,	42677,
+	42683,	42689,	42697,	42701,	42703,	42709,	42719,	42727,	42737,	42743,
+	42751,	42767,	42773,	42787,	42793,	42797,	42821,	42829,	42839,	42841,
+	42853,	42859,	42863,	42899,	42901,	42923,	42929,	42937,	42943,	42953,
+	42961,	42967,	42979,	42989,	43003,	43013,	43019,	43037,	43049,	43051,
+	43063,	43067,	43093,	43103,	43117,	43133,	43151,	43159,	43177,	43189,
+	43201,	43207,	43223,	43237,	43261,	43271,	43283,	43291,	43313,	43319,
+	43321,	43331,	43391,	43397,	43399,	43403,	43411,	43427,	43441,	43451,
+	43457,	43481,	43487,	43499,	43517,	43541,	43543,	43573,	43577,	43579,
+	43591,	43597,	43607,	43609,	43613,	43627,	43633,	43649,	43651,	43661,
+	43669,	43691,	43711,	43717,	43721,	43753,	43759,	43777,	43781,	43783,
+	43787,	43789,	43793,	43801,	43853,	43867,	43889,	43891,	43913,	43933,
+	43943,	43951,	43961,	43963,	43969,	43973,	43987,	43991,	43997,	44017,
+	44021,	44027,	44029,	44041,	44053,	44059,	44071,	44087,	44089,	44101,
+	44111,	44119,	44123,	44129,	44131,	44159,	44171,	44179,	44189,	44201,
+	44203,	44207,	44221,	44249,	44257,	44263,	44267,	44269,	44273,	44279,
+	44281,	44293,	44351,	44357,	44371,	44381,	44383,	44389,	44417,	44449,
+	44453,	44483,	44491,	44497,	44501,	44507,	44519,	44531,	44533,	44537,
+	44543,	44549,	44563,	44579,	44587,	44617,	44621,	44623,	44633,	44641,
+	44647,	44651,	44657,	44683,	44687,	44699,	44701,	44711,	44729,	44741,
+	44753,	44771,	44773,	44777,	44789,	44797,	44809,	44819,	44839,	44843,
+	44851,	44867,	44879,	44887,	44893,	44909,	44917,	44927,	44939,	44953,
+	44959,	44963,	44971,	44983,	44987,	45007,	45013,	45053,	45061,	45077,
+	45083,	45119,	45121,	45127,	45131,	45137,	45139,	45161,	45179,	45181,
+	45191,	45197,	45233,	45247,	45259,	45263,	45281,	45289,	45293,	45307,
+	45317,	45319,	45329,	45337,	45341,	45343,	45361,	45377,	45389,	45403,
+	45413,	45427,	45433,	45439,	45481,	45491,	45497,	45503,	45523,	45533,
+	45541,	45553,	45557,	45569,	45587,	45589,	45599,	45613,	45631,	45641,
+	45659,	45667,	45673,	45677,	45691,	45697,	45707,	45737,	45751,	45757,
+	45763,	45767,	45779,	45817,	45821,	45823,	45827,	45833,	45841,	45853,
+	45863,	45869,	45887,	45893,	45943,	45949,	45953,	45959,	45971,	45979,
+	45989,	46021,	46027,	46049,	46051,	46061,	46073,	46091,	46093,	46099,
+	46103,	46133,	46141,	46147,	46153,	46171,	46181,	46183,	46187,	46199,
+	46219,	46229,	46237,	46261,	46271,	46273,	46279,	46301,	46307,	46309,
+	46327,	46337,	46349,	46351,	46381,	46399,	46411,	46439,	46441,	46447,
+	46451,	46457,	46471,	46477,	46489,	46499,	46507,	46511,	46523,	46549,
+	46559,	46567,	46573,	46589,	46591,	46601,	46619,	46633,	46639,	46643,
+	46649,	46663,	46679,	46681,	46687,	46691,	46703,	46723,	46727,	46747,
+	46751,	46757,	46769,	46771,	46807,	46811,	46817,	46819,	46829,	46831,
+	46853,	46861,	46867,	46877,	46889,	46901,	46919,	46933,	46957,	46993,
+	46997,	47017,	47041,	47051,	47057,	47059,	47087,	47093,	47111,	47119,
+	47123,	47129,	47137,	47143,	47147,	47149,	47161,	47189,	47207,	47221,
+	47237,	47251,	47269,	47279,	47287,	47293,	47297,	47303,	47309,	47317,
+	47339,	47351,	47353,	47363,	47381,	47387,	47389,	47407,	47417,	47419,
+	47431,	47441,	47459,	47491,	47497,	47501,	47507,	47513,	47521,	47527,
+	47533,	47543,	47563,	47569,	47581,	47591,	47599,	47609,	47623,	47629,
+	47639,	47653,	47657,	47659,	47681,	47699,	47701,	47711,	47713,	47717,
+	47737,	47741,	47743,	47777,	47779,	47791,	47797,	47807,	47809,	47819,
+	47837,	47843,	47857,	47869,	47881,	47903,	47911,	47917,	47933,	47939,
+	47947,	47951,	47963,	47969,	47977,	47981,	48017,	48023,	48029,	48049,
+	48073,	48079,	48091,	48109,	48119,	48121,	48131,	48157,	48163,	48179,
+	48187,	48193,	48197,	48221,	48239,	48247,	48259,	48271,	48281,	48299,
+	48311,	48313,	48337,	48341,	48353,	48371,	48383,	48397,	48407,	48409,
+	48413,	48437,	48449,	48463,	48473,	48479,	48481,	48487,	48491,	48497,
+	48523,	48527,	48533,	48539,	48541,	48563,	48571,	48589,	48593,	48611,
+	48619,	48623,	48647,	48649,	48661,	48673,	48677,	48679,	48731,	48733,
+	48751,	48757,	48761,	48767,	48779,	48781,	48787,	48799,	48809,	48817,
+	48821,	48823,	48847,	48857,	48859,	48869,	48871,	48883,	48889,	48907,
+	48947,	48953,	48973,	48989,	48991,	49003,	49009,	49019,	49031,	49033,
+	49037,	49043,	49057,	49069,	49081,	49103,	49109,	49117,	49121,	49123,
+	49139,	49157,	49169,	49171,	49177,	49193,	49199,	49201,	49207,	49211,
+	49223,	49253,	49261,	49277,	49279,	49297,	49307,	49331,	49333,	49339,
+	49363,	49367,	49369,	49391,	49393,	49409,	49411,	49417,	49429,	49433,
+	49451,	49459,	49463,	49477,	49481,	49499,	49523,	49529,	49531,	49537,
+	49547,	49549,	49559,	49597,	49603,	49613,	49627,	49633,	49639,	49663,
+	49667,	49669,	49681,	49697,	49711,	49727,	49739,	49741,	49747,	49757,
+	49783,	49787,	49789,	49801,	49807,	49811,	49823,	49831,	49843,	49853,
+	49871,	49877,	49891,	49919,	49921,	49927,	49937,	49939,	49943,	49957,
+	49991,	49993,	49999,	50021,	50023,	50033,	50047,	50051,	50053,	50069,
+	50077,	50087,	50093,	50101,	50111,	50119,	50123,	50129,	50131,	50147,
+	50153,	50159,	50177,	50207,	50221,	50227,	50231,	50261,	50263,	50273,
+	50287,	50291,	50311,	50321,	50329,	50333,	50341,	50359,	50363,	50377,
+	50383,	50387,	50411,	50417,	50423,	50441,	50459,	50461,	50497,	50503,
+	50513,	50527,	50539,	50543,	50549,	50551,	50581,	50587,	50591,	50593,
+	50599,	50627,	50647,	50651,	50671,	50683,	50707,	50723,	50741,	50753,
+	50767,	50773,	50777,	50789,	50821,	50833,	50839,	50849,	50857,	50867,
+	50873,	50891,	50893,	50909,	50923,	50929,	50951,	50957,	50969,	50971,
+	50989,	50993,	51001,	51031,	51043,	51047,	51059,	51061,	51071,	51109,
+	51131,	51133,	51137,	51151,	51157,	51169,	51193,	51197,	51199,	51203,
+	51217,	51229,	51239,	51241,	51257,	51263,	51283,	51287,	51307,	51329,
+	51341,	51343,	51347,	51349,	51361,	51383,	51407,	51413,	51419,	51421,
+	51427,	51431,	51437,	51439,	51449,	51461,	51473,	51479,	51481,	51487,
+	51503,	51511,	51517,	51521,	51539,	51551,	51563,	51577,	51581,	51593,
+	51599,	51607,	51613,	51631,	51637,	51647,	51659,	51673,	51679,	51683,
+	51691,	51713,	51719,	51721,	51749,	51767,	51769,	51787,	51797,	51803,
+	51817,	51827,	51829,	51839,	51853,	51859,	51869,	51871,	51893,	51899,
+	51907,	51913,	51929,	51941,	51949,	51971,	51973,	51977,	51991,	52009,
+	52021,	52027,	52051,	52057,	52067,	52069,	52081,	52103,	52121,	52127,
+	52147,	52153,	52163,	52177,	52181,	52183,	52189,	52201,	52223,	52237,
+	52249,	52253,	52259,	52267,	52289,	52291,	52301,	52313,	52321,	52361,
+	52363,	52369,	52379,	52387,	52391,	52433,	52453,	52457,	52489,	52501,
+	52511,	52517,	52529,	52541,	52543,	52553,	52561,	52567,	52571,	52579,
+	52583,	52609,	52627,	52631,	52639,	52667,	52673,	52691,	52697,	52709,
+	52711,	52721,	52727,	52733,	52747,	52757,	52769,	52783,	52807,	52813,
+	52817,	52837,	52859,	52861,	52879,	52883,	52889,	52901,	52903,	52919,
+	52937,	52951,	52957,	52963,	52967,	52973,	52981,	52999,	53003,	53017,
+	53047,	53051,	53069,	53077,	53087,	53089,	53093,	53101,	53113,	53117,
+	53129,	53147,	53149,	53161,	53171,	53173,	53189,	53197,	53201,	53231,
+	53233,	53239,	53267,	53269,	53279,	53281,	53299,	53309,	53323,	53327,
+	53353,	53359,	53377,	53381,	53401,	53407,	53411,	53419,	53437,	53441,
+	53453,	53479,	53503,	53507,	53527,	53549,	53551,	53569,	53591,	53593,
+	53597,	53609,	53611,	53617,	53623,	53629,	53633,	53639,	53653,	53657,
+	53681,	53693,	53699,	53717,	53719,	53731,	53759,	53773,	53777,	53783,
+	53791,	53813,	53819,	53831,	53849,	53857,	53861,	53881,	53887,	53891,
+	53897,	53899,	53917,	53923,	53927,	53939,	53951,	53959,	53987,	53993,
+	54001,	54011,	54013,	54037,	54049,	54059,	54083,	54091,	54101,	54121,
+	54133,	54139,	54151,	54163,	54167,	54181,	54193,	54217,	54251,	54269,
+	54277,	54287,	54293,	54311,	54319,	54323,	54331,	54347,	54361,	54367,
+	54371,	54377,	54401,	54403,	54409,	54413,	54419,	54421,	54437,	54443,
+	54449,	54469,	54493,	54497,	54499,	54503,	54517,	54521,	54539,	54541,
+	54547,	54559,	54563,	54577,	54581,	54583,	54601,	54617,	54623,	54629,
+	54631,	54647,	54667,	54673,	54679,	54709,	54713,	54721,	54727,	54751,
+	54767,	54773,	54779,	54787,	54799,	54829,	54833,	54851,	54869,	54877,
+	54881,	54907,	54917,	54919,	54941,	54949,	54959,	54973,	54979,	54983,
+	55001,	55009,	55021,	55049,	55051,	55057,	55061,	55073,	55079,	55103,
+	55109,	55117,	55127,	55147,	55163,	55171,	55201,	55207,	55213,	55217,
+	55219,	55229,	55243,	55249,	55259,	55291,	55313,	55331,	55333,	55337,
+	55339,	55343,	55351,	55373,	55381,	55399,	55411,	55439,	55441,	55457,
+	55469,	55487,	55501,	55511,	55529,	55541,	55547,	55579,	55589,	55603,
+	55609,	55619,	55621,	55631,	55633,	55639,	55661,	55663,	55667,	55673,
+	55681,	55691,	55697,	55711,	55717,	55721,	55733,	55763,	55787,	55793,
+	55799,	55807,	55813,	55817,	55819,	55823,	55829,	55837,	55843,	55849,
+	55871,	55889,	55897,	55901,	55903,	55921,	55927,	55931,	55933,	55949,
+	55967,	55987,	55997,	56003,	56009,	56039,	56041,	56053,	56081,	56087,
+	56093,	56099,	56101,	56113,	56123,	56131,	56149,	56167,	56171,	56179,
+	56197,	56207,	56209,	56237,	56239,	56249,	56263,	56267,	56269,	56299,
+	56311,	56333,	56359,	56369,	56377,	56383,	56393,	56401,	56417,	56431,
+	56437,	56443,	56453,	56467,	56473,	56477,	56479,	56489,	56501,	56503,
+	56509,	56519,	56527,	56531,	56533,	56543,	56569,	56591,	56597,	56599,
+	56611,	56629,	56633,	56659,	56663,	56671,	56681,	56687,	56701,	56711,
+	56713,	56731,	56737,	56747,	56767,	56773,	56779,	56783,	56807,	56809,
+	56813,	56821,	56827,	56843,	56857,	56873,	56891,	56893,	56897,	56909,
+	56911,	56921,	56923,	56929,	56941,	56951,	56957,	56963,	56983,	56989,
+	56993,	56999,	57037,	57041,	57047,	57059,	57073,	57077,	57089,	57097,
+	57107,	57119,	57131,	57139,	57143,	57149,	57163,	57173,	57179,	57191,
+	57193,	57203,	57221,	57223,	57241,	57251,	57259,	57269,	57271,	57283,
+	57287,	57301,	57329,	57331,	57347,	57349,	57367,	57373,	57383,	57389,
+	57397,	57413,	57427,	57457,	57467,	57487,	57493,	57503,	57527,	57529,
+	57557,	57559,	57571,	57587,	57593,	57601,	57637,	57641,	57649,	57653,
+	57667,	57679,	57689,	57697,	57709,	57713,	57719,	57727,	57731,	57737,
+	57751,	57773,	57781,	57787,	57791,	57793,	57803,	57809,	57829,	57839,
+	57847,	57853,	57859,	57881,	57899,	57901,	57917,	57923,	57943,	57947,
+	57973,	57977,	57991,	58013,	58027,	58031,	58043,	58049,	58057,	58061,
+	58067,	58073,	58099,	58109,	58111,	58129,	58147,	58151,	58153,	58169,
+	58171,	58189,	58193,	58199,	58207,	58211,	58217,	58229,	58231,	58237,
+	58243,	58271,	58309,	58313,	58321,	58337,	58363,	58367,	58369,	58379,
+	58391,	58393,	58403,	58411,	58417,	58427,	58439,	58441,	58451,	58453,
+	58477,	58481,	58511,	58537,	58543,	58549,	58567,	58573,	58579,	58601,
+	58603,	58613,	58631,	58657,	58661,	58679,	58687,	58693,	58699,	58711,
+	58727,	58733,	58741,	58757,	58763,	58771,	58787,	58789,	58831,	58889,
+	58897,	58901,	58907,	58909,	58913,	58921,	58937,	58943,	58963,	58967,
+	58979,	58991,	58997,	59009,	59011,	59021,	59023,	59029,	59051,	59053,
+	59063,	59069,	59077,	59083,	59093,	59107,	59113,	59119,	59123,	59141,
+	59149,	59159,	59167,	59183,	59197,	59207,	59209,	59219,	59221,	59233,
+	59239,	59243,	59263,	59273,	59281,	59333,	59341,	59351,	59357,	59359,
+	59369,	59377,	59387,	59393,	59399,	59407,	59417,	59419,	59441,	59443,
+	59447,	59453,	59467,	59471,	59473,	59497,	59509,	59513,	59539,	59557,
+	59561,	59567,	59581,	59611,	59617,	59621,	59627,	59629,	59651,	59659,
+	59663,	59669,	59671,	59693,	59699,	59707,	59723,	59729,	59743,	59747,
+	59753,	59771,	59779,	59791,	59797,	59809,	59833,	59863,	59879,	59887,
+	59921,	59929,	59951,	59957,	59971,	59981,	59999,	60013,	60017,	60029,
+	60037,	60041,	60077,	60083,	60089,	60091,	60101,	60103,	60107,	60127,
+	60133,	60139,	60149,	60161,	60167,	60169,	60209,	60217,	60223,	60251,
+	60257,	60259,	60271,	60289,	60293,	60317,	60331,	60337,	60343,	60353,
+	60373,	60383,	60397,	60413,	60427,	60443,	60449,	60457,	60493,	60497,
+	60509,	60521,	60527,	60539,	60589,	60601,	60607,	60611,	60617,	60623,
+	60631,	60637,	60647,	60649,	60659,	60661,	60679,	60689,	60703,	60719,
+	60727,	60733,	60737,	60757,	60761,	60763,	60773,	60779,	60793,	60811,
+	60821,	60859,	60869,	60887,	60889,	60899,	60901,	60913,	60917,	60919,
+	60923,	60937,	60943,	60953,	60961,	61001,	61007,	61027,	61031,	61043,
+	61051,	61057,	61091,	61099,	61121,	61129,	61141,	61151,	61153,	61169,
+	61211,	61223,	61231,	61253,	61261,	61283,	61291,	61297,	61331,	61333,
+	61339,	61343,	61357,	61363,	61379,	61381,	61403,	61409,	61417,	61441,
+	61463,	61469,	61471,	61483,	61487,	61493,	61507,	61511,	61519,	61543,
+	61547,	61553,	61559,	61561,	61583,	61603,	61609,	61613,	61627,	61631,
+	61637,	61643,	61651,	61657,	61667,	61673,	61681,	61687,	61703,	61717,
+	61723,	61729,	61751,	61757,	61781,	61813,	61819,	61837,	61843,	61861,
+	61871,	61879,	61909,	61927,	61933,	61949,	61961,	61967,	61979,	61981,
+	61987,	61991,	62003,	62011,	62017,	62039,	62047,	62053,	62057,	62071,
+	62081,	62099,	62119,	62129,	62131,	62137,	62141,	62143,	62171,	62189,
+	62191,	62201,	62207,	62213,	62219,	62233,	62273,	62297,	62299,	62303,
+	62311,	62323,	62327,	62347,	62351,	62383,	62401,	62417,	62423,	62459,
+	62467,	62473,	62477,	62483,	62497,	62501,	62507,	62533,	62539,	62549,
+	62563,	62581,	62591,	62597,	62603,	62617,	62627,	62633,	62639,	62653,
+	62659,	62683,	62687,	62701,	62723,	62731,	62743,	62753,	62761,	62773,
+	62791,	62801,	62819,	62827,	62851,	62861,	62869,	62873,	62897,	62903,
+	62921,	62927,	62929,	62939,	62969,	62971,	62981,	62983,	62987,	62989,
+	63029,	63031,	63059,	63067,	63073,	63079,	63097,	63103,	63113,	63127,
+	63131,	63149,	63179,	63197,	63199,	63211,	63241,	63247,	63277,	63281,
+	63299,	63311,	63313,	63317,	63331,	63337,	63347,	63353,	63361,	63367,
+	63377,	63389,	63391,	63397,	63409,	63419,	63421,	63439,	63443,	63463,
+	63467,	63473,	63487,	63493,	63499,	63521,	63527,	63533,	63541,	63559,
+	63577,	63587,	63589,	63599,	63601,	63607,	63611,	63617,	63629,	63647,
+	63649,	63659,	63667,	63671,	63689,	63691,	63697,	63703,	63709,	63719,
+	63727,	63737,	63743,	63761,	63773,	63781,	63793,	63799,	63803,	63809,
+	63823,	63839,	63841,	63853,	63857,	63863,	63901,	63907,	63913,	63929,
+	63949,	63977,	63997,	64007,	64013,	64019,	64033,	64037,	64063,	64067,
+	64081,	64091,	64109,	64123,	64151,	64153,	64157,	64171,	64187,	64189,
+	64217,	64223,	64231,	64237,	64271,	64279,	64283,	64301,	64303,	64319,
+	64327,	64333,	64373,	64381,	64399,	64403,	64433,	64439,	64451,	64453,
+	64483,	64489,	64499,	64513,	64553,	64567,	64577,	64579,	64591,	64601,
+	64609,	64613,	64621,	64627,	64633,	64661,	64663,	64667,	64679,	64693,
+	64709,	64717,	64747,	64763,	64781,	64783,	64793,	64811,	64817,	64849,
+	64853,	64871,	64877,	64879,	64891,	64901,	64919,	64921,	64927,	64937,
+	64951,	64969,	64997,	65003,	65011,	65027,	65029,	65033,	65053,	65063,
+	65071,	65089,	65099,	65101,	65111,	65119,	65123,	65129,	65141,	65147,
+	65167,	65171,	65173,	65179,	65183,	65203,	65213,	65239,	65257,	65267,
+	65269,	65287,	65293,	65309,	65323,	65327,	65353,	65357,	65371,	65381,
+	65393,	65407,	65413,	65419,	65423,	65437,	65447,	65449,	65479,	65497,
+	65519,	65521,	65537,	65539,	65543,	65551,	65557,	65563,	65579,	65581,
+	65587,	65599,	65609,	65617,	65629,	65633,	65647,	65651,	65657,	65677,
+	65687,	65699,	65701,	65707,	65713,	65717,	65719,	65729,	65731,	65761,
+	65777,	65789,	65809,	65827,	65831,	65837,	65839,	65843,	65851,	65867,
+	65881,	65899,	65921,	65927,	65929,	65951,	65957,	65963,	65981,	65983,
+	65993,	66029,	66037,	66041,	66047,	66067,	66071,	66083,	66089,	66103,
+	66107,	66109,	66137,	66161,	66169,	66173,	66179,	66191,	66221,	66239,
+	66271,	66293,	66301,	66337,	66343,	66347,	66359,	66361,	66373,	66377,
+	66383,	66403,	66413,	66431,	66449,	66457,	66463,	66467,	66491,	66499,
+	66509,	66523,	66529,	66533,	66541,	66553,	66569,	66571,	66587,	66593,
+	66601,	66617,	66629,	66643,	66653,	66683,	66697,	66701,	66713,	66721,
+	66733,	66739,	66749,	66751,	66763,	66791,	66797,	66809,	66821,	66841,
+	66851,	66853,	66863,	66877,	66883,	66889,	66919,	66923,	66931,	66943,
+	66947,	66949,	66959,	66973,	66977,	67003,	67021,	67033,	67043,	67049,
+	67057,	67061,	67073,	67079,	67103,	67121,	67129,	67139,	67141,	67153,
+	67157,	67169,	67181,	67187,	67189,	67211,	67213,	67217,	67219,	67231,
+	67247,	67261,	67271,	67273,	67289,	67307,	67339,	67343,	67349,	67369,
+	67391,	67399,	67409,	67411,	67421,	67427,	67429,	67433,	67447,	67453,
+	67477,	67481,	67489,	67493,	67499,	67511,	67523,	67531,	67537,	67547,
+	67559,	67567,	67577,	67579,	67589,	67601,	67607,	67619,	67631,	67651,
+	67679,	67699,	67709,	67723,	67733,	67741,	67751,	67757,	67759,	67763,
+	67777,	67783,	67789,	67801,	67807,	67819,	67829,	67843,	67853,	67867,
+	67883,	67891,	67901,	67927,	67931,	67933,	67939,	67943,	67957,	67961,
+	67967,	67979,	67987,	67993,	68023,	68041,	68053,	68059,	68071,	68087,
+	68099,	68111,	68113,	68141,	68147,	68161,	68171,	68207,	68209,	68213,
+	68219,	68227,	68239,	68261,	68279,	68281,	68311,	68329,	68351,	68371,
+	68389,	68399,	68437,	68443,	68447,	68449,	68473,	68477,	68483,	68489,
+	68491,	68501,	68507,	68521,	68531,	68539,	68543,	68567,	68581,	68597,
+	68611,	68633,	68639,	68659,	68669,	68683,	68687,	68699,	68711,	68713,
+	68729,	68737,	68743,	68749,	68767,	68771,	68777,	68791,	68813,	68819,
+	68821,	68863,	68879,	68881,	68891,	68897,	68899,	68903,	68909,	68917,
+	68927,	68947,	68963,	68993,	69001,	69011,	69019,	69029,	69031,	69061,
+	69067,	69073,	69109,	69119,	69127,	69143,	69149,	69151,	69163,	69191,
+	69193,	69197,	69203,	69221,	69233,	69239,	69247,	69257,	69259,	69263,
+	69313,	69317,	69337,	69341,	69371,	69379,	69383,	69389,	69401,	69403,
+	69427,	69431,	69439,	69457,	69463,	69467,	69473,	69481,	69491,	69493,
+	69497,	69499,	69539,	69557,	69593,	69623,	69653,	69661,	69677,	69691,
+	69697,	69709,	69737,	69739,	69761,	69763,	69767,	69779,	69809,	69821,
+	69827,	69829,	69833,	69847,	69857,	69859,	69877,	69899,	69911,	69929,
+	69931,	69941,	69959,	69991,	69997,	70001,	70003,	70009,	70019,	70039,
+	70051,	70061,	70067,	70079,	70099,	70111,	70117,	70121,	70123,	70139,
+	70141,	70157,	70163,	70177,	70181,	70183,	70199,	70201,	70207,	70223,
+	70229,	70237,	70241,	70249,	70271,	70289,	70297,	70309,	70313,	70321,
+	70327,	70351,	70373,	70379,	70381,	70393,	70423,	70429,	70439,	70451,
+	70457,	70459,	70481,	70487,	70489,	70501,	70507,	70529,	70537,	70549,
+	70571,	70573,	70583,	70589,	70607,	70619,	70621,	70627,	70639,	70657,
+	70663,	70667,	70687,	70709,	70717,	70729,	70753,	70769,	70783,	70793,
+	70823,	70841,	70843,	70849,	70853,	70867,	70877,	70879,	70891,	70901,
+	70913,	70919,	70921,	70937,	70949,	70951,	70957,	70969,	70979,	70981,
+	70991,	70997,	70999,	71011,	71023,	71039,	71059,	71069,	71081,	71089,
+	71119,	71129,	71143,	71147,	71153,	71161,	71167,	71171,	71191,	71209,
+	71233,	71237,	71249,	71257,	71261,	71263,	71287,	71293,	71317,	71327,
+	71329,	71333,	71339,	71341,	71347,	71353,	71359,	71363,	71387,	71389,
+	71399,	71411,	71413,	71419,	71429,	71437,	71443,	71453,	71471,	71473,
+	71479,	71483,	71503,	71527,	71537,	71549,	71551,	71563,	71569,	71593,
+	71597,	71633,	71647,	71663,	71671,	71693,	71699,	71707,	71711,	71713,
+	71719,	71741,	71761,	71777,	71789,	71807,	71809,	71821,	71837,	71843,
+	71849,	71861,	71867,	71879,	71881,	71887,	71899,	71909,	71917,	71933,
+	71941,	71947,	71963,	71971,	71983,	71987,	71993,	71999,	72019,	72031,
+	72043,	72047,	72053,	72073,	72077,	72089,	72091,	72101,	72103,	72109,
+	72139,	72161,	72167,	72169,	72173,	72211,	72221,	72223,	72227,	72229,
+	72251,	72253,	72269,	72271,	72277,	72287,	72307,	72313,	72337,	72341,
+	72353,	72367,	72379,	72383,	72421,	72431,	72461,	72467,	72469,	72481,
+	72493,	72497,	72503,	72533,	72547,	72551,	72559,	72577,	72613,	72617,
+	72623,	72643,	72647,	72649,	72661,	72671,	72673,	72679,	72689,	72701,
+	72707,	72719,	72727,	72733,	72739,	72763,	72767,	72797,	72817,	72823,
+	72859,	72869,	72871,	72883,	72889,	72893,	72901,	72907,	72911,	72923,
+	72931,	72937,	72949,	72953,	72959,	72973,	72977,	72997,	73009,	73013,
+	73019,	73037,	73039,	73043,	73061,	73063,	73079,	73091,	73121,	73127,
+	73133,	73141,	73181,	73189,	73237,	73243,	73259,	73277,	73291,	73303,
+	73309,	73327,	73331,	73351,	73361,	73363,	73369,	73379,	73387,	73417,
+	73421,	73433,	73453,	73459,	73471,	73477,	73483,	73517,	73523,	73529,
+	73547,	73553,	73561,	73571,	73583,	73589,	73597,	73607,	73609,	73613,
+	73637,	73643,	73651,	73673,	73679,	73681,	73693,	73699,	73709,	73721,
+	73727,	73751,	73757,	73771,	73783,	73819,	73823,	73847,	73849,	73859,
+	73867,	73877,	73883,	73897,	73907,	73939,	73943,	73951,	73961,	73973,
+	73999,	74017,	74021,	74027,	74047,	74051,	74071,	74077,	74093,	74099,
+	74101,	74131,	74143,	74149,	74159,	74161,	74167,	74177,	74189,	74197,
+	74201,	74203,	74209,	74219,	74231,	74257,	74279,	74287,	74293,	74297,
+	74311,	74317,	74323,	74353,	74357,	74363,	74377,	74381,	74383,	74411,
+	74413,	74419,	74441,	74449,	74453,	74471,	74489,	74507,	74509,	74521,
+	74527,	74531,	74551,	74561,	74567,	74573,	74587,	74597,	74609,	74611,
+	74623,	74653,	74687,	74699,	74707,	74713,	74717,	74719,	74729,	74731,
+	74747,	74759,	74761,	74771,	74779,	74797,	74821,	74827,	74831,	74843,
+	74857,	74861,	74869,	74873,	74887,	74891,	74897,	74903,	74923,	74929,
+	74933,	74941,	74959,	75011,	75013,	75017,	75029,	75037,	75041,	75079,
+	75083,	75109,	75133,	75149,	75161,	75167,	75169,	75181,	75193,	75209,
+	75211,	75217,	75223,	75227,	75239,	75253,	75269,	75277,	75289,	75307,
+	75323,	75329,	75337,	75347,	75353,	75367,	75377,	75389,	75391,	75401,
+	75403,	75407,	75431,	75437,	75479,	75503,	75511,	75521,	75527,	75533,
+	75539,	75541,	75553,	75557,	75571,	75577,	75583,	75611,	75617,	75619,
+	75629,	75641,	75653,	75659,	75679,	75683,	75689,	75703,	75707,	75709,
+	75721,	75731,	75743,	75767,	75773,	75781,	75787,	75793,	75797,	75821,
+	75833,	75853,	75869,	75883,	75913,	75931,	75937,	75941,	75967,	75979,
+	75983,	75989,	75991,	75997,	76001,	76003,	76031,	76039,	76079,	76081,
+	76091,	76099,	76103,	76123,	76129,	76147,	76157,	76159,	76163,	76207,
+	76213,	76231,	76243,	76249,	76253,	76259,	76261,	76283,	76289,	76303,
+	76333,	76343,	76367,	76369,	76379,	76387,	76403,	76421,	76423,	76441,
+	76463,	76471,	76481,	76487,	76493,	76507,	76511,	76519,	76537,	76541,
+	76543,	76561,	76579,	76597,	76603,	76607,	76631,	76649,	76651,	76667,
+	76673,	76679,	76697,	76717,	76733,	76753,	76757,	76771,	76777,	76781,
+	76801,	76819,	76829,	76831,	76837,	76847,	76871,	76873,	76883,	76907,
+	76913,	76919,	76943,	76949,	76961,	76963,	76991,	77003,	77017,	77023,
+	77029,	77041,	77047,	77069,	77081,	77093,	77101,	77137,	77141,	77153,
+	77167,	77171,	77191,	77201,	77213,	77237,	77239,	77243,	77249,	77261,
+	77263,	77267,	77269,	77279,	77291,	77317,	77323,	77339,	77347,	77351,
+	77359,	77369,	77377,	77383,	77417,	77419,	77431,	77447,	77471,	77477,
+	77479,	77489,	77491,	77509,	77513,	77521,	77527,	77543,	77549,	77551,
+	77557,	77563,	77569,	77573,	77587,	77591,	77611,	77617,	77621,	77641,
+	77647,	77659,	77681,	77687,	77689,	77699,	77711,	77713,	77719,	77723,
+	77731,	77743,	77747,	77761,	77773,	77783,	77797,	77801,	77813,	77839,
+	77849,	77863,	77867,	77893,	77899,	77929,	77933,	77951,	77969,	77977,
+	77983,	77999,	78007,	78017,	78031,	78041,	78049,	78059,	78079,	78101,
+	78121,	78137,	78139,	78157,	78163,	78167,	78173,	78179,	78191,	78193,
+	78203,	78229,	78233,	78241,	78259,	78277,	78283,	78301,	78307,	78311,
+	78317,	78341,	78347,	78367,	78401,	78427,	78437,	78439,	78467,	78479,
+	78487,	78497,	78509,	78511,	78517,	78539,	78541,	78553,	78569,	78571,
+	78577,	78583,	78593,	78607,	78623,	78643,	78649,	78653,	78691,	78697,
+	78707,	78713,	78721,	78737,	78779,	78781,	78787,	78791,	78797,	78803,
+	78809,	78823,	78839,	78853,	78857,	78877,	78887,	78889,	78893,	78901,
+	78919,	78929,	78941,	78977,	78979,	78989,	79031,	79039,	79043,	79063,
+	79087,	79103,	79111,	79133,	79139,	79147,	79151,	79153,	79159,	79181,
+	79187,	79193,	79201,	79229,	79231,	79241,	79259,	79273,	79279,	79283,
+	79301,	79309,	79319,	79333,	79337,	79349,	79357,	79367,	79379,	79393,
+	79397,	79399,	79411,	79423,	79427,	79433,	79451,	79481,	79493,	79531,
+	79537,	79549,	79559,	79561,	79579,	79589,	79601,	79609,	79613,	79621,
+	79627,	79631,	79633,	79657,	79669,	79687,	79691,	79693,	79697,	79699,
+	79757,	79769,	79777,	79801,	79811,	79813,	79817,	79823,	79829,	79841,
+	79843,	79847,	79861,	79867,	79873,	79889,	79901,	79903,	79907,	79939,
+	79943,	79967,	79973,	79979,	79987,	79997,	79999,	80021,	80039,	80051,
+	80071,	80077,	80107,	80111,	80141,	80147,	80149,	80153,	80167,	80173,
+	80177,	80191,	80207,	80209,	80221,	80231,	80233,	80239,	80251,	80263,
+	80273,	80279,	80287,	80309,	80317,	80329,	80341,	80347,	80363,	80369,
+	80387,	80407,	80429,	80447,	80449,	80471,	80473,	80489,	80491,	80513,
+	80527,	80537,	80557,	80567,	80599,	80603,	80611,	80621,	80627,	80629,
+	80651,	80657,	80669,	80671,	80677,	80681,	80683,	80687,	80701,	80713,
+	80737,	80747,	80749,	80761,	80777,	80779,	80783,	80789,	80803,	80809,
+	80819,	80831,	80833,	80849,	80863,	80897,	80909,	80911,	80917,	80923,
+	80929,	80933,	80953,	80963,	80989,	81001,	81013,	81017,	81019,	81023,
+	81031,	81041,	81043,	81047,	81049,	81071,	81077,	81083,	81097,	81101,
+	81119,	81131,	81157,	81163,	81173,	81181,	81197,	81199,	81203,	81223,
+	81233,	81239,	81281,	81283,	81293,	81299,	81307,	81331,	81343,	81349,
+	81353,	81359,	81371,	81373,	81401,	81409,	81421,	81439,	81457,	81463,
+	81509,	81517,	81527,	81533,	81547,	81551,	81553,	81559,	81563,	81569,
+	81611,	81619,	81629,	81637,	81647,	81649,	81667,	81671,	81677,	81689,
+	81701,	81703,	81707,	81727,	81737,	81749,	81761,	81769,	81773,	81799,
+	81817,	81839,	81847,	81853,	81869,	81883,	81899,	81901,	81919,	81929,
+	81931,	81937,	81943,	81953,	81967,	81971,	81973,	82003,	82007,	82009,
+	82013,	82021,	82031,	82037,	82039,	82051,	82067,	82073,	82129,	82139,
+	82141,	82153,	82163,	82171,	82183,	82189,	82193,	82207,	82217,	82219,
+	82223,	82231,	82237,	82241,	82261,	82267,	82279,	82301,	82307,	82339,
+	82349,	82351,	82361,	82373,	82387,	82393,	82421,	82457,	82463,	82469,
+	82471,	82483,	82487,	82493,	82499,	82507,	82529,	82531,	82549,	82559,
+	82561,	82567,	82571,	82591,	82601,	82609,	82613,	82619,	82633,	82651,
+	82657,	82699,	82721,	82723,	82727,	82729,	82757,	82759,	82763,	82781,
+	82787,	82793,	82799,	82811,	82813,	82837,	82847,	82883,	82889,	82891,
+	82903,	82913,	82939,	82963,	82981,	82997,	83003,	83009,	83023,	83047,
+	83059,	83063,	83071,	83077,	83089,	83093,	83101,	83117,	83137,	83177,
+	83203,	83207,	83219,	83221,	83227,	83231,	83233,	83243,	83257,	83267,
+	83269,	83273,	83299,	83311,	83339,	83341,	83357,	83383,	83389,	83399,
+	83401,	83407,	83417,	83423,	83431,	83437,	83443,	83449,	83459,	83471,
+	83477,	83497,	83537,	83557,	83561,	83563,	83579,	83591,	83597,	83609,
+	83617,	83621,	83639,	83641,	83653,	83663,	83689,	83701,	83717,	83719,
+	83737,	83761,	83773,	83777,	83791,	83813,	83833,	83843,	83857,	83869,
+	83873,	83891,	83903,	83911,	83921,	83933,	83939,	83969,	83983,	83987,
+	84011,	84017,	84047,	84053,	84059,	84061,	84067,	84089,	84121,	84127,
+	84131,	84137,	84143,	84163,	84179,	84181,	84191,	84199,	84211,	84221,
+	84223,	84229,	84239,	84247,	84263,	84299,	84307,	84313,	84317,	84319,
+	84347,	84349,	84377,	84389,	84391,	84401,	84407,	84421,	84431,	84437,
+	84443,	84449,	84457,	84463,	84467,	84481,	84499,	84503,	84509,	84521,
+	84523,	84533,	84551,	84559,	84589,	84629,	84631,	84649,	84653,	84659,
+	84673,	84691,	84697,	84701,	84713,	84719,	84731,	84737,	84751,	84761,
+	84787,	84793,	84809,	84811,	84827,	84857,	84859,	84869,	84871,	84913,
+	84919,	84947,	84961,	84967,	84977,	84979,	84991,	85009,	85021,	85027,
+	85037,	85049,	85061,	85081,	85087,	85091,	85093,	85103,	85109,	85121,
+	85133,	85147,	85159,	85193,	85199,	85201,	85213,	85223,	85229,	85237,
+	85243,	85247,	85259,	85297,	85303,	85313,	85331,	85333,	85361,	85363,
+	85369,	85381,	85411,	85427,	85429,	85439,	85447,	85451,	85453,	85469,
+	85487,	85513,	85517,	85523,	85531,	85549,	85571,	85577,	85597,	85601,
+	85607,	85619,	85621,	85627,	85639,	85643,	85661,	85667,	85669,	85691,
+	85703,	85711,	85717,	85733,	85751,	85781,	85793,	85817,	85819,	85829,
+	85831,	85837,	85843,	85847,	85853,	85889,	85903,	85909,	85931,	85933,
+	85991,	85999,	86011,	86017,	86027,	86029,	86069,	86077,	86083,	86111,
+	86113,	86117,	86131,	86137,	86143,	86161,	86171,	86179,	86183,	86197,
+	86201,	86209,	86239,	86243,	86249,	86257,	86263,	86269,	86287,	86291,
+	86293,	86297,	86311,	86323,	86341,	86351,	86353,	86357,	86369,	86371,
+	86381,	86389,	86399,	86413,	86423,	86441,	86453,	86461,	86467,	86477,
+	86491,	86501,	86509,	86531,	86533,	86539,	86561,	86573,	86579,	86587,
+	86599,	86627,	86629,	86677,	86689,	86693,	86711,	86719,	86729,	86743,
+	86753,	86767,	86771,	86783,	86813,	86837,	86843,	86851,	86857,	86861,
+	86869,	86923,	86927,	86929,	86939,	86951,	86959,	86969,	86981,	86993,
+	87011,	87013,	87037,	87041,	87049,	87071,	87083,	87103,	87107,	87119,
+	87121,	87133,	87149,	87151,	87179,	87181,	87187,	87211,	87221,	87223,
+	87251,	87253,	87257,	87277,	87281,	87293,	87299,	87313,	87317,	87323,
+	87337,	87359,	87383,	87403,	87407,	87421,	87427,	87433,	87443,	87473,
+	87481,	87491,	87509,	87511,	87517,	87523,	87539,	87541,	87547,	87553,
+	87557,	87559,	87583,	87587,	87589,	87613,	87623,	87629,	87631,	87641,
+	87643,	87649,	87671,	87679,	87683,	87691,	87697,	87701,	87719,	87721,
+	87739,	87743,	87751,	87767,	87793,	87797,	87803,	87811,	87833,	87853,
+	87869,	87877,	87881,	87887,	87911,	87917,	87931,	87943,	87959,	87961,
+	87973,	87977,	87991,	88001,	88003,	88007,	88019,	88037,	88069,	88079,
+	88093,	88117,	88129,	88169,	88177,	88211,	88223,	88237,	88241,	88259,
+	88261,	88289,	88301,	88321,	88327,	88337,	88339,	88379,	88397,	88411,
+	88423,	88427,	88463,	88469,	88471,	88493,	88499,	88513,	88523,	88547,
+	88589,	88591,	88607,	88609,	88643,	88651,	88657,	88661,	88663,	88667,
+	88681,	88721,	88729,	88741,	88747,	88771,	88789,	88793,	88799,	88801,
+	88807,	88811,	88813,	88817,	88819,	88843,	88853,	88861,	88867,	88873,
+	88883,	88897,	88903,	88919,	88937,	88951,	88969,	88993,	88997,	89003,
+	89009,	89017,	89021,	89041,	89051,	89057,	89069,	89071,	89083,	89087,
+	89101,	89107,	89113,	89119,	89123,	89137,	89153,	89189,	89203,	89209,
+	89213,	89227,	89231,	89237,	89261,	89269,	89273,	89293,	89303,	89317,
+	89329,	89363,	89371,	89381,	89387,	89393,	89399,	89413,	89417,	89431,
+	89443,	89449,	89459,	89477,	89491,	89501,	89513,	89519,	89521,	89527,
+	89533,	89561,	89563,	89567,	89591,	89597,	89599,	89603,	89611,	89627,
+	89633,	89653,	89657,	89659,	89669,	89671,	89681,	89689,	89753,	89759,
+	89767,	89779,	89783,	89797,	89809,	89819,	89821,	89833,	89839,	89849,
+	89867,	89891,	89897,	89899,	89909,	89917,	89923,	89939,	89959,	89963,
+	89977,	89983,	89989,	90001,	90007,	90011,	90017,	90019,	90023,	90031,
+	90053,	90059,	90067,	90071,	90073,	90089,	90107,	90121,	90127,	90149,
+	90163,	90173,	90187,	90191,	90197,	90199,	90203,	90217,	90227,	90239,
+	90247,	90263,	90271,	90281,	90289,	90313,	90353,	90359,	90371,	90373,
+	90379,	90397,	90401,	90403,	90407,	90437,	90439,	90469,	90473,	90481,
+	90499,	90511,	90523,	90527,	90529,	90533,	90547,	90583,	90599,	90617,
+	90619,	90631,	90641,	90647,	90659,	90677,	90679,	90697,	90703,	90709,
+	90731,	90749,	90787,	90793,	90803,	90821,	90823,	90833,	90841,	90847,
+	90863,	90887,	90901,	90907,	90911,	90917,	90931,	90947,	90971,	90977,
+	90989,	90997,	91009,	91019,	91033,	91079,	91081,	91097,	91099,	91121,
+	91127,	91129,	91139,	91141,	91151,	91153,	91159,	91163,	91183,	91193,
+	91199,	91229,	91237,	91243,	91249,	91253,	91283,	91291,	91297,	91303,
+	91309,	91331,	91367,	91369,	91373,	91381,	91387,	91393,	91397,	91411,
+	91423,	91433,	91453,	91457,	91459,	91463,	91493,	91499,	91513,	91529,
+	91541,	91571,	91573,	91577,	91583,	91591,	91621,	91631,	91639,	91673,
+	91691,	91703,	91711,	91733,	91753,	91757,	91771,	91781,	91801,	91807,
+	91811,	91813,	91823,	91837,	91841,	91867,	91873,	91909,	91921,	91939,
+	91943,	91951,	91957,	91961,	91967,	91969,	91997,	92003,	92009,	92033,
+	92041,	92051,	92077,	92083,	92107,	92111,	92119,	92143,	92153,	92173,
+	92177,	92179,	92189,	92203,	92219,	92221,	92227,	92233,	92237,	92243,
+	92251,	92269,	92297,	92311,	92317,	92333,	92347,	92353,	92357,	92363,
+	92369,	92377,	92381,	92383,	92387,	92399,	92401,	92413,	92419,	92431,
+	92459,	92461,	92467,	92479,	92489,	92503,	92507,	92551,	92557,	92567,
+	92569,	92581,	92593,	92623,	92627,	92639,	92641,	92647,	92657,	92669,
+	92671,	92681,	92683,	92693,	92699,	92707,	92717,	92723,	92737,	92753,
+	92761,	92767,	92779,	92789,	92791,	92801,	92809,	92821,	92831,	92849,
+	92857,	92861,	92863,	92867,	92893,	92899,	92921,	92927,	92941,	92951,
+	92957,	92959,	92987,	92993,	93001,	93047,	93053,	93059,	93077,	93083,
+	93089,	93097,	93103,	93113,	93131,	93133,	93139,	93151,	93169,	93179,
+	93187,	93199,	93229,	93239,	93241,	93251,	93253,	93257,	93263,	93281,
+	93283,	93287,	93307,	93319,	93323,	93329,	93337,	93371,	93377,	93383,
+	93407,	93419,	93427,	93463,	93479,	93481,	93487,	93491,	93493,	93497,
+	93503,	93523,	93529,	93553,	93557,	93559,	93563,	93581,	93601,	93607,
+	93629,	93637,	93683,	93701,	93703,	93719,	93739,	93761,	93763,	93787,
+	93809,	93811,	93827,	93851,	93871,	93887,	93889,	93893,	93901,	93911,
+	93913,	93923,	93937,	93941,	93949,	93967,	93971,	93979,	93983,	93997,
+	94007,	94009,	94033,	94049,	94057,	94063,	94079,	94099,	94109,	94111,
+	94117,	94121,	94151,	94153,	94169,	94201,	94207,	94219,	94229,	94253,
+	94261,	94273,	94291,	94307,	94309,	94321,	94327,	94331,	94343,	94349,
+	94351,	94379,	94397,	94399,	94421,	94427,	94433,	94439,	94441,	94447,
+	94463,	94477,	94483,	94513,	94529,	94531,	94541,	94543,	94547,	94559,
+	94561,	94573,	94583,	94597,	94603,	94613,	94621,	94649,	94651,	94687,
+	94693,	94709,	94723,	94727,	94747,	94771,	94777,	94781,	94789,	94793,
+	94811,	94819,	94823,	94837,	94841,	94847,	94849,	94873,	94889,	94903,
+	94907,	94933,	94949,	94951,	94961,	94993,	94999,	95003,	95009,	95021,
+	95027,	95063,	95071,	95083,	95087,	95089,	95093,	95101,	95107,	95111,
+	95131,	95143,	95153,	95177,	95189,	95191,	95203,	95213,	95219,	95231,
+	95233,	95239,	95257,	95261,	95267,	95273,	95279,	95287,	95311,	95317,
+	95327,	95339,	95369,	95383,	95393,	95401,	95413,	95419,	95429,	95441,
+	95443,	95461,	95467,	95471,	95479,	95483,	95507,	95527,	95531,	95539,
+	95549,	95561,	95569,	95581,	95597,	95603,	95617,	95621,	95629,	95633,
+	95651,	95701,	95707,	95713,	95717,	95723,	95731,	95737,	95747,	95773,
+	95783,	95789,	95791,	95801,	95803,	95813,	95819,	95857,	95869,	95873,
+	95881,	95891,	95911,	95917,	95923,	95929,	95947,	95957,	95959,	95971,
+	95987,	95989,	96001,	96013,	96017,	96043,	96053,	96059,	96079,	96097,
+	96137,	96149,	96157,	96167,	96179,	96181,	96199,	96211,	96221,	96223,
+	96233,	96259,	96263,	96269,	96281,	96289,	96293,	96323,	96329,	96331,
+	96337,	96353,	96377,	96401,	96419,	96431,	96443,	96451,	96457,	96461,
+	96469,	96479,	96487,	96493,	96497,	96517,	96527,	96553,	96557,	96581,
+	96587,	96589,	96601,	96643,	96661,	96667,	96671,	96697,	96703,	96731,
+	96737,	96739,	96749,	96757,	96763,	96769,	96779,	96787,	96797,	96799,
+	96821,	96823,	96827,	96847,	96851,	96857,	96893,	96907,	96911,	96931,
+	96953,	96959,	96973,	96979,	96989,	96997,	97001,	97003,	97007,	97021,
+	97039,	97073,	97081,	97103,	97117,	97127,	97151,	97157,	97159,	97169,
+	97171,	97177,	97187,	97213,	97231,	97241,	97259,	97283,	97301,	97303,
+	97327,	97367,	97369,	97373,	97379,	97381,	97387,	97397,	97423,	97429,
+	97441,	97453,	97459,	97463,	97499,	97501,	97511,	97523,	97547,	97549,
+	97553,	97561,	97571,	97577,	97579,	97583,	97607,	97609,	97613,	97649,
+	97651,	97673,	97687,	97711,	97729,	97771,	97777,	97787,	97789,	97813,
+	97829,	97841,	97843,	97847,	97849,	97859,	97861,	97871,	97879,	97883,
+	97919,	97927,	97931,	97943,	97961,	97967,	97973,	97987,	98009,	98011,
+	98017,	98041,	98047,	98057,	98081,	98101,	98123,	98129,	98143,	98179,
+	98207,	98213,	98221,	98227,	98251,	98257,	98269,	98297,	98299,	98317,
+	98321,	98323,	98327,	98347,	98369,	98377,	98387,	98389,	98407,	98411,
+	98419,	98429,	98443,	98453,	98459,	98467,	98473,	98479,	98491,	98507,
+	98519,	98533,	98543,	98561,	98563,	98573,	98597,	98621,	98627,	98639,
+	98641,	98663,	98669,	98689,	98711,	98713,	98717,	98729,	98731,	98737,
+	98773,	98779,	98801,	98807,	98809,	98837,	98849,	98867,	98869,	98873,
+	98887,	98893,	98897,	98899,	98909,	98911,	98927,	98929,	98939,	98947,
+	98953,	98963,	98981,	98993,	98999,	99013,	99017,	99023,	99041,	99053,
+	99079,	99083,	99089,	99103,	99109,	99119,	99131,	99133,	99137,	99139,
+	99149,	99173,	99181,	99191,	99223,	99233,	99241,	99251,	99257,	99259,
+	99277,	99289,	99317,	99347,	99349,	99367,	99371,	99377,	99391,	99397,
+	99401,	99409,	99431,	99439,	99469,	99487,	99497,	99523,	99527,	99529,
+	99551,	99559,	99563,	99571,	99577,	99581,	99607,	99611,	99623,	99643,
+	99661,	99667,	99679,	99689,	99707,	99709,	99713,	99719,	99721,	99733,
+	99761,	99767,	99787,	99793,	99809,	99817,	99823,	99829,	99833,	99839,
+	99859,	99871,	99877,	99881,	99901,	99907,	99923,	99929,	99961,	99971,
+	99989,	99991,	100003,	100019,	100043,	100049,	100057,	100069,	100103,	100109,
+	100129,	100151,	100153,	100169,	100183,	100189,	100193,	100207,	100213,	100237,
+	100267,	100271,	100279,	100291,	100297,	100313,	100333,	100343,	100357,	100361,
+	100363,	100379,	100391,	100393,	100403,	100411,	100417,	100447,	100459,	100469,
+	100483,	100493,	100501,	100511,	100517,	100519,	100523,	100537,	100547,	100549,
+	100559,	100591,	100609,	100613,	100621,	100649,	100669,	100673,	100693,	100699,
+	100703,	100733,	100741,	100747,	100769,	100787,	100799,	100801,	100811,	100823,
+	100829,	100847,	100853,	100907,	100913,	100927,	100931,	100937,	100943,	100957,
+	100981,	100987,	100999,	101009,	101021,	101027,	101051,	101063,	101081,	101089,
+	101107,	101111,	101113,	101117,	101119,	101141,	101149,	101159,	101161,	101173,
+	101183,	101197,	101203,	101207,	101209,	101221,	101267,	101273,	101279,	101281,
+	101287,	101293,	101323,	101333,	101341,	101347,	101359,	101363,	101377,	101383,
+	101399,	101411,	101419,	101429,	101449,	101467,	101477,	101483,	101489,	101501,
+	101503,	101513,	101527,	101531,	101533,	101537,	101561,	101573,	101581,	101599,
+	101603,	101611,	101627,	101641,	101653,	101663,	101681,	101693,	101701,	101719,
+	101723,	101737,	101741,	101747,	101749,	101771,	101789,	101797,	101807,	101833,
+	101837,	101839,	101863,	101869,	101873,	101879,	101891,	101917,	101921,	101929,
+	101939,	101957,	101963,	101977,	101987,	101999,	102001,	102013,	102019,	102023,
+	102031,	102043,	102059,	102061,	102071,	102077,	102079,	102101,	102103,	102107,
+	102121,	102139,	102149,	102161,	102181,	102191,	102197,	102199,	102203,	102217,
+	102229,	102233,	102241,	102251,	102253,	102259,	102293,	102299,	102301,	102317,
+	102329,	102337,	102359,	102367,	102397,	102407,	102409,	102433,	102437,	102451,
+	102461,	102481,	102497,	102499,	102503,	102523,	102533,	102539,	102547,	102551,
+	102559,	102563,	102587,	102593,	102607,	102611,	102643,	102647,	102653,	102667,
+	102673,	102677,	102679,	102701,	102761,	102763,	102769,	102793,	102797,	102811,
+	102829,	102841,	102859,	102871,	102877,	102881,	102911,	102913,	102929,	102931,
+	102953,	102967,	102983,	103001,	103007,	103043,	103049,	103067,	103069,	103079,
+	103087,	103091,	103093,	103099,	103123,	103141,	103171,	103177,	103183,	103217,
+	103231,	103237,	103289,	103291,	103307,	103319,	103333,	103349,	103357,	103387,
+	103391,	103393,	103399,	103409,	103421,	103423,	103451,	103457,	103471,	103483,
+	103511,	103529,	103549,	103553,	103561,	103567,	103573,	103577,	103583,	103591,
+	103613,	103619,	103643,	103651,	103657,	103669,	103681,	103687,	103699,	103703,
+	103723,	103769,	103787,	103801,	103811,	103813,	103837,	103841,	103843,	103867,
+	103889,	103903,	103913,	103919,	103951,	103963,	103967,	103969,	103979,	103981,
+	103991,	103993,	103997,	104003,	104009,	104021,	104033,	104047,	104053,	104059,
+	104087,	104089,	104107,	104113,	104119,	104123,	104147,	104149,	104161,	104173,
+	104179,	104183,	104207,	104231,	104233,	104239,	104243,	104281,	104287,	104297,
+	104309,	104311,	104323,	104327,	104347,	104369,	104381,	104383,	104393,	104399,
+	104417,	104459,	104471,	104473,	104479,	104491,	104513,	104527,	104537,	104543,
+	104549,	104551,	104561,	104579,	104593,	104597,	104623,	104639,	104651,	104659,
+	104677,	104681,	104683,	104693,	104701,	104707,	104711,	104717,	104723,	104729,
+};
+
+//  return 1 if p is divisable by sp, 0 otherwise
+static int
+divides(mpint *dividend, ulong divisor)
+{
+	mpdigit d[2], q;
+	int i;
+
+	d[1] = 0;
+	for(i = dividend->top-1; i >= 0; i--){
+		d[0] = dividend->p[i];
+		mpdigdiv(d, divisor, &q);
+		d[1] = d[0] - divisor*q;
+	}
+	return d[1] == 0;
+}
+
+//  return -1 if p is divisable by one of the small primes, 0 otherwise
+int
+smallprimetest(mpint *p)
+{
+	int i;
+	ulong sp;
+
+	for(i = 0; i < nelem(smallprimes); i++){
+		sp = smallprimes[i];
+		if(p->top == 1 && p->p[0] <= sp)
+			break;
+		if(divides(p, sp))
+			return -1;
+	}
+	return 0;
+}
--- /dev/null
+++ b/libsec/thumb.c
@@ -1,0 +1,97 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+
+enum{ ThumbTab = 1<<10 };
+
+static void *
+emalloc(int n)
+{
+	void *p;
+	if(n==0)
+		n=1;
+	p = malloc(n);
+	if(p == nil){
+		exits("out of memory");
+	}
+	memset(p, 0, n);
+	return p;
+}
+
+void
+freeThumbprints(Thumbprint *table)
+{
+	Thumbprint *hd, *p, *q;
+	for(hd = table; hd < table+ThumbTab; hd++){
+		for(p = hd->next; p; p = q){
+			q = p->next;
+			free(p);
+		}
+	}
+	free(table);
+}
+
+int
+okThumbprint(uchar *sum, Thumbprint *table)
+{
+	Thumbprint *p;
+	int i = ((sum[0]<<8) + sum[1]) & (ThumbTab-1);
+
+	for(p = table[i].next; p; p = p->next)
+		if(memcmp(sum, p->sha1, SHA1dlen) == 0)
+			return 1;
+	return 0;
+}
+
+static void
+loadThumbprints(char *file, Thumbprint *table, Thumbprint *crltab)
+{
+	Thumbprint *entry;
+	Biobuf *bin;
+	char *line, *field[50];
+	uchar sum[SHA1dlen];
+	int i;
+
+	bin = Bopen(file, OREAD);
+	if(bin == nil)
+		return;
+	for(; (line = Brdstr(bin, '\n', 1)) != 0; free(line)){
+		if(tokenize(line, field, nelem(field)) < 2)
+			continue;
+		if(strcmp(field[0], "#include") == 0){
+			loadThumbprints(field[1], table, crltab);
+			continue;
+		}
+		if(strcmp(field[0], "x509") != 0 || strncmp(field[1], "sha1=", strlen("sha1=")) != 0)
+			continue;
+		field[1] += strlen("sha1=");
+		dec16(sum, sizeof(sum), field[1], strlen(field[1]));
+		if(crltab && okThumbprint(sum, crltab))
+			continue;
+		entry = (Thumbprint*)emalloc(sizeof(*entry));
+		memcpy(entry->sha1, sum, SHA1dlen);
+		i = ((sum[0]<<8) + sum[1]) & (ThumbTab-1);
+		entry->next = table[i].next;
+		table[i].next = entry;
+	}
+	Bterm(bin);
+}
+
+Thumbprint *
+initThumbprints(char *ok, char *crl)
+{
+	Thumbprint *table, *crltab = nil;
+
+	if(crl){
+		crltab = emalloc(ThumbTab * sizeof(*table));
+		loadThumbprints(crl, crltab, nil);
+	}
+	table = emalloc(ThumbTab * sizeof(*table));
+	loadThumbprints(ok, table, crltab);
+	free(crltab);
+	return table;
+}
+
--- /dev/null
+++ b/libsec/tlshand.c
@@ -1,0 +1,2291 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+
+// The main groups of functions are:
+//		client/server - main handshake protocol definition
+//		message functions - formating handshake messages
+//		cipher choices - catalog of digest and encrypt algorithms
+//		security functions - PKCS#1, sslHMAC, session keygen
+//		general utility functions - malloc, serialization
+// The handshake protocol builds on the TLS/SSL3 record layer protocol,
+// which is implemented in kernel device #a.  See also /lib/rfc/rfc2246.
+
+enum {
+	TLSFinishedLen = 12,
+	SSL3FinishedLen = MD5dlen+SHA1dlen,
+	MaxKeyData = 104,	// amount of secret we may need
+	MaxChunk = 1<<14,
+	RandomSize = 32,
+	SidSize = 32,
+	MasterSecretSize = 48,
+	AQueue = 0,
+	AFlush = 1,
+};
+
+typedef struct TlsSec TlsSec;
+
+typedef struct Bytes{
+	int len;
+	uchar data[1];  // [len]
+} Bytes;
+
+typedef struct Ints{
+	int len;
+	int data[1];  // [len]
+} Ints;
+
+typedef struct Algs{
+	char *enc;
+	char *digest;
+	int nsecret;
+	int tlsid;
+	int ok;
+} Algs;
+
+typedef struct Finished{
+	uchar verify[SSL3FinishedLen];
+	int n;
+} Finished;
+
+typedef struct TlsConnection{
+	TlsSec *sec;	// security management goo
+	int hand, ctl;	// record layer file descriptors
+	int erred;		// set when tlsError called
+	int (*trace)(char*fmt, ...); // for debugging
+	int version;	// protocol we are speaking
+	int verset;		// version has been set
+	int ver2hi;		// server got a version 2 hello
+	int isClient;	// is this the client or server?
+	Bytes *sid;		// SessionID
+	Bytes *cert;	// only last - no chain
+
+	Lock statelk;
+	int state;		// must be set using setstate
+
+	// input buffer for handshake messages
+	uchar buf[MaxChunk+2048];
+	uchar *rp, *ep;
+
+	uchar crandom[RandomSize];	// client random
+	uchar srandom[RandomSize];	// server random
+	int clientVersion;	// version in ClientHello
+	char *digest;	// name of digest algorithm to use
+	char *enc;		// name of encryption algorithm to use
+	int nsecret;	// amount of secret data to init keys
+
+	// for finished messages
+	MD5state	hsmd5;	// handshake hash
+	SHAstate	hssha1;	// handshake hash
+	Finished	finished;
+} TlsConnection;
+
+typedef struct Msg{
+	int tag;
+	union {
+		struct {
+			int version;
+			uchar 	random[RandomSize];
+			Bytes*	sid;
+			Ints*	ciphers;
+			Bytes*	compressors;
+		} clientHello;
+		struct {
+			int version;
+			uchar 	random[RandomSize];
+			Bytes*	sid;
+			int cipher;
+			int compressor;
+		} serverHello;
+		struct {
+			int ncert;
+			Bytes **certs;
+		} certificate;
+		struct {
+			Bytes *types;
+			int nca;
+			Bytes **cas;
+		} certificateRequest;
+		struct {
+			Bytes *key;
+		} clientKeyExchange;
+		Finished finished;
+	} u;
+} Msg;
+
+typedef struct TlsSec{
+	char *server;	// name of remote; nil for server
+	int ok;	// <0 killed; ==0 in progress; >0 reusable
+	RSApub *rsapub;
+	AuthRpc *rpc;	// factotum for rsa private key
+	uchar sec[MasterSecretSize];	// master secret
+	uchar crandom[RandomSize];	// client random
+	uchar srandom[RandomSize];	// server random
+	int clientVers;		// version in ClientHello
+	int vers;			// final version
+	// byte generation and handshake checksum
+	void (*prf)(uchar*, int, uchar*, int, char*, uchar*, int, uchar*, int);
+	void (*setFinished)(TlsSec*, MD5state, SHAstate, uchar*, int);
+	int nfin;
+} TlsSec;
+
+
+enum {
+	TLSVersion = 0x0301,
+	SSL3Version = 0x0300,
+	ProtocolVersion = 0x0301,	// maximum version we speak
+	MinProtoVersion = 0x0300,	// limits on version we accept
+	MaxProtoVersion	= 0x03ff,
+};
+
+// handshake type
+enum {
+	HHelloRequest,
+	HClientHello,
+	HServerHello,
+	HSSL2ClientHello = 9,  /* local convention;  see devtls.c */
+	HCertificate = 11,
+	HServerKeyExchange,
+	HCertificateRequest,
+	HServerHelloDone,
+	HCertificateVerify,
+	HClientKeyExchange,
+	HFinished = 20,
+	HMax
+};
+
+// alerts
+enum {
+	ECloseNotify = 0,
+	EUnexpectedMessage = 10,
+	EBadRecordMac = 20,
+	EDecryptionFailed = 21,
+	ERecordOverflow = 22,
+	EDecompressionFailure = 30,
+	EHandshakeFailure = 40,
+	ENoCertificate = 41,
+	EBadCertificate = 42,
+	EUnsupportedCertificate = 43,
+	ECertificateRevoked = 44,
+	ECertificateExpired = 45,
+	ECertificateUnknown = 46,
+	EIllegalParameter = 47,
+	EUnknownCa = 48,
+	EAccessDenied = 49,
+	EDecodeError = 50,
+	EDecryptError = 51,
+	EExportRestriction = 60,
+	EProtocolVersion = 70,
+	EInsufficientSecurity = 71,
+	EInternalError = 80,
+	EUserCanceled = 90,
+	ENoRenegotiation = 100,
+	EMax = 256
+};
+
+// cipher suites
+enum {
+	TLS_NULL_WITH_NULL_NULL	 		= 0x0000,
+	TLS_RSA_WITH_NULL_MD5 			= 0x0001,
+	TLS_RSA_WITH_NULL_SHA 			= 0x0002,
+	TLS_RSA_EXPORT_WITH_RC4_40_MD5 		= 0x0003,
+	TLS_RSA_WITH_RC4_128_MD5 		= 0x0004,
+	TLS_RSA_WITH_RC4_128_SHA 		= 0x0005,
+	TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5	= 0X0006,
+	TLS_RSA_WITH_IDEA_CBC_SHA 		= 0X0007,
+	TLS_RSA_EXPORT_WITH_DES40_CBC_SHA	= 0X0008,
+	TLS_RSA_WITH_DES_CBC_SHA		= 0X0009,
+	TLS_RSA_WITH_3DES_EDE_CBC_SHA		= 0X000A,
+	TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA	= 0X000B,
+	TLS_DH_DSS_WITH_DES_CBC_SHA		= 0X000C,
+	TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA	= 0X000D,
+	TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA	= 0X000E,
+	TLS_DH_RSA_WITH_DES_CBC_SHA		= 0X000F,
+	TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA	= 0X0010,
+	TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA	= 0X0011,
+	TLS_DHE_DSS_WITH_DES_CBC_SHA		= 0X0012,
+	TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA	= 0X0013,	// ZZZ must be implemented for tls1.0 compliance
+	TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA	= 0X0014,
+	TLS_DHE_RSA_WITH_DES_CBC_SHA		= 0X0015,
+	TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA	= 0X0016,
+	TLS_DH_anon_EXPORT_WITH_RC4_40_MD5	= 0x0017,
+	TLS_DH_anon_WITH_RC4_128_MD5 		= 0x0018,
+	TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA	= 0X0019,
+	TLS_DH_anon_WITH_DES_CBC_SHA		= 0X001A,
+	TLS_DH_anon_WITH_3DES_EDE_CBC_SHA	= 0X001B,
+
+	TLS_RSA_WITH_AES_128_CBC_SHA		= 0X002f,	// aes, aka rijndael with 128 bit blocks
+	TLS_DH_DSS_WITH_AES_128_CBC_SHA		= 0X0030,
+	TLS_DH_RSA_WITH_AES_128_CBC_SHA		= 0X0031,
+	TLS_DHE_DSS_WITH_AES_128_CBC_SHA	= 0X0032,
+	TLS_DHE_RSA_WITH_AES_128_CBC_SHA	= 0X0033,
+	TLS_DH_anon_WITH_AES_128_CBC_SHA	= 0X0034,
+	TLS_RSA_WITH_AES_256_CBC_SHA		= 0X0035,
+	TLS_DH_DSS_WITH_AES_256_CBC_SHA		= 0X0036,
+	TLS_DH_RSA_WITH_AES_256_CBC_SHA		= 0X0037,
+	TLS_DHE_DSS_WITH_AES_256_CBC_SHA	= 0X0038,
+	TLS_DHE_RSA_WITH_AES_256_CBC_SHA	= 0X0039,
+	TLS_DH_anon_WITH_AES_256_CBC_SHA	= 0X003A,
+	CipherMax
+};
+
+// compression methods
+enum {
+	CompressionNull = 0,
+	CompressionMax
+};
+
+static Algs cipherAlgs[] = {
+	{"rc4_128", "md5",	2 * (16 + MD5dlen), TLS_RSA_WITH_RC4_128_MD5},
+	{"rc4_128", "sha1",	2 * (16 + SHA1dlen), TLS_RSA_WITH_RC4_128_SHA},
+	{"3des_ede_cbc","sha1",2*(4*8+SHA1dlen), TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+};
+
+static uchar compressors[] = {
+	CompressionNull,
+};
+
+static TlsConnection *tlsServer2(int ctl, int hand, uchar *cert, int ncert, int (*trace)(char*fmt, ...));
+static TlsConnection *tlsClient2(int ctl, int hand, uchar *csid, int ncsid, int (*trace)(char*fmt, ...));
+
+static void	msgClear(Msg *m);
+static char* msgPrint(char *buf, int n, Msg *m);
+static int	msgRecv(TlsConnection *c, Msg *m);
+static int	msgSend(TlsConnection *c, Msg *m, int act);
+static void	tlsError(TlsConnection *c, int err, char *msg, ...);
+#pragma	varargck argpos	tlsError 3
+static int setVersion(TlsConnection *c, int version);
+static int finishedMatch(TlsConnection *c, Finished *f);
+static void tlsConnectionFree(TlsConnection *c);
+
+static int setAlgs(TlsConnection *c, int a);
+static int okCipher(Ints *cv);
+static int okCompression(Bytes *cv);
+static int initCiphers(void);
+static Ints* makeciphers(void);
+
+static TlsSec* tlsSecInits(int cvers, uchar *csid, int ncsid, uchar *crandom, uchar *ssid, int *nssid, uchar *srandom);
+static int	tlsSecSecrets(TlsSec *sec, int vers, uchar *epm, int nepm, uchar *kd, int nkd);
+static TlsSec*	tlsSecInitc(int cvers, uchar *crandom);
+static int	tlsSecSecretc(TlsSec *sec, uchar *sid, int nsid, uchar *srandom, uchar *cert, int ncert, int vers, uchar **epm, int *nepm, uchar *kd, int nkd);
+static int	tlsSecFinished(TlsSec *sec, MD5state md5, SHAstate sha1, uchar *fin, int nfin, int isclient);
+static void	tlsSecOk(TlsSec *sec);
+static void	tlsSecKill(TlsSec *sec);
+static void	tlsSecClose(TlsSec *sec);
+static void	setMasterSecret(TlsSec *sec, Bytes *pm);
+static void	serverMasterSecret(TlsSec *sec, uchar *epm, int nepm);
+static void	setSecrets(TlsSec *sec, uchar *kd, int nkd);
+static int	clientMasterSecret(TlsSec *sec, RSApub *pub, uchar **epm, int *nepm);
+static Bytes *pkcs1_encrypt(Bytes* data, RSApub* key, int blocktype);
+static Bytes *pkcs1_decrypt(TlsSec *sec, uchar *epm, int nepm);
+static void	tlsSetFinished(TlsSec *sec, MD5state hsmd5, SHAstate hssha1, uchar *finished, int isClient);
+static void	sslSetFinished(TlsSec *sec, MD5state hsmd5, SHAstate hssha1, uchar *finished, int isClient);
+static void	sslPRF(uchar *buf, int nbuf, uchar *key, int nkey, char *label,
+			uchar *seed0, int nseed0, uchar *seed1, int nseed1);
+static int setVers(TlsSec *sec, int version);
+
+static AuthRpc* factotum_rsa_open(uchar *cert, int certlen);
+static mpint* factotum_rsa_decrypt(AuthRpc *rpc, mpint *cipher);
+static void factotum_rsa_close(AuthRpc*rpc);
+
+static void* emalloc(int);
+static void* erealloc(void*, int);
+static void put32(uchar *p, u32int);
+static void put24(uchar *p, int);
+static void put16(uchar *p, int);
+static u32int get32(uchar *p);
+static int get24(uchar *p);
+static int get16(uchar *p);
+static Bytes* newbytes(int len);
+static Bytes* makebytes(uchar* buf, int len);
+static void freebytes(Bytes* b);
+static Ints* newints(int len);
+static Ints* makeints(int* buf, int len);
+static void freeints(Ints* b);
+
+//================= client/server ========================
+
+//	push TLS onto fd, returning new (application) file descriptor
+//		or -1 if error.
+int
+tlsServer(int fd, TLSconn *conn)
+{
+	char buf[8];
+	char dname[64];
+	int n, data, ctl, hand;
+	TlsConnection *tls;
+
+	if(conn == nil)
+		return -1;
+	ctl = open("#a/tls/clone", ORDWR);
+	if(ctl < 0)
+		return -1;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0){
+		close(ctl);
+		return -1;
+	}
+	buf[n] = 0;
+	sprint(conn->dir, "#a/tls/%s", buf);
+	sprint(dname, "#a/tls/%s/hand", buf);
+	hand = open(dname, ORDWR);
+	if(hand < 0){
+		close(ctl);
+		return -1;
+	}
+	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
+	tls = tlsServer2(ctl, hand, conn->cert, conn->certlen, conn->trace);
+	sprint(dname, "#a/tls/%s/data", buf);
+	data = open(dname, ORDWR);
+	close(fd);
+	close(hand);
+	close(ctl);
+	if(data < 0){
+		return -1;
+	}
+	if(tls == nil){
+		close(data);
+		return -1;
+	}
+	if(conn->cert)
+		free(conn->cert);
+	conn->cert = 0;  // client certificates are not yet implemented
+	conn->certlen = 0;
+	conn->sessionIDlen = tls->sid->len;
+	conn->sessionID = emalloc(conn->sessionIDlen);
+	memcpy(conn->sessionID, tls->sid->data, conn->sessionIDlen);
+	tlsConnectionFree(tls);
+	return data;
+}
+
+//	push TLS onto fd, returning new (application) file descriptor
+//		or -1 if error.
+int
+tlsClient(int fd, TLSconn *conn)
+{
+	char buf[8];
+	char dname[64];
+	int n, data, ctl, hand;
+	TlsConnection *tls;
+
+	if(!conn)
+		return -1;
+	ctl = open("#a/tls/clone", ORDWR);
+	if(ctl < 0)
+		return -1;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0){
+		close(ctl);
+		return -1;
+	}
+	buf[n] = 0;
+	sprint(conn->dir, "#a/tls/%s", buf);
+	sprint(dname, "#a/tls/%s/hand", buf);
+	hand = open(dname, ORDWR);
+	if(hand < 0){
+		close(ctl);
+		return -1;
+	}
+	sprint(dname, "#a/tls/%s/data", buf);
+	data = open(dname, ORDWR);
+	if(data < 0)
+		return -1;
+	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
+	tls = tlsClient2(ctl, hand, conn->sessionID, conn->sessionIDlen, conn->trace);
+	close(fd);
+	close(hand);
+	close(ctl);
+	if(tls == nil){
+		close(data);
+		return -1;
+	}
+	conn->certlen = tls->cert->len;
+	conn->cert = emalloc(conn->certlen);
+	memcpy(conn->cert, tls->cert->data, conn->certlen);
+	conn->sessionIDlen = tls->sid->len;
+	conn->sessionID = emalloc(conn->sessionIDlen);
+	memcpy(conn->sessionID, tls->sid->data, conn->sessionIDlen);
+	tlsConnectionFree(tls);
+	return data;
+}
+
+static TlsConnection *
+tlsServer2(int ctl, int hand, uchar *cert, int ncert, int (*trace)(char*fmt, ...))
+{
+	TlsConnection *c;
+	Msg m;
+	Bytes *csid;
+	uchar sid[SidSize], kd[MaxKeyData];
+	char *secrets;
+	int cipher, compressor, nsid, rv;
+
+	if(trace)
+		trace("tlsServer2\n");
+	if(!initCiphers())
+		return nil;
+	c = emalloc(sizeof(TlsConnection));
+	c->ctl = ctl;
+	c->hand = hand;
+	c->trace = trace;
+	c->version = ProtocolVersion;
+
+	memset(&m, 0, sizeof(m));
+	if(!msgRecv(c, &m)){
+		if(trace)
+			trace("initial msgRecv failed\n");
+		goto Err;
+	}
+	if(m.tag != HClientHello) {
+		tlsError(c, EUnexpectedMessage, "expected a client hello");
+		goto Err;
+	}
+	c->clientVersion = m.u.clientHello.version;
+	if(trace)
+		trace("ClientHello version %x\n", c->clientVersion);
+	if(setVersion(c, m.u.clientHello.version) < 0) {
+		tlsError(c, EIllegalParameter, "incompatible version");
+		goto Err;
+	}
+
+	memmove(c->crandom, m.u.clientHello.random, RandomSize);
+	cipher = okCipher(m.u.clientHello.ciphers);
+	if(cipher < 0) {
+		// reply with EInsufficientSecurity if we know that's the case
+		if(cipher == -2)
+			tlsError(c, EInsufficientSecurity, "cipher suites too weak");
+		else
+			tlsError(c, EHandshakeFailure, "no matching cipher suite");
+		goto Err;
+	}
+	if(!setAlgs(c, cipher)){
+		tlsError(c, EHandshakeFailure, "no matching cipher suite");
+		goto Err;
+	}
+	compressor = okCompression(m.u.clientHello.compressors);
+	if(compressor < 0) {
+		tlsError(c, EHandshakeFailure, "no matching compressor");
+		goto Err;
+	}
+
+	csid = m.u.clientHello.sid;
+	if(trace)
+		trace("  cipher %d, compressor %d, csidlen %d\n", cipher, compressor, csid->len);
+	c->sec = tlsSecInits(c->clientVersion, csid->data, csid->len, c->crandom, sid, &nsid, c->srandom);
+	if(c->sec == nil){
+		tlsError(c, EHandshakeFailure, "can't initialize security: %r");
+		goto Err;
+	}
+	c->sec->rpc = factotum_rsa_open(cert, ncert);
+	if(c->sec->rpc == nil){
+		tlsError(c, EHandshakeFailure, "factotum_rsa_open: %r");
+		goto Err;
+	}
+	c->sec->rsapub = X509toRSApub(cert, ncert, nil, 0);
+	msgClear(&m);
+
+	m.tag = HServerHello;
+	m.u.serverHello.version = c->version;
+	memmove(m.u.serverHello.random, c->srandom, RandomSize);
+	m.u.serverHello.cipher = cipher;
+	m.u.serverHello.compressor = compressor;
+	c->sid = makebytes(sid, nsid);
+	m.u.serverHello.sid = makebytes(c->sid->data, c->sid->len);
+	if(!msgSend(c, &m, AQueue))
+		goto Err;
+	msgClear(&m);
+
+	m.tag = HCertificate;
+	m.u.certificate.ncert = 1;
+	m.u.certificate.certs = emalloc(m.u.certificate.ncert * sizeof(Bytes));
+	m.u.certificate.certs[0] = makebytes(cert, ncert);
+	if(!msgSend(c, &m, AQueue))
+		goto Err;
+	msgClear(&m);
+
+	m.tag = HServerHelloDone;
+	if(!msgSend(c, &m, AFlush))
+		goto Err;
+	msgClear(&m);
+
+	if(!msgRecv(c, &m))
+		goto Err;
+	if(m.tag != HClientKeyExchange) {
+		tlsError(c, EUnexpectedMessage, "expected a client key exchange");
+		goto Err;
+	}
+	if(tlsSecSecrets(c->sec, c->version, m.u.clientKeyExchange.key->data, m.u.clientKeyExchange.key->len, kd, c->nsecret) < 0){
+		tlsError(c, EHandshakeFailure, "couldn't set secrets: %r");
+		goto Err;
+	}
+	if(trace)
+		trace("tls secrets\n");
+	secrets = (char*)emalloc(2*c->nsecret);
+	enc64(secrets, 2*c->nsecret, kd, c->nsecret);
+	rv = fprint(c->ctl, "secret %s %s 0 %s", c->digest, c->enc, secrets);
+	memset(secrets, 0, 2*c->nsecret);
+	free(secrets);
+	memset(kd, 0, c->nsecret);
+	if(rv < 0){
+		tlsError(c, EHandshakeFailure, "can't set keys: %r");
+		goto Err;
+	}
+	msgClear(&m);
+
+	/* no CertificateVerify; skip to Finished */
+	if(tlsSecFinished(c->sec, c->hsmd5, c->hssha1, c->finished.verify, c->finished.n, 1) < 0){
+		tlsError(c, EInternalError, "can't set finished: %r");
+		goto Err;
+	}
+	if(!msgRecv(c, &m))
+		goto Err;
+	if(m.tag != HFinished) {
+		tlsError(c, EUnexpectedMessage, "expected a finished");
+		goto Err;
+	}
+	if(!finishedMatch(c, &m.u.finished)) {
+		tlsError(c, EHandshakeFailure, "finished verification failed");
+		goto Err;
+	}
+	msgClear(&m);
+
+	/* change cipher spec */
+	if(fprint(c->ctl, "changecipher") < 0){
+		tlsError(c, EInternalError, "can't enable cipher: %r");
+		goto Err;
+	}
+
+	if(tlsSecFinished(c->sec, c->hsmd5, c->hssha1, c->finished.verify, c->finished.n, 0) < 0){
+		tlsError(c, EInternalError, "can't set finished: %r");
+		goto Err;
+	}
+	m.tag = HFinished;
+	m.u.finished = c->finished;
+	if(!msgSend(c, &m, AFlush))
+		goto Err;
+	msgClear(&m);
+	if(trace)
+		trace("tls finished\n");
+
+	if(fprint(c->ctl, "opened") < 0)
+		goto Err;
+	tlsSecOk(c->sec);
+	return c;
+
+Err:
+	msgClear(&m);
+	tlsConnectionFree(c);
+	return 0;
+}
+
+static TlsConnection *
+tlsClient2(int ctl, int hand, uchar *csid, int ncsid, int (*trace)(char*fmt, ...))
+{
+	TlsConnection *c;
+	Msg m;
+	uchar kd[MaxKeyData], *epm;
+	char *secrets;
+	int creq, nepm, rv;
+
+	if(!initCiphers())
+		return nil;
+	epm = nil;
+	c = emalloc(sizeof(TlsConnection));
+	c->version = ProtocolVersion;
+	c->ctl = ctl;
+	c->hand = hand;
+	c->trace = trace;
+	c->isClient = 1;
+	c->clientVersion = c->version;
+
+	c->sec = tlsSecInitc(c->clientVersion, c->crandom);
+	if(c->sec == nil)
+		goto Err;
+
+	/* client hello */
+	memset(&m, 0, sizeof(m));
+	m.tag = HClientHello;
+	m.u.clientHello.version = c->clientVersion;
+	memmove(m.u.clientHello.random, c->crandom, RandomSize);
+	m.u.clientHello.sid = makebytes(csid, ncsid);
+	m.u.clientHello.ciphers = makeciphers();
+	m.u.clientHello.compressors = makebytes(compressors,sizeof(compressors));
+	if(!msgSend(c, &m, AFlush))
+		goto Err;
+	msgClear(&m);
+
+	/* server hello */
+	if(!msgRecv(c, &m))
+		goto Err;
+	if(m.tag != HServerHello) {
+		tlsError(c, EUnexpectedMessage, "expected a server hello");
+		goto Err;
+	}
+	if(setVersion(c, m.u.serverHello.version) < 0) {
+		tlsError(c, EIllegalParameter, "incompatible version %r");
+		goto Err;
+	}
+	memmove(c->srandom, m.u.serverHello.random, RandomSize);
+	c->sid = makebytes(m.u.serverHello.sid->data, m.u.serverHello.sid->len);
+	if(c->sid->len != 0 && c->sid->len != SidSize) {
+		tlsError(c, EIllegalParameter, "invalid server session identifier");
+		goto Err;
+	}
+	if(!setAlgs(c, m.u.serverHello.cipher)) {
+		tlsError(c, EIllegalParameter, "invalid cipher suite");
+		goto Err;
+	}
+	if(m.u.serverHello.compressor != CompressionNull) {
+		tlsError(c, EIllegalParameter, "invalid compression");
+		goto Err;
+	}
+	msgClear(&m);
+
+	/* certificate */
+	if(!msgRecv(c, &m) || m.tag != HCertificate) {
+		tlsError(c, EUnexpectedMessage, "expected a certificate");
+		goto Err;
+	}
+	if(m.u.certificate.ncert < 1) {
+		tlsError(c, EIllegalParameter, "runt certificate");
+		goto Err;
+	}
+	c->cert = makebytes(m.u.certificate.certs[0]->data, m.u.certificate.certs[0]->len);
+	msgClear(&m);
+
+	/* server key exchange (optional) */
+	if(!msgRecv(c, &m))
+		goto Err;
+	if(m.tag == HServerKeyExchange) {
+		tlsError(c, EUnexpectedMessage, "got an server key exchange");
+		goto Err;
+		// If implementing this later, watch out for rollback attack
+		// described in Wagner Schneier 1996, section 4.4.
+	}
+
+	/* certificate request (optional) */
+	creq = 0;
+	if(m.tag == HCertificateRequest) {
+		creq = 1;
+		msgClear(&m);
+		if(!msgRecv(c, &m))
+			goto Err;
+	}
+
+	if(m.tag != HServerHelloDone) {
+		tlsError(c, EUnexpectedMessage, "expected a server hello done");
+		goto Err;
+	}
+	msgClear(&m);
+
+	if(tlsSecSecretc(c->sec, c->sid->data, c->sid->len, c->srandom,
+			c->cert->data, c->cert->len, c->version, &epm, &nepm,
+			kd, c->nsecret) < 0){
+		tlsError(c, EBadCertificate, "invalid x509/rsa certificate");
+		goto Err;
+	}
+	secrets = (char*)emalloc(2*c->nsecret);
+	enc64(secrets, 2*c->nsecret, kd, c->nsecret);
+	rv = fprint(c->ctl, "secret %s %s 1 %s", c->digest, c->enc, secrets);
+	memset(secrets, 0, 2*c->nsecret);
+	free(secrets);
+	memset(kd, 0, c->nsecret);
+	if(rv < 0){
+		tlsError(c, EHandshakeFailure, "can't set keys: %r");
+		goto Err;
+	}
+
+	if(creq) {
+		/* send a zero length certificate */
+		m.tag = HCertificate;
+		if(!msgSend(c, &m, AFlush))
+			goto Err;
+		msgClear(&m);
+	}
+
+	/* client key exchange */
+	m.tag = HClientKeyExchange;
+	m.u.clientKeyExchange.key = makebytes(epm, nepm);
+	free(epm);
+	epm = nil;
+	if(m.u.clientKeyExchange.key == nil) {
+		tlsError(c, EHandshakeFailure, "can't set secret: %r");
+		goto Err;
+	}
+	if(!msgSend(c, &m, AFlush))
+		goto Err;
+	msgClear(&m);
+
+	/* change cipher spec */
+	if(fprint(c->ctl, "changecipher") < 0){
+		tlsError(c, EInternalError, "can't enable cipher: %r");
+		goto Err;
+	}
+
+	// Cipherchange must occur immediately before Finished to avoid
+	// potential hole;  see section 4.3 of Wagner Schneier 1996.
+	if(tlsSecFinished(c->sec, c->hsmd5, c->hssha1, c->finished.verify, c->finished.n, 1) < 0){
+		tlsError(c, EInternalError, "can't set finished 1: %r");
+		goto Err;
+	}
+	m.tag = HFinished;
+	m.u.finished = c->finished;
+
+	if(!msgSend(c, &m, AFlush)) {
+		fprint(2, "tlsClient nepm=%d\n", nepm);
+		tlsError(c, EInternalError, "can't flush after client Finished: %r");
+		goto Err;
+	}
+	msgClear(&m);
+
+	if(tlsSecFinished(c->sec, c->hsmd5, c->hssha1, c->finished.verify, c->finished.n, 0) < 0){
+		fprint(2, "tlsClient nepm=%d\n", nepm);
+		tlsError(c, EInternalError, "can't set finished 0: %r");
+		goto Err;
+	}
+	if(!msgRecv(c, &m)) {
+		fprint(2, "tlsClient nepm=%d\n", nepm);
+		tlsError(c, EInternalError, "can't read server Finished: %r");
+		goto Err;
+	}
+	if(m.tag != HFinished) {
+		fprint(2, "tlsClient nepm=%d\n", nepm);
+		tlsError(c, EUnexpectedMessage, "expected a Finished msg from server");
+		goto Err;
+	}
+
+	if(!finishedMatch(c, &m.u.finished)) {
+		tlsError(c, EHandshakeFailure, "finished verification failed");
+		goto Err;
+	}
+	msgClear(&m);
+
+	if(fprint(c->ctl, "opened") < 0){
+		if(trace)
+			trace("unable to do final open: %r\n");
+		goto Err;
+	}
+	tlsSecOk(c->sec);
+	return c;
+
+Err:
+	free(epm);
+	msgClear(&m);
+	tlsConnectionFree(c);
+	return 0;
+}
+
+
+//================= message functions ========================
+
+static uchar sendbuf[9000], *sendp;
+
+static int
+msgSend(TlsConnection *c, Msg *m, int act)
+{
+	uchar *p; // sendp = start of new message;  p = write pointer
+	int nn, n, i;
+
+	if(sendp == nil)
+		sendp = sendbuf;
+	p = sendp;
+	if(c->trace)
+		c->trace("send %s", msgPrint((char*)p, (sizeof sendbuf) - (p-sendbuf), m));
+
+	p[0] = m->tag;	// header - fill in size later
+	p += 4;
+
+	switch(m->tag) {
+	default:
+		tlsError(c, EInternalError, "can't encode a %d", m->tag);
+		goto Err;
+	case HClientHello:
+		// version
+		put16(p, m->u.clientHello.version);
+		p += 2;
+
+		// random
+		memmove(p, m->u.clientHello.random, RandomSize);
+		p += RandomSize;
+
+		// sid
+		n = m->u.clientHello.sid->len;
+		assert(n < 256);
+		p[0] = n;
+		memmove(p+1, m->u.clientHello.sid->data, n);
+		p += n+1;
+
+		n = m->u.clientHello.ciphers->len;
+		assert(n > 0 && n < 200);
+		put16(p, n*2);
+		p += 2;
+		for(i=0; i<n; i++) {
+			put16(p, m->u.clientHello.ciphers->data[i]);
+			p += 2;
+		}
+
+		n = m->u.clientHello.compressors->len;
+		assert(n > 0);
+		p[0] = n;
+		memmove(p+1, m->u.clientHello.compressors->data, n);
+		p += n+1;
+		break;
+	case HServerHello:
+		put16(p, m->u.serverHello.version);
+		p += 2;
+
+		// random
+		memmove(p, m->u.serverHello.random, RandomSize);
+		p += RandomSize;
+
+		// sid
+		n = m->u.serverHello.sid->len;
+		assert(n < 256);
+		p[0] = n;
+		memmove(p+1, m->u.serverHello.sid->data, n);
+		p += n+1;
+
+		put16(p, m->u.serverHello.cipher);
+		p += 2;
+		p[0] = m->u.serverHello.compressor;
+		p += 1;
+		break;
+	case HServerHelloDone:
+		break;
+	case HCertificate:
+		nn = 0;
+		for(i = 0; i < m->u.certificate.ncert; i++)
+			nn += 3 + m->u.certificate.certs[i]->len;
+		if(p + 3 + nn - sendbuf > sizeof(sendbuf)) {
+			tlsError(c, EInternalError, "output buffer too small for certificate");
+			goto Err;
+		}
+		put24(p, nn);
+		p += 3;
+		for(i = 0; i < m->u.certificate.ncert; i++){
+			put24(p, m->u.certificate.certs[i]->len);
+			p += 3;
+			memmove(p, m->u.certificate.certs[i]->data, m->u.certificate.certs[i]->len);
+			p += m->u.certificate.certs[i]->len;
+		}
+		break;
+	case HClientKeyExchange:
+		n = m->u.clientKeyExchange.key->len;
+		if(c->version != SSL3Version){
+			put16(p, n);
+			p += 2;
+		}
+		memmove(p, m->u.clientKeyExchange.key->data, n);
+		p += n;
+		break;
+	case HFinished:
+		memmove(p, m->u.finished.verify, m->u.finished.n);
+		p += m->u.finished.n;
+		break;
+	}
+
+	// go back and fill in size
+	n = p-sendp;
+	assert(p <= sendbuf+sizeof(sendbuf));
+	put24(sendp+1, n-4);
+
+	// remember hash of Handshake messages
+	if(m->tag != HHelloRequest) {
+		md5(sendp, n, 0, &c->hsmd5);
+		sha1(sendp, n, 0, &c->hssha1);
+	}
+
+	sendp = p;
+	if(act == AFlush){
+		sendp = sendbuf;
+		if(write(c->hand, sendbuf, p-sendbuf) < 0){
+			fprint(2, "write error: %r\n");
+			goto Err;
+		}
+	}
+	msgClear(m);
+	return 1;
+Err:
+	msgClear(m);
+	return 0;
+}
+
+static uchar*
+tlsReadN(TlsConnection *c, int n)
+{
+	uchar *p;
+	int nn, nr;
+
+	nn = c->ep - c->rp;
+	if(nn < n){
+		if(c->rp != c->buf){
+			memmove(c->buf, c->rp, nn);
+			c->rp = c->buf;
+			c->ep = &c->buf[nn];
+		}
+		for(; nn < n; nn += nr) {
+			nr = read(c->hand, &c->rp[nn], n - nn);
+			if(nr <= 0)
+				return nil;
+			c->ep += nr;
+		}
+	}
+	p = c->rp;
+	c->rp += n;
+	return p;
+}
+
+static int
+msgRecv(TlsConnection *c, Msg *m)
+{
+	uchar *p;
+	int type, n, nn, i, nsid, nrandom, nciph;
+
+	for(;;) {
+		p = tlsReadN(c, 4);
+		if(p == nil)
+			return 0;
+		type = p[0];
+		n = get24(p+1);
+
+		if(type != HHelloRequest)
+			break;
+		if(n != 0) {
+			tlsError(c, EDecodeError, "invalid hello request during handshake");
+			return 0;
+		}
+	}
+
+	if(n > sizeof(c->buf)) {
+		tlsError(c, EDecodeError, "handshake message too long %d %d", n, sizeof(c->buf));
+		return 0;
+	}
+
+	if(type == HSSL2ClientHello){
+		/* Cope with an SSL3 ClientHello expressed in SSL2 record format.
+			This is sent by some clients that we must interoperate
+			with, such as Java's JSSE and Microsoft's Internet Explorer. */
+		p = tlsReadN(c, n);
+		if(p == nil)
+			return 0;
+		md5(p, n, 0, &c->hsmd5);
+		sha1(p, n, 0, &c->hssha1);
+		m->tag = HClientHello;
+		if(n < 22)
+			goto Short;
+		m->u.clientHello.version = get16(p+1);
+		p += 3;
+		n -= 3;
+		nn = get16(p); /* cipher_spec_len */
+		nsid = get16(p + 2);
+		nrandom = get16(p + 4);
+		p += 6;
+		n -= 6;
+		if(nsid != 0 	/* no sid's, since shouldn't restart using ssl2 header */
+				|| nrandom < 16 || nn % 3)
+			goto Err;
+		if(c->trace && (n - nrandom != nn))
+			c->trace("n-nrandom!=nn: n=%d nrandom=%d nn=%d\n", n, nrandom, nn);
+		/* ignore ssl2 ciphers and look for {0x00, ssl3 cipher} */
+		nciph = 0;
+		for(i = 0; i < nn; i += 3)
+			if(p[i] == 0)
+				nciph++;
+		m->u.clientHello.ciphers = newints(nciph);
+		nciph = 0;
+		for(i = 0; i < nn; i += 3)
+			if(p[i] == 0)
+				m->u.clientHello.ciphers->data[nciph++] = get16(&p[i + 1]);
+		p += nn;
+		m->u.clientHello.sid = makebytes(nil, 0);
+		if(nrandom > RandomSize)
+			nrandom = RandomSize;
+		memset(m->u.clientHello.random, 0, RandomSize - nrandom);
+		memmove(&m->u.clientHello.random[RandomSize - nrandom], p, nrandom);
+		m->u.clientHello.compressors = newbytes(1);
+		m->u.clientHello.compressors->data[0] = CompressionNull;
+		goto Ok;
+	}
+
+	md5(p, 4, 0, &c->hsmd5);
+	sha1(p, 4, 0, &c->hssha1);
+
+	p = tlsReadN(c, n);
+	if(p == nil)
+		return 0;
+
+	md5(p, n, 0, &c->hsmd5);
+	sha1(p, n, 0, &c->hssha1);
+
+	m->tag = type;
+
+	switch(type) {
+	default:
+		tlsError(c, EUnexpectedMessage, "can't decode a %d", type);
+		goto Err;
+	case HClientHello:
+		if(n < 2)
+			goto Short;
+		m->u.clientHello.version = get16(p);
+		p += 2;
+		n -= 2;
+
+		if(n < RandomSize)
+			goto Short;
+		memmove(m->u.clientHello.random, p, RandomSize);
+		p += RandomSize;
+		n -= RandomSize;
+		if(n < 1 || n < p[0]+1)
+			goto Short;
+		m->u.clientHello.sid = makebytes(p+1, p[0]);
+		p += m->u.clientHello.sid->len+1;
+		n -= m->u.clientHello.sid->len+1;
+
+		if(n < 2)
+			goto Short;
+		nn = get16(p);
+		p += 2;
+		n -= 2;
+
+		if((nn & 1) || n < nn || nn < 2)
+			goto Short;
+		m->u.clientHello.ciphers = newints(nn >> 1);
+		for(i = 0; i < nn; i += 2)
+			m->u.clientHello.ciphers->data[i >> 1] = get16(&p[i]);
+		p += nn;
+		n -= nn;
+
+		if(n < 1 || n < p[0]+1 || p[0] == 0)
+			goto Short;
+		nn = p[0];
+		m->u.clientHello.compressors = newbytes(nn);
+		memmove(m->u.clientHello.compressors->data, p+1, nn);
+		n -= nn + 1;
+		break;
+	case HServerHello:
+		if(n < 2)
+			goto Short;
+		m->u.serverHello.version = get16(p);
+		p += 2;
+		n -= 2;
+
+		if(n < RandomSize)
+			goto Short;
+		memmove(m->u.serverHello.random, p, RandomSize);
+		p += RandomSize;
+		n -= RandomSize;
+
+		if(n < 1 || n < p[0]+1)
+			goto Short;
+		m->u.serverHello.sid = makebytes(p+1, p[0]);
+		p += m->u.serverHello.sid->len+1;
+		n -= m->u.serverHello.sid->len+1;
+
+		if(n < 3)
+			goto Short;
+		m->u.serverHello.cipher = get16(p);
+		m->u.serverHello.compressor = p[2];
+		n -= 3;
+		break;
+	case HCertificate:
+		if(n < 3)
+			goto Short;
+		nn = get24(p);
+		p += 3;
+		n -= 3;
+		if(n != nn)
+			goto Short;
+		/* certs */
+		i = 0;
+		while(n > 0) {
+			if(n < 3)
+				goto Short;
+			nn = get24(p);
+			p += 3;
+			n -= 3;
+			if(nn > n)
+				goto Short;
+			m->u.certificate.ncert = i+1;
+			m->u.certificate.certs = erealloc(m->u.certificate.certs, (i+1)*sizeof(Bytes));
+			m->u.certificate.certs[i] = makebytes(p, nn);
+			p += nn;
+			n -= nn;
+			i++;
+		}
+		break;
+	case HCertificateRequest:
+		if(n < 2)
+			goto Short;
+		nn = get16(p);
+		p += 2;
+		n -= 2;
+		if(nn < 1 || nn > n)
+			goto Short;
+		m->u.certificateRequest.types = makebytes(p, nn);
+		nn = get24(p);
+		p += 3;
+		n -= 3;
+		if(nn == 0 || n != nn)
+			goto Short;
+		/* cas */
+		i = 0;
+		while(n > 0) {
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2;
+			n -= 2;
+			if(nn < 1 || nn > n)
+				goto Short;
+			m->u.certificateRequest.nca = i+1;
+			m->u.certificateRequest.cas = erealloc(m->u.certificateRequest.cas, (i+1)*sizeof(Bytes));
+			m->u.certificateRequest.cas[i] = makebytes(p, nn);
+			p += nn;
+			n -= nn;
+			i++;
+		}
+		break;
+	case HServerHelloDone:
+		break;
+	case HClientKeyExchange:
+		/*
+		 * this message depends upon the encryption selected
+		 * assume rsa.
+		 */
+		if(c->version == SSL3Version)
+			nn = n;
+		else{
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2;
+			n -= 2;
+		}
+		if(n < nn)
+			goto Short;
+		m->u.clientKeyExchange.key = makebytes(p, nn);
+		n -= nn;
+		break;
+	case HFinished:
+		m->u.finished.n = c->finished.n;
+		if(n < m->u.finished.n)
+			goto Short;
+		memmove(m->u.finished.verify, p, m->u.finished.n);
+		n -= m->u.finished.n;
+		break;
+	}
+
+	if(type != HClientHello && n != 0)
+		goto Short;
+Ok:
+	if(c->trace){
+		char buf[8000];
+		c->trace("recv %s", msgPrint(buf, sizeof buf, m));
+	}
+	return 1;
+Short:
+	tlsError(c, EDecodeError, "handshake message has invalid length");
+Err:
+	msgClear(m);
+	return 0;
+}
+
+static void
+msgClear(Msg *m)
+{
+	int i;
+
+	switch(m->tag) {
+	default:
+		sysfatal("msgClear: unknown message type: %d\n", m->tag);
+	case HHelloRequest:
+		break;
+	case HClientHello:
+		freebytes(m->u.clientHello.sid);
+		freeints(m->u.clientHello.ciphers);
+		freebytes(m->u.clientHello.compressors);
+		break;
+	case HServerHello:
+		freebytes(m->u.clientHello.sid);
+		break;
+	case HCertificate:
+		for(i=0; i<m->u.certificate.ncert; i++)
+			freebytes(m->u.certificate.certs[i]);
+		free(m->u.certificate.certs);
+		break;
+	case HCertificateRequest:
+		freebytes(m->u.certificateRequest.types);
+		for(i=0; i<m->u.certificateRequest.nca; i++)
+			freebytes(m->u.certificateRequest.cas[i]);
+		free(m->u.certificateRequest.cas);
+		break;
+	case HServerHelloDone:
+		break;
+	case HClientKeyExchange:
+		freebytes(m->u.clientKeyExchange.key);
+		break;
+	case HFinished:
+		break;
+	}
+	memset(m, 0, sizeof(Msg));
+}
+
+static char *
+bytesPrint(char *bs, char *be, char *s0, Bytes *b, char *s1)
+{
+	int i;
+
+	if(s0)
+		bs = seprint(bs, be, "%s", s0);
+	bs = seprint(bs, be, "[");
+	if(b == nil)
+		bs = seprint(bs, be, "nil");
+	else
+		for(i=0; i<b->len; i++)
+			bs = seprint(bs, be, "%.2x ", b->data[i]);
+	bs = seprint(bs, be, "]");
+	if(s1)
+		bs = seprint(bs, be, "%s", s1);
+	return bs;
+}
+
+static char *
+intsPrint(char *bs, char *be, char *s0, Ints *b, char *s1)
+{
+	int i;
+
+	if(s0)
+		bs = seprint(bs, be, "%s", s0);
+	bs = seprint(bs, be, "[");
+	if(b == nil)
+		bs = seprint(bs, be, "nil");
+	else
+		for(i=0; i<b->len; i++)
+			bs = seprint(bs, be, "%x ", b->data[i]);
+	bs = seprint(bs, be, "]");
+	if(s1)
+		bs = seprint(bs, be, "%s", s1);
+	return bs;
+}
+
+static char*
+msgPrint(char *buf, int n, Msg *m)
+{
+	int i;
+	char *bs = buf, *be = buf+n;
+
+	switch(m->tag) {
+	default:
+		bs = seprint(bs, be, "unknown %d\n", m->tag);
+		break;
+	case HClientHello:
+		bs = seprint(bs, be, "ClientHello\n");
+		bs = seprint(bs, be, "\tversion: %.4x\n", m->u.clientHello.version);
+		bs = seprint(bs, be, "\trandom: ");
+		for(i=0; i<RandomSize; i++)
+			bs = seprint(bs, be, "%.2x", m->u.clientHello.random[i]);
+		bs = seprint(bs, be, "\n");
+		bs = bytesPrint(bs, be, "\tsid: ", m->u.clientHello.sid, "\n");
+		bs = intsPrint(bs, be, "\tciphers: ", m->u.clientHello.ciphers, "\n");
+		bs = bytesPrint(bs, be, "\tcompressors: ", m->u.clientHello.compressors, "\n");
+		break;
+	case HServerHello:
+		bs = seprint(bs, be, "ServerHello\n");
+		bs = seprint(bs, be, "\tversion: %.4x\n", m->u.serverHello.version);
+		bs = seprint(bs, be, "\trandom: ");
+		for(i=0; i<RandomSize; i++)
+			bs = seprint(bs, be, "%.2x", m->u.serverHello.random[i]);
+		bs = seprint(bs, be, "\n");
+		bs = bytesPrint(bs, be, "\tsid: ", m->u.serverHello.sid, "\n");
+		bs = seprint(bs, be, "\tcipher: %.4x\n", m->u.serverHello.cipher);
+		bs = seprint(bs, be, "\tcompressor: %.2x\n", m->u.serverHello.compressor);
+		break;
+	case HCertificate:
+		bs = seprint(bs, be, "Certificate\n");
+		for(i=0; i<m->u.certificate.ncert; i++)
+			bs = bytesPrint(bs, be, "\t", m->u.certificate.certs[i], "\n");
+		break;
+	case HCertificateRequest:
+		bs = seprint(bs, be, "CertificateRequest\n");
+		bs = bytesPrint(bs, be, "\ttypes: ", m->u.certificateRequest.types, "\n");
+		bs = seprint(bs, be, "\tcertificateauthorities\n");
+		for(i=0; i<m->u.certificateRequest.nca; i++)
+			bs = bytesPrint(bs, be, "\t\t", m->u.certificateRequest.cas[i], "\n");
+		break;
+	case HServerHelloDone:
+		bs = seprint(bs, be, "ServerHelloDone\n");
+		break;
+	case HClientKeyExchange:
+		bs = seprint(bs, be, "HClientKeyExchange\n");
+		bs = bytesPrint(bs, be, "\tkey: ", m->u.clientKeyExchange.key, "\n");
+		break;
+	case HFinished:
+		bs = seprint(bs, be, "HFinished\n");
+		for(i=0; i<m->u.finished.n; i++)
+			bs = seprint(bs, be, "%.2x", m->u.finished.verify[i]);
+		bs = seprint(bs, be, "\n");
+		break;
+	}
+	USED(bs);
+	return buf;
+}
+
+static void
+tlsError(TlsConnection *c, int err, char *fmt, ...)
+{
+	char msg[512];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(msg, msg+sizeof(msg), fmt, arg);
+	va_end(arg);
+	if(c->trace)
+		c->trace("tlsError: %s\n", msg);
+	else if(c->erred)
+		fprint(2, "double error: %r, %s", msg);
+	else
+		werrstr("tls: local %s", msg);
+	c->erred = 1;
+	fprint(c->ctl, "alert %d", err);
+}
+
+// commit to specific version number
+static int
+setVersion(TlsConnection *c, int version)
+{
+	if(c->verset || version > MaxProtoVersion || version < MinProtoVersion)
+		return -1;
+	if(version > c->version)
+		version = c->version;
+	if(version == SSL3Version) {
+		c->version = version;
+		c->finished.n = SSL3FinishedLen;
+	}else if(version == TLSVersion){
+		c->version = version;
+		c->finished.n = TLSFinishedLen;
+	}else
+		return -1;
+	c->verset = 1;
+	return fprint(c->ctl, "version 0x%x", version);
+}
+
+// confirm that received Finished message matches the expected value
+static int
+finishedMatch(TlsConnection *c, Finished *f)
+{
+	return memcmp(f->verify, c->finished.verify, f->n) == 0;
+}
+
+// free memory associated with TlsConnection struct
+//		(but don't close the TLS channel itself)
+static void
+tlsConnectionFree(TlsConnection *c)
+{
+	tlsSecClose(c->sec);
+	freebytes(c->sid);
+	freebytes(c->cert);
+	memset(c, 0, sizeof(c));
+	free(c);
+}
+
+
+//================= cipher choices ========================
+
+static int weakCipher[CipherMax] =
+{
+	1,	/* TLS_NULL_WITH_NULL_NULL */
+	1,	/* TLS_RSA_WITH_NULL_MD5 */
+	1,	/* TLS_RSA_WITH_NULL_SHA */
+	1,	/* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
+	0,	/* TLS_RSA_WITH_RC4_128_MD5 */
+	0,	/* TLS_RSA_WITH_RC4_128_SHA */
+	1,	/* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
+	0,	/* TLS_RSA_WITH_IDEA_CBC_SHA */
+	1,	/* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
+	0,	/* TLS_RSA_WITH_DES_CBC_SHA */
+	0,	/* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
+	1,	/* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
+	0,	/* TLS_DH_DSS_WITH_DES_CBC_SHA */
+	0,	/* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
+	1,	/* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
+	0,	/* TLS_DH_RSA_WITH_DES_CBC_SHA */
+	0,	/* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
+	1,	/* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
+	0,	/* TLS_DHE_DSS_WITH_DES_CBC_SHA */
+	0,	/* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
+	1,	/* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
+	0,	/* TLS_DHE_RSA_WITH_DES_CBC_SHA */
+	0,	/* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
+	1,	/* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
+	1,	/* TLS_DH_anon_WITH_RC4_128_MD5 */
+	1,	/* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
+	1,	/* TLS_DH_anon_WITH_DES_CBC_SHA */
+	1,	/* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
+};
+
+static int
+setAlgs(TlsConnection *c, int a)
+{
+	int i;
+
+	for(i = 0; i < nelem(cipherAlgs); i++){
+		if(cipherAlgs[i].tlsid == a){
+			c->enc = cipherAlgs[i].enc;
+			c->digest = cipherAlgs[i].digest;
+			c->nsecret = cipherAlgs[i].nsecret;
+			if(c->nsecret > MaxKeyData)
+				return 0;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int
+okCipher(Ints *cv)
+{
+	int weak, i, j, c;
+
+	weak = 1;
+	for(i = 0; i < cv->len; i++) {
+		c = cv->data[i];
+		if(c >= CipherMax)
+			weak = 0;
+		else
+			weak &= weakCipher[c];
+		for(j = 0; j < nelem(cipherAlgs); j++)
+			if(cipherAlgs[j].ok && cipherAlgs[j].tlsid == c)
+				return c;
+	}
+	if(weak)
+		return -2;
+	return -1;
+}
+
+static int
+okCompression(Bytes *cv)
+{
+	int i, j, c;
+
+	for(i = 0; i < cv->len; i++) {
+		c = cv->data[i];
+		for(j = 0; j < nelem(compressors); j++) {
+			if(compressors[j] == c)
+				return c;
+		}
+	}
+	return -1;
+}
+
+static Lock	ciphLock;
+static int	nciphers;
+
+static int
+initCiphers(void)
+{
+	enum {MaxAlgF = 1024, MaxAlgs = 10};
+	char s[MaxAlgF], *flds[MaxAlgs];
+	int i, j, n, ok;
+
+	lock(&ciphLock);
+	if(nciphers){
+		unlock(&ciphLock);
+		return nciphers;
+	}
+	j = open("#a/tls/encalgs", OREAD);
+	if(j < 0){
+		werrstr("can't open #a/tls/encalgs: %r");
+		return 0;
+	}
+	n = read(j, s, MaxAlgF-1);
+	close(j);
+	if(n <= 0){
+		werrstr("nothing in #a/tls/encalgs: %r");
+		return 0;
+	}
+	s[n] = 0;
+	n = getfields(s, flds, MaxAlgs, 1, " \t\r\n");
+	for(i = 0; i < nelem(cipherAlgs); i++){
+		ok = 0;
+		for(j = 0; j < n; j++){
+			if(strcmp(cipherAlgs[i].enc, flds[j]) == 0){
+				ok = 1;
+				break;
+			}
+		}
+		cipherAlgs[i].ok = ok;
+	}
+
+	j = open("#a/tls/hashalgs", OREAD);
+	if(j < 0){
+		werrstr("can't open #a/tls/hashalgs: %r");
+		return 0;
+	}
+	n = read(j, s, MaxAlgF-1);
+	close(j);
+	if(n <= 0){
+		werrstr("nothing in #a/tls/hashalgs: %r");
+		return 0;
+	}
+	s[n] = 0;
+	n = getfields(s, flds, MaxAlgs, 1, " \t\r\n");
+	for(i = 0; i < nelem(cipherAlgs); i++){
+		ok = 0;
+		for(j = 0; j < n; j++){
+			if(strcmp(cipherAlgs[i].digest, flds[j]) == 0){
+				ok = 1;
+				break;
+			}
+		}
+		cipherAlgs[i].ok &= ok;
+		if(cipherAlgs[i].ok)
+			nciphers++;
+	}
+	unlock(&ciphLock);
+	return nciphers;
+}
+
+static Ints*
+makeciphers(void)
+{
+	Ints *is;
+	int i, j;
+
+	is = newints(nciphers);
+	j = 0;
+	for(i = 0; i < nelem(cipherAlgs); i++){
+		if(cipherAlgs[i].ok)
+			is->data[j++] = cipherAlgs[i].tlsid;
+	}
+	return is;
+}
+
+
+
+//================= security functions ========================
+
+// given X.509 certificate, set up connection to factotum
+//	for using corresponding private key
+static AuthRpc*
+factotum_rsa_open(uchar *cert, int certlen)
+{
+	int afd;
+	char *s;
+	mpint *pub = nil;
+	RSApub *rsapub;
+	AuthRpc *rpc;
+
+	// start talking to factotum
+	if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0)
+		return nil;
+	if((rpc = auth_allocrpc(afd)) == nil){
+		close(afd);
+		return nil;
+	}
+	s = "proto=rsa service=tls role=client";
+	if(auth_rpc(rpc, "start", s, strlen(s)) != ARok){
+		factotum_rsa_close(rpc);
+		return nil;
+	}
+
+	// roll factotum keyring around to match certificate
+	rsapub = X509toRSApub(cert, certlen, nil, 0);
+	while(1){
+		if(auth_rpc(rpc, "read", nil, 0) != ARok){
+			factotum_rsa_close(rpc);
+			rpc = nil;
+			goto done;
+		}
+		pub = strtomp(rpc->arg, nil, 16, nil);
+		assert(pub != nil);
+		if(mpcmp(pub,rsapub->n) == 0)
+			break;
+	}
+done:
+	mpfree(pub);
+	rsapubfree(rsapub);
+	return rpc;
+}
+
+static mpint*
+factotum_rsa_decrypt(AuthRpc *rpc, mpint *cipher)
+{
+	char *p;
+	int rv;
+
+	if((p = mptoa(cipher, 16, nil, 0)) == nil)
+		return nil;
+	rv = auth_rpc(rpc, "write", p, strlen(p));
+	free(p);
+	if(rv != ARok || auth_rpc(rpc, "read", nil, 0) != ARok)
+		return nil;
+	mpfree(cipher);
+	return strtomp(rpc->arg, nil, 16, nil);
+}
+
+static void
+factotum_rsa_close(AuthRpc*rpc)
+{
+	if(!rpc)
+		return;
+	close(rpc->afd);
+	auth_freerpc(rpc);
+}
+
+static void
+tlsPmd5(uchar *buf, int nbuf, uchar *key, int nkey, uchar *label, int nlabel, uchar *seed0, int nseed0, uchar *seed1, int nseed1)
+{
+	uchar ai[MD5dlen], tmp[MD5dlen];
+	int i, n;
+	MD5state *s;
+
+	// generate a1
+	s = hmac_md5(label, nlabel, key, nkey, nil, nil);
+	s = hmac_md5(seed0, nseed0, key, nkey, nil, s);
+	hmac_md5(seed1, nseed1, key, nkey, ai, s);
+
+	while(nbuf > 0) {
+		s = hmac_md5(ai, MD5dlen, key, nkey, nil, nil);
+		s = hmac_md5(label, nlabel, key, nkey, nil, s);
+		s = hmac_md5(seed0, nseed0, key, nkey, nil, s);
+		hmac_md5(seed1, nseed1, key, nkey, tmp, s);
+		n = MD5dlen;
+		if(n > nbuf)
+			n = nbuf;
+		for(i = 0; i < n; i++)
+			buf[i] ^= tmp[i];
+		buf += n;
+		nbuf -= n;
+		hmac_md5(ai, MD5dlen, key, nkey, tmp, nil);
+		memmove(ai, tmp, MD5dlen);
+	}
+}
+
+static void
+tlsPsha1(uchar *buf, int nbuf, uchar *key, int nkey, uchar *label, int nlabel, uchar *seed0, int nseed0, uchar *seed1, int nseed1)
+{
+	uchar ai[SHA1dlen], tmp[SHA1dlen];
+	int i, n;
+	SHAstate *s;
+
+	// generate a1
+	s = hmac_sha1(label, nlabel, key, nkey, nil, nil);
+	s = hmac_sha1(seed0, nseed0, key, nkey, nil, s);
+	hmac_sha1(seed1, nseed1, key, nkey, ai, s);
+
+	while(nbuf > 0) {
+		s = hmac_sha1(ai, SHA1dlen, key, nkey, nil, nil);
+		s = hmac_sha1(label, nlabel, key, nkey, nil, s);
+		s = hmac_sha1(seed0, nseed0, key, nkey, nil, s);
+		hmac_sha1(seed1, nseed1, key, nkey, tmp, s);
+		n = SHA1dlen;
+		if(n > nbuf)
+			n = nbuf;
+		for(i = 0; i < n; i++)
+			buf[i] ^= tmp[i];
+		buf += n;
+		nbuf -= n;
+		hmac_sha1(ai, SHA1dlen, key, nkey, tmp, nil);
+		memmove(ai, tmp, SHA1dlen);
+	}
+}
+
+// fill buf with md5(args)^sha1(args)
+static void
+tlsPRF(uchar *buf, int nbuf, uchar *key, int nkey, char *label, uchar *seed0, int nseed0, uchar *seed1, int nseed1)
+{
+	int i;
+	int nlabel = strlen(label);
+	int n = (nkey + 1) >> 1;
+
+	for(i = 0; i < nbuf; i++)
+		buf[i] = 0;
+	tlsPmd5(buf, nbuf, key, n, (uchar*)label, nlabel, seed0, nseed0, seed1, nseed1);
+	tlsPsha1(buf, nbuf, key+nkey-n, n, (uchar*)label, nlabel, seed0, nseed0, seed1, nseed1);
+}
+
+/*
+ * for setting server session id's
+ */
+static Lock	sidLock;
+static long	maxSid = 1;
+
+/* the keys are verified to have the same public components
+ * and to function correctly with pkcs 1 encryption and decryption. */
+static TlsSec*
+tlsSecInits(int cvers, uchar *csid, int ncsid, uchar *crandom, uchar *ssid, int *nssid, uchar *srandom)
+{
+	TlsSec *sec = emalloc(sizeof(*sec));
+
+	USED(csid); USED(ncsid);  // ignore csid for now
+
+	memmove(sec->crandom, crandom, RandomSize);
+	sec->clientVers = cvers;
+
+	put32(sec->srandom, time(0));
+	genrandom(sec->srandom+4, RandomSize-4);
+	memmove(srandom, sec->srandom, RandomSize);
+
+	/*
+	 * make up a unique sid: use our pid, and and incrementing id
+	 * can signal no sid by setting nssid to 0.
+	 */
+	memset(ssid, 0, SidSize);
+	put32(ssid, getpid());
+	lock(&sidLock);
+	put32(ssid+4, maxSid++);
+	unlock(&sidLock);
+	*nssid = SidSize;
+	return sec;
+}
+
+static int
+tlsSecSecrets(TlsSec *sec, int vers, uchar *epm, int nepm, uchar *kd, int nkd)
+{
+	if(epm != nil){
+		if(setVers(sec, vers) < 0)
+			goto Err;
+		serverMasterSecret(sec, epm, nepm);
+	}else if(sec->vers != vers){
+		werrstr("mismatched session versions");
+		goto Err;
+	}
+	setSecrets(sec, kd, nkd);
+	return 0;
+Err:
+	sec->ok = -1;
+	return -1;
+}
+
+static TlsSec*
+tlsSecInitc(int cvers, uchar *crandom)
+{
+	TlsSec *sec = emalloc(sizeof(*sec));
+	sec->clientVers = cvers;
+	put32(sec->crandom, time(0));
+	genrandom(sec->crandom+4, RandomSize-4);
+	memmove(crandom, sec->crandom, RandomSize);
+	return sec;
+}
+
+static int
+tlsSecSecretc(TlsSec *sec, uchar *sid, int nsid, uchar *srandom, uchar *cert, int ncert, int vers, uchar **epm, int *nepm, uchar *kd, int nkd)
+{
+	RSApub *pub;
+
+	pub = nil;
+
+	USED(sid);
+	USED(nsid);
+	
+	memmove(sec->srandom, srandom, RandomSize);
+
+	if(setVers(sec, vers) < 0)
+		goto Err;
+
+	pub = X509toRSApub(cert, ncert, nil, 0);
+	if(pub == nil){
+		werrstr("invalid x509/rsa certificate");
+		goto Err;
+	}
+	if(clientMasterSecret(sec, pub, epm, nepm) < 0)
+		goto Err;
+	rsapubfree(pub);
+	setSecrets(sec, kd, nkd);
+	return 0;
+
+Err:
+	if(pub != nil)
+		rsapubfree(pub);
+	sec->ok = -1;
+	return -1;
+}
+
+static int
+tlsSecFinished(TlsSec *sec, MD5state md5, SHAstate sha1, uchar *fin, int nfin, int isclient)
+{
+	if(sec->nfin != nfin){
+		sec->ok = -1;
+		werrstr("invalid finished exchange");
+		return -1;
+	}
+	md5.malloced = 0;
+	sha1.malloced = 0;
+	(*sec->setFinished)(sec, md5, sha1, fin, isclient);
+	return 1;
+}
+
+static void
+tlsSecOk(TlsSec *sec)
+{
+	if(sec->ok == 0)
+		sec->ok = 1;
+}
+
+static void
+tlsSecKill(TlsSec *sec)
+{
+	if(!sec)
+		return;
+	factotum_rsa_close(sec->rpc);
+	sec->ok = -1;
+}
+
+static void
+tlsSecClose(TlsSec *sec)
+{
+	if(!sec)
+		return;
+	factotum_rsa_close(sec->rpc);
+	free(sec->server);
+	free(sec);
+}
+
+static int
+setVers(TlsSec *sec, int v)
+{
+	if(v == SSL3Version){
+		sec->setFinished = sslSetFinished;
+		sec->nfin = SSL3FinishedLen;
+		sec->prf = sslPRF;
+	}else if(v == TLSVersion){
+		sec->setFinished = tlsSetFinished;
+		sec->nfin = TLSFinishedLen;
+		sec->prf = tlsPRF;
+	}else{
+		werrstr("invalid version");
+		return -1;
+	}
+	sec->vers = v;
+	return 0;
+}
+
+/*
+ * generate secret keys from the master secret.
+ *
+ * different crypto selections will require different amounts
+ * of key expansion and use of key expansion data,
+ * but it's all generated using the same function.
+ */
+static void
+setSecrets(TlsSec *sec, uchar *kd, int nkd)
+{
+	(*sec->prf)(kd, nkd, sec->sec, MasterSecretSize, "key expansion",
+			sec->srandom, RandomSize, sec->crandom, RandomSize);
+}
+
+/*
+ * set the master secret from the pre-master secret.
+ */
+static void
+setMasterSecret(TlsSec *sec, Bytes *pm)
+{
+	(*sec->prf)(sec->sec, MasterSecretSize, pm->data, MasterSecretSize, "master secret",
+			sec->crandom, RandomSize, sec->srandom, RandomSize);
+}
+
+static void
+serverMasterSecret(TlsSec *sec, uchar *epm, int nepm)
+{
+	Bytes *pm;
+
+	pm = pkcs1_decrypt(sec, epm, nepm);
+
+	// if the client messed up, just continue as if everything is ok,
+	// to prevent attacks to check for correctly formatted messages.
+	// Hence the fprint(2,) can't be replaced by tlsError(), which sends an Alert msg to the client.
+	if(sec->ok < 0 || pm == nil || get16(pm->data) != sec->clientVers){
+		fprint(2, "serverMasterSecret failed ok=%d pm=%p pmvers=%x cvers=%x nepm=%d\n",
+			sec->ok, pm, pm ? get16(pm->data) : -1, sec->clientVers, nepm);
+		sec->ok = -1;
+		if(pm != nil)
+			freebytes(pm);
+		pm = newbytes(MasterSecretSize);
+		genrandom(pm->data, MasterSecretSize);
+	}
+	setMasterSecret(sec, pm);
+	memset(pm->data, 0, pm->len);	
+	freebytes(pm);
+}
+
+static int
+clientMasterSecret(TlsSec *sec, RSApub *pub, uchar **epm, int *nepm)
+{
+	Bytes *pm, *key;
+
+	pm = newbytes(MasterSecretSize);
+	put16(pm->data, sec->clientVers);
+	genrandom(pm->data+2, MasterSecretSize - 2);
+
+	setMasterSecret(sec, pm);
+
+	key = pkcs1_encrypt(pm, pub, 2);
+	memset(pm->data, 0, pm->len);
+	freebytes(pm);
+	if(key == nil){
+		werrstr("tls pkcs1_encrypt failed");
+		return -1;
+	}
+
+	*nepm = key->len;
+	*epm = malloc(*nepm);
+	if(*epm == nil){
+		freebytes(key);
+		werrstr("out of memory");
+		return -1;
+	}
+	memmove(*epm, key->data, *nepm);
+
+	freebytes(key);
+
+	return 1;
+}
+
+static void
+sslSetFinished(TlsSec *sec, MD5state hsmd5, SHAstate hssha1, uchar *finished, int isClient)
+{
+	DigestState *s;
+	uchar h0[MD5dlen], h1[SHA1dlen], pad[48];
+	char *label;
+
+	if(isClient)
+		label = "CLNT";
+	else
+		label = "SRVR";
+
+	md5((uchar*)label, 4, nil, &hsmd5);
+	md5(sec->sec, MasterSecretSize, nil, &hsmd5);
+	memset(pad, 0x36, 48);
+	md5(pad, 48, nil, &hsmd5);
+	md5(nil, 0, h0, &hsmd5);
+	memset(pad, 0x5C, 48);
+	s = md5(sec->sec, MasterSecretSize, nil, nil);
+	s = md5(pad, 48, nil, s);
+	md5(h0, MD5dlen, finished, s);
+
+	sha1((uchar*)label, 4, nil, &hssha1);
+	sha1(sec->sec, MasterSecretSize, nil, &hssha1);
+	memset(pad, 0x36, 40);
+	sha1(pad, 40, nil, &hssha1);
+	sha1(nil, 0, h1, &hssha1);
+	memset(pad, 0x5C, 40);
+	s = sha1(sec->sec, MasterSecretSize, nil, nil);
+	s = sha1(pad, 40, nil, s);
+	sha1(h1, SHA1dlen, finished + MD5dlen, s);
+}
+
+// fill "finished" arg with md5(args)^sha1(args)
+static void
+tlsSetFinished(TlsSec *sec, MD5state hsmd5, SHAstate hssha1, uchar *finished, int isClient)
+{
+	uchar h0[MD5dlen], h1[SHA1dlen];
+	char *label;
+
+	// get current hash value, but allow further messages to be hashed in
+	md5(nil, 0, h0, &hsmd5);
+	sha1(nil, 0, h1, &hssha1);
+
+	if(isClient)
+		label = "client finished";
+	else
+		label = "server finished";
+	tlsPRF(finished, TLSFinishedLen, sec->sec, MasterSecretSize, label, h0, MD5dlen, h1, SHA1dlen);
+}
+
+static void
+sslPRF(uchar *buf, int nbuf, uchar *key, int nkey, char *label, uchar *seed0, int nseed0, uchar *seed1, int nseed1)
+{
+	DigestState *s;
+	uchar sha1dig[SHA1dlen], md5dig[MD5dlen], tmp[26];
+	int i, n, len;
+
+	USED(label);
+	len = 1;
+	while(nbuf > 0){
+		if(len > 26)
+			return;
+		for(i = 0; i < len; i++)
+			tmp[i] = 'A' - 1 + len;
+		s = sha1(tmp, len, nil, nil);
+		s = sha1(key, nkey, nil, s);
+		s = sha1(seed0, nseed0, nil, s);
+		sha1(seed1, nseed1, sha1dig, s);
+		s = md5(key, nkey, nil, nil);
+		md5(sha1dig, SHA1dlen, md5dig, s);
+		n = MD5dlen;
+		if(n > nbuf)
+			n = nbuf;
+		memmove(buf, md5dig, n);
+		buf += n;
+		nbuf -= n;
+		len++;
+	}
+}
+
+static mpint*
+bytestomp(Bytes* bytes)
+{
+	mpint* ans;
+
+	ans = betomp(bytes->data, bytes->len, nil);
+	return ans;
+}
+
+/*
+ * Convert mpint* to Bytes, putting high order byte first.
+ */
+static Bytes*
+mptobytes(mpint* big)
+{
+	int n, m;
+	uchar *a;
+	Bytes* ans;
+
+	n = (mpsignif(big)+7)/8;
+	m = mptobe(big, nil, n, &a);
+	ans = makebytes(a, m);
+	return ans;
+}
+
+// Do RSA computation on block according to key, and pad
+// result on left with zeros to make it modlen long.
+static Bytes*
+rsacomp(Bytes* block, RSApub* key, int modlen)
+{
+	mpint *x, *y;
+	Bytes *a, *ybytes;
+	int ylen;
+
+	x = bytestomp(block);
+	y = rsaencrypt(key, x, nil);
+	mpfree(x);
+	ybytes = mptobytes(y);
+	ylen = ybytes->len;
+
+	if(ylen < modlen) {
+		a = newbytes(modlen);
+		memset(a->data, 0, modlen-ylen);
+		memmove(a->data+modlen-ylen, ybytes->data, ylen);
+		freebytes(ybytes);
+		ybytes = a;
+	}
+	else if(ylen > modlen) {
+		// assume it has leading zeros (mod should make it so)
+		a = newbytes(modlen);
+		memmove(a->data, ybytes->data, modlen);
+		freebytes(ybytes);
+		ybytes = a;
+	}
+	mpfree(y);
+	return ybytes;
+}
+
+// encrypt data according to PKCS#1, /lib/rfc/rfc2437 9.1.2.1
+static Bytes*
+pkcs1_encrypt(Bytes* data, RSApub* key, int blocktype)
+{
+	Bytes *pad, *eb, *ans;
+	int i, dlen, padlen, modlen;
+
+	modlen = (mpsignif(key->n)+7)/8;
+	dlen = data->len;
+	if(modlen < 12 || dlen > modlen - 11)
+		return nil;
+	padlen = modlen - 3 - dlen;
+	pad = newbytes(padlen);
+	genrandom(pad->data, padlen);
+	for(i = 0; i < padlen; i++) {
+		if(blocktype == 0)
+			pad->data[i] = 0;
+		else if(blocktype == 1)
+			pad->data[i] = 255;
+		else if(pad->data[i] == 0)
+			pad->data[i] = 1;
+	}
+	eb = newbytes(modlen);
+	eb->data[0] = 0;
+	eb->data[1] = blocktype;
+	memmove(eb->data+2, pad->data, padlen);
+	eb->data[padlen+2] = 0;
+	memmove(eb->data+padlen+3, data->data, dlen);
+	ans = rsacomp(eb, key, modlen);
+	freebytes(eb);
+	freebytes(pad);
+	return ans;
+}
+
+// decrypt data according to PKCS#1, with given key.
+// expect a block type of 2.
+static Bytes*
+pkcs1_decrypt(TlsSec *sec, uchar *epm, int nepm)
+{
+	Bytes *eb, *ans = nil;
+	int i, modlen;
+	mpint *x, *y;
+
+	modlen = (mpsignif(sec->rsapub->n)+7)/8;
+	if(nepm != modlen)
+		return nil;
+	x = betomp(epm, nepm, nil);
+	y = factotum_rsa_decrypt(sec->rpc, x);
+	if(y == nil)
+		return nil;
+	eb = mptobytes(y);
+	if(eb->len < modlen){ // pad on left with zeros
+		ans = newbytes(modlen);
+		memset(ans->data, 0, modlen-eb->len);
+		memmove(ans->data+modlen-eb->len, eb->data, eb->len);
+		freebytes(eb);
+		eb = ans;
+	}
+	if(eb->data[0] == 0 && eb->data[1] == 2) {
+		for(i = 2; i < modlen; i++)
+			if(eb->data[i] == 0)
+				break;
+		if(i < modlen - 1)
+			ans = makebytes(eb->data+i+1, modlen-(i+1));
+	}
+	freebytes(eb);
+	return ans;
+}
+
+
+//================= general utility functions ========================
+
+static void *
+emalloc(int n)
+{
+	void *p;
+	if(n==0)
+		n=1;
+	p = malloc(n);
+	if(p == nil){
+		exits("out of memory");
+	}
+	memset(p, 0, n);
+	return p;
+}
+
+static void *
+erealloc(void *ReallocP, int ReallocN)
+{
+	if(ReallocN == 0)
+		ReallocN = 1;
+	if(!ReallocP)
+		ReallocP = emalloc(ReallocN);
+	else if(!(ReallocP = realloc(ReallocP, ReallocN))){
+		exits("out of memory");
+	}
+	return(ReallocP);
+}
+
+static void
+put32(uchar *p, u32int x)
+{
+	p[0] = x>>24;
+	p[1] = x>>16;
+	p[2] = x>>8;
+	p[3] = x;
+}
+
+static void
+put24(uchar *p, int x)
+{
+	p[0] = x>>16;
+	p[1] = x>>8;
+	p[2] = x;
+}
+
+static void
+put16(uchar *p, int x)
+{
+	p[0] = x>>8;
+	p[1] = x;
+}
+
+static u32int
+get32(uchar *p)
+{
+	return (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
+}
+
+static int
+get24(uchar *p)
+{
+	return (p[0]<<16)|(p[1]<<8)|p[2];
+}
+
+static int
+get16(uchar *p)
+{
+	return (p[0]<<8)|p[1];
+}
+
+/* ANSI offsetof() */
+#define OFFSET(x, s) ((int)(&(((s*)0)->x)))
+
+/*
+ * malloc and return a new Bytes structure capable of
+ * holding len bytes. (len >= 0)
+ * Used to use crypt_malloc, which aborts if malloc fails.
+ */
+static Bytes*
+newbytes(int len)
+{
+	Bytes* ans;
+
+	ans = (Bytes*)malloc(OFFSET(data[0], Bytes) + len);
+	ans->len = len;
+	return ans;
+}
+
+/*
+ * newbytes(len), with data initialized from buf
+ */
+static Bytes*
+makebytes(uchar* buf, int len)
+{
+	Bytes* ans;
+
+	ans = newbytes(len);
+	memmove(ans->data, buf, len);
+	return ans;
+}
+
+static void
+freebytes(Bytes* b)
+{
+	if(b != nil)
+		free(b);
+}
+
+/* len is number of ints */
+static Ints*
+newints(int len)
+{
+	Ints* ans;
+
+	ans = (Ints*)malloc(OFFSET(data[0], Ints) + len*sizeof(int));
+	ans->len = len;
+	return ans;
+}
+
+static Ints*
+makeints(int* buf, int len)
+{
+	Ints* ans;
+
+	ans = newints(len);
+	if(len > 0)
+		memmove(ans->data, buf, len*sizeof(int));
+	return ans;
+}
+
+static void
+freeints(Ints* b)
+{
+	if(b != nil)
+		free(b);
+}
--- /dev/null
+++ b/libsec/x509.c
@@ -1,0 +1,2520 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+
+typedef DigestState*(*DigestFun)(uchar*,ulong,uchar*,DigestState*);
+
+/* ANSI offsetof, backwards. */
+#define	OFFSETOF(a, b)	offsetof(b, a)
+
+/*=============================================================*/
+/*  general ASN1 declarations and parsing
+ *
+ *  For now, this is used only for extracting the key from an
+ *  X509 certificate, so the entire collection is hidden.  But
+ *  someday we should probably make the functions visible and
+ *  give them their own man page.
+ */
+typedef struct Elem Elem;
+typedef struct Tag Tag;
+typedef struct Value Value;
+typedef struct Bytes Bytes;
+typedef struct Ints Ints;
+typedef struct Bits Bits;
+typedef struct Elist Elist;
+
+/* tag classes */
+#define Universal 0
+#define Context 0x80
+
+/* universal tags */
+#define BOOLEAN 1
+#define INTEGER 2
+#define BIT_STRING 3
+#define OCTET_STRING 4
+#define NULLTAG 5
+#define OBJECT_ID 6
+#define ObjectDescriptor 7
+#define EXTERNAL 8
+#define REAL 9
+#define ENUMERATED 10
+#define EMBEDDED_PDV 11
+#define SEQUENCE 16		/* also SEQUENCE OF */
+#define SETOF 17				/* also SETOF OF */
+#define NumericString 18
+#define PrintableString 19
+#define TeletexString 20
+#define VideotexString 21
+#define IA5String 22
+#define UTCTime 23
+#define GeneralizedTime 24
+#define GraphicString 25
+#define VisibleString 26
+#define GeneralString 27
+#define UniversalString 28
+#define BMPString 30
+
+struct Bytes {
+	int	len;
+	uchar	data[1];
+};
+
+struct Ints {
+	int	len;
+	int	data[1];
+};
+
+struct Bits {
+	int	len;		/* number of bytes */
+	int	unusedbits;	/* unused bits in last byte */
+	uchar	data[1];	/* most-significant bit first */
+};
+
+struct Tag {
+	int	class;
+	int	num;
+};
+
+enum { VBool, VInt, VOctets, VBigInt, VReal, VOther,
+	VBitString, VNull, VEOC, VObjId, VString, VSeq, VSet };
+struct Value {
+	int	tag;		/* VBool, etc. */
+	union {
+		int	boolval;
+		int	intval;
+		Bytes*	octetsval;
+		Bytes*	bigintval;
+		Bytes*	realval;	/* undecoded; hardly ever used */
+		Bytes*	otherval;
+		Bits*	bitstringval;
+		Ints*	objidval;
+		char*	stringval;
+		Elist*	seqval;
+		Elist*	setval;
+	} u;  /* (Don't use anonymous unions, for ease of porting) */
+};
+
+struct Elem {
+	Tag	tag;
+	Value	val;
+};
+
+struct Elist {
+	Elist*	tl;
+	Elem	hd;
+};
+
+/* decoding errors */
+enum { ASN_OK, ASN_ESHORT, ASN_ETOOBIG, ASN_EVALLEN,
+		ASN_ECONSTR, ASN_EPRIM, ASN_EINVAL, ASN_EUNIMPL };
+
+
+/* here are the functions to consider making extern someday */
+static Bytes*	newbytes(int len);
+static Bytes*	makebytes(uchar* buf, int len);
+static void	freebytes(Bytes* b);
+static Bytes*	catbytes(Bytes* b1, Bytes* b2);
+static Ints*	newints(int len);
+static Ints*	makeints(int* buf, int len);
+static void	freeints(Ints* b);
+static Bits*	newbits(int len);
+static Bits*	makebits(uchar* buf, int len, int unusedbits);
+static void	freebits(Bits* b);
+static Elist*	mkel(Elem e, Elist* tail);
+static void	freeelist(Elist* el);
+static int	elistlen(Elist* el);
+static int	is_seq(Elem* pe, Elist** pseq);
+static int	is_set(Elem* pe, Elist** pset);
+static int	is_int(Elem* pe, int* pint);
+static int	is_bigint(Elem* pe, Bytes** pbigint);
+static int	is_bitstring(Elem* pe, Bits** pbits);
+static int	is_octetstring(Elem* pe, Bytes** poctets);
+static int	is_oid(Elem* pe, Ints** poid);
+static int	is_string(Elem* pe, char** pstring);
+static int	is_time(Elem* pe, char** ptime);
+static int	decode(uchar* a, int alen, Elem* pelem);
+static int	decode_seq(uchar* a, int alen, Elist** pelist);
+static int	decode_value(uchar* a, int alen, int kind, int isconstr, Value* pval);
+static int	encode(Elem e, Bytes** pbytes);
+static int	oid_lookup(Ints* o, Ints** tab);
+static void	freevalfields(Value* v);
+static mpint	*asn1mpint(Elem *e);
+
+
+
+#define TAG_MASK 0x1F
+#define CONSTR_MASK 0x20
+#define CLASS_MASK 0xC0
+#define MAXOBJIDLEN 20
+
+static int ber_decode(uchar** pp, uchar* pend, Elem* pelem);
+static int tag_decode(uchar** pp, uchar* pend, Tag* ptag, int* pisconstr);
+static int length_decode(uchar** pp, uchar* pend, int* plength);
+static int value_decode(uchar** pp, uchar* pend, int length, int kind, int isconstr, Value* pval);
+static int int_decode(uchar** pp, uchar* pend, int count, int unsgned, int* pint);
+static int uint7_decode(uchar** pp, uchar* pend, int* pint);
+static int octet_decode(uchar** pp, uchar* pend, int length, int isconstr, Bytes** pbytes);
+static int seq_decode(uchar** pp, uchar* pend, int length, int isconstr, Elist** pelist);
+static int enc(uchar** pp, Elem e, int lenonly);
+static int val_enc(uchar** pp, Elem e, int *pconstr, int lenonly);
+static void uint7_enc(uchar** pp, int num, int lenonly);
+static void int_enc(uchar** pp, int num, int unsgned, int lenonly);
+
+static void *
+emalloc(int n)
+{
+	void *p;
+	if(n==0)
+		n=1;
+	p = malloc(n);
+	if(p == nil){
+		exits("out of memory");
+	}
+	memset(p, 0, n);
+	return p;
+}
+
+static char*
+estrdup(char *s)
+{
+	char *d, *d0;
+
+	if(!s)
+		return 0;
+	d = d0 = emalloc(strlen(s)+1);
+	while(*d++ = *s++)
+		;
+	return d0;
+}
+
+
+/*
+ * Decode a[0..len] as a BER encoding of an ASN1 type.
+ * The return value is one of ASN_OK, etc.
+ * Depending on the error, the returned elem may or may not
+ * be nil.
+ */
+static int
+decode(uchar* a, int alen, Elem* pelem)
+{
+	uchar* p = a;
+
+	return  ber_decode(&p, &a[alen], pelem);
+}
+
+/*
+ * Like decode, but continue decoding after first element
+ * of array ends.
+ */
+static int
+decode_seq(uchar* a, int alen, Elist** pelist)
+{
+	uchar* p = a;
+
+	return seq_decode(&p, &a[alen], -1, 1, pelist);
+}
+
+/*
+ * Decode the whole array as a BER encoding of an ASN1 value,
+ * (i.e., the part after the tag and length).
+ * Assume the value is encoded as universal tag "kind".
+ * The constr arg is 1 if the value is constructed, 0 if primitive.
+ * If there's an error, the return string will contain the error.
+ * Depending on the error, the returned value may or may not
+ * be nil.
+ */
+static int
+decode_value(uchar* a, int alen, int kind, int isconstr, Value* pval)
+{
+	uchar* p = a;
+
+	return value_decode(&p, &a[alen], alen, kind, isconstr, pval);
+}
+
+/*
+ * All of the following decoding routines take arguments:
+ *	uchar **pp;
+ *	uchar *pend;
+ * Where parsing is supposed to start at **pp, and when parsing
+ * is done, *pp is updated to point at next char to be parsed.
+ * The pend pointer is just past end of string; an error should
+ * be returned parsing hasn't finished by then.
+ *
+ * The returned int is ASN_OK if all went fine, else ASN_ESHORT, etc.
+ * The remaining argument(s) are pointers to where parsed entity goes.
+ */
+
+/* Decode an ASN1 'Elem' (tag, length, value) */
+static int
+ber_decode(uchar** pp, uchar* pend, Elem* pelem)
+{
+	int err;
+	int isconstr;
+	int length;
+	Tag tag;
+	Value val;
+
+	err = tag_decode(pp, pend, &tag, &isconstr);
+	if(err == ASN_OK) {
+		err = length_decode(pp, pend, &length);
+		if(err == ASN_OK) {
+			if(tag.class == Universal)
+				err = value_decode(pp, pend, length, tag.num, isconstr, &val);
+			else
+				err = value_decode(pp, pend, length, OCTET_STRING, 0, &val);
+			if(err == ASN_OK) {
+				pelem->tag = tag;
+				pelem->val = val;
+			}
+		}
+	}
+	return err;
+}
+
+/* Decode a tag field */
+static int
+tag_decode(uchar** pp, uchar* pend, Tag* ptag, int* pisconstr)
+{
+	int err;
+	int v;
+	uchar* p;
+
+	err = ASN_OK;
+	p = *pp;
+	if(pend-p >= 2) {
+		v = *p++;
+		ptag->class = v&CLASS_MASK;
+		if(v&CONSTR_MASK)
+			*pisconstr = 1;
+		else
+			*pisconstr = 0;
+		v &= TAG_MASK;
+		if(v == TAG_MASK)
+			err = uint7_decode(&p, pend, &v);
+		ptag->num = v;
+	}
+	else
+		err = ASN_ESHORT;
+	*pp = p;
+	return err;
+}
+
+/* Decode a length field */
+static int
+length_decode(uchar** pp, uchar* pend, int* plength)
+{
+	int err;
+	int num;
+	int v;
+	uchar* p;
+
+	err = ASN_OK;
+	num = 0;
+	p = *pp;
+	if(p < pend) {
+		v = *p++;
+		if(v&0x80)
+			err = int_decode(&p, pend, v&0x7F, 1, &num);
+		else if(v == 0x80)
+			num = -1;
+		else
+			num = v;
+	}
+	else
+		err = ASN_ESHORT;
+	*pp = p;
+	*plength = num;
+	return err;
+}
+
+/* Decode a value field  */
+static int
+value_decode(uchar** pp, uchar* pend, int length, int kind, int isconstr, Value* pval)
+{
+	int err;
+	Bytes* va;
+	int num;
+	int bitsunused;
+	int subids[MAXOBJIDLEN];
+	int isubid;
+	Elist*	vl;
+	uchar* p;
+	uchar* pe;
+
+	err = ASN_OK;
+	p = *pp;
+	if(length == -1) {	/* "indefinite" length spec */
+		if(!isconstr)
+			err = ASN_EINVAL;
+	}
+	else if(p + length > pend)
+		err = ASN_EVALLEN;
+	if(err != ASN_OK)
+		return err;
+
+	switch(kind) {
+	case 0:
+		/* marker for end of indefinite constructions */
+		if(length == 0)
+			pval->tag = VNull;
+		else
+			err = ASN_EINVAL;
+		break;
+
+	case BOOLEAN:
+		if(isconstr)
+			err = ASN_ECONSTR;
+		else if(length != 1)
+			err = ASN_EVALLEN;
+		else {
+			pval->tag = VBool;
+			pval->u.boolval = (*p++ != 0);
+		}
+		break;
+
+	case INTEGER:
+	case ENUMERATED:
+		if(isconstr)
+			err = ASN_ECONSTR;
+		else if(length <= 4) {
+			err = int_decode(&p, pend, length, 0, &num);
+			if(err == ASN_OK) {
+				pval->tag = VInt;
+				pval->u.intval = num;
+			}
+		}
+		else {
+			pval->tag = VBigInt;
+			pval->u.bigintval = makebytes(p, length);
+			p += length;
+		}
+		break;
+
+	case BIT_STRING:
+		pval->tag = VBitString;
+		if(isconstr) {
+			if(length == -1 && p + 2 <= pend && *p == 0 && *(p+1) ==0) {
+				pval->u.bitstringval = makebits(0, 0, 0);
+				p += 2;
+			}
+			else
+				/* TODO: recurse and concat results */
+				err = ASN_EUNIMPL;
+		}
+		else {
+			if(length < 2) {
+				if(length == 1 && *p == 0) {
+					pval->u.bitstringval = makebits(0, 0, 0);
+					p++;
+				}
+				else
+					err = ASN_EINVAL;
+			}
+			else {
+				bitsunused = *p;
+				if(bitsunused > 7)
+					err = ASN_EINVAL;
+				else if(length > 0x0FFFFFFF)
+					err = ASN_ETOOBIG;
+				else {
+					pval->u.bitstringval = makebits(p+1, length-1, bitsunused);
+					p += length;
+				}
+			}
+		}
+		break;
+
+	case OCTET_STRING:
+	case ObjectDescriptor:
+		err = octet_decode(&p, pend, length, isconstr, &va);
+		if(err == ASN_OK) {
+			pval->tag = VOctets;
+			pval->u.octetsval = va;
+		}
+		break;
+
+	case NULLTAG:
+		if(isconstr)
+			err = ASN_ECONSTR;
+		else if(length != 0)
+			err = ASN_EVALLEN;
+		else
+			pval->tag = VNull;
+		break;
+
+	case OBJECT_ID:
+		if(isconstr)
+			err = ASN_ECONSTR;
+		else if(length == 0)
+			err = ASN_EVALLEN;
+		else {
+			isubid = 0;
+			pe = p+length;
+			while(p < pe && isubid < MAXOBJIDLEN) {
+				err = uint7_decode(&p, pend, &num);
+				if(err != ASN_OK)
+					break;
+				if(isubid == 0) {
+					subids[isubid++] = num / 40;
+					subids[isubid++] = num % 40;
+				}
+				else
+					subids[isubid++] = num;
+			}
+			if(err == ASN_OK) {
+				if(p != pe)
+					err = ASN_EVALLEN;
+				else {
+					pval->tag = VObjId;
+					pval->u.objidval = makeints(subids, isubid);
+				}
+			}
+		}
+		break;
+
+	case EXTERNAL:
+	case EMBEDDED_PDV:
+		/* TODO: parse this internally */
+		if(p+length > pend)
+			err = ASN_EVALLEN;
+		else {
+			pval->tag = VOther;
+			pval->u.otherval = makebytes(p, length);
+			p += length;
+		}
+		break;
+
+	case REAL:
+		/* Let the application decode */
+		if(isconstr)
+			err = ASN_ECONSTR;
+		else if(p+length > pend)
+			err = ASN_EVALLEN;
+		else {
+			pval->tag = VReal;
+			pval->u.realval = makebytes(p, length);
+			p += length;
+		}
+		break;
+
+	case SEQUENCE:
+		err = seq_decode(&p, pend, length, isconstr, &vl);
+		if(err == ASN_OK) {
+			pval->tag = VSeq ;
+			pval->u.seqval = vl;
+		}
+		break;
+
+	case SETOF:
+		err = seq_decode(&p, pend, length, isconstr, &vl);
+		if(err == ASN_OK) {
+			pval->tag = VSet;
+			pval->u.setval = vl;
+		}
+		break;
+
+	case NumericString:
+	case PrintableString:
+	case TeletexString:
+	case VideotexString:
+	case IA5String:
+	case UTCTime:
+	case GeneralizedTime:
+	case GraphicString:
+	case VisibleString:
+	case GeneralString:
+	case UniversalString:
+	case BMPString:
+		/* TODO: figure out when character set conversion is necessary */
+		err = octet_decode(&p, pend, length, isconstr, &va);
+		if(err == ASN_OK) {
+			pval->tag = VString;
+			pval->u.stringval = (char*)emalloc(va->len+1);
+			memmove(pval->u.stringval, va->data, va->len);
+			pval->u.stringval[va->len] = 0;
+			free(va);
+		}
+		break;
+
+	default:
+		if(p+length > pend)
+			err = ASN_EVALLEN;
+		else {
+			pval->tag = VOther;
+			pval->u.otherval = makebytes(p, length);
+			p += length;
+		}
+		break;
+	}
+	*pp = p;
+	return err;
+}
+
+/*
+ * Decode an int in format where count bytes are
+ * concatenated to form value.
+ * Although ASN1 allows any size integer, we return
+ * an error if the result doesn't fit in a 32-bit int.
+ * If unsgned is not set, make sure to propagate sign bit.
+ */
+static int
+int_decode(uchar** pp, uchar* pend, int count, int unsgned, int* pint)
+{
+	int err;
+	int num;
+	uchar* p;
+
+	p = *pp;
+	err = ASN_OK;
+	num = 0;
+	if(p+count <= pend) {
+		if((count > 4) || (unsgned && count == 4 && (*p&0x80)))
+			err = ASN_ETOOBIG;
+		else {
+			if(!unsgned && count > 0 && count < 4 && (*p&0x80))
+				num = -1;		// set all bits, initially
+			while(count--)
+				num = (num << 8)|(*p++);
+		}
+	}
+	else
+		err = ASN_ESHORT;
+	*pint = num;
+	*pp = p;
+	return err;
+}
+
+/*
+ * Decode an unsigned int in format where each
+ * byte except last has high bit set, and remaining
+ * seven bits of each byte are concatenated to form value.
+ * Although ASN1 allows any size integer, we return
+ * an error if the result doesn't fit in a 32 bit int.
+ */
+static int
+uint7_decode(uchar** pp, uchar* pend, int* pint)
+{
+	int err;
+	int num;
+	int more;
+	int v;
+	uchar* p;
+
+	p = *pp;
+	err = ASN_OK;
+	num = 0;
+	more = 1;
+	while(more && p < pend) {
+		v = *p++;
+		if(num&0x7F000000) {
+			err = ASN_ETOOBIG;
+			break;
+		}
+		num <<= 7;
+		more = v&0x80;
+		num |= (v&0x7F);
+	}
+	if(p == pend)
+		err = ASN_ESHORT;
+	*pint = num;
+	*pp = p;
+	return err;
+}
+
+/*
+ * Decode an octet string, recursively if isconstr.
+ * We've already checked that length==-1 implies isconstr==1,
+ * and otherwise that specified length fits within (*pp..pend)
+ */
+static int
+octet_decode(uchar** pp, uchar* pend, int length, int isconstr, Bytes** pbytes)
+{
+	int err;
+	uchar* p;
+	Bytes* ans;
+	Bytes* newans;
+	uchar* pstart;
+	uchar* pold;
+	Elem	elem;
+
+	err = ASN_OK;
+	p = *pp;
+	ans = nil;
+	if(length >= 0 && !isconstr) {
+		ans = makebytes(p, length);
+		p += length;
+	}
+	else {
+		/* constructed, either definite or indefinite length */
+		pstart = p;
+		for(;;) {
+			if(length >= 0 && p >= pstart + length) {
+				if(p != pstart + length)
+					err = ASN_EVALLEN;
+				break;
+			}
+			pold = p;
+			err = ber_decode(&p, pend, &elem);
+			if(err != ASN_OK)
+				break;
+			switch(elem.val.tag) {
+			case VOctets:
+				newans = catbytes(ans, elem.val.u.octetsval);
+				freebytes(ans);
+				ans = newans;
+				break;
+
+			case VEOC:
+				if(length != -1) {
+					p = pold;
+					err = ASN_EINVAL;
+				}
+				goto cloop_done;
+
+			default:
+				p = pold;
+				err = ASN_EINVAL;
+				goto cloop_done;
+			}
+		}
+cloop_done:
+		;
+	}
+	*pp = p;
+	*pbytes = ans;
+	return err;
+}
+
+/*
+ * Decode a sequence or set.
+ * We've already checked that length==-1 implies isconstr==1,
+ * and otherwise that specified length fits within (*p..pend)
+ */
+static int
+seq_decode(uchar** pp, uchar* pend, int length, int isconstr, Elist** pelist)
+{
+	int err;
+	uchar* p;
+	uchar* pstart;
+	uchar* pold;
+	Elist* ans;
+	Elem elem;
+	Elist* lve;
+	Elist* lveold;
+
+	err = ASN_OK;
+	ans = nil;
+	p = *pp;
+	if(!isconstr)
+		err = ASN_EPRIM;
+	else {
+		/* constructed, either definite or indefinite length */
+		lve = nil;
+		pstart = p;
+		for(;;) {
+			if(length >= 0 && p >= pstart + length) {
+				if(p != pstart + length)
+					err = ASN_EVALLEN;
+				break;
+			}
+			pold = p;
+			err = ber_decode(&p, pend, &elem);
+			if(err != ASN_OK)
+				break;
+			if(elem.val.tag == VEOC) {
+				if(length != -1) {
+					p = pold;
+					err = ASN_EINVAL;
+				}
+				break;
+			}
+			else
+				lve = mkel(elem, lve);
+		}
+		if(err == ASN_OK) {
+			/* reverse back to original order */
+			while(lve != nil) {
+				lveold = lve;
+				lve = lve->tl;
+				lveold->tl = ans;
+				ans = lveold;
+			}
+		}
+	}
+	*pp = p;
+	*pelist = ans;
+	return err;
+}
+
+/*
+ * Encode e by BER rules, putting answer in *pbytes.
+ * This is done by first calling enc with lenonly==1
+ * to get the length of the needed buffer,
+ * then allocating the buffer and using enc again to fill it up.
+ */
+static int
+encode(Elem e, Bytes** pbytes)
+{
+	uchar* p;
+	Bytes* ans;
+	int err;
+	uchar uc;
+
+	p = &uc;
+	err = enc(&p, e, 1);
+	if(err == ASN_OK) {
+		ans = newbytes(p-&uc);
+		p = ans->data;
+		err = enc(&p, e, 0);
+		*pbytes = ans;
+	}
+	return err;
+}
+
+/*
+ * The various enc functions take a pointer to a pointer
+ * into a buffer, and encode their entity starting there,
+ * updating the pointer afterwards.
+ * If lenonly is 1, only the pointer update is done,
+ * allowing enc to be called first to calculate the needed
+ * buffer length.
+ * If lenonly is 0, it is assumed that the answer will fit.
+ */
+
+static int
+enc(uchar** pp, Elem e, int lenonly)
+{
+	int err;
+	int vlen;
+	int constr;
+	Tag tag;
+	int v;
+	int ilen;
+	uchar* p;
+	uchar* psave;
+
+	p = *pp;
+	err = val_enc(&p, e, &constr, 1);
+	if(err != ASN_OK)
+		return err;
+	vlen = p - *pp;
+	p = *pp;
+	tag = e.tag;
+	v = tag.class|constr;
+	if(tag.num < 31) {
+		if(!lenonly)
+			*p = (v|tag.num);
+		p++;
+	}
+	else {
+		if(!lenonly)
+			*p = (v|31);
+		p++;
+		if(tag.num < 0)
+			return ASN_EINVAL;
+		uint7_enc(&p, tag.num, lenonly);
+	}
+	if(vlen < 0x80) {
+		if(!lenonly)
+			*p = vlen;
+		p++;
+	}
+	else {
+		psave = p;
+		int_enc(&p, vlen, 1, 1);
+		ilen = p-psave;
+		p = psave;
+		if(!lenonly) {
+			*p++ = (0x80 | ilen);
+			int_enc(&p, vlen, 1, 0);
+		}
+		else
+			p += 1 + ilen;
+	}
+	if(!lenonly)
+		val_enc(&p, e, &constr, 0);
+	else
+		p += vlen;
+	*pp = p;
+	return err;
+}
+
+static int
+val_enc(uchar** pp, Elem e, int *pconstr, int lenonly)
+{
+	int err;
+	uchar* p;
+	int kind;
+	int cl;
+	int v;
+	Bytes* bb = nil;
+	Bits* bits;
+	Ints* oid;
+	int k;
+	Elist* el;
+	char* s;
+
+	p = *pp;
+	err = ASN_OK;
+	kind = e.tag.num;
+	cl = e.tag.class;
+	*pconstr = 0;
+	if(cl != Universal) {
+		switch(e.val.tag) {
+		case VBool:
+			kind = BOOLEAN;
+			break;
+		case VInt:
+			kind = INTEGER;
+			break;
+		case VBigInt:
+			kind = INTEGER;
+			break;
+		case VOctets:
+			kind = OCTET_STRING;
+			break;
+		case VReal:
+			kind = REAL;
+			break;
+		case VOther:
+			kind = OCTET_STRING;
+			break;
+		case VBitString:
+			kind = BIT_STRING;
+			break;
+		case VNull:
+			kind = NULLTAG;
+			break;
+		case VObjId:
+			kind = OBJECT_ID;
+			break;
+		case VString:
+			kind = UniversalString;
+			break;
+		case VSeq:
+			kind = SEQUENCE;
+			break;
+		case VSet:
+			kind = SETOF;
+			break;
+		}
+	}
+	switch(kind) {
+	case BOOLEAN:
+		if(is_int(&e, &v)) {
+			if(v != 0)
+				v = 255;
+			 int_enc(&p, v, 1, lenonly);
+		}
+		else
+			err = ASN_EINVAL;
+		break;
+
+	case INTEGER:
+	case ENUMERATED:
+		if(is_int(&e, &v))
+			int_enc(&p, v, 0, lenonly);
+		else {
+			if(is_bigint(&e, &bb)) {
+				if(!lenonly)
+					memmove(p, bb->data, bb->len);
+				p += bb->len;
+			}
+			else
+				err = ASN_EINVAL;
+		}
+		break;
+
+	case BIT_STRING:
+		if(is_bitstring(&e, &bits)) {
+			if(bits->len == 0) {
+				if(!lenonly)
+					*p = 0;
+				p++;
+			}
+			else {
+				v = bits->unusedbits;
+				if(v < 0 || v > 7)
+					err = ASN_EINVAL;
+				else {
+					if(!lenonly) {
+						*p = v;
+						memmove(p+1, bits->data, bits->len);
+					}
+					p += 1 + bits->len;
+				}
+			}
+		}
+		else
+			err = ASN_EINVAL;
+		break;
+
+	case OCTET_STRING:
+	case ObjectDescriptor:
+	case EXTERNAL:
+	case REAL:
+	case EMBEDDED_PDV:
+		bb = nil;
+		switch(e.val.tag) {
+		case VOctets:
+			bb = e.val.u.octetsval;
+			break;
+		case VReal:
+			bb = e.val.u.realval;
+			break;
+		case VOther:
+			bb = e.val.u.otherval;
+			break;
+		}
+		if(bb != nil) {
+			if(!lenonly)
+				memmove(p, bb->data, bb->len);
+			p += bb->len;
+		}
+			else
+				err = ASN_EINVAL;
+		break;
+
+	case NULLTAG:
+		break;
+
+	case OBJECT_ID:
+		if(is_oid(&e, &oid)) {
+			for(k = 0; k < oid->len; k++) {
+				v = oid->data[k];
+				if(k == 0) {
+					v *= 40;
+					if(oid->len > 1)
+						v += oid->data[++k];
+				}
+				uint7_enc(&p, v, lenonly);
+			}
+		}
+		else
+			err = ASN_EINVAL;
+		break;
+
+	case SEQUENCE:
+	case SETOF:
+		el = nil;
+		if(e.val.tag == VSeq)
+			el = e.val.u.seqval;
+		else if(e.val.tag == VSet)
+			el = e.val.u.setval;
+		else
+			err = ASN_EINVAL;
+		if(el != nil) {
+			*pconstr = CONSTR_MASK;
+			for(; el != nil; el = el->tl) {
+				err = enc(&p, el->hd, lenonly);
+				if(err != ASN_OK)
+					break;
+			}
+		}
+		break;
+
+	case NumericString:
+	case PrintableString:
+	case TeletexString:
+	case VideotexString:
+	case IA5String:
+	case UTCTime:
+	case GeneralizedTime:
+	case GraphicString:
+	case VisibleString:
+	case GeneralString:
+	case UniversalString:
+	case BMPString:
+		if(e.val.tag == VString) {
+			s = e.val.u.stringval;
+			if(s != nil) {
+				v = strlen(s);
+				if(!lenonly)
+					memmove(p, s, v);
+				p += v;
+			}
+		}
+		else
+			err = ASN_EINVAL;
+		break;
+
+	default:
+		err = ASN_EINVAL;
+	}
+	*pp = p;
+	return err;
+}
+
+/*
+ * Encode num as unsigned 7 bit values with top bit 1 on all bytes
+ * except last, only putting in bytes if !lenonly.
+ */
+static void
+uint7_enc(uchar** pp, int num, int lenonly)
+{
+	int n;
+	int v;
+	int k;
+	uchar* p;
+
+	p = *pp;
+	n = 1;
+	v = num >> 7;
+	while(v > 0) {
+		v >>= 7;
+		n++;
+	}
+	if(lenonly)
+		p += n;
+	else {
+		for(k = (n - 1)*7; k > 0; k -= 7)
+			*p++= ((num >> k)|0x80);
+		*p++ = (num&0x7F);
+	}
+	*pp = p;
+}
+
+/*
+ * Encode num as unsigned or signed integer,
+ * only putting in bytes if !lenonly.
+ * Encoding is length followed by bytes to concatenate.
+ */
+static void
+int_enc(uchar** pp, int num, int unsgned, int lenonly)
+{
+	int v;
+	int n;
+	int prevv;
+	int k;
+	uchar* p;
+
+	p = *pp;
+	v = num;
+	if(v < 0)
+		v = -(v + 1);
+	n = 1;
+	prevv = v;
+	v >>= 8;
+	while(v > 0) {
+		prevv = v;
+		v >>= 8;
+		n++;
+	}
+	if(!unsgned && (prevv&0x80))
+		n++;
+	if(lenonly)
+		p += n;
+	else {
+		for(k = (n - 1)*8; k >= 0; k -= 8)
+			*p++ = (num >> k);
+	}
+	*pp = p;
+}
+
+static int
+ints_eq(Ints* a, Ints* b)
+{
+	int	alen;
+	int	i;
+
+	alen = a->len;
+	if(alen != b->len)
+		return 0;
+	for(i = 0; i < alen; i++)
+		if(a->data[i] != b->data[i])
+			return 0;
+	return 1;
+}
+
+/*
+ * Look up o in tab (which must have nil entry to terminate).
+ * Return index of matching entry, or -1 if none.
+ */
+static int
+oid_lookup(Ints* o, Ints** tab)
+{
+	int i;
+
+	for(i = 0; tab[i] != nil; i++)
+		if(ints_eq(o, tab[i]))
+			return  i;
+	return -1;
+}
+
+/*
+ * Return true if *pe is a SEQUENCE, and set *pseq to
+ * the value of the sequence if so.
+ */
+static int
+is_seq(Elem* pe, Elist** pseq)
+{
+	if(pe->tag.class == Universal && pe->tag.num == SEQUENCE && pe->val.tag == VSeq) {
+		*pseq = pe->val.u.seqval;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+is_set(Elem* pe, Elist** pset)
+{
+	if(pe->tag.class == Universal && pe->tag.num == SETOF && pe->val.tag == VSet) {
+		*pset = pe->val.u.setval;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+is_int(Elem* pe, int* pint)
+{
+	if(pe->tag.class == Universal) {
+		if(pe->tag.num == INTEGER && pe->val.tag == VInt) {
+			*pint = pe->val.u.intval;
+			return 1;
+		}
+		else if(pe->tag.num == BOOLEAN && pe->val.tag == VBool) {
+			*pint = pe->val.u.boolval;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * for convience, all VInt's are readable via this routine,
+ * as well as all VBigInt's
+ */
+static int
+is_bigint(Elem* pe, Bytes** pbigint)
+{
+	int v, n, i;
+
+	if(pe->tag.class == Universal && pe->tag.num == INTEGER) {
+		if(pe->val.tag == VBigInt)
+			*pbigint = pe->val.u.bigintval;
+		else if(pe->val.tag == VInt){
+			v = pe->val.u.intval;
+			for(n = 1; n < 4; n++)
+				if((1 << (8 * n)) > v)
+					break;
+			*pbigint = newbytes(n);
+			for(i = 0; i < n; i++)
+				(*pbigint)->data[i] = (v >> ((n - 1 - i) * 8));
+		}else
+			return 0;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+is_bitstring(Elem* pe, Bits** pbits)
+{
+	if(pe->tag.class == Universal && pe->tag.num == BIT_STRING && pe->val.tag == VBitString) {
+		*pbits = pe->val.u.bitstringval;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+is_octetstring(Elem* pe, Bytes** poctets)
+{
+	if(pe->tag.class == Universal && pe->tag.num == OCTET_STRING && pe->val.tag == VOctets) {
+		*poctets = pe->val.u.octetsval;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+is_oid(Elem* pe, Ints** poid)
+{
+	if(pe->tag.class == Universal && pe->tag.num == OBJECT_ID && pe->val.tag == VObjId) {
+		*poid = pe->val.u.objidval;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+is_string(Elem* pe, char** pstring)
+{
+	if(pe->tag.class == Universal) {
+		switch(pe->tag.num) {
+		case NumericString:
+		case PrintableString:
+		case TeletexString:
+		case VideotexString:
+		case IA5String:
+		case GraphicString:
+		case VisibleString:
+		case GeneralString:
+		case UniversalString:
+		case BMPString:
+			if(pe->val.tag == VString) {
+				*pstring = pe->val.u.stringval;
+				return 1;
+			}
+		}
+	}
+	return 0;
+}
+
+static int
+is_time(Elem* pe, char** ptime)
+{
+	if(pe->tag.class == Universal
+	   && (pe->tag.num == UTCTime || pe->tag.num == GeneralizedTime)
+	   && pe->val.tag == VString) {
+		*ptime = pe->val.u.stringval;
+		return 1;
+	}
+	return 0;
+}
+
+
+/*
+ * malloc and return a new Bytes structure capable of
+ * holding len bytes. (len >= 0)
+ */
+static Bytes*
+newbytes(int len)
+{
+	Bytes* ans;
+
+	ans = (Bytes*)emalloc(OFFSETOF(data[0], Bytes) + len);
+	ans->len = len;
+	return ans;
+}
+
+/*
+ * newbytes(len), with data initialized from buf
+ */
+static Bytes*
+makebytes(uchar* buf, int len)
+{
+	Bytes* ans;
+
+	ans = newbytes(len);
+	memmove(ans->data, buf, len);
+	return ans;
+}
+
+static void
+freebytes(Bytes* b)
+{
+	if(b != nil)
+		free(b);
+}
+
+/*
+ * Make a new Bytes, containing bytes of b1 followed by those of b2.
+ * Either b1 or b2 or both can be nil.
+ */
+static Bytes*
+catbytes(Bytes* b1, Bytes* b2)
+{
+	Bytes* ans;
+	int n;
+
+	if(b1 == nil) {
+		if(b2 == nil)
+			ans = newbytes(0);
+		else
+			ans = makebytes(b2->data, b2->len);
+	}
+	else if(b2 == nil) {
+		ans = makebytes(b1->data, b1->len);
+	}
+	else {
+		n = b1->len + b2->len;
+		ans = newbytes(n);
+		ans->len = n;
+		memmove(ans->data, b1->data, b1->len);
+		memmove(ans->data+b1->len, b2->data, b2->len);
+	}
+	return ans;
+}
+
+/* len is number of ints */
+static Ints*
+newints(int len)
+{
+	Ints* ans;
+
+	ans = (Ints*)emalloc(OFFSETOF(data[0], Ints) + len*sizeof(int));
+	ans->len = len;
+	return ans;
+}
+
+static Ints*
+makeints(int* buf, int len)
+{
+	Ints* ans;
+
+	ans = newints(len);
+	if(len > 0)
+		memmove(ans->data, buf, len*sizeof(int));
+	return ans;
+}
+
+static void
+freeints(Ints* b)
+{
+	if(b != nil)
+		free(b);
+}
+
+/* len is number of bytes */
+static Bits*
+newbits(int len)
+{
+	Bits* ans;
+
+	ans = (Bits*)emalloc(OFFSETOF(data[0], Bits) + len);
+	ans->len = len;
+	ans->unusedbits = 0;
+	return ans;
+}
+
+static Bits*
+makebits(uchar* buf, int len, int unusedbits)
+{
+	Bits* ans;
+
+	ans = newbits(len);
+	memmove(ans->data, buf, len);
+	ans->unusedbits = unusedbits;
+	return ans;
+}
+
+static void
+freebits(Bits* b)
+{
+	if(b != nil)
+		free(b);
+}
+
+static Elist*
+mkel(Elem e, Elist* tail)
+{
+	Elist* el;
+
+	el = (Elist*)emalloc(sizeof(Elist));
+	el->hd = e;
+	el->tl = tail;
+	return el;
+}
+
+static int
+elistlen(Elist* el)
+{
+	int ans = 0;
+	while(el != nil) {
+		ans++;
+		el = el->tl;
+	}
+	return ans;
+}
+
+/* Frees elist, but not fields inside values of constituent elems */
+static void
+freeelist(Elist* el)
+{
+	Elist* next;
+
+	while(el != nil) {
+		next = el->tl;
+		free(el);
+		el = next;
+	}
+}
+
+/* free any allocated structures inside v (recursively freeing Elists) */
+static void
+freevalfields(Value* v)
+{
+	Elist* el;
+	Elist* l;
+	if(v == nil)
+		return;
+	switch(v->tag) {
+ 	case VOctets:
+		freebytes(v->u.octetsval);
+		break;
+	case VBigInt:
+		freebytes(v->u.bigintval);
+		break;
+	case VReal:
+		freebytes(v->u.realval);
+		break;
+	case VOther:
+		freebytes(v->u.otherval);
+		break;
+	case VBitString:
+		freebits(v->u.bitstringval);
+		break;
+	case VObjId:
+		freeints(v->u.objidval);
+		break;
+	case VString:
+		if (v->u.stringval)
+			free(v->u.stringval);
+		break;
+	case VSeq:
+		el = v->u.seqval;
+		for(l = el; l != nil; l = l->tl)
+			freevalfields(&l->hd.val);
+		if (el)
+			freeelist(el);
+		break;
+	case VSet:
+		el = v->u.setval;
+		for(l = el; l != nil; l = l->tl)
+			freevalfields(&l->hd.val);
+		if (el)
+			freeelist(el);
+		break;
+	}
+}
+
+/* end of general ASN1 functions */
+
+
+
+
+
+/*=============================================================*/
+/*
+ * Decode and parse an X.509 Certificate, defined by this ASN1:
+ *	Certificate ::= SEQUENCE {
+ *		certificateInfo CertificateInfo,
+ *		signatureAlgorithm AlgorithmIdentifier,
+ *		signature BIT STRING }
+ *
+ *	CertificateInfo ::= SEQUENCE {
+ *		version [0] INTEGER DEFAULT v1 (0),
+ *		serialNumber INTEGER,
+ *		signature AlgorithmIdentifier,
+ *		issuer Name,
+ *		validity Validity,
+ *		subject Name,
+ *		subjectPublicKeyInfo SubjectPublicKeyInfo }
+ *	(version v2 has two more fields, optional unique identifiers for
+ *  issuer and subject; since we ignore these anyway, we won't parse them)
+ *
+ *	Validity ::= SEQUENCE {
+ *		notBefore UTCTime,
+ *		notAfter UTCTime }
+ *
+ *	SubjectPublicKeyInfo ::= SEQUENCE {
+ *		algorithm AlgorithmIdentifier,
+ *		subjectPublicKey BIT STRING }
+ *
+ *	AlgorithmIdentifier ::= SEQUENCE {
+ *		algorithm OBJECT IDENTIFER,
+ *		parameters ANY DEFINED BY ALGORITHM OPTIONAL }
+ *
+ *	Name ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ *	RelativeDistinguishedName ::= SETOF SIZE(1..MAX) OF AttributeTypeAndValue
+ *
+ *	AttributeTypeAndValue ::= SEQUENCE {
+ *		type OBJECT IDENTIFER,
+ *		value DirectoryString }
+ *	(selected attributes have these Object Ids:
+ *		commonName {2 5 4 3}
+ *		countryName {2 5 4 6}
+ *		localityName {2 5 4 7}
+ *		stateOrProvinceName {2 5 4 8}
+ *		organizationName {2 5 4 10}
+ *		organizationalUnitName {2 5 4 11}
+ *	)
+ *
+ *	DirectoryString ::= CHOICE {
+ *		teletexString TeletexString,
+ *		printableString PrintableString,
+ *		universalString UniversalString }
+ *
+ *  See rfc1423, rfc2437 for AlgorithmIdentifier, subjectPublicKeyInfo, signature.
+ *
+ *  Not yet implemented:
+ *   CertificateRevocationList ::= SIGNED SEQUENCE{
+ *           signature       AlgorithmIdentifier,
+ *           issuer          Name,
+ *           lastUpdate      UTCTime,
+ *           nextUpdate      UTCTime,
+ *           revokedCertificates
+ *                           SEQUENCE OF CRLEntry OPTIONAL}
+ *   CRLEntry ::= SEQUENCE{
+ *           userCertificate SerialNumber,
+ *           revocationDate UTCTime}
+ */
+
+typedef struct CertX509 {
+	int	serial;
+	char*	issuer;
+	char*	validity_start;
+	char*	validity_end;
+	char*	subject;
+	int	publickey_alg;
+	Bytes*	publickey;
+	int	signature_alg;
+	Bytes*	signature;
+} CertX509;
+
+/* Algorithm object-ids */
+enum {
+	ALG_rsaEncryption,
+	ALG_md2WithRSAEncryption,
+	ALG_md4WithRSAEncryption,
+	ALG_md5WithRSAEncryption,
+	ALG_sha1WithRSAEncryption,
+	ALG_md5,
+	NUMALGS
+};
+typedef struct Ints7 {
+	int		len;
+	int		data[7];
+} Ints7;
+static Ints7 oid_rsaEncryption = {7, 1, 2, 840, 113549, 1, 1, 1 };
+static Ints7 oid_md2WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 2 };
+static Ints7 oid_md4WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 3 };
+static Ints7 oid_md5WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 4 };
+static Ints7 oid_sha1WithRSAEncryption ={7, 1, 2, 840, 113549, 1, 1, 5 };
+static Ints7 oid_md5 ={6, 1, 2, 840, 113549, 2, 5, 0 };
+static Ints *alg_oid_tab[NUMALGS+1] = {
+	(Ints*)&oid_rsaEncryption,
+	(Ints*)&oid_md2WithRSAEncryption,
+	(Ints*)&oid_md4WithRSAEncryption,
+	(Ints*)&oid_md5WithRSAEncryption,
+	(Ints*)&oid_sha1WithRSAEncryption,
+	(Ints*)&oid_md5,
+	nil
+};
+static DigestFun digestalg[NUMALGS+1] = { md5, md5, md5, md5, sha1, md5, nil };
+
+static void
+freecert(CertX509* c)
+{
+	if (!c) return;
+	if(c->issuer != nil)
+		free(c->issuer);
+	if(c->validity_start != nil)
+		free(c->validity_start);
+	if(c->validity_end != nil)
+		free(c->validity_end);
+	if(c->subject != nil)
+		free(c->subject);
+	freebytes(c->publickey);
+	freebytes(c->signature);
+}
+
+/*
+ * Parse the Name ASN1 type.
+ * The sequence of RelativeDistinguishedName's gives a sort of pathname,
+ * from most general to most specific.  Each element of the path can be
+ * one or more (but usually just one) attribute-value pair, such as
+ * countryName="US".
+ * We'll just form a "postal-style" address string by concatenating the elements
+ * from most specific to least specific, separated by commas.
+ * Return name-as-string (which must be freed by caller).
+ */
+static char*
+parse_name(Elem* e)
+{
+	Elist* el;
+	Elem* es;
+	Elist* esetl;
+	Elem* eat;
+	Elist* eatl;
+	char* s;
+	enum { MAXPARTS = 100 };
+	char* parts[MAXPARTS];
+	int i;
+	int plen;
+	char* ans = nil;
+
+	if(!is_seq(e, &el))
+		goto errret;
+	i = 0;
+	plen = 0;
+	while(el != nil) {
+		es = &el->hd;
+		if(!is_set(es, &esetl))
+			goto errret;
+		while(esetl != nil) {
+			eat = &esetl->hd;
+			if(!is_seq(eat, &eatl) || elistlen(eatl) != 2)
+				goto errret;
+			if(!is_string(&eatl->tl->hd, &s) || i>=MAXPARTS)
+				goto errret;
+			parts[i++] = s;
+			plen += strlen(s) + 2;		/* room for ", " after */
+			esetl = esetl->tl;
+		}
+		el = el->tl;
+	}
+	if(i > 0) {
+		ans = (char*)emalloc(plen);
+		*ans = '\0';
+		while(--i >= 0) {
+			s = parts[i];
+			strcat(ans, s);
+			if(i > 0)
+				strcat(ans, ", ");
+		}
+	}
+
+errret:
+	return ans;
+}
+
+/*
+ * Parse an AlgorithmIdentifer ASN1 type.
+ * Look up the oid in oid_tab and return one of OID_rsaEncryption, etc..,
+ * or -1 if not found.
+ * For now, ignore parameters, since none of our algorithms need them.
+ */
+static int
+parse_alg(Elem* e)
+{
+	Elist* el;
+	Ints* oid;
+
+	if(!is_seq(e, &el) || el == nil || !is_oid(&el->hd, &oid))
+		return -1;
+	return oid_lookup(oid, alg_oid_tab);
+}
+
+static CertX509*
+decode_cert(Bytes* a)
+{
+	int ok = 0;
+	int n;
+	CertX509* c = nil;
+	Elem  ecert;
+	Elem* ecertinfo;
+	Elem* esigalg;
+	Elem* esig;
+	Elem* eserial;
+	Elem* eissuer;
+	Elem* evalidity;
+	Elem* esubj;
+	Elem* epubkey;
+	Elist* el;
+	Elist* elcert = nil;
+	Elist* elcertinfo = nil;
+	Elist* elvalidity = nil;
+	Elist* elpubkey = nil;
+	Bits* bits = nil;
+	Bytes* b;
+	Elem* e;
+
+	if(decode(a->data, a->len, &ecert) != ASN_OK)
+		goto errret;
+
+	c = (CertX509*)emalloc(sizeof(CertX509));
+	c->serial = -1;
+	c->issuer = nil;
+	c->validity_start = nil;
+	c->validity_end = nil;
+	c->subject = nil;
+	c->publickey_alg = -1;
+	c->publickey = nil;
+	c->signature_alg = -1;
+	c->signature = nil;
+
+	/* Certificate */
+ 	if(!is_seq(&ecert, &elcert) || elistlen(elcert) !=3)
+		goto errret;
+ 	ecertinfo = &elcert->hd;
+ 	el = elcert->tl;
+ 	esigalg = &el->hd;
+	c->signature_alg = parse_alg(esigalg);
+ 	el = el->tl;
+ 	esig = &el->hd;
+
+	/* Certificate Info */
+	if(!is_seq(ecertinfo, &elcertinfo))
+		goto errret;
+	n = elistlen(elcertinfo);
+  	if(n < 6)
+		goto errret;
+	eserial =&elcertinfo->hd;
+ 	el = elcertinfo->tl;
+ 	/* check for optional version, marked by explicit context tag 0 */
+	if(eserial->tag.class == Context && eserial->tag.num == 0) {
+ 		eserial = &el->hd;
+ 		if(n < 7)
+ 			goto errret;
+ 		el = el->tl;
+ 	}
+
+	if(parse_alg(&el->hd) != c->signature_alg)
+		goto errret;
+ 	el = el->tl;
+ 	eissuer = &el->hd;
+ 	el = el->tl;
+ 	evalidity = &el->hd;
+ 	el = el->tl;
+ 	esubj = &el->hd;
+ 	el = el->tl;
+ 	epubkey = &el->hd;
+ 	if(!is_int(eserial, &c->serial)) {
+		if(!is_bigint(eserial, &b))
+			goto errret;
+		c->serial = -1;	/* else we have to change cert struct */
+  	}
+	c->issuer = parse_name(eissuer);
+	if(c->issuer == nil)
+		goto errret;
+	/* Validity */
+  	if(!is_seq(evalidity, &elvalidity))
+		goto errret;
+	if(elistlen(elvalidity) != 2)
+		goto errret;
+	e = &elvalidity->hd;
+	if(!is_time(e, &c->validity_start))
+		goto errret;
+	e->val.u.stringval = nil;	/* string ownership transfer */
+	e = &elvalidity->tl->hd;
+ 	if(!is_time(e, &c->validity_end))
+		goto errret;
+	e->val.u.stringval = nil;	/* string ownership transfer */
+
+	/* resume CertificateInfo */
+ 	c->subject = parse_name(esubj);
+	if(c->subject == nil)
+		goto errret;
+
+	/* SubjectPublicKeyInfo */
+ 	if(!is_seq(epubkey, &elpubkey))
+		goto errret;
+	if(elistlen(elpubkey) != 2)
+		goto errret;
+
+	c->publickey_alg = parse_alg(&elpubkey->hd);
+	if(c->publickey_alg < 0)
+		goto errret;
+  	if(!is_bitstring(&elpubkey->tl->hd, &bits))
+		goto errret;
+	if(bits->unusedbits != 0)
+		goto errret;
+ 	c->publickey = makebytes(bits->data, bits->len);
+
+	/*resume Certificate */
+	if(c->signature_alg < 0)
+		goto errret;
+ 	if(!is_bitstring(esig, &bits))
+		goto errret;
+ 	c->signature = makebytes(bits->data, bits->len);
+	ok = 1;
+
+errret:
+	freevalfields(&ecert.val);	/* recurses through lists, too */
+	if(!ok){
+		freecert(c);
+		c = nil;
+	}
+	return c;
+}
+
+/*
+ *	RSAPublickKey :: SEQUENCE {
+ *		modulus INTEGER,
+ *		publicExponent INTEGER
+ *	}
+ */
+static RSApub*
+decode_rsapubkey(Bytes* a)
+{
+	Elem e;
+	Elist *el;
+	mpint *mp;
+	RSApub* key;
+
+	key = rsapuballoc();
+	if(decode(a->data, a->len, &e) != ASN_OK)
+		goto errret;
+	if(!is_seq(&e, &el) || elistlen(el) != 2)
+		goto errret;
+
+	key->n = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->ek = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+	return key;
+errret:
+	rsapubfree(key);
+	return nil;
+}
+
+/*
+ *	RSAPrivateKey ::= SEQUENCE {
+ *		version Version,
+ *		modulus INTEGER, -- n
+ *		publicExponent INTEGER, -- e
+ *		privateExponent INTEGER, -- d
+ *		prime1 INTEGER, -- p
+ *		prime2 INTEGER, -- q
+ *		exponent1 INTEGER, -- d mod (p-1)
+ *		exponent2 INTEGER, -- d mod (q-1)
+ *		coefficient INTEGER -- (inverse of q) mod p }
+ */
+static RSApriv*
+decode_rsaprivkey(Bytes* a)
+{
+	int version;
+	Elem e;
+	Elist *el;
+	mpint *mp;
+	RSApriv* key;
+
+	key = rsaprivalloc();
+	if(decode(a->data, a->len, &e) != ASN_OK)
+		goto errret;
+	if(!is_seq(&e, &el) || elistlen(el) != 9)
+		goto errret;
+	if(!is_int(&el->hd, &version) || version != 0)
+		goto errret;
+
+	el = el->tl;
+	key->pub.n = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->pub.ek = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->dk = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->q = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->p = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->kq = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->kp = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	el = el->tl;
+	key->c2 = mp = asn1mpint(&el->hd);
+	if(mp == nil)
+		goto errret;
+
+	return key;
+errret:
+	rsaprivfree(key);
+	return nil;
+}
+
+static mpint*
+asn1mpint(Elem *e)
+{
+	Bytes *b;
+	mpint *mp;
+	int v;
+
+	if(is_int(e, &v))
+		return itomp(v, nil);
+	if(is_bigint(e, &b)) {
+		mp = betomp(b->data, b->len, nil);
+		freebytes(b);
+		return mp;
+	}
+	return nil;
+}
+
+static mpint*
+pkcs1pad(Bytes *b, mpint *modulus)
+{
+	int n = (mpsignif(modulus)+7)/8;
+	int pm1, i;
+	uchar *p;
+	mpint *mp;
+
+	pm1 = n - 1 - b->len;
+	p = (uchar*)emalloc(n);
+	p[0] = 0;
+	p[1] = 1;
+	for(i = 2; i < pm1; i++)
+		p[i] = 0xFF;
+	p[pm1] = 0;
+	memcpy(&p[pm1+1], b->data, b->len);
+	mp = betomp(p, n, nil);
+	free(p);
+	return mp;
+}
+
+RSApriv*
+asn1toRSApriv(uchar *kd, int kn)
+{
+	Bytes *b;
+	RSApriv *key;
+
+	b = makebytes(kd, kn);
+	key = decode_rsaprivkey(b);
+	freebytes(b);
+	return key;
+}
+
+/*
+ * digest(CertificateInfo)
+ * Our ASN.1 library doesn't return pointers into the original
+ * data array, so we need to do a little hand decoding.
+ */
+static void
+digest_certinfo(Bytes *cert, DigestFun digestfun, uchar *digest)
+{
+	uchar *info, *p, *pend;
+	ulong infolen;
+	int isconstr, length;
+	Tag tag;
+	Elem elem;
+
+	p = cert->data;
+	pend = cert->data + cert->len;
+	if(tag_decode(&p, pend, &tag, &isconstr) != ASN_OK ||
+			tag.class != Universal || tag.num != SEQUENCE ||
+			length_decode(&p, pend, &length) != ASN_OK ||
+			p+length > pend)
+		return;
+	info = p;
+	if(ber_decode(&p, pend, &elem) != ASN_OK || elem.tag.num != SEQUENCE)
+		return;
+	infolen = p - info;
+	(*digestfun)(info, infolen, digest, nil);
+}
+
+static char*
+verify_signature(Bytes* signature, RSApub *pk, uchar *edigest, Elem **psigalg)
+{
+	Elem e;
+	Elist *el;
+	Bytes *digest;
+	uchar *pkcs1buf, *buf;
+	int buflen;
+	mpint *pkcs1;
+
+	/* see 9.2.1 of rfc2437 */
+	pkcs1 = betomp(signature->data, signature->len, nil);
+	mpexp(pkcs1, pk->ek, pk->n, pkcs1);
+	buflen = mptobe(pkcs1, nil, 0, &pkcs1buf);
+	buf = pkcs1buf;
+	if(buflen < 4 || buf[0] != 1)
+		return "expected 1";
+	buf++;
+	while(buf[0] == 0xff)
+		buf++;
+	if(buf[0] != 0)
+		return "expected 0";
+	buf++;
+	buflen -= buf-pkcs1buf;
+	if(decode(buf, buflen, &e) != ASN_OK || !is_seq(&e, &el) || elistlen(el) != 2 ||
+			!is_octetstring(&el->tl->hd, &digest))
+		return "signature parse error";
+	*psigalg = &el->hd;
+	if(memcmp(digest->data, edigest, digest->len) == 0)
+		return nil;
+	return "digests did not match";
+}
+	
+RSApub*
+X509toRSApub(uchar *cert, int ncert, char *name, int nname)
+{
+	char *e;
+	Bytes *b;
+	CertX509 *c;
+	RSApub *pk;
+
+	b = makebytes(cert, ncert);
+	c = decode_cert(b);
+	freebytes(b);
+	if(c == nil)
+		return nil;
+	if(name != nil && c->subject != nil){
+		e = strchr(c->subject, ',');
+		if(e != nil)
+			*e = 0;  // take just CN part of Distinguished Name
+		strncpy(name, c->subject, nname);
+	}
+	pk = decode_rsapubkey(c->publickey);
+	freecert(c);
+	return pk;
+}
+
+char*
+X509verify(uchar *cert, int ncert, RSApub *pk)
+{
+	char *e;
+	Bytes *b;
+	CertX509 *c;
+	uchar digest[SHA1dlen];
+	Elem *sigalg;
+
+	b = makebytes(cert, ncert);
+	c = decode_cert(b);
+	if(c != nil)
+		digest_certinfo(b, digestalg[c->signature_alg], digest);
+	freebytes(b);
+	if(c == nil)
+		return "cannot decode cert";
+	e = verify_signature(c->signature, pk, digest, &sigalg);
+	freecert(c);
+	return e;
+}
+
+/* ------- Elem constructors ---------- */
+static Elem
+Null(void)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = NULLTAG;
+	e.val.tag = VNull;
+	return e;
+}
+
+static Elem
+mkint(int j)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = INTEGER;
+	e.val.tag = VInt;
+	e.val.u.intval = j;
+	return e;
+}
+
+static Elem
+mkbigint(mpint *p)
+{
+	Elem e;
+	uchar *buf;
+	int buflen;
+
+	e.tag.class = Universal;
+	e.tag.num = INTEGER;
+	e.val.tag = VBigInt;
+	buflen = mptobe(p, nil, 0, &buf);
+	e.val.u.bigintval = makebytes(buf, buflen);
+	free(buf);
+	return e;
+}
+
+static Elem
+mkstring(char *s)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = IA5String;
+	e.val.tag = VString;
+	e.val.u.stringval = estrdup(s);
+	return e;
+}
+
+static Elem
+mkoctet(uchar *buf, int buflen)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = OCTET_STRING;
+	e.val.tag = VOctets;
+	e.val.u.octetsval = makebytes(buf, buflen);
+	return e;
+}
+
+static Elem
+mkbits(uchar *buf, int buflen)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = BIT_STRING;
+	e.val.tag = VBitString;
+	e.val.u.bitstringval = makebits(buf, buflen, 0);
+	return e;
+}
+
+static Elem
+mkutc(long t)
+{
+	Elem e;
+	char utc[50];
+	Tm *tm = gmtime(t);
+
+	e.tag.class = Universal;
+	e.tag.num = UTCTime;
+	e.val.tag = VString;
+	snprint(utc, 50, "%.2d%.2d%.2d%.2d%.2d%.2dZ",
+		tm->year % 100, tm->mon+1, tm->mday, tm->hour, tm->min, tm->sec);
+	e.val.u.stringval = estrdup(utc);
+	return e;
+}
+
+static Elem
+mkoid(Ints *oid)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = OBJECT_ID;
+	e.val.tag = VObjId;
+	e.val.u.objidval = makeints(oid->data, oid->len);
+	return e;
+}
+
+static Elem
+mkseq(Elist *el)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = SEQUENCE;
+	e.val.tag = VSeq;
+	e.val.u.seqval = el;
+	return e;
+}
+
+static Elem
+mkset(Elist *el)
+{
+	Elem e;
+
+	e.tag.class = Universal;
+	e.tag.num = SETOF;
+	e.val.tag = VSet;
+	e.val.u.setval = el;
+	return e;
+}
+
+static Elem
+mkalg(int alg)
+{
+	return mkseq(mkel(mkoid(alg_oid_tab[alg]), mkel(Null(), nil)));
+}
+
+typedef struct Ints7pref {
+	int		len;
+	int		data[7];
+	char	prefix[4];
+} Ints7pref;
+Ints7pref DN_oid[] = {
+	{4, 2, 5, 4, 6, 0, 0, 0,  "C="},
+	{4, 2, 5, 4, 8, 0, 0, 0,  "ST="},
+	{4, 2, 5, 4, 7, 0, 0, 0,  "L="},
+	{4, 2, 5, 4, 10, 0, 0, 0, "O="},
+	{4, 2, 5, 4, 11, 0, 0, 0, "OU="},
+	{4, 2, 5, 4, 3, 0, 0, 0,  "CN="},
+ 	{7, 1,2,840,113549,1,9,1, "E="},
+};
+
+static Elem
+mkname(Ints7pref *oid, char *subj)
+{
+	return mkset(mkel(mkseq(mkel(mkoid((Ints*)oid), mkel(mkstring(subj), nil))), nil));
+}
+
+static Elem
+mkDN(char *dn)
+{
+	int i, j, nf;
+	char *f[20], *prefix, *d2 = estrdup(dn);
+	Elist* el = nil;
+
+	nf = tokenize(d2, f, nelem(f));
+	for(i=nf-1; i>=0; i--){
+		for(j=0; j<nelem(DN_oid); j++){
+			prefix = DN_oid[j].prefix;
+			if(strncmp(f[i],prefix,strlen(prefix))==0){
+				el = mkel(mkname(&DN_oid[j],f[i]+strlen(prefix)), el);
+				break;
+			}
+		}
+	}
+	free(d2);
+	return mkseq(el);
+}
+
+
+uchar*
+X509gen(RSApriv *priv, char *subj, ulong valid[2], int *certlen)
+{
+	int serial = 0;
+	uchar *cert = nil;
+	RSApub *pk = rsaprivtopub(priv);
+	Bytes *certbytes, *pkbytes, *certinfobytes, *sigbytes;
+	Elem e, certinfo, issuer, subject, pubkey, validity, sig;
+	uchar digest[MD5dlen], *buf;
+	int buflen;
+	mpint *pkcs1;
+
+	e.val.tag = VInt;  /* so freevalfields at errret is no-op */
+	issuer = mkDN(subj);
+	subject = mkDN(subj);
+	pubkey = mkseq(mkel(mkbigint(pk->n),mkel(mkint(mptoi(pk->ek)),nil)));
+	if(encode(pubkey, &pkbytes) != ASN_OK)
+		goto errret;
+	freevalfields(&pubkey.val);
+	pubkey = mkseq(
+		mkel(mkalg(ALG_rsaEncryption),
+		mkel(mkbits(pkbytes->data, pkbytes->len),
+		nil)));
+	freebytes(pkbytes);
+	validity = mkseq(
+		mkel(mkutc(valid[0]),
+		mkel(mkutc(valid[1]),
+		nil)));
+	certinfo = mkseq(
+		mkel(mkint(serial),
+		mkel(mkalg(ALG_md5WithRSAEncryption),
+		mkel(issuer,
+		mkel(validity,
+		mkel(subject,
+		mkel(pubkey,
+		nil)))))));
+	if(encode(certinfo, &certinfobytes) != ASN_OK)
+		goto errret;
+	md5(certinfobytes->data, certinfobytes->len, digest, 0);
+	freebytes(certinfobytes);
+	sig = mkseq(
+		mkel(mkalg(ALG_md5),
+		mkel(mkoctet(digest, MD5dlen),
+		nil)));
+	if(encode(sig, &sigbytes) != ASN_OK)
+		goto errret;
+	pkcs1 = pkcs1pad(sigbytes, pk->n);
+	freebytes(sigbytes);
+	rsadecrypt(priv, pkcs1, pkcs1);
+	buflen = mptobe(pkcs1, nil, 0, &buf);
+	mpfree(pkcs1);
+	e = mkseq(
+		mkel(certinfo,
+		mkel(mkalg(ALG_md5WithRSAEncryption),
+		mkel(mkbits(buf, buflen),
+		nil))));
+	free(buf);
+	if(encode(e, &certbytes) != ASN_OK)
+		goto errret;
+	if(certlen)
+		*certlen = certbytes->len;
+	cert = certbytes->data;
+errret:
+	freevalfields(&e.val);
+	return cert;
+}
+
+uchar*
+X509req(RSApriv *priv, char *subj, int *certlen)
+{
+	/* RFC 2314, PKCS #10 Certification Request Syntax */
+	int version = 0;
+	uchar *cert = nil;
+	RSApub *pk = rsaprivtopub(priv);
+	Bytes *certbytes, *pkbytes, *certinfobytes, *sigbytes;
+	Elem e, certinfo, subject, pubkey, sig;
+	uchar digest[MD5dlen], *buf;
+	int buflen;
+	mpint *pkcs1;
+
+	e.val.tag = VInt;  /* so freevalfields at errret is no-op */
+	subject = mkDN(subj);
+	pubkey = mkseq(mkel(mkbigint(pk->n),mkel(mkint(mptoi(pk->ek)),nil)));
+	if(encode(pubkey, &pkbytes) != ASN_OK)
+		goto errret;
+	freevalfields(&pubkey.val);
+	pubkey = mkseq(
+		mkel(mkalg(ALG_rsaEncryption),
+		mkel(mkbits(pkbytes->data, pkbytes->len),
+		nil)));
+	freebytes(pkbytes);
+	certinfo = mkseq(
+		mkel(mkint(version),
+		mkel(subject,
+		mkel(pubkey,
+		nil))));
+	if(encode(certinfo, &certinfobytes) != ASN_OK)
+		goto errret;
+	md5(certinfobytes->data, certinfobytes->len, digest, 0);
+	freebytes(certinfobytes);
+	sig = mkseq(
+		mkel(mkalg(ALG_md5),
+		mkel(mkoctet(digest, MD5dlen),
+		nil)));
+	if(encode(sig, &sigbytes) != ASN_OK)
+		goto errret;
+	pkcs1 = pkcs1pad(sigbytes, pk->n);
+	freebytes(sigbytes);
+	rsadecrypt(priv, pkcs1, pkcs1);
+	buflen = mptobe(pkcs1, nil, 0, &buf);
+	mpfree(pkcs1);
+	e = mkseq(
+		mkel(certinfo,
+		mkel(mkalg(ALG_md5),
+		mkel(mkbits(buf, buflen),
+		nil))));
+	free(buf);
+	if(encode(e, &certbytes) != ASN_OK)
+		goto errret;
+	if(certlen)
+		*certlen = certbytes->len;
+	cert = certbytes->data;
+errret:
+	freevalfields(&e.val);
+	return cert;
+}
+
+static char*
+tagdump(Tag tag)
+{
+	if(tag.class != Universal)
+		return smprint("class%d,num%d", tag.class, tag.num);
+	switch(tag.num){
+		case BOOLEAN: return "BOOLEAN"; break;
+		case INTEGER: return "INTEGER"; break;
+		case BIT_STRING: return "BIT STRING"; break;
+		case OCTET_STRING: return "OCTET STRING"; break;
+		case NULLTAG: return "NULLTAG"; break;
+		case OBJECT_ID: return "OID"; break;
+		case ObjectDescriptor: return "OBJECT_DES"; break;
+		case EXTERNAL: return "EXTERNAL"; break;
+		case REAL: return "REAL"; break;
+		case ENUMERATED: return "ENUMERATED"; break;
+		case EMBEDDED_PDV: return "EMBEDDED PDV"; break;
+		case SEQUENCE: return "SEQUENCE"; break;
+		case SETOF: return "SETOF"; break;
+		case NumericString: return "NumericString"; break;
+		case PrintableString: return "PrintableString"; break;
+		case TeletexString: return "TeletexString"; break;
+		case VideotexString: return "VideotexString"; break;
+		case IA5String: return "IA5String"; break;
+		case UTCTime: return "UTCTime"; break;
+		case GeneralizedTime: return "GeneralizedTime"; break;
+		case GraphicString: return "GraphicString"; break;
+		case VisibleString: return "VisibleString"; break;
+		case GeneralString: return "GeneralString"; break;
+		case UniversalString: return "UniversalString"; break;
+		case BMPString: return "BMPString"; break;
+		default:
+			return smprint("Universal,num%d", tag.num);
+	}
+}
+
+static void
+edump(Elem e)
+{
+	Value v;
+	Elist *el;
+	int i;
+
+	print("%s{", tagdump(e.tag));
+	v = e.val;
+	switch(v.tag){
+	case VBool: print("Bool %d",v.u.boolval); break;
+	case VInt: print("Int %d",v.u.intval); break;
+	case VOctets: print("Octets[%d] %.2x%.2x...",v.u.octetsval->len,v.u.octetsval->data[0],v.u.octetsval->data[1]); break;
+	case VBigInt: print("BigInt[%d] %.2x%.2x...",v.u.bigintval->len,v.u.bigintval->data[0],v.u.bigintval->data[1]); break;
+	case VReal: print("Real..."); break;
+	case VOther: print("Other..."); break;
+	case VBitString: print("BitString..."); break;
+	case VNull: print("Null"); break;
+	case VEOC: print("EOC..."); break;
+	case VObjId: print("ObjId");
+		for(i = 0; i<v.u.objidval->len; i++)
+			print(" %d", v.u.objidval->data[i]);
+		break;
+	case VString: print("String \"%s\"",v.u.stringval); break;
+	case VSeq: print("Seq\n");
+		for(el = v.u.seqval; el!=nil; el = el->tl)
+			edump(el->hd);
+		break;
+	case VSet: print("Set\n");
+		for(el = v.u.setval; el!=nil; el = el->tl)
+			edump(el->hd);
+		break;
+	}
+	print("}\n");
+}
+
+void
+asn1dump(uchar *der, int len)
+{
+	Elem e;
+
+	if(decode(der, len, &e) != ASN_OK){
+		print("didn't parse\n");
+		exits("didn't parse");
+	}
+	edump(e);
+}
+
+void
+X509dump(uchar *cert, int ncert)
+{
+	char *e;
+	Bytes *b;
+	CertX509 *c;
+	RSApub *pk;
+	uchar digest[SHA1dlen];
+	Elem *sigalg;
+
+	print("begin X509dump\n");
+	b = makebytes(cert, ncert);
+	c = decode_cert(b);
+	if(c != nil)
+		digest_certinfo(b, digestalg[c->signature_alg], digest);
+	freebytes(b);
+	if(c == nil){
+		print("cannot decode cert");
+		return;
+	}
+
+	print("serial %d\n", c->serial);
+	print("issuer %s\n", c->issuer);
+	print("validity %s %s\n", c->validity_start, c->validity_end);
+	print("subject %s\n", c->subject);
+	pk = decode_rsapubkey(c->publickey);
+	print("pubkey e=%B n(%d)=%B\n", pk->ek, mpsignif(pk->n), pk->n);
+
+	print("sigalg=%d digest=%.*H\n", c->signature_alg, MD5dlen, digest);
+	e = verify_signature(c->signature, pk, digest, &sigalg);
+	if(e==nil){
+		e = "nil (meaning ok)";
+		print("sigalg=\n");
+		if(sigalg)
+			edump(*sigalg);
+	}
+	print("self-signed verify_signature returns: %s\n", e);
+
+	rsapubfree(pk);
+	freecert(c);
+	print("end X509dump\n");
+}
--- /dev/null
+++ b/main.c
@@ -1,0 +1,126 @@
+#include "u.h"
+#include "libc.h"
+#include "kern/dat.h"
+#include "kern/fns.h"
+
+#include "drawterm.h"
+
+char *authaddr = "auth";
+char *cpuaddr = "cpu";
+char *argv0;
+char *user;
+
+extern int errfmt(Fmt*);
+void
+sizebug(void)
+{
+	/*
+	 * Needed by various parts of the code.
+	 * This is a huge bug.
+	 */
+	assert(sizeof(char)==1);
+	assert(sizeof(short)==2);
+	assert(sizeof(ushort)==2);
+	assert(sizeof(int)==4);
+	assert(sizeof(uint)==4);
+	assert(sizeof(long)==4);
+	assert(sizeof(ulong)==4);
+	assert(sizeof(vlong)==8);
+	assert(sizeof(uvlong)==8);
+}
+
+int
+main(int argc, char **argv)
+{
+	int fd;
+	char buf[1024], *s;
+	int n;
+
+	sizebug();
+	fmtinstall('r', errfmt);
+
+	osinit();
+	procinit0();
+	printinit();
+	screeninit();
+
+	chandevreset();
+	chandevinit();
+	quotefmtinstall();
+
+	if(bind("#c", "/dev", MBEFORE) < 0)
+		panic("bind #c: %r");
+	if(bind("#m", "/dev", MBEFORE) < 0)
+		panic("bind #m: %r");
+	if(bind("#i", "/dev", MBEFORE) < 0)
+		panic("bind #i: %r");
+	if(bind("#I", "/net", MBEFORE) < 0)
+		panic("bind #I: %r");
+	if(bind("#U", "/", MAFTER) < 0)
+		panic("bind #U: %r");
+
+	if(open("/dev/cons", OREAD) != 0)
+		panic("open0: %r");
+	if(open("/dev/cons", OWRITE) != 1)
+		panic("open1: %r");
+	if(open("/dev/cons", OWRITE) != 2)
+		panic("open2: %r");
+
+	cpumain(argc, argv);
+	return 0;
+}
+
+char*
+getkey(char *user, char *dom)
+{
+	char buf[1024];
+
+	snprint(buf, sizeof buf, "%s@%s password", user, dom);
+	return readcons(buf, nil, 1);
+}
+
+char*
+findkey(char **puser, char *dom)
+{
+	char buf[1024], *f[50], *p, *ep, *nextp, *pass, *user;
+	int nf, haveproto,  havedom, i;
+
+	for(p=secstorebuf; *p; p=nextp){
+		nextp = strchr(p, '\n');
+		if(nextp == nil){
+			ep = p+strlen(p);
+			nextp = "";
+		}else{
+			ep = nextp++;
+		}
+		if(ep-p >= sizeof buf){
+			print("warning: skipping long line in secstore factotum file\n");
+			continue;
+		}
+		memmove(buf, p, ep-p);
+		buf[ep-p] = 0;
+		nf = tokenize(buf, f, nelem(f));
+		if(nf == 0 || strcmp(f[0], "key") != 0)
+			continue;
+		pass = nil;
+		haveproto = havedom = 0;
+		for(i=1; i<nf; i++){
+			if(strncmp(f[i], "user=", 5) == 0)
+				user = f[i]+5;
+			if(strncmp(f[i], "!password=", 10) == 0)
+				pass = f[i]+10;
+			if(strncmp(f[i], "dom=", 4) == 0 && strcmp(f[i]+4, dom) == 0)
+				havedom = 1;
+			if(strcmp(f[i], "proto=p9sk1") == 0)
+				haveproto = 1;
+		}
+		if(!haveproto || !havedom || !pass || !user)
+			continue;
+		*puser = strdup(user);
+		pass = strdup(pass);
+		memset(buf, 0, sizeof buf);
+		return pass;
+	}
+	return nil;
+}
+
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,95 @@
+#CONF=FreeBSD
+#CONF=FreeBSD-power	# MAC OSX
+#CONF=Irix
+CONF=posix
+#CONF=OSF1
+#CONF=Solaris
+#CONF=Solaris-386
+#CONF=Solaris-sparc
+#CONF=Windows
+
+<mkfile-$CONF
+TARG=drawterm
+
+OFILES=\
+	main.$O\
+	cpu.$O\
+	readcons.$O\
+	secstore.$O
+
+DIRS=	kern exportfs libauthsrv libsec libmp libmemdraw libmemlayer libdraw libc
+LIBS=\
+	kern/libkern.$L\
+	exportfs/libexportfs.$L\
+	libauthsrv/libauthsrv.$L\
+	libsec/libsec.$L\
+	libmp/libmp.$L\
+	libmemdraw/libmemdraw.$L\
+	libmemlayer/libmemlayer.$L\
+	libdraw/libdraw.$L\
+	libc/libc.$L\
+	$SYS-$objtype/libmachdep.$L \
+	gui-win32/libgui.$L\
+#	gui-x11/libx11.$L\
+
+$TARG: $OFILES $LIBS
+	$LD $LDFLAGS $prereq $OSLIBS 
+
+# $TARG: $OFILES $LIBS
+#	$CC -o $TARG $OFILES $LIBS $LDFLAGS
+
+%.$O: %.c
+	$CC $CFLAGS $stem.c
+
+clean:
+	rm -f *.$O */*.$O */*.$L drawterm  *.$L
+
+#$LIBS:
+#	for (i in $DIRS) {
+#		cd $i
+#		mk
+#	}
+
+kern/libkern.$L:
+	cd kern; mk
+
+exportfs/libexportfs.$L:
+	cd exportfs; mk
+
+libauthsrv/libauthsrv.$L:
+	cd libauthsrv; mk
+
+libmp/libmp.$L:
+	cd libmp; mk
+
+libsec/libsec.$L:
+	cd libsec; mk
+
+libmemdraw/libmemdraw.$L:
+	cd libmemdraw; mk
+
+libmemlayer/libmemlayer.$L:
+	cd libmemlayer; mk
+
+libdraw/libdraw.$L:
+	cd libdraw; mk
+
+libc/libc.$L:
+	cd libc; mk
+
+gui-x11/libx11.$L:
+	cd gui-x11; mk
+
+gui-win32/libgui.$L:
+	cd gui-win32; mk
+
+drawterm.res:\
+	drawterm.ico\
+
+$SYS-$objtype/libmachdep.$L:
+	arch=$SYS-$objtype
+	cd $arch; mk
+
+#libmachdep.$L:
+#	arch=`{uname -m|sed 's/i.86/386/;s/Power Macintosh/power/'}
+#	cd gcc$$arch mk
--- /dev/null
+++ b/mkfile-Windows
@@ -1,0 +1,54 @@
+WIN=win
+SYS=win32
+OS=WinXP
+objtype=386
+BIN=$9pm/bin
+DSRC=C:/src/dt2k
+
+# compiler, linker, librarian
+CC=cl
+LD=link
+AR=lib
+AS=ml
+RC=rc
+
+# file extensions
+O=obj
+L=lib
+CPUS=
+CFLAGS=-c -nologo -W3 -YX -Zi -MT -Zl -I. -I$DSRC -I$DSRC/include -I$DSRC/kern -DWINDOWS
+# -nologo	a little less verbose
+# -W3		level 3 warning - u.h contains pragma's that inhibit some of the very silly warnings
+# -YX		use precompiled headers (.PCH files)
+# -G5		generate code that runs best on Pentiums - has bugs on 4.2
+# -Zi		generate debug info (.PDB files)
+# -MT		Link with LIBCMT.LIB (multi thread static)
+# -Zl 		don't include libcmt.lib and oldnames.lib in object files
+# -Oi		enable intrinic function, i.e abs, sin, sqrt, etc
+# -FAs		generate asm listing with source
+# -G5		Pentium code
+# -Ob2		Let the compiler inline function -O2 only inlines functions marked inline
+# -Fr or -FR	generate .SBR files (browsing information) without or with local symbols
+
+OSLIBS=\
+	drawterm.res\
+	libcmt.lib\
+	oldnames.lib\
+	user32.lib\
+	advapi32.lib\
+	kernel32.lib\
+	gdi32.lib\
+
+LDFLAGS=-entry:mainCRTStartup -debug -nologo -incremental:no
+#-nodefaultlib:libcmt.lib -nodefaultlib:oldnames.lib 
+#TARG=drawterm.exe
+LDFLAGS=-nologo -incremental:no -debug -subsystem:windows -out:drawterm.exe
+ARFLAGS=-nologo
+
+# System specific targets
+DEVFS=devntfs
+DEVIP=devip-win
+OSHOOKS=os-windows
+
+%.res:	%.rc
+	$RC $RCFLAGS $stem.rc
--- /dev/null
+++ b/mkfile-posix
@@ -1,0 +1,9 @@
+CC=gcc
+objtype=`{uname -m|sed 's/i.86/386/;s/Power Macintosh/power/'}
+CFLAGS=-I$DSRC/include -c -ggdb -D_THREAD_SAFE -pthread # not ready for this yet: -Wall
+LDFLAGS=-pthread -L/usr/X11R6/lib -lX11 -ggdb
+O=o
+L=a
+SYS=posix
+#OS=FreeBSD
+OSHOOKS=posix
--- /dev/null
+++ b/mkone
@@ -1,0 +1,50 @@
+LDFLAGS=
+YFLAGS=-d
+AFLAGS=
+
+default:V:	$O.out
+
+all:V:	$O.out
+
+$O.out:	$OFILES $LIB
+	$LD $LDFLAGS -o $target $prereq
+
+%.$O:	$HFILES		# don't combine with following %.$O rules
+
+%.$O:	%.c
+	$CC $CFLAGS $stem.c
+
+%.$O:	%.s
+	$AS $AFLAGS $stem.s
+
+y.tab.h y.tab.c:	$YFILES
+	$YACC $YFLAGS $prereq
+
+install:V:	$BIN/$TARG
+
+$BIN/$TARG:	$O.out
+	cp $prereq $BIN/$TARG
+
+installall:V:
+	for(objtype in $CPUS)
+		mk install
+
+nuke:V:
+	rm -f *.[$OS] [$OS].out y.tab.? y.debug y.output *.acid $TARG
+
+clean:V:
+	rm -f *.[$OS] [$OS].out y.tab.? y.debug y.output $TARG
+
+safeinstall:V: $O.out
+	test -e $BIN/$TARG && mv $BIN/$TARG $BIN/_$TARG
+	cp $prereq $BIN/$TARG
+
+update:V:
+	update $UPDATEFLAGS $UPDATE
+
+safeinstallall:V:
+	for (objtype in $CPUS)
+		mk safeinstall
+
+%.acid: %.$O $HFILES
+	$CC $CFLAGS -a $stem.c >$target
--- /dev/null
+++ b/posix-386/Makefile
@@ -1,0 +1,16 @@
+LIB=../libmachdep.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/posix-386/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+ulong
+getcallerpc(void *a)
+{
+	return ((ulong*)a)[-1];
+}
--- /dev/null
+++ b/posix-386/md5block.s
@@ -1,0 +1,241 @@
+/*
+ *  rfc1321 requires that I include this.  The code is new.  The constants
+ *  all come from the rfc (hence the copyright).  We trade a table for the
+ *  macros in rfc.  The total size is a lot less. -- presotto
+ *
+ *	Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ *	rights reserved.
+ *
+ *	License to copy and use this software is granted provided that it
+ *	is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ *	Algorithm" in all material mentioning or referencing this software
+ *	or this function.
+ *
+ *	License is also granted to make and use derivative works provided
+ *	that such works are identified as "derived from the RSA Data
+ *	Security, Inc. MD5 Message-Digest Algorithm" in all material
+ *	mentioning or referencing the derived work.
+ *
+ *	RSA Data Security, Inc. makes no representations concerning either
+ *	the merchantability of this software or the suitability of this
+ *	software forany particular purpose. It is provided "as is"
+ *	without express or implied warranty of any kind.
+ *	These notices must be retained in any copies of any part of this
+ *	documentation and/or software.
+ */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+#define PAYME(x) $##x
+
+/*
+ * SI is data
+ *	a += FN(B,C,D);
+ *	a += x[sh] + t[sh];
+ *	a = (a << S11) | (a >> (32 - S11));
+ *	a += b;
+ */
+
+#define BODY1(off,V,FN,SH,A,B,C,D)\
+	FN(B,C,D)\
+	leal V(A, %edi, 1), A;\
+	addl off(%ebp), A;\
+	roll PAYME(SH), A;\
+	addl B, A;\
+
+#define BODY(off,V,FN,SH,A,B,C,D)\
+	FN(B,C,D)\
+	leal V(A, %edi, 1), A;\
+	addl (off)(%ebp), A;\
+	roll PAYME(SH), A;\
+	addl B,A;\
+
+/*
+ * fn1 = ((c ^ d) & b) ^ d
+ */
+#define FN1(B,C,D)\
+	movl C, %edi;\
+	xorl D, %edi;\
+	andl B, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn2 = ((b ^ c) & d) ^ c;
+ */
+#define FN2(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	andl D, %edi;\
+	xorl C, %edi;\
+
+/*
+ * fn3 = b ^ c ^ d;
+ */
+#define FN3(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn4 = c ^ (b | ~d);
+ */
+#define FN4(B,C,D)\
+	movl D, %edi;\
+	xorl $-1, %edi;\
+	orl B, %edi;\
+	xorl C, %edi;\
+
+#define	DATA	8
+#define	LEN	12
+#define	STATE	16
+
+#define EDATA	(-4)
+#define OLDEBX	(-8)
+#define OLDESI	(-12)
+#define OLDEDI	(-16)
+
+	.text
+
+	.p2align 2,0x90
+	.globl _md5block
+		.type _md5block, @function
+	_md5block:
+
+	/* Prelude */
+	pushl %ebp
+	movl %ebx, OLDEBX(%esp)
+	movl %esi, OLDESI(%esp)
+	movl %edi, OLDEDI(%esp)
+
+	movl	DATA(%esp), %eax
+	addl	LEN(%esp), %eax
+	movl	%eax, EDATA(%esp)
+
+	movl DATA(%esp), %ebp
+
+mainloop:
+	movl STATE(%esp), %esi
+	movl (%esi), %eax
+	movl 4(%esi), %ebx
+	movl 8(%esi), %ecx
+	movl 12(%esi), %edx
+
+	BODY1( 0*4,0xd76aa478,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1( 1*4,0xe8c7b756,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1( 2*4,0x242070db,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1( 3*4,0xc1bdceee,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+	BODY1( 4*4,0xf57c0faf,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1( 5*4,0x4787c62a,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1( 6*4,0xa8304613,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1( 7*4,0xfd469501,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+	BODY1( 8*4,0x698098d8,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1( 9*4,0x8b44f7af,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1(10*4,0xffff5bb1,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1(11*4,0x895cd7be,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+	BODY1(12*4,0x6b901122,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1(13*4,0xfd987193,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1(14*4,0xa679438e,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1(15*4,0x49b40821,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+
+	BODY( 1*4,0xf61e2562,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY( 6*4,0xc040b340,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY(11*4,0x265e5a51,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY( 0*4,0xe9b6c7aa,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+	BODY( 5*4,0xd62f105d,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY(10*4,0x02441453,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY(15*4,0xd8a1e681,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY( 4*4,0xe7d3fbc8,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+	BODY( 9*4,0x21e1cde6,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY(14*4,0xc33707d6,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY( 3*4,0xf4d50d87,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY( 8*4,0x455a14ed,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+	BODY(13*4,0xa9e3e905,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY( 2*4,0xfcefa3f8,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY( 7*4,0x676f02d9,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY(12*4,0x8d2a4c8a,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+
+	BODY( 5*4,0xfffa3942,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY( 8*4,0x8771f681,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY(11*4,0x6d9d6122,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY(14*4,0xfde5380c,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+	BODY( 1*4,0xa4beea44,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY( 4*4,0x4bdecfa9,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY( 7*4,0xf6bb4b60,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY(10*4,0xbebfbc70,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+	BODY(13*4,0x289b7ec6,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY( 0*4,0xeaa127fa,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY( 3*4,0xd4ef3085,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY( 6*4,0x04881d05,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+	BODY( 9*4,0xd9d4d039,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY(12*4,0xe6db99e5,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY(15*4,0x1fa27cf8,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY( 2*4,0xc4ac5665,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+
+	BODY( 0*4,0xf4292244,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY( 7*4,0x432aff97,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY(14*4,0xab9423a7,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY( 5*4,0xfc93a039,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	BODY(12*4,0x655b59c3,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY( 3*4,0x8f0ccc92,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY(10*4,0xffeff47d,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY( 1*4,0x85845dd1,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	BODY( 8*4,0x6fa87e4f,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY(15*4,0xfe2ce6e0,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY( 6*4,0xa3014314,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY(13*4,0x4e0811a1,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	BODY( 4*4,0xf7537e82,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY(11*4,0xbd3af235,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY( 2*4,0x2ad7d2bb,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY( 9*4,0xeb86d391,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	addl $(16*4), %ebp
+	movl STATE(%esp), %edi
+	addl %eax,0(%edi)
+	addl %ebx,4(%edi)
+	addl %ecx,8(%edi)
+	addl %edx,12(%edi)
+
+	movl EDATA(%esp), %edi
+	cmpl %edi, %ebp
+	jb mainloop
+
+	/* Postlude */
+	movl OLDEBX(%esp), %ebx
+	movl OLDESI(%esp), %esi
+	movl OLDEDI(%esp), %edi
+	movl %esp, %ebp
+	leave
+	ret
+
--- /dev/null
+++ b/posix-386/sha1block.s
@@ -1,0 +1,214 @@
+.text
+
+.p2align 2,0x90
+.globl _sha1block
+	.type _sha1block, @function
+_sha1block:
+
+/* x = (wp[off-f] ^ wp[off-8] ^ wp[off-14] ^ wp[off-16]) <<< 1;
+ * wp[off] = x;
+ * x += A <<< 5;
+ * E += 0xca62c1d6 + x;
+ * x = FN(B,C,D);
+ * E += x;
+ * B >>> 2
+ */
+#define BSWAPDI	BYTE $0x0f; BYTE $0xcf;
+
+#define BODY(off,FN,V,A,B,C,D,E)\
+	movl (off-64)(%ebp), %edi;\
+	xorl (off-56)(%ebp), %edi;\
+	xorl (off-32)(%ebp), %edi;\
+	xorl (off-12)(%ebp), %edi;\
+	roll $1, %edi;\
+	movl %edi, off(%ebp);\
+	leal V(%edi, E, 1), E;\
+	movl A, %edi;\
+	roll $5, %edi;\
+	addl %edi, E;\
+	FN(B,C,D)\
+	addl %edi, E;\
+	rorl $2, B;\
+
+#define BODY0(off,FN,V,A,B,C,D,E)\
+	movl off(%ebx), %edi;\
+	bswap %edi;\
+	movl %edi, off(%ebp);\
+	leal V(%edi,E,1), E;\
+	movl A, %edi;\
+	roll $5,%edi;\
+	addl %edi,E;\
+	FN(B,C,D)\
+	addl %edi,E;\
+	rorl $2,B;\
+
+/*
+ * fn1 = (((C^D)&B)^D);
+ */
+#define FN1(B,C,D)\
+	movl C, %edi;\
+	xorl D, %edi;\
+	andl B, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn24 = B ^ C ^ D
+ */
+#define FN24(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn3 = ((B ^ C) & (D ^= B)) ^ B
+ * D ^= B to restore D
+ */
+#define FN3(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	xorl B, D;\
+	andl D, %edi;\
+	xorl B, %edi;\
+	xorl B, D;\
+
+/*
+ * stack offsets
+ * void sha1block(uchar *DATA, int LEN, ulong *STATE)
+ */
+#define	DATA	8
+#define	LEN	12
+#define	STATE	16
+
+/*
+ * stack offsets for locals
+ * ulong w[80];
+ * uchar *edata;
+ * ulong *w15, *w40, *w60, *w80;
+ * register local
+ * ulong *wp = %ebp
+ * ulong a = eax, b = ebx, c = ecx, d = edx, e = esi
+ * ulong tmp = edi
+ */
+#define WARRAY	(-4-(80*4))
+#define TMP1	(-8-(80*4))
+#define TMP2	(-12-(80*4))
+#define W15	(-16-(80*4))
+#define W40	(-20-(80*4))
+#define W60	(-24-(80*4))
+#define W80	(-28-(80*4))
+#define EDATA	(-32-(80*4))
+#define OLDEBX	(-36-(80*4))
+#define OLDESI	(-40-(80*4))
+#define OLDEDI	(-44-(80*4))
+
+	/* Prelude */
+	pushl %ebp
+	mov %ebx, OLDEBX(%esp)
+	mov %esi, OLDESI(%esp)
+	mov %edi, OLDEDI(%esp)
+
+	movl DATA(%esp), %eax
+	addl LEN(%esp), %eax
+	movl %eax, EDATA(%esp)
+
+	leal (WARRAY+15*4)(%esp), %edi	/* aw15 */
+	movl %edi, W15(%esp)
+	leal (WARRAY+40*4)(%esp), %edx	/* aw40 */
+	movl %edx, W40(%esp)
+	leal (WARRAY+60*4)(%esp), %ecx	/* aw60 */
+	movl %ecx, W60(%esp)
+	leal (WARRAY+80*4)(%esp), %edi	/* aw80 */
+	movl %edi, W80(%esp)
+
+mainloop:
+	leal WARRAY(%esp), %ebp		/* warray */
+
+	movl STATE(%esp), %edi		/* state */
+	movl (%edi),%eax
+	movl 4(%edi),%ebx
+	movl %ebx, TMP1(%esp)		/* tmp1 */
+	movl 8(%edi), %ecx
+	movl 12(%edi), %edx
+	movl 16(%edi), %esi
+
+	movl DATA(%esp), %ebx		/* data */
+
+loop1:
+	BODY0(0,FN1,0x5a827999,%eax,TMP1(%esp),%ecx,%edx,%esi)
+	movl %esi,TMP2(%esp)
+	BODY0(4,FN1,0x5a827999,%esi,%eax,TMP1(%esp),%ecx,%edx)
+	movl TMP1(%esp),%esi
+	BODY0(8,FN1,0x5a827999,%edx,TMP2(%esp),%eax,%esi,%ecx)
+	BODY0(12,FN1,0x5a827999,%ecx,%edx,TMP2(%esp),%eax,%esi)
+	movl %esi,TMP1(%esp)
+	BODY0(16,FN1,0x5a827999,%esi,%ecx,%edx,TMP2(%esp),%eax)
+	movl TMP2(%esp),%esi
+
+	addl $20, %ebx
+	addl $20, %ebp
+	cmpl W15(%esp), %ebp	/* w15 */
+	jb loop1
+
+	BODY0(0,FN1,0x5a827999,%eax,TMP1(%esp),%ecx,%edx,%esi)
+	addl $4, %ebx
+	MOVL %ebx, DATA(%esp)	/* data */
+	MOVL TMP1(%esp),%ebx
+
+	BODY(4,FN1,0x5a827999,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN1,0x5a827999,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN1,0x5a827999,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN1,0x5a827999,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20, %ebp
+
+loop2:
+	BODY(0,FN24,0x6ed9eba1,%eax,%ebx,%ecx,%edx,%esi)
+	BODY(4,FN24,0x6ed9eba1,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN24,0x6ed9eba1,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN24,0x6ed9eba1,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN24,0x6ed9eba1,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20,%ebp
+	cmpl W40(%esp), %ebp
+	jb loop2
+
+loop3:
+	BODY(0,FN3,0x8f1bbcdc,%eax,%ebx,%ecx,%edx,%esi)
+	BODY(4,FN3,0x8f1bbcdc,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN3,0x8f1bbcdc,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN3,0x8f1bbcdc,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN3,0x8f1bbcdc,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20, %ebp
+	cmpl W60(%esp), %ebp 	/* w60 */
+	jb loop3
+
+loop4:
+	BODY(0,FN24,0xca62c1d6,%eax,%ebx,%ecx,%edx,%esi)
+	BODY(4,FN24,0xca62c1d6,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN24,0xca62c1d6,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN24,0xca62c1d6,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN24,0xca62c1d6,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20, %ebp
+	cmpl W80(%esp), %ebp 	/* w80 */
+	jb loop4
+
+	movl STATE(%esp), %edi	/* state */
+	addl %eax, 0(%edi)
+	addl %ebx, 4(%edi)
+	addl %ecx, 8(%edi)
+	addl %edx, 12(%edi)
+	addl %esi, 16(%edi)
+
+	movl EDATA(%esp), %edi	/* edata */
+	cmpl %edi, DATA(%esp)	/* data */
+	jb mainloop
+
+	/* Postlude */
+	mov OLDEBX(%esp), %ebx
+	mov OLDESI(%esp), %esi
+	mov OLDEDI(%esp), %edi
+	movl %esp, %ebp
+	leave
+	ret
--- /dev/null
+++ b/posix-386/tas.c
@@ -1,0 +1,23 @@
+#include "u.h"
+#include "libc.h"
+
+int
+tas(long *x)
+{
+	int     v;
+
+	__asm__(	"movl   $1, %%eax\n\t"
+			"xchgl  %%eax,(%%ecx)"
+			: "=a" (v)
+			: "c" (x)
+	);
+	switch(v) {
+	case 0:
+	case 1:
+		return v;
+	default:
+		print("canlock: corrupted 0x%lux\n", v);
+		return 1;
+	}
+}
+
--- /dev/null
+++ b/posix-power/Makefile
@@ -1,0 +1,16 @@
+LIB=../libmachdep.a
+CC=gcc
+CFLAGS=-I../include -I. -c -ggdb -D_THREAD_SAFE -pthread
+O=o
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+$(LIB): $(OFILES)
+	ar r $(LIB) $(OFILES)
+	ranlib $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/posix-power/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+ulong
+getcallerpc(void *a)
+{
+	return ((ulong*)a)[-1];
+}
--- /dev/null
+++ b/posix-power/tas.c
@@ -1,0 +1,42 @@
+#include "u.h"
+#include "libc.h"
+
+/*
+ * first argument (l) is in r3 at entry.
+ * r3 contains return value upon return.
+ */
+int
+tas(long *x)
+{
+	int     v;
+	/*
+	 * this __asm__ works with gcc 2.95.2 (mac os x 10.1).
+	 * this assembly language destroys r0 (0), some other register (v),
+	 * r4 (x) and r5 (temp).
+	 */
+	__asm__("\n	sync\n"
+	"	li	r0,0\n"
+	"	mr	r4,%1		/* &l->val */\n"
+	"	lis	r5,0xdead	/* assemble constant 0xdeaddead */\n"
+	"	ori	r5,r5,0xdead	/* \" */\n"
+	"tas1:\n"
+	"	dcbf	r4,r0	/* cache flush; \"fix for 603x bug\" */\n"
+	"	lwarx	%0,r4,r0	/* v = l->val with reservation */\n"
+	"	cmp	cr0,0,%0,r0	/* v == 0 */\n"
+	"	bne	tas0\n"
+	"	stwcx.	r5,r4,r0   /* if (l->val same) l->val = 0xdeaddead */\n"
+	"	bne	tas1\n"
+	"tas0:\n"
+	"	sync\n"
+	"	isync\n"
+	: "=r" (v)
+	: "r"  (x)
+	: "cc", "memory", "r0", "r4", "r5"
+	);
+	switch(v) {
+	case 0:		return 0;
+	case 0xdeaddead: return 1;
+	default:	print("tas: corrupted 0x%lux\n", v);
+	}
+	return 0;
+}
--- /dev/null
+++ b/readcons.c
@@ -1,0 +1,110 @@
+#include <u.h>
+#include <libc.h>
+#include "drawterm.h"
+
+void*
+erealloc(void *v, ulong n)
+{
+	v = realloc(v, n);
+	if(v == nil)
+		sysfatal("out of memory");
+	return v;
+}
+
+char*
+estrdup(char *s)
+{
+	s = strdup(s);
+	if(s == nil)
+		sysfatal("out of memory");
+	return s;
+}
+
+char*
+estrappend(char *s, char *fmt, ...)
+{
+	char *t;
+	va_list arg;
+
+	va_start(arg, fmt);
+	t = vsmprint(fmt, arg);
+	if(t == nil)
+		sysfatal("out of memory");
+	va_end(arg);
+	s = erealloc(s, strlen(s)+strlen(t)+1);
+	strcat(s, t);
+	free(t);
+	return s;
+}
+
+/*
+ *  prompt for a string with a possible default response
+ */
+char*
+readcons(char *prompt, char *def, int raw)
+{
+	int fdin, fdout, ctl, n;
+	char line[10];
+	char *s;
+
+	fdin = open("/dev/cons", OREAD);
+	if(fdin < 0)
+		fdin = 0;
+	fdout = open("/dev/cons", OWRITE);
+	if(fdout < 0)
+		fdout = 1;
+	if(def != nil)
+		fprint(fdout, "%s[%s]: ", prompt, def);
+	else
+		fprint(fdout, "%s: ", prompt);
+	if(raw){
+		ctl = open("/dev/consctl", OWRITE);
+		if(ctl >= 0)
+			write(ctl, "rawon", 5);
+	} else
+		ctl = -1;
+	s = estrdup("");
+	for(;;){
+		n = read(fdin, line, 1);
+		if(n == 0){
+		Error:
+			close(fdin);
+			close(fdout);
+			if(ctl >= 0)
+				close(ctl);
+			free(s);
+			return nil;
+		}
+		if(n < 0)
+			goto Error;
+		if(line[0] == 0x7f)
+			goto Error;
+		if(n == 0 || line[0] == '\n' || line[0] == '\r'){
+			if(raw){
+				write(ctl, "rawoff", 6);
+				write(fdout, "\n", 1);
+			}
+			close(ctl);
+			close(fdin);
+			close(fdout);
+			if(*s == 0 && def != nil)
+				s = estrappend(s, "%s", def);
+			return s;
+		}
+		if(line[0] == '\b'){
+			if(strlen(s) > 0)
+				s[strlen(s)-1] = 0;
+		} else if(line[0] == 0x15) {	/* ^U: line kill */
+			if(def != nil)
+				fprint(fdout, "\n%s[%s]: ", prompt, def);
+			else
+				fprint(fdout, "\n%s: ", prompt);
+			
+			s[0] = 0;
+		} else {
+			s = estrappend(s, "%c", line[0]);
+		}
+	}
+	return nil; /* not reached */
+}
+
--- /dev/null
+++ b/resource.h
@@ -1,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by drawterm.rc
+//
+#define IDI_ICON1                       101
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        102
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1000
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
--- /dev/null
+++ b/secstore.c
@@ -1,0 +1,667 @@
+/*
+ * Various files from /sys/src/cmd/auth/secstore, just enough
+ * to download a file at boot time.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include "drawterm.h"
+
+static void*
+emalloc(ulong n)
+{
+	return mallocz(n, 1);
+}
+
+enum{ CHK = 16};
+enum{ MAXFILESIZE = 10*1024*1024 };
+
+enum{// PW status bits
+	Enabled 	= (1<<0),
+	STA 		= (1<<1),	// extra SecurID step
+};
+
+static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n";
+
+int
+secdial(char *secstore)
+{
+	char *p, buf[80], *f[3];
+	int fd, nf;
+
+	p = secstore; /* take it from writehostowner, if set there */
+	if(*p == 0)	  /* else use the authserver */
+		p = "$auth";
+
+	/* translate $auth ourselves.
+	 * authaddr is something like il!host!566 or tcp!host!567.
+	 * extract host, accounting for a change of format to something
+	 * like il!host or tcp!host or host.
+	 */
+	if(strcmp(p, "$auth")==0){
+		if(authaddr == nil)
+			return -1;
+		strecpy(buf, buf+sizeof buf, authaddr);
+		nf = getfields(buf, f, nelem(f), 0, "!");
+		switch(nf){
+		default:
+			return -1;
+		case 1:
+			p = f[0];
+			break;
+		case 2:
+		case 3:
+			p = f[1];
+			break;
+		}
+	}
+	fd = dial(netmkaddr(p, "tcp", "5356"), 0, 0, 0);
+	if(fd >= 0)
+		return fd;
+	return -1;
+}
+
+int
+havesecstore(char *addr, char *owner)
+{
+	int m, n, fd;
+	uchar buf[500];
+
+	n = snprint((char*)buf, sizeof buf, testmess, owner);
+	hnputs(buf, 0x8000+n-2);
+
+	fd = secdial(addr);
+	if(fd < 0)
+		return 0;
+	if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2){
+		close(fd);
+		return 0;
+	}
+	n = ((buf[0]&0x7f)<<8) + buf[1];
+	if(n+1 > sizeof buf){
+		werrstr("implausibly large count %d", n);
+		close(fd);
+		return 0;
+	}
+	m = readn(fd, buf, n);
+	close(fd);
+	if(m != n){
+		if(m >= 0)
+			werrstr("short read from secstore");
+		return 0;
+	}
+	buf[n] = 0;
+	if(strcmp((char*)buf, "!account expired") == 0){
+		werrstr("account expired");
+		return 0;
+	}
+	return strcmp((char*)buf, "!account exists") == 0;
+}
+
+// delimited, authenticated, encrypted connection
+enum{ Maxmsg=4096 };	// messages > Maxmsg bytes are truncated
+typedef struct SConn SConn;
+
+extern SConn* newSConn(int);	// arg is open file descriptor
+struct SConn{
+	void *chan;
+	int secretlen;
+	int (*secret)(SConn*, uchar*, int);// 
+	int (*read)(SConn*, uchar*, int); // <0 if error;  errmess in buffer
+	int (*write)(SConn*, uchar*, int);
+	void (*free)(SConn*);		// also closes file descriptor
+};
+// secret(s,b,dir) sets secret for digest, encrypt, using the secretlen
+//		bytes in b to form keys 	for the two directions;
+//	  set dir=0 in client, dir=1 in server
+
+// error convention: write !message in-band
+#define readstr secstore_readstr
+static void writerr(SConn*, char*);
+static int readstr(SConn*, char*);  // call with buf of size Maxmsg+1
+	// returns -1 upon error, with error message in buf
+
+typedef struct ConnState {
+	uchar secret[SHA1dlen];
+	ulong seqno;
+	RC4state rc4;
+} ConnState;
+
+typedef struct SS{
+	int fd;		// file descriptor for read/write of encrypted data
+	int alg;	// if nonzero, "alg sha rc4_128"
+	ConnState in, out;
+} SS;
+
+static int
+SC_secret(SConn *conn, uchar *sigma, int direction)
+{
+	SS *ss = (SS*)(conn->chan);
+	int nsigma = conn->secretlen;
+
+	if(direction != 0){
+		hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->out.secret, nil);
+		hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->in.secret, nil);
+	}else{
+		hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->out.secret, nil);
+		hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->in.secret, nil);
+	}
+	setupRC4state(&ss->in.rc4, ss->in.secret, 16); // restrict to 128 bits
+	setupRC4state(&ss->out.rc4, ss->out.secret, 16);
+	ss->alg = 1;
+	return 0;
+}
+
+static void
+hash(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+	DigestState sha;
+	uchar seq[4];
+
+	seq[0] = seqno>>24;
+	seq[1] = seqno>>16;
+	seq[2] = seqno>>8;
+	seq[3] = seqno;
+	memset(&sha, 0, sizeof sha);
+	sha1(secret, SHA1dlen, nil, &sha);
+	sha1(data, len, nil, &sha);
+	sha1(seq, 4, d, &sha);
+}
+
+static int
+verify(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+	DigestState sha;
+	uchar seq[4];
+	uchar digest[SHA1dlen];
+
+	seq[0] = seqno>>24;
+	seq[1] = seqno>>16;
+	seq[2] = seqno>>8;
+	seq[3] = seqno;
+	memset(&sha, 0, sizeof sha);
+	sha1(secret, SHA1dlen, nil, &sha);
+	sha1(data, len, nil, &sha);
+	sha1(seq, 4, digest, &sha);
+	return memcmp(d, digest, SHA1dlen);
+}
+
+static int
+SC_read(SConn *conn, uchar *buf, int n)
+{
+	SS *ss = (SS*)(conn->chan);
+	uchar count[2], digest[SHA1dlen];
+	int len, nr;
+
+	if(read(ss->fd, count, 2) != 2 || count[0]&0x80 == 0){
+		werrstr("!SC_read invalid count");
+		return -1;
+	}
+	len = (count[0]&0x7f)<<8 | count[1];	// SSL-style count; no pad
+	if(ss->alg){
+		len -= SHA1dlen;
+		if(len <= 0 || readn(ss->fd, digest, SHA1dlen) != SHA1dlen){
+			werrstr("!SC_read missing sha1");
+			return -1;
+		}
+		if(len > n || readn(ss->fd, buf, len) != len){
+			werrstr("!SC_read missing data");
+			return -1;
+		}
+		rc4(&ss->in.rc4, digest, SHA1dlen);
+		rc4(&ss->in.rc4, buf, len);
+		if(verify(ss->in.secret, buf, len, ss->in.seqno, digest) != 0){
+			werrstr("!SC_read integrity check failed");
+			return -1;
+		}
+	}else{
+		if(len <= 0 || len > n){
+			werrstr("!SC_read implausible record length");
+			return -1;
+		}
+		if( (nr = readn(ss->fd, buf, len)) != len){
+			werrstr("!SC_read expected %d bytes, but got %d", len, nr);
+			return -1;
+		}
+	}
+	ss->in.seqno++;
+	return len;
+}
+
+static int
+SC_write(SConn *conn, uchar *buf, int n)
+{
+	SS *ss = (SS*)(conn->chan);
+	uchar count[2], digest[SHA1dlen];
+	int len;
+
+	if(n <= 0 || n > Maxmsg+1){
+		werrstr("!SC_write invalid n %d", n);
+		return -1;
+	}
+	len = n;
+	if(ss->alg)
+		len += SHA1dlen;
+	count[0] = 0x80 | len>>8;
+	count[1] = len;
+	if(write(ss->fd, count, 2) != 2){
+		werrstr("!SC_write invalid count");
+		return -1;
+	}
+	if(ss->alg){
+		hash(ss->out.secret, buf, n, ss->out.seqno, digest);
+		rc4(&ss->out.rc4, digest, SHA1dlen);
+		rc4(&ss->out.rc4, buf, n);
+		if(write(ss->fd, digest, SHA1dlen) != SHA1dlen ||
+				write(ss->fd, buf, n) != n){
+			werrstr("!SC_write error on send");
+			return -1;
+		}
+	}else{
+		if(write(ss->fd, buf, n) != n){
+			werrstr("!SC_write error on send");
+			return -1;
+		}
+	}
+	ss->out.seqno++;
+	return n;
+}
+
+static void
+SC_free(SConn *conn)
+{
+	SS *ss = (SS*)(conn->chan);
+
+	close(ss->fd);
+	free(ss);
+	free(conn);
+}
+
+SConn*
+newSConn(int fd)
+{
+	SS *ss;
+	SConn *conn;
+
+	if(fd < 0)
+		return nil;
+	ss = (SS*)emalloc(sizeof(*ss));
+	conn = (SConn*)emalloc(sizeof(*conn));
+	ss->fd  = fd;
+	ss->alg = 0;
+	conn->chan = (void*)ss;
+	conn->secretlen = SHA1dlen;
+	conn->free = SC_free;
+	conn->secret = SC_secret;
+	conn->read = SC_read;
+	conn->write = SC_write;
+	return conn;
+}
+
+static void
+writerr(SConn *conn, char *s)
+{
+	char buf[Maxmsg];
+
+	snprint(buf, Maxmsg, "!%s", s);
+	conn->write(conn, (uchar*)buf, strlen(buf));
+}
+
+static int
+readstr(SConn *conn, char *s)
+{
+	int n;
+
+	n = conn->read(conn, (uchar*)s, Maxmsg);
+	if(n >= 0){
+		s[n] = 0;
+		if(s[0] == '!'){
+			memmove(s, s+1, n);
+			n = -1;
+		}
+	}else{
+		strcpy(s, "read error");
+	}
+	return n;
+}
+
+static char*
+getfile(SConn *conn, uchar *key, int nkey)
+{
+	char *buf;
+	int nbuf, n, nr, len;
+	char s[Maxmsg+1], *gf, *p, *q;
+	uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw;
+	AESstate aes;
+	DigestState *sha;
+
+	gf = "factotum";
+	memset(&aes, 0, sizeof aes);
+
+	snprint(s, Maxmsg, "GET %s\n", gf);
+	conn->write(conn, (uchar*)s, strlen(s));
+
+	/* get file size */
+	s[0] = '\0';
+	if(readstr(conn, s) < 0){
+		werrstr("secstore: %r");
+		return nil;
+	}
+	if((len = atoi(s)) < 0){
+		werrstr("secstore: remote file %s does not exist", gf);
+		return nil;
+	}else if(len > MAXFILESIZE){//assert
+		werrstr("secstore: implausible file size %d for %s", len, gf);
+		return nil;
+	}
+
+	ibr = ibw = ib;
+	buf = nil;
+	nbuf = 0;
+	for(nr=0; nr < len;){
+		if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
+			werrstr("secstore: empty file chunk n=%d nr=%d len=%d: %r", n, nr, len);
+			return nil;
+		}
+		nr += n;
+		ibw += n;
+		if(!aes.setup){ /* first time, read 16 byte IV */
+			if(n < 16){
+				werrstr("secstore: no IV in file");
+				return nil;
+			}
+			sha = sha1((uchar*)"aescbc file", 11, nil, nil);
+			sha1(key, nkey, skey, sha);
+			setupAESstate(&aes, skey, AESbsize, ibr);
+			memset(skey, 0, sizeof skey);
+			ibr += AESbsize;
+			n -= AESbsize;
+		}
+		aesCBCdecrypt(ibw-n, n, &aes);
+		n = ibw-ibr-CHK;
+		if(n > 0){
+			buf = realloc(buf, nbuf+n+1);
+			if(buf == nil)
+				panic("out of memory");
+			memmove(buf+nbuf, ibr, n);
+			nbuf += n;
+			ibr += n;
+		}
+		memmove(ib, ibr, ibw-ibr);
+		ibw = ib + (ibw-ibr);
+		ibr = ib;
+	}
+	n = ibw-ibr;
+	if((n != CHK) || (memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0)){
+		werrstr("secstore: decrypted file failed to authenticate!");
+		free(buf);
+		return nil;
+	}
+	if(nbuf == 0){
+		werrstr("secstore got empty file");
+		return nil;
+	}
+	buf[nbuf] = '\0';
+	return buf;
+}
+
+static char VERSION[] = "secstore";
+
+typedef struct PAKparams{
+	mpint *q, *p, *r, *g;
+} PAKparams;
+
+static PAKparams *pak;
+
+// This group was generated by the seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E.
+static void
+initPAKparams(void)
+{
+	if(pak)
+		return;
+	pak = (PAKparams*)emalloc(sizeof(*pak));
+	pak->q = strtomp("E0F0EF284E10796C5A2A511E94748BA03C795C13", nil, 16, nil);
+	pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBBD"
+		"B12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86"
+		"3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9"
+		"3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", nil, 16, nil);
+	pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241CEF"
+		"2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E887"
+		"D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D21"
+		"C4656848614D888A4", nil, 16, nil);
+	pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D2327173444"
+		"ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD41"
+		"0E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734E3E"
+		"2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", nil, 16, nil);
+}
+
+// H = (sha(ver,C,sha(passphrase)))^r mod p,
+// a hash function expensive to attack by brute force.
+static void
+longhash(char *ver, char *C, uchar *passwd, mpint *H)
+{
+	uchar *Cp;
+	int i, n, nver, nC;
+	uchar buf[140], key[1];
+
+	nver = strlen(ver);
+	nC = strlen(C);
+	n = nver + nC + SHA1dlen;
+	Cp = (uchar*)emalloc(n);
+	memmove(Cp, ver, nver);
+	memmove(Cp+nver, C, nC);
+	memmove(Cp+nver+nC, passwd, SHA1dlen);
+	for(i = 0; i < 7; i++){
+		key[0] = 'A'+i;
+		hmac_sha1(Cp, n, key, sizeof key, buf+i*SHA1dlen, nil);
+	}
+	memset(Cp, 0, n);
+	free(Cp);
+	betomp(buf, sizeof buf, H);
+	mpmod(H, pak->p, H);
+	mpexp(H, pak->r, pak->p, H);
+}
+
+// Hi = H^-1 mod p
+static char *
+PAK_Hi(char *C, char *passphrase, mpint *H, mpint *Hi)
+{
+	uchar passhash[SHA1dlen];
+
+	sha1((uchar *)passphrase, strlen(passphrase), passhash, nil);
+	initPAKparams();
+	longhash(VERSION, C, passhash, H);
+	mpinvert(H, pak->p, Hi);
+	return mptoa(Hi, 64, nil, 0);
+}
+
+// another, faster, hash function for each party to
+// confirm that the other has the right secrets.
+static void
+shorthash(char *mess, char *C, char *S, char *m, char *mu, char *sigma, char *Hi, uchar *digest)
+{
+	SHA1state *state;
+
+	state = sha1((uchar*)mess, strlen(mess), 0, 0);
+	state = sha1((uchar*)C, strlen(C), 0, state);
+	state = sha1((uchar*)S, strlen(S), 0, state);
+	state = sha1((uchar*)m, strlen(m), 0, state);
+	state = sha1((uchar*)mu, strlen(mu), 0, state);
+	state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+	state = sha1((uchar*)Hi, strlen(Hi), 0, state);
+	state = sha1((uchar*)mess, strlen(mess), 0, state);
+	state = sha1((uchar*)C, strlen(C), 0, state);
+	state = sha1((uchar*)S, strlen(S), 0, state);
+	state = sha1((uchar*)m, strlen(m), 0, state);
+	state = sha1((uchar*)mu, strlen(mu), 0, state);
+	state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+	sha1((uchar*)Hi, strlen(Hi), digest, state);
+}
+
+// On input, conn provides an open channel to the server;
+//	C is the name this client calls itself;
+//	pass is the user's passphrase
+// On output, session secret has been set in conn
+//	(unless return code is negative, which means failure).
+//    If pS is not nil, it is set to the (alloc'd) name the server calls itself.
+static int
+PAKclient(SConn *conn, char *C, char *pass, char **pS)
+{
+	char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *hexHi;
+	char kc[2*SHA1dlen+1];
+	uchar digest[SHA1dlen];
+	int rc = -1, n;
+	mpint *x, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0);
+	mpint *H = mpnew(0), *Hi = mpnew(0);
+
+	hexHi = PAK_Hi(C, pass, H, Hi);
+
+	// random 1<=x<=q-1; send C, m=g**x H
+	x = mprand(164, genrandom, nil);
+	mpmod(x, pak->q, x);
+	if(mpcmp(x, mpzero) == 0)
+		mpassign(mpone, x);
+	mpexp(pak->g, x, pak->p, m);
+	mpmul(m, H, m);
+	mpmod(m, pak->p, m);
+	hexm = mptoa(m, 64, nil, 0);
+	mess = (char*)emalloc(2*Maxmsg+2);
+	mess2 = mess+Maxmsg+1;
+	snprint(mess, Maxmsg, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm);
+	conn->write(conn, (uchar*)mess, strlen(mess));
+
+	// recv g**y, S, check hash1(g**xy)
+	if(readstr(conn, mess) < 0){
+		fprint(2, "error: %s\n", mess);
+		writerr(conn, "couldn't read g**y");
+		goto done;
+	}
+	eol = strchr(mess, '\n');
+	if(strncmp("mu=", mess, 3) != 0 || !eol || strncmp("\nk=", eol, 3) != 0){
+		writerr(conn, "verifier syntax error");
+		goto done;
+	}
+	hexmu = mess+3;
+	*eol = 0;
+	ks = eol+3;
+	eol = strchr(ks, '\n');
+	if(!eol || strncmp("\nS=", eol, 3) != 0){
+		writerr(conn, "verifier syntax error for secstore 1.0");
+		goto done;
+	}
+	*eol = 0;
+	S = eol+3;
+	eol = strchr(S, '\n');
+	if(!eol){
+		writerr(conn, "verifier syntax error for secstore 1.0");
+		goto done;
+	}
+	*eol = 0;
+	if(pS)
+		*pS = strdup(S);
+	strtomp(hexmu, nil, 64, mu);
+	mpexp(mu, x, pak->p, sigma);
+	hexsigma = mptoa(sigma, 64, nil, 0);
+	shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+	enc64(kc, sizeof kc, digest, SHA1dlen);
+	if(strcmp(ks, kc) != 0){
+		writerr(conn, "verifier didn't match");
+		goto done;
+	}
+
+	// send hash2(g**xy)
+	shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+	enc64(kc, sizeof kc, digest, SHA1dlen);
+	snprint(mess2, Maxmsg, "k'=%s\n", kc);
+	conn->write(conn, (uchar*)mess2, strlen(mess2));
+
+	// set session key
+	shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+	memset(hexsigma, 0, strlen(hexsigma));
+	n = conn->secret(conn, digest, 0);
+	memset(digest, 0, SHA1dlen);
+	if(n < 0){//assert
+		writerr(conn, "can't set secret");
+		goto done;
+	}
+
+	rc = 0;
+done:
+	mpfree(x);
+	mpfree(sigma);
+	mpfree(mu);
+	mpfree(m);
+	mpfree(Hi);
+	mpfree(H);
+	free(hexsigma);
+	free(hexHi);
+	free(hexm);
+	free(mess);
+	return rc;
+}
+
+char*
+secstorefetch(char *addr, char *owner, char *password)
+{
+	int fd;
+	char *rv;
+	char s[Maxmsg+1], bye[10];
+	SConn *conn;
+	char *pass, *sta;
+
+	sta = nil;
+	conn = nil;
+	rv = nil;
+	if(password != nil && *password)
+		pass = strdup(password);
+	else
+		pass = readcons("secstore password", nil, 1);
+	if(pass==nil || strlen(pass)==0){
+		werrstr("cancel");
+		goto Out;
+	}
+	if((fd = secdial(addr)) < 0)
+		goto Out;
+	if((conn = newSConn(fd)) == nil)
+		goto Out;
+	if(PAKclient(conn, owner, pass, nil) < 0){
+		werrstr("password mistyped?");
+		goto Out;
+	}
+	if(readstr(conn, s) < 0)
+		goto Out;
+	if(strcmp(s, "STA") == 0){
+		sta = readcons("STA PIN+SecureID", nil, 1);
+		if(sta==nil || strlen(sta)==0){
+			werrstr("cancel");
+			goto Out;
+		}
+		if(strlen(sta) >= sizeof s - 3){
+			werrstr("STA response too long");
+			goto Out;
+		}
+		strcpy(s+3, sta);
+		conn->write(conn, (uchar*)s, strlen(s));
+		readstr(conn, s);
+	}
+	if(strcmp(s, "OK") !=0){
+		werrstr("%s", s);
+		goto Out;
+	}
+	if((rv = getfile(conn, (uchar*)pass, strlen(pass))) == nil)
+		goto Out;
+	strcpy(bye, "BYE");
+	conn->write(conn, (uchar*)bye, 3);
+
+Out:
+	if(conn)
+		conn->free(conn);
+	if(pass)
+		free(pass);
+	if(sta)
+		free(sta);
+	return rv;
+}
+
--- /dev/null
+++ b/win32-386/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+ulong
+getcallerpc(void *a)
+{
+	return ((ulong*)a)[-1];
+}
--- /dev/null
+++ b/win32-386/md5block.s
@@ -1,0 +1,241 @@
+/*
+ *  rfc1321 requires that I include this.  The code is new.  The constants
+ *  all come from the rfc (hence the copyright).  We trade a table for the
+ *  macros in rfc.  The total size is a lot less. -- presotto
+ *
+ *	Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ *	rights reserved.
+ *
+ *	License to copy and use this software is granted provided that it
+ *	is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ *	Algorithm" in all material mentioning or referencing this software
+ *	or this function.
+ *
+ *	License is also granted to make and use derivative works provided
+ *	that such works are identified as "derived from the RSA Data
+ *	Security, Inc. MD5 Message-Digest Algorithm" in all material
+ *	mentioning or referencing the derived work.
+ *
+ *	RSA Data Security, Inc. makes no representations concerning either
+ *	the merchantability of this software or the suitability of this
+ *	software forany particular purpose. It is provided "as is"
+ *	without express or implied warranty of any kind.
+ *	These notices must be retained in any copies of any part of this
+ *	documentation and/or software.
+ */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+#define PAYME(x) $##x
+
+/*
+ * SI is data
+ *	a += FN(B,C,D);
+ *	a += x[sh] + t[sh];
+ *	a = (a << S11) | (a >> (32 - S11));
+ *	a += b;
+ */
+
+#define BODY1(off,V,FN,SH,A,B,C,D)\
+	FN(B,C,D)\
+	leal V(A, %edi, 1), A;\
+	addl off(%ebp), A;\
+	roll PAYME(SH), A;\
+	addl B, A;\
+
+#define BODY(off,V,FN,SH,A,B,C,D)\
+	FN(B,C,D)\
+	leal V(A, %edi, 1), A;\
+	addl (off)(%ebp), A;\
+	roll PAYME(SH), A;\
+	addl B,A;\
+
+/*
+ * fn1 = ((c ^ d) & b) ^ d
+ */
+#define FN1(B,C,D)\
+	movl C, %edi;\
+	xorl D, %edi;\
+	andl B, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn2 = ((b ^ c) & d) ^ c;
+ */
+#define FN2(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	andl D, %edi;\
+	xorl C, %edi;\
+
+/*
+ * fn3 = b ^ c ^ d;
+ */
+#define FN3(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn4 = c ^ (b | ~d);
+ */
+#define FN4(B,C,D)\
+	movl D, %edi;\
+	xorl $-1, %edi;\
+	orl B, %edi;\
+	xorl C, %edi;\
+
+#define	DATA	8
+#define	LEN	12
+#define	STATE	16
+
+#define EDATA	(-4)
+#define OLDEBX	(-8)
+#define OLDESI	(-12)
+#define OLDEDI	(-16)
+
+	.text
+
+	.p2align 2,0x90
+	.globl _md5block
+		.type _md5block, @function
+	_md5block:
+
+	/* Prelude */
+	pushl %ebp
+	movl %ebx, OLDEBX(%esp)
+	movl %esi, OLDESI(%esp)
+	movl %edi, OLDEDI(%esp)
+
+	movl	DATA(%esp), %eax
+	addl	LEN(%esp), %eax
+	movl	%eax, EDATA(%esp)
+
+	movl DATA(%esp), %ebp
+
+mainloop:
+	movl STATE(%esp), %esi
+	movl (%esi), %eax
+	movl 4(%esi), %ebx
+	movl 8(%esi), %ecx
+	movl 12(%esi), %edx
+
+	BODY1( 0*4,0xd76aa478,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1( 1*4,0xe8c7b756,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1( 2*4,0x242070db,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1( 3*4,0xc1bdceee,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+	BODY1( 4*4,0xf57c0faf,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1( 5*4,0x4787c62a,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1( 6*4,0xa8304613,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1( 7*4,0xfd469501,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+	BODY1( 8*4,0x698098d8,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1( 9*4,0x8b44f7af,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1(10*4,0xffff5bb1,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1(11*4,0x895cd7be,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+	BODY1(12*4,0x6b901122,FN1,S11,%eax,%ebx,%ecx,%edx)
+	BODY1(13*4,0xfd987193,FN1,S12,%edx,%eax,%ebx,%ecx)
+	BODY1(14*4,0xa679438e,FN1,S13,%ecx,%edx,%eax,%ebx)
+	BODY1(15*4,0x49b40821,FN1,S14,%ebx,%ecx,%edx,%eax)
+
+
+	BODY( 1*4,0xf61e2562,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY( 6*4,0xc040b340,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY(11*4,0x265e5a51,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY( 0*4,0xe9b6c7aa,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+	BODY( 5*4,0xd62f105d,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY(10*4,0x02441453,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY(15*4,0xd8a1e681,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY( 4*4,0xe7d3fbc8,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+	BODY( 9*4,0x21e1cde6,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY(14*4,0xc33707d6,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY( 3*4,0xf4d50d87,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY( 8*4,0x455a14ed,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+	BODY(13*4,0xa9e3e905,FN2,S21,%eax,%ebx,%ecx,%edx)
+	BODY( 2*4,0xfcefa3f8,FN2,S22,%edx,%eax,%ebx,%ecx)
+	BODY( 7*4,0x676f02d9,FN2,S23,%ecx,%edx,%eax,%ebx)
+	BODY(12*4,0x8d2a4c8a,FN2,S24,%ebx,%ecx,%edx,%eax)
+
+
+	BODY( 5*4,0xfffa3942,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY( 8*4,0x8771f681,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY(11*4,0x6d9d6122,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY(14*4,0xfde5380c,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+	BODY( 1*4,0xa4beea44,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY( 4*4,0x4bdecfa9,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY( 7*4,0xf6bb4b60,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY(10*4,0xbebfbc70,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+	BODY(13*4,0x289b7ec6,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY( 0*4,0xeaa127fa,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY( 3*4,0xd4ef3085,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY( 6*4,0x04881d05,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+	BODY( 9*4,0xd9d4d039,FN3,S31,%eax,%ebx,%ecx,%edx)
+	BODY(12*4,0xe6db99e5,FN3,S32,%edx,%eax,%ebx,%ecx)
+	BODY(15*4,0x1fa27cf8,FN3,S33,%ecx,%edx,%eax,%ebx)
+	BODY( 2*4,0xc4ac5665,FN3,S34,%ebx,%ecx,%edx,%eax)
+
+
+	BODY( 0*4,0xf4292244,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY( 7*4,0x432aff97,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY(14*4,0xab9423a7,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY( 5*4,0xfc93a039,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	BODY(12*4,0x655b59c3,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY( 3*4,0x8f0ccc92,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY(10*4,0xffeff47d,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY( 1*4,0x85845dd1,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	BODY( 8*4,0x6fa87e4f,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY(15*4,0xfe2ce6e0,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY( 6*4,0xa3014314,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY(13*4,0x4e0811a1,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	BODY( 4*4,0xf7537e82,FN4,S41,%eax,%ebx,%ecx,%edx)
+	BODY(11*4,0xbd3af235,FN4,S42,%edx,%eax,%ebx,%ecx)
+	BODY( 2*4,0x2ad7d2bb,FN4,S43,%ecx,%edx,%eax,%ebx)
+	BODY( 9*4,0xeb86d391,FN4,S44,%ebx,%ecx,%edx,%eax)
+
+	addl $(16*4), %ebp
+	movl STATE(%esp), %edi
+	addl %eax,0(%edi)
+	addl %ebx,4(%edi)
+	addl %ecx,8(%edi)
+	addl %edx,12(%edi)
+
+	movl EDATA(%esp), %edi
+	cmpl %edi, %ebp
+	jb mainloop
+
+	/* Postlude */
+	movl OLDEBX(%esp), %ebx
+	movl OLDESI(%esp), %esi
+	movl OLDEDI(%esp), %edi
+	movl %esp, %ebp
+	leave
+	ret
+
--- /dev/null
+++ b/win32-386/mkfile
@@ -1,0 +1,10 @@
+<$DSRC/mkfile-$CONF
+TARG=libmachdep.$L
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+HFILES=\
+
+<$DSRC/mklib-$CONF
--- /dev/null
+++ b/win32-386/sha1block.s
@@ -1,0 +1,214 @@
+.text
+
+.p2align 2,0x90
+.globl _sha1block
+	.type _sha1block, @function
+_sha1block:
+
+/* x = (wp[off-f] ^ wp[off-8] ^ wp[off-14] ^ wp[off-16]) <<< 1;
+ * wp[off] = x;
+ * x += A <<< 5;
+ * E += 0xca62c1d6 + x;
+ * x = FN(B,C,D);
+ * E += x;
+ * B >>> 2
+ */
+#define BSWAPDI	BYTE $0x0f; BYTE $0xcf;
+
+#define BODY(off,FN,V,A,B,C,D,E)\
+	movl (off-64)(%ebp), %edi;\
+	xorl (off-56)(%ebp), %edi;\
+	xorl (off-32)(%ebp), %edi;\
+	xorl (off-12)(%ebp), %edi;\
+	roll $1, %edi;\
+	movl %edi, off(%ebp);\
+	leal V(%edi, E, 1), E;\
+	movl A, %edi;\
+	roll $5, %edi;\
+	addl %edi, E;\
+	FN(B,C,D)\
+	addl %edi, E;\
+	rorl $2, B;\
+
+#define BODY0(off,FN,V,A,B,C,D,E)\
+	movl off(%ebx), %edi;\
+	bswap %edi;\
+	movl %edi, off(%ebp);\
+	leal V(%edi,E,1), E;\
+	movl A, %edi;\
+	roll $5,%edi;\
+	addl %edi,E;\
+	FN(B,C,D)\
+	addl %edi,E;\
+	rorl $2,B;\
+
+/*
+ * fn1 = (((C^D)&B)^D);
+ */
+#define FN1(B,C,D)\
+	movl C, %edi;\
+	xorl D, %edi;\
+	andl B, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn24 = B ^ C ^ D
+ */
+#define FN24(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	xorl D, %edi;\
+
+/*
+ * fn3 = ((B ^ C) & (D ^= B)) ^ B
+ * D ^= B to restore D
+ */
+#define FN3(B,C,D)\
+	movl B, %edi;\
+	xorl C, %edi;\
+	xorl B, D;\
+	andl D, %edi;\
+	xorl B, %edi;\
+	xorl B, D;\
+
+/*
+ * stack offsets
+ * void sha1block(uchar *DATA, int LEN, ulong *STATE)
+ */
+#define	DATA	8
+#define	LEN	12
+#define	STATE	16
+
+/*
+ * stack offsets for locals
+ * ulong w[80];
+ * uchar *edata;
+ * ulong *w15, *w40, *w60, *w80;
+ * register local
+ * ulong *wp = %ebp
+ * ulong a = eax, b = ebx, c = ecx, d = edx, e = esi
+ * ulong tmp = edi
+ */
+#define WARRAY	(-4-(80*4))
+#define TMP1	(-8-(80*4))
+#define TMP2	(-12-(80*4))
+#define W15	(-16-(80*4))
+#define W40	(-20-(80*4))
+#define W60	(-24-(80*4))
+#define W80	(-28-(80*4))
+#define EDATA	(-32-(80*4))
+#define OLDEBX	(-36-(80*4))
+#define OLDESI	(-40-(80*4))
+#define OLDEDI	(-44-(80*4))
+
+	/* Prelude */
+	pushl %ebp
+	mov %ebx, OLDEBX(%esp)
+	mov %esi, OLDESI(%esp)
+	mov %edi, OLDEDI(%esp)
+
+	movl DATA(%esp), %eax
+	addl LEN(%esp), %eax
+	movl %eax, EDATA(%esp)
+
+	leal (WARRAY+15*4)(%esp), %edi	/* aw15 */
+	movl %edi, W15(%esp)
+	leal (WARRAY+40*4)(%esp), %edx	/* aw40 */
+	movl %edx, W40(%esp)
+	leal (WARRAY+60*4)(%esp), %ecx	/* aw60 */
+	movl %ecx, W60(%esp)
+	leal (WARRAY+80*4)(%esp), %edi	/* aw80 */
+	movl %edi, W80(%esp)
+
+mainloop:
+	leal WARRAY(%esp), %ebp		/* warray */
+
+	movl STATE(%esp), %edi		/* state */
+	movl (%edi),%eax
+	movl 4(%edi),%ebx
+	movl %ebx, TMP1(%esp)		/* tmp1 */
+	movl 8(%edi), %ecx
+	movl 12(%edi), %edx
+	movl 16(%edi), %esi
+
+	movl DATA(%esp), %ebx		/* data */
+
+loop1:
+	BODY0(0,FN1,0x5a827999,%eax,TMP1(%esp),%ecx,%edx,%esi)
+	movl %esi,TMP2(%esp)
+	BODY0(4,FN1,0x5a827999,%esi,%eax,TMP1(%esp),%ecx,%edx)
+	movl TMP1(%esp),%esi
+	BODY0(8,FN1,0x5a827999,%edx,TMP2(%esp),%eax,%esi,%ecx)
+	BODY0(12,FN1,0x5a827999,%ecx,%edx,TMP2(%esp),%eax,%esi)
+	movl %esi,TMP1(%esp)
+	BODY0(16,FN1,0x5a827999,%esi,%ecx,%edx,TMP2(%esp),%eax)
+	movl TMP2(%esp),%esi
+
+	addl $20, %ebx
+	addl $20, %ebp
+	cmpl W15(%esp), %ebp	/* w15 */
+	jb loop1
+
+	BODY0(0,FN1,0x5a827999,%eax,TMP1(%esp),%ecx,%edx,%esi)
+	addl $4, %ebx
+	MOVL %ebx, DATA(%esp)	/* data */
+	MOVL TMP1(%esp),%ebx
+
+	BODY(4,FN1,0x5a827999,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN1,0x5a827999,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN1,0x5a827999,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN1,0x5a827999,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20, %ebp
+
+loop2:
+	BODY(0,FN24,0x6ed9eba1,%eax,%ebx,%ecx,%edx,%esi)
+	BODY(4,FN24,0x6ed9eba1,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN24,0x6ed9eba1,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN24,0x6ed9eba1,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN24,0x6ed9eba1,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20,%ebp
+	cmpl W40(%esp), %ebp
+	jb loop2
+
+loop3:
+	BODY(0,FN3,0x8f1bbcdc,%eax,%ebx,%ecx,%edx,%esi)
+	BODY(4,FN3,0x8f1bbcdc,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN3,0x8f1bbcdc,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN3,0x8f1bbcdc,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN3,0x8f1bbcdc,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20, %ebp
+	cmpl W60(%esp), %ebp 	/* w60 */
+	jb loop3
+
+loop4:
+	BODY(0,FN24,0xca62c1d6,%eax,%ebx,%ecx,%edx,%esi)
+	BODY(4,FN24,0xca62c1d6,%esi,%eax,%ebx,%ecx,%edx)
+	BODY(8,FN24,0xca62c1d6,%edx,%esi,%eax,%ebx,%ecx)
+	BODY(12,FN24,0xca62c1d6,%ecx,%edx,%esi,%eax,%ebx)
+	BODY(16,FN24,0xca62c1d6,%ebx,%ecx,%edx,%esi,%eax)
+
+	addl $20, %ebp
+	cmpl W80(%esp), %ebp 	/* w80 */
+	jb loop4
+
+	movl STATE(%esp), %edi	/* state */
+	addl %eax, 0(%edi)
+	addl %ebx, 4(%edi)
+	addl %ecx, 8(%edi)
+	addl %edx, 12(%edi)
+	addl %esi, 16(%edi)
+
+	movl EDATA(%esp), %edi	/* edata */
+	cmpl %edi, DATA(%esp)	/* data */
+	jb mainloop
+
+	/* Postlude */
+	mov OLDEBX(%esp), %ebx
+	mov OLDESI(%esp), %esi
+	mov OLDEDI(%esp), %edi
+	movl %esp, %ebp
+	leave
+	ret
--- /dev/null
+++ b/win32-386/tas.c
@@ -1,0 +1,22 @@
+// could also use windozy InterlockedCompareExchange(p, 1, 0), but why
+int
+tas(long *p)
+{	
+	int v;
+	
+	_asm {
+		mov	eax, p
+		mov	ebx, 1
+		xchg	ebx, [eax]
+		mov	v, ebx
+	}
+
+	switch(v) {
+	case 0:
+	case 1:
+		return v;
+	default:
+		print("canlock: corrupted 0x%lux\n", v);
+		return 1;
+	}
+}