hlfw.ca

drawcpu

Download patch

ref: dfaaee1464be873eea263b02c90a3b05605e6fa8
author: halfwit <michaelmisch1985@gmail.com>
date: Thu Feb 22 14:39:12 PST 2024

Initial work to get at least part of the way there

--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,5 @@
+**/*.o
+**/*.a
+
+*.a
+*.o
\ No newline at end of file
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,22 @@
+Copyright © 2021 Plan 9 Foundation
+Portions Copyright © 2005 Russ Cox, MIT
+Portions Copyright © 20XX 9front authors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+++ b/Make.config
@@ -1,0 +1,3 @@
+AUDIO=none
+P9ANY_VERSION=1
+include $(ROOT)/Make.$(CONF)
--- /dev/null
+++ b/Make.dragonfly
@@ -1,0 +1,21 @@
+# DragonFlyBSD
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+X11=/usr/local
+CC=gcc
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=x11
+LDADD=-L$(X11)/lib64 -L$(X11)/lib -lX11 -ggdb
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+AUDIO=none
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.fbdev
@@ -1,0 +1,22 @@
+# Unix
+#PTHREAD=	# for Mac
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+CC=gcc
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=fbdev
+LDADD=-ggdb -lm -lasound
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+# AUDIO=none
+AUDIO=alsa
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/; s/armv[567].*/arm/; s/aarch64/arm64/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.freebsd
@@ -1,0 +1,21 @@
+# OpenBSD
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+X11=/usr/local
+CC=clang
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=x11
+LDADD=-L$(X11)/lib64 -L$(X11)/lib -lX11 -ggdb
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+AUDIO=unix
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.irix
@@ -1,0 +1,24 @@
+# Unix
+PTHREAD=	# for Mac
+#PTHREAD=-pthread
+AR=ar
+AS=as
+ASFLAGS=-c -mips3
+RANLIB=true
+X11=/usr/X11R6
+#CC=gcc
+#CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+CC=cc
+CFLAGS=-g -O2 -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include  -DIRIX
+O=o
+OS=posix
+GUI=x11
+LDADD=-L$(X11)/lib -lX11 -g -lpthread
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+MAKE=gmake
+
+all: default
+
+libmachdep.a:
+	(cd posix-mips && $(MAKE))
--- /dev/null
+++ b/Make.linux
@@ -1,0 +1,21 @@
+# Linux
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+CC?=cc
+CFLAGS=-Wall -Wno-missing-braces -Wno-parentheses -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -D_THREAD_SAFE -DPTHREAD $(PTHREAD) `pkg-config --cflags libpipewire-0.3` -D_REENTRANT -O2
+O=o
+OS=posix
+GUI=wl
+LDADD=-lwayland-client -lxkbcommon -ggdb -lm -lrt -lpipewire-0.3
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+# AUDIO=none
+AUDIO=pipewire
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/; s/armv[567].*/arm/; s/aarch64/arm64/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.linux386
@@ -1,0 +1,22 @@
+# Unix
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+X11=/usr/X11R6
+CC=gcc
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=x11
+LDADD=-L$(X11)/lib64 -L$(X11)/lib -lX11 -ggdb -lm
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+# AUDIO=none
+AUDIO=unix
+
+all: default
+
+libmachdep.a:
+	arch=386; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.netbsd
@@ -1,0 +1,22 @@
+# NetBSD
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+X11=/usr/X11R7
+CC=gcc
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=x11
+LDADD=-Wl,-rpath,$(X11)/lib64 -Wl,-rpath,$(X11)/lib -L$(X11)/lib64 -L$(X11)/lib -lX11 -ggdb -lossaudio
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+AUDIO=unix
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/'`; \
+	(cd posix-$$arch &&  make)
+
--- /dev/null
+++ b/Make.openbsd
@@ -1,0 +1,20 @@
+# OpenBSD
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+X11=/usr/X11R6
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=x11
+LDADD=-L$(X11)/lib64 -L$(X11)/lib -lX11 -lsndio -ggdb
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+AUDIO=sndio
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/; s/macppc/power/; s/socppc/power/; s/x86_64/amd64/; s/sparc64/sun4u/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.osx-cocoa
@@ -1,0 +1,20 @@
+# Mac OS X
+PTHREAD=	# for Mac
+AR=ar
+AS=as
+RANLIB=ranlib
+CC=gcc
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=cocoa
+LDADD=-ggdb -framework Cocoa -framework Metal -framework QuartzCore
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+AUDIO=none
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/x86_64/amd64/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.pthread
@@ -1,0 +1,23 @@
+# Unix
+#PTHREAD=	# for Mac
+PTHREAD=-pthread -DPTHREAD
+AR=ar
+AS=no-as-here
+RANLIB=ranlib
+X11=/usr/X11R6
+CC=gcc
+CFLAGS=-Wall -Wno-missing-braces -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=x11
+LDADD=-L$(X11)/lib64 -L$(X11)/lib -lX11 -ggdb
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+# AUDIO=none
+AUDIO=unix
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/; s/armv[567].*/arm/; s/aarch64/arm64/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.unix
@@ -1,0 +1,23 @@
+# Unix
+#PTHREAD=	# for Mac
+PTHREAD=-pthread
+AR=ar
+AS=as
+RANLIB=ranlib
+X11=/usr/X11R6
+CC=gcc
+CFLAGS=-Wall -Wno-missing-braces -Wno-parentheses -ggdb -I$(ROOT) -I$(ROOT)/include -I$(ROOT)/kern -c -I$(X11)/include -D_THREAD_SAFE $(PTHREAD) -O2
+O=o
+OS=posix
+GUI=x11
+LDADD=-L$(X11)/lib64 -L$(X11)/lib -lX11 -ggdb -lm
+LDFLAGS=$(PTHREAD)
+TARG=drawcpu
+# AUDIO=none
+AUDIO=unix
+
+all: default
+
+libmachdep.a:
+	arch=`uname -m|sed 's/i.86/386/;s/Power Macintosh/power/; s/x86_64/amd64/; s/armv[567].*/arm/; s/aarch64/arm64/'`; \
+	(cd posix-$$arch &&  make)
--- /dev/null
+++ b/Make.win32
@@ -1,0 +1,42 @@
+# Windows via mingw32
+# MING=mingw32- is necessary if you're cross-compiling
+# on another platform.  Otherwise the binaries are just
+# named gcc, etc.
+
+AUDIO=win32
+MING=i686-w64-mingw32-
+#MING=
+AR=$(MING)ar
+CC=$(MING)gcc
+AS=$(MING)as
+RANLIB=$(MING)ranlib
+WINDRES=$(MING)windres
+CFLAGS=-Wall -Wno-missing-braces -I$(ROOT)/include -I$(ROOT) -I$(ROOT)/kern -c -D_X86_ -DIS_32 -DWINDOWS -DUNICODE -O2
+O=o
+FS=fs-win32
+IP=win32
+OS=win32
+GUI=win32
+LDADD=-lkernel32 -ladvapi32 -lgdi32 -lmpr -lwsock32 -lws2_32 -lmsvcrt -lmingw32 -lwinmm
+TARG=drawcpu.exe
+XOFILES=glenda-t.$O
+
+# Windows via MSVC
+#AR=???
+#CC=cl
+#CFLAGS=-c -nologo -W3 -YX -Zi -MT -Zl -Iinclude -DWINDOWS
+#O=obj
+#FS=fs-win32
+#IP=win32
+#OS=win32
+#GUI=win32
+
+all: default
+
+# for root
+libmachdep.a:
+	(cd win32-386; make)
+
+glenda-t.$O: glenda-t.rc glenda-t.ico
+	$(WINDRES) -i glenda-t.rc -o glenda-t.o
+
--- /dev/null
+++ b/Make.win64
@@ -1,0 +1,32 @@
+# Windows via mingw-w64
+# MING=mingw32- is necessary if you're cross-compiling
+# on another platform.  Otherwise the binaries are just
+# named gcc, etc.
+
+AUDIO=win32
+MING=x86_64-w64-mingw32-
+#MING=
+AR=$(MING)ar
+CC=$(MING)gcc
+AS=$(MING)as
+RANLIB=$(MING)ranlib
+WINDRES=$(MING)windres
+CFLAGS=-Wall -Wno-missing-braces -I$(ROOT)/include -I$(ROOT) -I$(ROOT)/kern -c -DWINDOWS -DUNICODE -O2
+O=o
+FS=fs-win32
+IP=win32
+OS=win32
+GUI=win32
+LDADD=-lgdi32 -lws2_32 -lwinmm -mwindows
+TARG=drawcpu.exe
+XOFILES=glenda-t.$O
+
+all: default
+
+# for root
+libmachdep.a:
+	(cd posix-amd64; make)
+
+glenda-t.$O: glenda-t.rc glenda-t.ico
+	$(WINDRES) -i glenda-t.rc -o glenda-t.o
+
--- /dev/null
+++ b/Makefile
@@ -1,0 +1,80 @@
+ROOT=.
+
+include Make.config
+
+OFILES=\
+	main.$O\
+	session.$O\
+	aan.$O\
+	auth.$O\
+	secstore.$O\
+	latin1.$O\
+	$(OS)-factotum.$O\
+	$(XOFILES)\
+
+LIBS1=\
+	kern/libkern.a\
+	exportfs/libexportfs.a\
+	libauth/libauth.a\
+	libauthsrv/libauthsrv.a\
+	libsec/libsec.a\
+	libmp/libmp.a\
+	libmemdraw/libmemdraw.a\
+	libmemlayer/libmemlayer.a\
+	libdraw/libdraw.a\
+	gui-$(GUI)/libgui.a\
+	libc/libc.a\
+	libip/libip.a\
+	#librc/librc.a\
+
+# stupid gcc
+LIBS=$(LIBS1) $(LIBS1) $(LIBS1) libmachdep.a
+
+default: $(TARG)
+$(TARG): $(OFILES) $(LIBS)
+	$(CC) $(LDFLAGS) -o $(TARG) $(OFILES) $(LIBS) $(LDADD)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+clean:
+	rm -f *.o */*.o */*.a *.a drawcpu drawcpu.exe
+
+kern/libkern.a:
+	(cd kern; $(MAKE))
+
+exportfs/libexportfs.a:
+	(cd exportfs; $(MAKE))
+
+libauth/libauth.a:
+	(cd libauth; $(MAKE))
+	
+libauthsrv/libauthsrv.a:
+	(cd libauthsrv; $(MAKE))
+
+libmp/libmp.a:
+	(cd libmp; $(MAKE))
+
+libsec/libsec.a:
+	(cd libsec; $(MAKE))
+
+librc/librc.a:
+	(cd librc; $(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))
+
+libip/libip.a:
+	(cd libip; $(MAKE))
+
+gui-$(GUI)/libgui.a:
+	(cd gui-$(GUI); $(MAKE))
--- /dev/null
+++ b/README
@@ -1,0 +1,45 @@
+DESCRIPTION
+-----------
+This is a fork of Russ Cox's drawcpu to incorporate features
+from Plan9front (http://9front.org), most importantly DP9IK
+authentication support (see authsrv(6)) and the TLS based rcpu(1)
+protocol.
+
+
+INSTALLATION
+--------------
+To build on Unix, run CONF=unix make.
+
+To build on Solaris using Sun cc, run CONF=sun make.
+
+To build on Windows, you can't use Visual C. Use Mingw on cygwin.
+
+To build on Mac OS X with X11 (xquartz), run CONF=osx-x11 make.
+
+To build on Mac OS X with Cocoa, run CONF=osx-cocoa make and "cp drawcpu gui-cocoa/drawcpu.app/".
+
+To build for Android, make sure Make.android* and gui-android/Makefile are correct for your build and target systems, then run make -f Make.android
+
+USAGE
+-------
+On Android the five checkboxes at the top represent the three mouse buttons and mousewheel, determining which "buttons" are clicked. The "kb" button toggles the soft keyboard.
+
+
+CAVEATS
+--------
+Be aware that right now on Android the login details are saved as a plaintext string if saved, and there is no secstore support.
+
+
+BINARIES
+---------
+http://drawcpu.9front.org/
+
+
+SOURCE
+------
+http://git.9front.org/plan9front/drawcpu/HEAD/info.html
+
+
+HELP
+----
+No.
--- /dev/null
+++ b/TODO
@@ -1,0 +1,40 @@
+NOTES:
+Some builtins will be required **TEST** 
+ - 'mount'
+ - 'bind'
+ - 'auth/factotum'
+ - 'auth/secstore'
+ - 'ns'
+ - Can run a command or interactive shell
+ - The profile is ran (!!!)
+
+TODO:
+ - [ ] have it export a var $service=unix or $service=windows
+ - [ ] Some people probably want aan?
+ - [ ] Move rc code to use our libc.h, u.h everywhere so our defs of open + such are correct
+ - [ ] gui-cocoa try to capture the windows we create so we can send mouse/keyboard to it, plist for launchd
+ - [ ] gui-wl cannibalize the wayland shims
+ - [ ] handle notes and signals
+ - [x] Remove listen1 outright, it's messing everything up.
+ - [x] tlssrv, but use internal factotum bits
+ - [ ] create namespaces with our bind/mount and allow report via ns(1)
+ - [ ] define the gui interface
+ - [ ] Makefile librc --> librc.a
+ - [ ] move much of io.c and exec.c into our cpu space
+      - It looks like it's mostly broken out into unix.c/plan9.c to where the defs we'd want are and builtins
+      - Could pull in exec.c into the cpu scope instead of where it is, provide pieces into the library or abstracted out? Unsure. 
+
+Kernel space needs:
+ - devdraw, devkbd, devmouse, devaudio - wrong side
+ - devcmd - we'll need more insight as to what this really does
+ - devcons - needed, console
+ - devenv - needed, env vars
+ - devfs - needed, local files
+ - devip - needed, networking
+ - devlfd - I am unsure
+ - devmnt - needed, client shares
+ - devpipe - probably needed for any pipe
+ - devroot - needed, base of stack
+ - devssl - needed, ssl
+ - devtab - meta, needed
+ - devtls - needed! probably before connection even happens, though it may be odd scoping there, we may just need to bind it up just after the accept
--- /dev/null
+++ b/aan.c
@@ -1,0 +1,218 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+enum {
+	Hdrsz = 3*4,
+	Bufsize = 8*1024,
+};
+
+typedef struct Hdr Hdr;
+typedef struct Buf Buf;
+typedef struct Client Client;
+
+struct Hdr {
+	uchar	nb[4];		// Number of data bytes in this message
+	uchar	msg[4];		// Message number
+	uchar	acked[4];	// Number of messages acked
+};
+
+struct Buf {
+	Hdr	hdr;
+	uchar	buf[Bufsize];
+
+	Buf	*next;
+};
+
+struct Client {
+	QLock	lk;
+
+	char	*addr;
+	int	netfd;
+	int	pipefd;
+	int	timeout;
+
+	int	reader;
+	int	writer;
+	int	syncer;
+
+	ulong	inmsg;
+	ulong	outmsg;
+
+	Buf	*unackedhead;
+	Buf	**unackedtail;
+};
+
+static void
+reconnect(Client *c)
+{
+	Buf *b;
+	int n;
+	ulong to;
+
+	qlock(&c->lk);
+	to = (ulong)time(0) + c->timeout;
+Again:
+	for(;;){
+		if(c->netfd >= 0){
+			close(c->netfd);
+			c->netfd = -1;
+		}
+		if((c->netfd = dial(c->addr,nil,nil,nil)) >= 0)
+			break;		
+		if((ulong)time(0) >= to)
+			sysfatal("dial timed out: %r");
+		sleep(1000);
+	}
+	for(b = c->unackedhead; b != nil; b = b->next){
+		n = GBIT32(b->hdr.nb);
+		PBIT32(b->hdr.acked, c->inmsg);
+		if(write(c->netfd, &b->hdr, Hdrsz) != Hdrsz
+		|| write(c->netfd, b->buf, n) != n){
+			print("write error: %r\n");
+			goto Again;
+		}
+	}
+	qunlock(&c->lk);
+}
+
+static void
+aanwriter(void *arg)
+{
+	Client *c = (Client*)arg;
+	Buf *b;
+	int n;
+	ulong m;
+
+	for(;;){
+		b = malloc(sizeof(Buf));
+		if(b == nil)
+			break;
+		if((n = read(c->pipefd, b->buf, Bufsize)) < 0){
+			free(b);
+			break;
+		}
+
+		qlock(&c->lk);
+		m = c->outmsg++;
+		PBIT32(b->hdr.nb, n);
+		PBIT32(b->hdr.msg, m);
+		PBIT32(b->hdr.acked, c->inmsg);
+
+		b->next = nil;
+		if(c->unackedhead == nil)
+			c->unackedtail = &c->unackedhead;
+		*c->unackedtail = b;
+		c->unackedtail = &b->next;
+
+		if(c->netfd < 0
+		|| write(c->netfd, &b->hdr, Hdrsz) != Hdrsz
+		|| write(c->netfd, b->buf, n) != n){
+			qunlock(&c->lk);
+			continue;
+		}
+		qunlock(&c->lk);
+
+		if(n == 0)
+			break;
+	}
+	close(c->pipefd);
+	c->pipefd = -1;
+}
+
+static void
+aansyncer(void *arg)
+{
+	Client *c = (Client*)arg;
+	Hdr hdr;
+
+	for(;;){
+		sleep(4000);
+		qlock(&c->lk);
+		if(c->netfd >= 0){
+			PBIT32(hdr.nb, 0);
+			PBIT32(hdr.acked, c->inmsg);
+			PBIT32(hdr.msg, -1);
+			write(c->netfd, &hdr, Hdrsz);
+		}
+		qunlock(&c->lk);
+	}
+}
+
+static void
+aanreader(void *arg)
+{
+	Client *c = (Client*)arg;
+	ulong a, m, lastacked = 0;
+	Buf *b, *x;
+	int n;
+
+Restart:
+	b = mallocz(sizeof(Buf), 1);
+	for(;;){
+		if(readn(c->netfd, &b->hdr, Hdrsz) != Hdrsz)
+			break;
+		a = GBIT32(b->hdr.acked);
+		m = GBIT32(b->hdr.msg);
+		n = GBIT32(b->hdr.nb);
+		if(n == 0){
+			if(m == (ulong)-1)
+				continue;
+			goto Closed;
+		} else if(n < 0 || n > Bufsize)
+			goto Closed;
+
+		if(readn(c->netfd, b->buf, n) != n)
+			break;
+		if(m != c->inmsg)
+			continue;
+		c->inmsg++;
+
+		if((long)(a - lastacked) > 0){
+			qlock(&c->lk);
+			while((x = c->unackedhead) != nil){
+				assert(GBIT32(x->hdr.msg) == lastacked);
+				c->unackedhead = x->next;
+				free(x);
+				if(++lastacked == a)
+					break;
+			}
+			qunlock(&c->lk);
+		}
+
+		if(c->pipefd < 0)
+			goto Closed;
+		write(c->pipefd, b->buf, n);
+	}
+	free(b);
+	reconnect(c);
+	goto Restart;
+Closed:
+	free(b);
+	if(c->pipefd >= 0)
+		write(c->pipefd, "", 0);
+}
+
+int
+aanclient(char *addr, int timeout)
+{
+	Client *c;
+	int pfd[2];
+
+	if(pipe(pfd) < 0)
+		sysfatal("pipe: %r");
+
+	c = mallocz(sizeof(Client), 1);
+	c->addr = addr;
+	c->netfd = -1;
+	c->pipefd = pfd[1];
+	c->inmsg = 0;
+	c->outmsg = 0;
+	c->timeout = 60;
+	reconnect(c);
+	c->timeout = timeout;
+	c->writer = kproc("aanwriter", aanwriter, c);
+	c->reader = kproc("aanreader", aanreader, c);
+	c->syncer = kproc("aansyncer", aansyncer, c);
+	return pfd[0];
+}
--- /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/auth.c
@@ -1,0 +1,203 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <authsrv.h>
+#include <libsec.h>
+#include "drawcpu.h"
+
+char *authserver; // Likely unused
+
+AuthInfo*
+establish(Ticket *t, uchar *rand, int dp9ik)
+{
+	AuthInfo *ai;
+	ai = mallocz(sizeof(AuthInfo), 1);
+	ai->suid = t->suid;
+	ai->cuid = t->cuid;
+	if(dp9ik){
+		static char info[] = "Plan 9 session secret";
+
+		ai->nsecret = 256;
+		ai->secret = mallocz(ai->nsecret, 1);
+		hkdf_x(rand, 2*NONCELEN,
+			(uchar*)info, sizeof(info)-1,
+			(uchar*)t->key, NONCELEN,
+			ai->secret, ai->nsecret,
+			hmac_sha2_256, SHA2_256dlen
+		);
+	} else {
+		ai->nsecret = 8;
+		ai->secret = mallocz(ai->nsecret, 1);
+		des56to64((uchar*)t->key, ai->secret);
+	}
+
+	return ai;
+}
+
+
+AuthInfo*
+auth_host(int fd, char *authdom, char *pass)
+{
+    char *user;
+	char cpd[ANAMELEN+DOMLEN+1], spd[2*DOMLEN+18], *proto, *dom;
+	char trbuf[TICKREQLEN+PAKYLEN], abuf[MAXTICKETLEN+MAXAUTHENTLEN];
+	uchar srand[2*NONCELEN], cchal[CHALLEN], y[PAKYLEN];
+    Authkey key;
+    Authenticator auth;
+	AuthInfo *ai;
+	PAKpriv p;
+	Ticketreq tr;
+	Ticket t;
+	int afd;
+
+	if((afd = open("/mnt/factotum/ctl", ORDWR)) >= 0)
+		return auth_proxy(0, nil, "proto=p9any role=server");
+	
+    int n, m, dp9ik = 0;
+    /* Start p9any */
+#if P9ANY_VERSION==2
+	n = sprintf(spd, "v.2 p9sk1@%s dp9ik@%s ", authdom, authdom);
+#else
+	n = sprintf(spd, "p9sk1@%s dp9ik@%s ", authdom, authdom);
+#endif
+	if(write(fd, spd, n+1) != n+1){
+        fprint(dbg, "short write on p9any\n");
+        return nil;
+    }
+    
+	if(read(fd, cpd, ANAMELEN+DOMLEN+1) <= 0){
+        fprint(dbg, "short read on client proto\n");
+        return nil;
+    }
+
+	proto = strtok(cpd, " ");
+	dom = strtok(NULL, " ");
+	if(proto == NULL || dom == NULL){
+        fprint(dbg, "unable to read requested proto/dom pair\n");
+        return nil;
+    }
+
+	if(strcmp(proto, "dp9ik") == 0)
+		dp9ik = 1;
+
+#if P9ANY_VERSION==2
+	if(write(fd, "OK\0", 3) != 3){
+        fprint(dbg, "short write on proto challenge OK");
+        return nil;
+    }
+#endif
+
+	/* p9any success, start selected protocol */
+    user = getenv("USER");
+
+	memset(&tr, 0, sizeof(tr));
+	tr.type = AuthTreq;
+	strcpy(tr.authid, user);
+	strcpy(tr.authdom, authdom);
+	genrandom((uchar*)tr.chal, CHALLEN);
+
+	if((n = readn(fd, cchal, CHALLEN)) != CHALLEN){
+        fprint(dbg, "Short read on p9sk1 challenge\n");
+        return nil;
+    }
+
+	m = TICKREQLEN;
+    passtokey(&key, pass);
+	if(dp9ik){
+		authpak_hash(&key, user);
+		tr.type = AuthPAK;
+		m += PAKYLEN;
+	}
+	n = convTR2M(&tr, trbuf, m);
+	if(dp9ik)
+		authpak_new(&p, &key, (uchar *)trbuf+n, 1);
+
+    //print(trbuf);
+	if(write(fd, trbuf, m) < m){
+        fprint(dbg, "short read sending ticket request\n");
+        return nil;
+    }
+	if(dp9ik){
+		if(readn(fd, y, PAKYLEN) < PAKYLEN){
+            fprint(dbg, "short read on client pk");
+            return nil;
+        }
+		if(authpak_finish(&p, &key, y)){
+            fprint(dbg, "unable to decrypt message in auth_host\n");
+            return nil;
+        }
+	}
+	if((n = readn(fd, abuf, MAXTICKETLEN+MAXAUTHENTLEN)) != MAXTICKETLEN+MAXAUTHENTLEN){
+        fprint(dbg, "short read receiving ticket\n");
+        return nil;
+    }
+	m = convM2T(abuf, n, &t, &key);
+	if(m <= 0 || convM2A(abuf+m, n-m, &auth, &t) <= 0){
+        fprint(dbg, "short read on ticket\n");
+        return nil;
+    }
+	if(dp9ik && t.form == 0){
+        fprint(dbg, "auth_host: auth protocol botch");
+        return nil;
+    }
+
+	if(t.num != AuthTs || tsmemcmp(t.chal, tr.chal, CHALLEN) != 0){
+        fprint(dbg, "auth protocol botch\n");
+        return nil;
+    }
+
+	if(auth.num != AuthAc || tsmemcmp(auth.chal, tr.chal, CHALLEN) != 0){
+        fprint(dbg, "auth portocol botch");
+        return nil;
+    }
+	memmove(srand, auth.rand, NONCELEN);
+	genrandom(srand + NONCELEN, NONCELEN);
+	auth.num = AuthAs;
+	memmove(auth.chal, cchal, CHALLEN);
+	memmove(auth.rand, srand + NONCELEN, NONCELEN);
+	if((n = convA2M(&auth, abuf, sizeof(abuf), &t)) < 0){
+        fprint(dbg, "unable to convert authenticator to message\n");
+        return nil;
+    }
+	if(write(fd, abuf, n) != n){
+        fprint(dbg, "short write sending authenticator\n");
+        return nil;
+    }
+	ai = establish(&t, srand, dp9ik);
+	memset(&key, 0, sizeof(key));
+	memset(&t, 0, sizeof(t));
+	memset(&auth, 0, sizeof(auth));
+	return ai;
+}
+
+int
+p9authsrv(char *authdom, char *pass)
+{
+    AuthInfo *ai;
+    TLSconn *conn;
+
+    int fd = open("/dev/cons", O_RDWR);
+    ai = auth_host(fd, authdom, pass);
+    if(ai == nil){
+        fprint(dbg, "can't authenticate: %r\n");
+        return -1;
+    }
+
+    conn = mallocz(sizeof(TLSconn), 1);
+	conn->pskID = "p9secret";
+	conn->psk = ai->secret;
+	conn->psklen = ai->nsecret;
+
+	fd = tlsServer(fd, conn);
+	if(fd < 0){
+        fprint(dbg, "tlsServer: %r\n");
+		return -1;
+    }
+    
+	auth_freeAI(ai);
+	free(conn->sessionID);
+	free(conn);
+
+    return fd;
+}
--- /dev/null
+++ b/cpu.c.bak
@@ -1,0 +1,937 @@
+/*
+ * cpu.c - Make a connection to a cpu server
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <authsrv.h>
+#include <libsec.h>
+#include "args.h"
+#include "drawcpu.h"
+
+#define MaxStr 128
+
+static void	usage(void);
+static void	writestr(int, char*, char*, int);
+static int	readstr(int, char*, int);
+static char 	*keyspec = "";
+static AuthInfo *p9any(int);
+static int	getkey(Authkey*, char*, char*, char*, char*);
+static int	findkey(Authkey*, char*, char*, char*);
+
+static char	*host;
+static int	aanfilter;
+static int	aanto = 3600 * 24;
+static int	norcpu;
+static int	nokbd;
+static int	nogfx;
+static int	nineflag;
+
+static char	*ealgs = "rc4_256 sha1";
+
+/* authentication mechanisms */
+static int	p9authssl(int);
+static int	p9authtls(int);
+
+char *authserver;
+char *secstore;
+char *user, *pass;
+char secstorebuf[65536];
+char *geometry;
+
+extern void	guimain(void);
+
+char*
+estrdup(char *s)
+{
+	s = strdup(s);
+	if(s == nil)
+		sysfatal("out of memory");
+	return s;
+}
+
+static void
+ending(void)
+{
+	panic("ending");
+}
+
+int
+mountfactotum(void)
+{
+	int fd;
+	
+	if((fd = dialfactotum()) < 0)
+		return -1;
+	if(sysmount(fd, -1, "/mnt/factotum", MREPL, "") < 0){
+		fprint(2, "mount factotum: %r\n");
+		return -1;
+	}
+	if((fd = open("/mnt/factotum/ctl", OREAD)) < 0){
+		fprint(2, "open /mnt/factotum/ctl: %r\n");
+		return -1;
+	}
+	close(fd);
+	return 0;
+}
+
+static int
+startaan(char *host, int fd)
+{
+	static char script[] =
+"~ $#netdir 1 || netdir=/net/tcp/clone\n"
+"netdir=`{basename -d $netdir} || exit\n"
+"<>$netdir/clone {\n"
+"	netdir=$netdir/`{read} || exit\n"
+"	>[3] $netdir/ctl {\n"
+"		echo -n 'announce *!0' >[1=3]\n"
+"		echo `{cat $netdir/local} || exit\n"
+"		bind '#|' /mnt/aan || exit\n"
+"		exec aan -m $aanto $netdir <>/mnt/aan/data1 >[1=0] >[2]/dev/null &\n"
+"	}\n"
+"}\n"
+"<>/mnt/aan/data >[1=0] >[2]/dev/null {\n"
+"	rfork n\n"
+"	fn server {\n"
+"		echo -n aanserver $netdir >/proc/$pid/args\n"
+"		rm -f /env/^('fn#server' aanto)\n"
+"		. <{n=`{read} && ! ~ $#n 0 && read -c $n} >[2=1]\n"
+"	}\n"
+"	exec tlssrv -A /bin/rc -c server\n"
+"	exit\n"
+"}\n";
+	char buf[128], *p, *na;
+	int n;
+
+	snprint(buf, sizeof buf, "aanto=%d\n", aanto);
+	if(fprint(fd, "%7ld\n%s%s", strlen(buf)+strlen(script), buf, script) < 0)
+		sysfatal("sending aan script: %r");
+	n = read(fd, buf, sizeof(buf)-1);
+	close(fd);
+
+	while(n > 0 && buf[n-1] == '\n') n--;
+	if(n <= 0) return -1;
+	buf[n] = 0;
+	if((p = strrchr(buf, '!')) != nil)
+		na = strdup(netmkaddr(host, "tcp", p+1));
+	else
+		na = strdup(buf);
+
+	return aanclient(na, aanto);
+}
+
+static void
+rcpuexit(void)
+{
+	char *s = getenv("rstatus");
+	if(s != nil)
+		exit(*s);
+}
+
+void
+rcpu(char *host, char *cmd)
+{
+	static char script[] = 
+"mount -nc /fd/0 /mnt/term || exit\n"
+"bind -q /mnt/term/dev/cons /dev/cons\n"
+"if(test -r /mnt/term/dev/kbd){\n"
+"	</dev/cons >/dev/cons >[2=1] aux/kbdfs -dq -m /mnt/term/dev\n"
+"	bind -q /mnt/term/dev/cons /dev/cons\n"
+"}\n"
+"</dev/cons >/dev/cons >[2=1] service=cpu %s\n"
+"echo -n $status >/mnt/term/env/rstatus >[2]/dev/null\n"
+"echo -n hangup >/proc/$pid/notepg\n";
+	int fd;
+
+	if((fd = dial(netmkaddr(host, "tcp", "rcpu"), nil, nil, nil)) < 0)
+		return;
+
+	/* provide /dev/kbd for kbdfs */
+	if(!nokbd)
+		bind("#b", "/dev", MAFTER);
+
+	fd = p9authtls(fd);
+	if(aanfilter){
+		fd = startaan(host, fd);
+		if(fd < 0)
+			sysfatal("startaan: %r");
+		fd = p9authtls(fd);
+	}
+	memset(secstorebuf, 0, sizeof(secstorebuf));	/* forget secstore secrets */
+
+	if(cmd == nil)
+		cmd = smprint(script, "rc -li");
+	else {
+		char *run = smprint("rc -lc %q", cmd);
+		cmd = smprint(script, run);
+		free(run);
+	}
+	if(fprint(fd, "%7ld\n%s", strlen(cmd), cmd) < 0)
+		sysfatal("sending script: %r");
+	free(cmd);
+
+	/* /env/rstatus is written by the remote script to communicate exit status */
+	remove("/env/rstatus");
+	atexit(rcpuexit);
+
+	/* Begin serving the namespace */
+	exportfs(fd, fd);
+}
+
+void
+ncpu(char *host, char *cmd)
+{
+	char buf[MaxStr];
+	int fd;
+
+	if((fd = dial(netmkaddr(host, "tcp", "ncpu"), nil, nil, nil)) < 0)
+		return;
+
+	/* negotiate authentication mechanism */
+	strcpy(buf, "p9");
+	if(ealgs != nil){
+		strcat(buf, " ");
+		strcat(buf, ealgs);
+	}
+	writestr(fd, buf, "negotiating authentication method", 0);
+	if(readstr(fd, buf, sizeof buf) < 0)
+		sysfatal("can't negotiate authentication method: %r");
+	if(*buf)
+		sysfatal("%s", buf);
+
+	/* authenticate and encrypt the channel */
+	fd = p9authssl(fd);
+
+	/* tell remote side the command */
+	if(cmd != nil){
+		cmd = smprint("!%s", cmd);
+		writestr(fd, cmd, "cmd", 0);
+		free(cmd);
+	}
+
+	/* Tell the remote side where our working directory is */
+	if(getcwd(buf, sizeof(buf)) == 0)
+		writestr(fd, "NO", "dir", 0);
+	else
+		writestr(fd, buf, "dir", 0);
+
+	/* 
+	 *  Wait for the other end to execute and start our file service
+	 *  of /mnt/term
+	 */
+	if(readstr(fd, buf, sizeof(buf)) < 0)
+		sysfatal("waiting for FS: %r");
+	if(strncmp("FS", buf, 2) != 0) {
+		print("remote cpu: %s", buf);
+		exits(buf);
+	}
+
+	if(readstr(fd, buf, sizeof(buf)) < 0)
+		sysfatal("waiting for remote export: %r");
+	if(strcmp(buf, "/") != 0){
+		print("remote cpu: %s", buf);
+		exits(buf);
+	}
+	write(fd, "OK", 2);
+
+	/* Begin serving the gnot namespace */
+	exportfs(fd, fd);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-9GBO] "
+		"[-h host] [-u user] [-a authserver] [-s secstore] "
+		"[-e 'crypt hash'] [-k keypattern] "
+		"[-p] [-t timeout] "
+		"[-r root] "
+		"[-g geometry] "
+		"[-c cmd ...]\n", argv0);
+	exits("usage");
+}
+
+static char *cmd;
+
+extern void cpubody(void);
+
+void
+cpumain(int argc, char **argv)
+{
+	char *s;
+
+	user = getenv("USER");
+	host = getenv("cpu");
+	authserver = getenv("auth");
+
+	ARGBEGIN{
+	case '9':
+		nineflag = 1;
+		/* wet floor */
+	case 'G':
+		nogfx = 1;
+		/* wet floor */
+	case 'B':
+		nokbd = 1;
+		break;
+	case 'O':
+		norcpu = 1;
+		break;
+	case 'p':
+		aanfilter = 1;
+		break;
+	case 't':
+		aanto = (int)strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'h':
+		host = EARGF(usage());
+		break;
+	case 'a':
+		authserver = EARGF(usage());
+		break;
+	case 's':
+		secstore = EARGF(usage());
+		break;
+	case 'e':
+		ealgs = EARGF(usage());
+		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
+			ealgs = nil;
+		break;
+	case 'k':
+		keyspec = EARGF(usage());
+		break;
+	case 'u':
+		user = EARGF(usage());
+		break;
+	case 'r':
+		s = smprint("/root/%s", EARGF(usage()));
+		cleanname(s);
+		if(bind(s, "/root", MREPL) < 0)
+			panic("bind /root: %r");
+		free(s);
+		break;
+	case 'c':
+		cmd = estrdup(EARGF(usage()));
+		while((s = ARGF()) != nil){
+			char *old = cmd;
+			cmd = smprint("%s %q", cmd, s);
+			free(old);
+		}
+		break;
+	case 'g':
+		/* X11 geometry string
+		 *	[=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]
+		 */
+		geometry = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc != 0)
+		usage();
+
+	if(nineflag){
+		exportfs(lfdfd(0), lfdfd(1));
+		return;
+	}
+
+	if((pass = getenv("PASS")) != nil)
+		remove("/env/PASS");
+
+	if(!nogfx)
+		guimain();
+	else
+		cpubody();
+}
+
+void
+cpubody(void)
+{
+	char *s;
+
+	if(!nogfx){
+		if(bind("#i", "/dev", MBEFORE) < 0)
+			panic("bind #i: %r");
+		if(bind("#m", "/dev", MBEFORE) < 0)
+			panic("bind #m: %r");
+		if(cmd == nil)
+			atexit(ending);
+	}
+	if(bind("/root", "/", MAFTER) < 0)
+		panic("bind /root: %r");
+
+	if(host == nil)
+		if((host = readcons("cpu", "cpu", 0)) == nil)
+			sysfatal("user terminated input");
+
+	if(authserver == nil)
+		if((authserver = readcons("auth", host, 0)) == nil)
+			sysfatal("user terminated input");
+
+	if(user == nil)
+		if((user = readcons("user", "glenda", 0)) == nil)
+			sysfatal("user terminated input");
+
+	if(mountfactotum() < 0){
+		if(secstore == nil)
+			secstore = authserver;
+	 	if(havesecstore(secstore, user)){
+			s = secstorefetch(secstore, user, pass);
+			if(s){
+				if(strlen(s) >= sizeof secstorebuf)
+					sysfatal("secstore data too big");
+				strcpy(secstorebuf, s);
+			}
+		}
+	}
+
+	if(!norcpu)
+		rcpu(host, cmd);
+
+	ncpu(host, cmd);
+
+	sysfatal("can't dial %s: %r", host);
+}
+
+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)
+		sysfatal("writing network: %s: %r", thing);
+}
+
+int
+readstr(int fd, char *str, int len)
+{
+	int n;
+
+	while(len) {
+		n = read(fd, str, 1);
+		if(n < 0) 
+			return -1;
+		if(n == 0){
+			werrstr("hung up");
+			return -1;
+		}
+		if(*str == '\0')
+			return 0;
+		str++;
+		len--;
+	}
+	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
+p9authssl(int fd)
+{
+	uchar key[16];
+	uchar digest[SHA1dlen];
+	char fromclientsecret[21];
+	char fromserversecret[21];
+	AuthInfo *ai;
+
+	ai = p9any(fd);
+	memset(secstorebuf, 0, sizeof(secstorebuf));	/* forget secstore secrets */
+	if(ai == nil)
+		sysfatal("can't authenticate: %r");
+
+	if(ealgs == nil)
+		return fd;
+
+	if(ai->nsecret < 8){
+		sysfatal("p9authssl: secret too small");
+		return -1;
+	}
+	memmove(key+4, ai->secret, 8);
+
+	/* exchange random numbers */
+	genrandom(key, 4);
+	if(write(fd, key, 4) != 4){
+		sysfatal("p9authssl: write random: %r");
+		return -1;
+	}
+	if(readn(fd, key+12, 4) != 4){
+		sysfatal("p9authssl: read random: %r");
+		return -1;
+	}
+
+	/* scramble into two secrets */
+	sha1(key, sizeof(key), digest, nil);
+	mksecret(fromclientsecret, digest);
+	mksecret(fromserversecret, digest+10);
+
+	/* set up encryption */
+	fd = pushssl(fd, ealgs, fromclientsecret, fromserversecret, nil);
+	if(fd < 0)
+		sysfatal("p9authssl: pushssl: %r");
+
+	return fd;
+}
+
+/*
+ * p9any authentication followed by tls-psk encryption
+ */
+static int
+p9authtls(int fd)
+{
+	AuthInfo *ai;
+	TLSconn *conn;
+
+	ai = p9any(fd);
+	if(ai == nil)
+		sysfatal("can't authenticate: %r");
+
+	conn = mallocz(sizeof(TLSconn), 1);
+	conn->pskID = "p9secret";
+	conn->psk = ai->secret;
+	conn->psklen = ai->nsecret;
+
+	fd = tlsClient(fd, conn);
+	if(fd < 0)
+		sysfatal("tlsClient: %r");
+
+	auth_freeAI(ai);
+	free(conn->sessionID);
+	free(conn);
+
+	return fd;
+}
+
+int
+authdial(char *net, char *dom)
+{
+	return dial(netmkaddr(authserver, "tcp", "ticket"), 0, 0, 0);
+}
+
+static int
+getastickets(Authkey *key, Ticketreq *tr, uchar *y, char *tbuf, int tbuflen)
+{
+	int asfd, rv;
+	char *dom;
+
+	dom = tr->authdom;
+	asfd = authdial(nil, dom);
+	if(asfd < 0)
+		return -1;
+	if(y != nil){
+		PAKpriv p;
+
+		rv = -1;
+		tr->type = AuthPAK;
+		if(_asrequest(asfd, tr) != 0 || write(asfd, y, PAKYLEN) != PAKYLEN)
+			goto Out;
+
+		authpak_new(&p, key, (uchar*)tbuf, 1);
+		if(write(asfd, tbuf, PAKYLEN) != PAKYLEN)
+			goto Out;
+
+		if(_asrdresp(asfd, tbuf, 2*PAKYLEN) != 2*PAKYLEN)
+			goto Out;
+	
+		memmove(y, tbuf, PAKYLEN);
+		if(authpak_finish(&p, key, (uchar*)tbuf+PAKYLEN))
+			goto Out;
+	}
+	tr->type = AuthTreq;
+	rv = _asgetticket(asfd, tr, tbuf, tbuflen);
+Out:
+	close(asfd);
+	return rv;
+}
+
+static int
+mkservertickets(Authkey *key, Ticketreq *tr, uchar *y, char *tbuf, int tbuflen)
+{
+	Ticket t;
+	int ret;
+
+	if(strcmp(tr->authid, tr->hostid) != 0)
+		return -1;
+	memset(&t, 0, sizeof(t));
+	ret = 0;
+	if(y != nil){
+		PAKpriv p;
+
+		t.form = 1;
+		memmove(tbuf, y, PAKYLEN);
+		authpak_new(&p, key, y, 0);
+		authpak_finish(&p, key, (uchar*)tbuf);
+	}
+	memmove(t.chal, tr->chal, CHALLEN);
+	strcpy(t.cuid, tr->uid);
+	strcpy(t.suid, tr->uid);
+	genrandom((uchar*)t.key, sizeof(t.key));
+	t.num = AuthTc;
+	ret += convT2M(&t, tbuf+ret, tbuflen-ret, key);
+	t.num = AuthTs;
+	ret += convT2M(&t, tbuf+ret, tbuflen-ret, key);
+	memset(&t, 0, sizeof(t));
+
+	return ret;
+}
+
+static int
+gettickets(Authkey *key, Ticketreq *tr, uchar *y, char *tbuf, int tbuflen)
+{
+	int ret;
+	ret = getastickets(key, tr, y, tbuf, tbuflen);
+	if(ret > 0)
+		return ret;
+	return mkservertickets(key, tr, y, tbuf, tbuflen);
+}
+
+/*
+ *  prompt user for a key.  don't care about memory leaks, runs standalone
+ */
+static Attr*
+promptforkey(char *params)
+{
+	char *v;
+	int fd;
+	Attr *a, *attr;
+	char *def;
+
+	fd = open("/dev/cons", ORDWR);
+	if(fd < 0)
+		sysfatal("opening /dev/cons: %r");
+
+	attr = _parseattr(params);
+	fprint(fd, "\n!Adding key:");
+	for(a=attr; a; a=a->next)
+		if(a->type != AttrQuery && a->name[0] != '!')
+			fprint(fd, " %q=%q", a->name, a->val);
+	fprint(fd, "\n");
+
+	for(a=attr; a; a=a->next){
+		v = a->name;
+		if(a->type != AttrQuery || v[0]=='!')
+			continue;
+		def = nil;
+		if(strcmp(v, "user") == 0)
+			def = getuser();
+		a->val = readcons(v, def, 0);
+		if(a->val == nil)
+			sysfatal("user terminated key input");
+		a->type = AttrNameval;
+	}
+	for(a=attr; a; a=a->next){
+		v = a->name;
+		if(a->type != AttrQuery || v[0]!='!')
+			continue;
+		def = nil;
+		if(strcmp(v+1, "user") == 0)
+			def = getuser();
+		a->val = readcons(v+1, def, 1);
+		if(a->val == nil)
+			sysfatal("user terminated key input");
+		a->type = AttrNameval;
+	}
+	fprint(fd, "!\n");
+	close(fd);
+	return attr;
+}
+
+/*
+ *  send a key to the mounted factotum
+ */
+static int
+sendkey(Attr *attr)
+{
+	int fd, rv;
+	char buf[1024];
+
+	fd = open("/mnt/factotum/ctl", ORDWR);
+	if(fd < 0)
+		sysfatal("opening /mnt/factotum/ctl: %r");
+	rv = fprint(fd, "key %A\n", attr);
+	read(fd, buf, sizeof buf);
+	close(fd);
+	return rv;
+}
+
+int
+askuser(char *params)
+{
+	Attr *attr;
+	
+	fmtinstall('A', _attrfmt);
+	
+	attr = promptforkey(params);
+	if(attr == nil)
+		sysfatal("no key supplied");
+	if(sendkey(attr) < 0)
+		sysfatal("sending key to factotum: %r");
+	return 0;
+}
+
+AuthInfo*
+p9anyfactotum(int fd, int afd)
+{
+	return auth_proxy(fd, askuser, "proto=p9any role=client %s", keyspec);
+}
+
+AuthInfo*
+p9any(int fd)
+{
+	char buf[1024], buf2[1024], *bbuf, *p, *proto, *dom;
+	uchar crand[2*NONCELEN], cchal[CHALLEN], y[PAKYLEN];
+	char tbuf[2*MAXTICKETLEN+MAXAUTHENTLEN+PAKYLEN], trbuf[TICKREQLEN+PAKYLEN];
+	Authkey authkey;
+	Authenticator auth;
+	int afd, i, n, m, v2, dp9ik;
+	Ticketreq tr;
+	Ticket t;
+	AuthInfo *ai;
+
+	if((afd = open("/mnt/factotum/ctl", ORDWR)) >= 0)
+		return p9anyfactotum(fd, afd);
+	werrstr("");
+
+	if(readstr(fd, buf, sizeof buf) < 0)
+		sysfatal("cannot read p9any negotiation: %r");
+	bbuf = buf;
+	v2 = 0;
+	if(strncmp(buf, "v.2 ", 4) == 0){
+		v2 = 1;
+		bbuf += 4;
+	}
+	dp9ik = 0;
+	proto = nil;
+	while(bbuf != nil){
+		if((p = strchr(bbuf, ' ')))
+			*p++ = 0;
+		if((dom = strchr(bbuf, '@')) == nil)
+			sysfatal("bad p9any domain");
+		*dom++ = 0;
+		if(strcmp(bbuf, "p9sk1") == 0 || strcmp(bbuf, "dp9ik") == 0){
+			proto = bbuf;
+			if(strcmp(proto, "dp9ik") == 0){
+				dp9ik = 1;
+				break;
+			}
+		}
+		bbuf = p;
+	}
+	if(proto == nil)
+		sysfatal("server did not offer p9sk1 or dp9ik");
+	proto = estrdup(proto);
+	sprint(buf2, "%s %s", proto, dom);
+	if(write(fd, buf2, strlen(buf2)+1) != strlen(buf2)+1)
+		sysfatal("cannot write user/domain choice in p9any");
+	if(v2){
+		if(readstr(fd, buf, sizeof buf) < 0)
+			sysfatal("cannot read OK in p9any: %r");
+		if(memcmp(buf, "OK\0", 3) != 0)
+			sysfatal("did not get OK in p9any: got %s", buf);
+	}
+	genrandom(crand, 2*NONCELEN);
+	genrandom(cchal, CHALLEN);
+	if(write(fd, cchal, CHALLEN) != CHALLEN)
+		sysfatal("cannot write p9sk1 challenge: %r");
+
+	n = TICKREQLEN;
+	if(dp9ik)
+		n += PAKYLEN;
+
+	if(readn(fd, trbuf, n) != n || convM2TR(trbuf, TICKREQLEN, &tr) <= 0)
+		sysfatal("cannot read ticket request in p9sk1: %r");
+
+	if(!findkey(&authkey, user, tr.authdom, proto)){
+again:		if(!getkey(&authkey, user, tr.authdom, proto, pass))
+			sysfatal("no password");
+	}
+
+	strecpy(tr.hostid, tr.hostid+sizeof tr.hostid, user);
+	strecpy(tr.uid, tr.uid+sizeof tr.uid, user);
+
+	if(dp9ik){
+		memmove(y, trbuf+TICKREQLEN, PAKYLEN);
+		n = gettickets(&authkey, &tr, y, tbuf, sizeof(tbuf));
+	} else {
+		n = gettickets(&authkey, &tr, nil, tbuf, sizeof(tbuf));
+	}
+	if(n <= 0)
+		sysfatal("cannot get auth tickets in p9sk1: %r");
+
+	m = convM2T(tbuf, n, &t, &authkey);
+	if(m <= 0 || t.num != AuthTc){
+		print("?password mismatch with auth server\n");
+		if(pass != nil && *pass)
+			sysfatal("wrong password");
+		goto again;
+	}
+	n -= m;
+	memmove(tbuf, tbuf+m, n);
+
+	if(dp9ik && write(fd, y, PAKYLEN) != PAKYLEN)
+		sysfatal("cannot send authpak public key back: %r");
+
+	auth.num = AuthAc;
+	memmove(auth.rand, crand, NONCELEN);
+	memmove(auth.chal, tr.chal, CHALLEN);
+	m = convA2M(&auth, tbuf+n, sizeof(tbuf)-n, &t);
+	n += m;
+
+	if(write(fd, tbuf, n) != n)
+		sysfatal("cannot send ticket and authenticator back: %r");
+
+	if((n=readn(fd, tbuf, m)) != m || memcmp(tbuf, "cpu:", 4) == 0){
+		if(n <= 4)
+			sysfatal("cannot read authenticator");
+
+		/*
+		 * didn't send back authenticator:
+		 * sent back fatal error message.
+		 */
+		memmove(buf, tbuf, n);
+		i = readn(fd, buf+n, sizeof buf-n-1);
+		if(i > 0)
+			n += i;
+		buf[n] = 0;
+		sysfatal("server says: %s", buf);
+	}
+	
+	if(convM2A(tbuf, n, &auth, &t) <= 0
+	|| auth.num != AuthAs || tsmemcmp(auth.chal, cchal, CHALLEN) != 0){
+		print("?you and auth server agree about password.\n");
+		print("?server is confused.\n");
+		sysfatal("server lies");
+	}
+	memmove(crand+NONCELEN, auth.rand, NONCELEN);
+
+	// print("i am %s there.\n", t.suid);
+
+	ai = mallocz(sizeof(AuthInfo), 1);
+	ai->suid = estrdup(t.suid);
+	ai->cuid = estrdup(t.cuid);
+	if(dp9ik){
+		static char info[] = "Plan 9 session secret";
+		ai->nsecret = 256;
+		ai->secret = mallocz(ai->nsecret, 1);
+		hkdf_x(	crand, 2*NONCELEN,
+			(uchar*)info, sizeof(info)-1,
+			(uchar*)t.key, NONCELEN,
+			ai->secret, ai->nsecret,
+			hmac_sha2_256, SHA2_256dlen);
+	} else {
+		ai->nsecret = 8;
+		ai->secret = mallocz(ai->nsecret, 1);
+		des56to64((uchar*)t.key, ai->secret);
+	}
+
+	memset(&t, 0, sizeof(t));
+	memset(&auth, 0, sizeof(auth));
+	memset(&authkey, 0, sizeof(authkey));
+	memset(cchal, 0, sizeof(cchal));
+	memset(crand, 0, sizeof(crand));
+	free(proto);
+
+	return ai;
+}
+
+static int
+findkey(Authkey *key, char *user, char *dom, char *proto)
+{
+	char buf[8192], *f[50], *p, *ep, *nextp, *hex, *pass, *id, *role;
+	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;
+		id = pass = hex = role = nil;
+		havedom = haveproto = 0;
+		for(i=1; i<nf; i++){
+			if(strncmp(f[i], "user=", 5) == 0)
+				id = f[i]+5;
+			if(strncmp(f[i], "!password=", 10) == 0)
+				pass = f[i]+10;
+			if(strncmp(f[i], "!hex=", 5) == 0)
+				hex = f[i]+5;
+			if(strncmp(f[i], "role=", 5) == 0)
+				role = f[i]+5;
+			if(strncmp(f[i], "dom=", 4) == 0 && strcmp(f[i]+4, dom) == 0)
+				havedom = 1;
+			if(strncmp(f[i], "proto=", 6) == 0 && strcmp(f[i]+6, proto) == 0)
+				haveproto = 1;
+		}
+		if(!haveproto || !havedom)
+			continue;
+		if(role != nil && strcmp(role, "client") != 0)
+			continue;
+		if(id == nil || strcmp(user, id) != 0)
+			continue;
+		if(pass == nil && hex == nil)
+			continue;
+		if(hex != nil){
+			memset(key, 0, sizeof(*key));
+			if(strcmp(proto, "dp9ik") == 0) {
+				if(dec16(key->aes, AESKEYLEN, hex, strlen(hex)) != AESKEYLEN)
+					continue;
+			} else {
+				if(dec16((uchar*)key->des, DESKEYLEN, hex, strlen(hex)) != DESKEYLEN)
+					continue;
+			}
+		} else {
+			passtokey(key, pass);
+		}
+		if(strcmp(proto, "dp9ik") == 0)
+			authpak_hash(key, user);
+		memset(buf, 0, sizeof buf);
+		return 1;
+	}
+	memset(buf, 0, sizeof buf);
+	return 0;
+}
+
+static int
+getkey(Authkey *key, char *user, char *dom, char *proto, char *pass)
+{
+	char buf[1024];
+
+	if(pass != nil && *pass)
+		pass = estrdup(pass);
+	else {
+		snprint(buf, sizeof buf, "%s@%s %s password", user, dom, proto);
+		pass = readcons(buf, nil, 1);
+		memset(buf, 0, sizeof buf);
+	}
+	if(pass != nil){
+		snprint(secstorebuf, sizeof(secstorebuf), "key proto=%q dom=%q user=%q !password=%q\n",
+			proto, dom, user, pass);
+		memset(pass, 0, strlen(pass));
+		free(pass);
+		return findkey(key, user, dom, proto);
+	}
+	return 0;
+}
binary files /dev/null b/drawcpu differ
--- /dev/null
+++ b/drawcpu.1
@@ -1,0 +1,239 @@
+.TH DRAWCPU 1
+
+.SH NAME
+drawcpu  \- connection to cpu, fs, and auth servers
+
+.SH SYNOPSIS
+.B drawcpu
+[
+.B -9GBO
+] [
+.B -h
+.I host
+] [
+.B -u
+.I user
+] [
+.B -a
+.I authserver
+] [
+.B -s
+.I secstore
+] [
+.B -e
+\fR'\fIcrypt hash\fR'
+] [
+.B -k
+.I keypattern
+] [
+.B -p
+] [
+.B -t
+.I timeout
+] [
+.B -r
+.I root
+] [
+.B -g
+.I geometry
+] [
+.B -c
+.I cmd \fR...]
+
+.SH DESCRIPTION
+.I Drawterm
+is a client for connecting venerable systems to Plan 9 systems. The standard behavior with no options provided is to begin a graphical session which will prompt for a cpu server, auth server, and password. If $USER is not set, drawcpu will prompt for a username as well. 
+
+The goal of drawcpu is to provide an abstraction layer from the client operating system to the desired Plan 9 system. Client-side devices that can be represented as filesystems will be and are served from the namespace drawcpu operates in. Generally, this means that drawcpu behaves like a Plan 9 kernel and will attempt to reconstruct a Plan 9 terminal-like experience from a non-Plan 9 system.
+
+.PP
+The options are:
+.PD
+
+.TP
+.B -9
+Start drawcpu in 9P mode;
+exporting the namespace over 9P on standard input and output.
+Graphics and keyboard input are disabled.
+
+.TP
+.B -G
+Start drawcpu in text-only mode (no graphics).
+
+.TP
+.B -B
+Disable kbdfs.
+
+.TP
+.B -O
+Use the old
+.IR cpu (1)
+to connect to the cpu server rather than 
+.IR rcpu (1)\fR.
+
+.TP
+.B -h \fIhost
+Connect to \fIhost\fR for cpu.
+
+.TP
+.B -u \fIuser
+Remote user id.
+
+.TP
+.B -a \fIauthserver
+Connect to \fIauthserver\fR for auth.
+
+.TP
+.B -s \fIsecstore
+Sets the address of the
+.IR secstore (8)
+server. If the -s option is absent,
+.IR secstore (1)
+will attempt to dial tcp!$auth!secstore (or the otherwise specified auth server).
+
+.TP
+.B -e \fR'\fIcrypt hash\fR'
+Specifies the \fR'\fIcrypt hash\fR'
+for the connection. The default is 'rc4_256 sha1'. Usage of no encryption can be specified by 'clear' as per
+.IR ssl (3)\fR. Note that this option is deprecated and only relevant to the outdated
+.IR cpu (1)
+protocol.
+
+.TP
+.B -k \fIkeypattern
+Use keypattern to select a key to authenticate to the remote side (see 
+.IR auth (2)\fR).
+
+.TP
+.B -p
+Protect the connection against connection resets by establishing
+.IR aan (8)
+tunnel.
+
+.TP
+.B -t \fItimeout
+Set the timeout for
+.IR aan (8)
+to a value in 
+.I seconds\fR (default is one day).
+
+.TP
+.B -r \fIroot
+Specifies the root directory on the client. The default is
+.I /root
+and all further paths are relative thereto.
+
+.TP
+.B -c \fIcmd \fR...
+The command to run can be passed with -c cmd ..., otherwise an interactive shell is started. The user's profile is run before the command with $service set to cpu to allow further customization of the environment (see 
+.IR rc (1)
+for more information).
+
+.PP
+.SH ENVIRONMENT VARIABLES
+.IP USER
+Unless otherwise specified, the user stored in $USER is used for authentication.
+
+.IP PASS
+If $PASS is set, no password will be prompted for in authentication and the contents of $PASS will be used instead.
+
+.IP cpu
+Unless otherwise specified, the address stored in $cpu is used for the
+.IR rcpu (1)
+connection (if set).
+
+.IP auth
+Unless otherwise specified, the address stored in $auth is used for the 
+.IR rcpu (1)
+connection (if set).
+
+.PP
+.SH SERVICES
+A number of services are provided in drawcpu. The exact functionality and availability of certain features may be dependent on your platform or architecture: 
+
+.TP
+.B /mnt/term
+A mount of the client machine's root filesystem as well as certain virtual filesystems to present Plan 9 devices and interfaces that are not available on non-Plan 9 systems. On Windows this is a directory containing the available lettered disks (C:, A:, etc.). Additionally, there is always a /mnt/term/root folder which is a copy of the client machine's namespace with no virtual filesystems present as to avoid conflicting names (such as with Linux's /root).
+
+.TP
+.B #b
+Assuming the -B flag is not set, /dev/kbd will be provided for kbdfs (see
+.IR kbd (3)\fR).
+
+.TP
+.B #i
+Assuming the -G flag is not set, various drawing device files will be provided in /dev (see
+.IR draw (3)\fR).
+
+.TP
+.B #m
+Assuming the -G flag is not set, files for controlling the mouse will provided in /dev (see
+.IR mouse (3)\fR).
+
+.TP
+.B #c
+A number of console device files giving access to the console screen and miscellaneous information are provided and mounted in /dev (see
+.IR cons (3)\fR).
+
+.TP
+.B #I
+The network filesystem is served and bound over /net, providing the interface to Internet Protocol stacks (see
+.IR ip (3)\fR).
+
+.TP
+.B #A
+An audio device filesystem is served, if possible, as a one-level directory in /dev (see
+.IR audio (3)\fR).
+Note that this device, if unable to be served, will not cause a panic in drawcpu.
+
+.PP
+.SH EXAMPLES
+Make a headless rcpu session connecting to 10.30.167.25 using plan9.postnix.us as the auth server with
+.IR aan (8)
+enabled:
+.IP
+.EX
+drawcpu -G -h 10.30.167.25 -a plan9.postnix.us -p
+.EE
+.PP
+
+Make a session using cpu rather than rcpu to tenshi.postnix.us; this command is the same for connecting to a Plan 9 4th edition system:
+.IP
+.EX
+drawcpu -O -h tenshi.postnix.us
+.EE
+.PP
+
+.PP
+.SH SOURCE
+.B https://git.9front.org/plan9front/drawcpu/HEAD/info.html
+
+.PP
+.SH "SEE ALSO"
+.IR rc (1),
+.IR cpu (1),
+.IR rcpu(1),
+.IR con (1),
+.IR import (4),
+.IR exportfs (4),
+.IR tlssrv (8),
+.IR aan (8)
+
+.PP
+.SH BUGS
+Drawterm is 
+.I not
+a Plan 9 program.
+
+.PP
+.SH HISTORY
+Drawterm was originally developed by Russ Cox (rsc) for Plan 9 4th edition. This original version is still usable on Plan 9 and its forks which use the p9sk1 and older
+.IR authsrv (6)
+protocols.
+.B https://swtch.com/drawcpu/
+
+The 9front project has forked drawcpu to incorporate features from 9front, most importantly dp9ik authentication support (see 
+.IR authsrv (6)\fR)
+and the TLS-based
+.IR rcpu (1)
+protocol.
--- /dev/null
+++ b/drawcpu.h
@@ -1,0 +1,12 @@
+extern int havesecstore(char *addr, char *owner);
+extern char *secstore;
+extern char *secstorefetch(char *addr, char *owner, char *passwd);
+extern char *authserver;
+extern int exportfs(int, int);
+extern int dialfactotum(void);
+extern char *getuser(void);
+extern int session(int);
+extern char *estrdup(char*);
+extern int aanclient(char*, int);
+extern int p9authsrv(char*, char*);
+extern int dbg;
\ No newline at end of file
--- /dev/null
+++ b/drawcpu.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     "glenda-t.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/drawcpu.res differ
--- /dev/null
+++ b/exportfs/Makefile
@@ -1,0 +1,16 @@
+ROOT=..
+include ../Make.config
+LIB=libexportfs.a
+
+OFILES=\
+	exportfs.$O\
+	exportsrv.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/exportfs/exportfs.c
@@ -1,0 +1,504 @@
+/*
+ * exportfs - Export a plan 9 name space across a network
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <libsec.h>
+#include "drawcpu.h"
+#define Extern
+#include "exportfs.h"
+
+#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[2];
+
+int
+exportfs(int rfd, int wfd)
+{
+	char buf[ERRMAX], ebuf[ERRMAX];
+	Fsrpc *r;
+	int i, n;
+
+
+	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[0] = rfd;
+	netfd[1] = wfd;
+
+	strcpy(buf, "this is buf");
+	strcpy(ebuf, "this is ebuf");
+	DEBUG(DFD, "exportfs: started\n");
+
+	messagesize = iounit(rfd);
+	if(messagesize == 0)
+		messagesize = IOUNIT+IOHDRSZ;
+
+	Workq = emallocz(sizeof(Fsrpc)*Nr_workbufs);
+	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[0], r->buf, messagesize);
+		if(n <= 0)
+			fatal(nil);
+
+		if(convM2S(r->buf, n, &r->work) == 0){
+			iprint("convM2S %d byte message\n", n);
+			for(i=0; i<n; i++){
+				iprint(" %.2ux", r->buf[i]);
+				if(i%16 == 15)
+					iprint("\n");
+			}
+			if(i%16)
+				iprint("\n");
+			fatal("convM2S format error");
+		}
+
+if(0) iprint("<- %F\n", &r->work);
+		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;
+
+if(0) iprint("-> %F\n", t);
+	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[1], data, n))!=n){
+		iprint("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;
+
+	if (s != nil) {
+		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");
+
+	if (s != nil) { 
+		DEBUG(DFD, "%s\n", buf);
+		sysfatal("%s", 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(x)
+#define noted(x)
+
--- /dev/null
+++ b/exportfs/exportsrv.c
@@ -1,0 +1,671 @@
+#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";
+
+void*
+emallocz(uint n)
+{
+	void *v;
+
+	v = mallocz(n, 1);
+	if(v == nil)
+		panic("out of memory");
+	return v;
+}
+
+
+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)
+{
+	Fcall rhdr;
+	Fid *f;
+
+	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) <= BIT16SZ){
+		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 = (uintptr)rendezvous((void*)(uintptr)p->pid, 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((void*)(uintptr)pid, 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((void*)(uintptr)pid, 0);
+DEBUG(DFD, "blockingslave %d rendez got %p\n", pid, m);
+	
+	for(;;) {
+		p = rendezvous((void*)(uintptr)pid, (void*)(uintptr)pid);
+		if((uintptr)p == ~(uintptr)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);
+}
binary files /dev/null b/glenda-t.ico differ
--- /dev/null
+++ b/glenda-t.rc
@@ -1,0 +1,1 @@
+101 ICON DISCARDABLE "glenda-t.ico"
--- /dev/null
+++ b/glenda-t.xbm
@@ -1,0 +1,46 @@
+#define glenda_t_width 64
+#define glenda_t_height 64
+static unsigned char glenda_t_bits[] = {
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x03, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x40, 0x04, 0xc4,
+   0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x84, 0x01, 0x00, 0x00, 0x00,
+   0x00, 0x10, 0x20, 0x84, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x88,
+   0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x08, 0x27, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x0b, 0x88, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0x00,
+   0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x0f, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x38, 0x10, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xf2,
+   0x9f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x78, 0xff, 0xff, 0x01, 0x00, 0x00,
+   0x00, 0x00, 0xf0, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff,
+   0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf1, 0xff, 0xff, 0x3f, 0x00, 0x00,
+   0x00, 0x00, 0xfa, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff,
+   0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x7f, 0x70, 0xdb, 0x01, 0x00,
+   0x00, 0x00, 0xfe, 0x07, 0x80, 0x7f, 0x01, 0x00, 0x00, 0x00, 0xff, 0x01,
+   0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xb8, 0x01, 0x00,
+   0x00, 0x00, 0x7f, 0x00, 0x00, 0x70, 0x03, 0x00, 0x00, 0x00, 0x3f, 0x00,
+   0x00, 0xf0, 0x01, 0x00, 0x00, 0x80, 0xbf, 0x6a, 0xff, 0xff, 0x1f, 0x00,
+   0x00, 0xc0, 0xef, 0xdb, 0x7f, 0xeb, 0x3b, 0x00, 0x00, 0xc0, 0xff, 0xff,
+   0xf7, 0xd7, 0x3f, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xfe, 0x37, 0x00,
+   0x00, 0xe0, 0xff, 0x6f, 0xd4, 0xfd, 0x23, 0x00, 0x00, 0x00, 0xff, 0x3f,
+   0x10, 0x9f, 0x17, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x80, 0x0e, 0x00,
+   0x00, 0x80, 0x07, 0x40, 0x01, 0x81, 0x07, 0x00, 0x00, 0x80, 0x07, 0x50,
+   0x0f, 0x86, 0x03, 0x00, 0x00, 0x80, 0x0f, 0x50, 0xf4, 0x8f, 0x03, 0x00,
+   0x00, 0x80, 0x1f, 0xc0, 0xf5, 0x9a, 0x03, 0x00, 0x00, 0x80, 0x1f, 0x00,
+   0x83, 0xc2, 0x03, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x82, 0xc0, 0x03, 0x00,
+   0x00, 0xf0, 0x7f, 0x00, 0x08, 0xe0, 0x03, 0x00, 0x00, 0xf8, 0x7f, 0x00,
+   0x00, 0xf0, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x00, 0x00, 0xf8, 0xff, 0x03,
+   0x00, 0xfc, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x01,
+   0x00, 0xfc, 0xff, 0x7f, 0x00, 0xf8, 0xff, 0x03, 0x00, 0xfc, 0xff, 0xff,
+   0x00, 0xfc, 0xff, 0x07, 0x00, 0xfe, 0xff, 0xff, 0x00, 0xfe, 0xff, 0x0f,
+   0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x3f, 0xe0, 0xff, 0xbf, 0xfe,
+   0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff,
+   0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff,
+   0xf0, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff,
+   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff,
+   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff,
+   0xdf, 0xb6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7d, 0xff, 0xff,
+   0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+   0xff, 0xcf, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xeb,
+   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
--- /dev/null
+++ b/gui-cocoa/Makefile
@@ -1,0 +1,14 @@
+ROOT=..
+include ../Make.config
+LIB=libgui.a
+
+OFILES=\
+	screen.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.m
+	$(CC) $(CFLAGS) -fobjc-arc $*.m
--- /dev/null
+++ b/gui-cocoa/drawterm.app/Info.plist
@@ -1,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>drawcpu</string>
+	<key>CFBundleIconFile</key>
+	<string>glenda-t.icns</string>
+	<key>CFBundleIdentifier</key>
+	<string>drawcpu</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>drawcpu</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2018 9front project. All cats reserved.</string>
+	<key>NSMainNibFile</key>
+	<string>MainWindow</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
binary files /dev/null b/gui-cocoa/drawterm.app/glenda-t.icns differ
--- /dev/null
+++ b/gui-cocoa/screen.m
@@ -1,0 +1,959 @@
+#define Rect RectC
+#import <Cocoa/Cocoa.h>
+#import <Metal/Metal.h>
+#import <QuartzCore/CAMetalLayer.h>
+#undef Rect
+
+#undef nil
+#define Point Point9
+#include "u.h"
+#include "lib.h"
+#include "kern/dat.h"
+#include "kern/fns.h"
+#include "error.h"
+#include "user.h"
+#include <draw.h>
+#include <memdraw.h>
+#include "screen.h"
+#include "keyboard.h"
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+#define LOG(fmt, ...) if(DEBUG)NSLog((@"%s:%d %s " fmt), __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
+
+Memimage *gscreen;
+
+@interface DrawLayer : CAMetalLayer
+@property id<MTLTexture> texture;
+@end
+
+@interface DrawtermView : NSView<NSTextInputClient>
+- (void)reshape;
+- (void)clearMods;
+- (void)clearInput;
+- (void)mouseevent:(NSEvent *)e;
+- (void)resetLastInputRect;
+- (void)enlargeLastInputRect:(NSRect)r;
+@end
+
+@interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate>
+@end
+
+static AppDelegate *myApp;
+static DrawtermView *myview;
+static NSCursor *currentCursor;
+
+static ulong pal[256];
+
+static int readybit;
+static Rendez rend;
+
+static int
+isready(void*a)
+{
+	return readybit;
+}
+
+void
+guimain(void)
+{
+	LOG();
+	@autoreleasepool{
+		[NSApplication sharedApplication];
+		[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+		myApp = [AppDelegate new];
+		[NSApp setDelegate:myApp];
+		[NSApp run];
+	}
+}
+
+void
+screeninit(void)
+{
+	memimageinit();
+	NSSize s = [myview convertSizeToBacking:myview.frame.size];
+	screensize(Rect(0, 0, s.width, s.height), ARGB32);
+	gscreen->clipr = Rect(0, 0, s.width, s.height);
+	LOG(@"%g %g", s.width, s.height);
+	terminit();
+	readybit = 1;
+	wakeup(&rend);
+}
+
+void
+screensize(Rectangle r, ulong chan)
+{
+	Memimage *i;
+
+	if((i = allocmemimage(r, chan)) == nil)
+		return;
+	if(gscreen != nil)
+		freememimage(gscreen);
+@autoreleasepool{
+	DrawLayer *layer = (DrawLayer *)myview.layer;
+	MTLTextureDescriptor *textureDesc = [MTLTextureDescriptor
+		texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
+		width:Dx(r)
+		height:Dy(r)
+		mipmapped:NO];
+	textureDesc.allowGPUOptimizedContents = YES;
+	textureDesc.usage = MTLTextureUsageShaderRead;
+	textureDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
+	layer.texture = [layer.device newTextureWithDescriptor:textureDesc];
+
+	CGFloat scale = myview.window.backingScaleFactor;
+	[layer setDrawableSize:NSMakeSize(Dx(r), Dy(r))];
+	[layer setContentsScale:scale];
+}
+	gscreen = i;
+	gscreen->clipr = ZR;
+}
+
+Memdata*
+attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
+{
+	LOG();
+	*r = gscreen->clipr;
+	*chan = gscreen->chan;
+	*depth = gscreen->depth;
+	*width = gscreen->width;
+	*softscreen = 1;
+
+	gscreen->data->ref++;
+	return gscreen->data;
+}
+
+char *
+clipread(void)
+{
+	@autoreleasepool{
+		NSPasteboard *pb = [NSPasteboard generalPasteboard];
+		NSString *s = [pb stringForType:NSPasteboardTypeString];
+		if(s)
+			return strdup([s UTF8String]);
+	}
+	return nil;
+}
+
+int
+clipwrite(char *buf)
+{
+	@autoreleasepool{
+		NSString *s = [[NSString alloc] initWithUTF8String:buf];
+		NSPasteboard *pb = [NSPasteboard generalPasteboard];
+		[pb clearContents];
+		[pb writeObjects:@[s]];
+	}
+	return strlen(buf);
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+	LOG(@"<- %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r));
+	if(rectclip(&r, gscreen->clipr) == 0)
+		return;
+	LOG(@"-> %d %d %d %d", r.min.x, r.min.y, Dx(r), Dy(r));
+	@autoreleasepool{
+		[((DrawLayer *)myview.layer).texture
+			replaceRegion:MTLRegionMake2D(r.min.x, r.min.y, Dx(r), Dy(r))
+			mipmapLevel:0
+			withBytes:byteaddr(gscreen, Pt(r.min.x, r.min.y))
+			bytesPerRow:gscreen->width * 4];
+		NSRect sr = [[myview window] convertRectFromBacking:NSMakeRect(r.min.x, r.min.y, Dx(r), Dy(r))];
+		dispatch_async(dispatch_get_main_queue(), ^(void){@autoreleasepool{
+			LOG(@"setNeedsDisplayInRect %g %g %g %g", sr.origin.x, sr.origin.y, sr.size.width, sr.size.height);
+			[myview setNeedsDisplayInRect:sr];
+			[myview enlargeLastInputRect:sr];
+		}});
+		// ReplaceRegion is somehow asynchronous since 10.14.5.  We wait sometime to request a update again.
+		dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 8 * NSEC_PER_MSEC);
+		dispatch_after(time, dispatch_get_main_queue(), ^(void){@autoreleasepool{
+			LOG(@"setNeedsDisplayInRect %g %g %g %g again", sr.origin.x, sr.origin.y, sr.size.width, sr.size.height);
+			[myview setNeedsDisplayInRect:sr];
+		}});
+		time = dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC);
+		dispatch_after(time, dispatch_get_main_queue(), ^(void){@autoreleasepool{
+			LOG(@"setNeedsDisplayInRect %g %g %g %g again", sr.origin.x, sr.origin.y, sr.size.width, sr.size.height);
+			[myview setNeedsDisplayInRect:sr];
+		}});
+	}
+}
+
+void
+getcolor(ulong i, ulong *r, ulong *g, ulong *b)
+{
+	ulong v;
+
+	v = pal[i];
+	*r = (v>>16)&0xFF;
+	*g = (v>>8)&0xFF;
+	*b = v&0xFF;
+}
+
+void
+setcolor(ulong i, ulong r, ulong g, ulong b)
+{
+	pal[i] = ((r&0xFF)<<16) & ((g&0xFF)<<8) & (b&0xFF);
+}
+
+void
+setcursor(void)
+{
+	static unsigned char data[64], data2[256];
+	unsigned char *planes[2] = {&data[0], &data[32]};
+	unsigned char *planes2[2] = {&data2[0], &data2[128]};
+	unsigned int i, x, y, a;
+	unsigned char pu, pb, pl, pr, pc;  // upper, bottom, left, right, center
+	unsigned char pul, pur, pbl, pbr;
+	unsigned char ful, fur, fbl, fbr;
+
+	lock(&cursor.lk);
+	for(i = 0; i < 32; i++){
+		data[i] = ~cursor.set[i] & cursor.clr[i];
+		data[i+32] = cursor.set[i] | cursor.clr[i];
+	}
+	for(a=0; a<2; a++){
+		for(y=0; y<16; y++){
+			for(x=0; x<2; x++){
+				pc = planes[a][x+2*y];
+				pu = y==0 ? pc : planes[a][x+2*(y-1)];
+				pb = y==15 ? pc : planes[a][x+2*(y+1)];
+				pl = (pc>>1) | (x==0 ? pc&0x80 : (planes[a][x-1+2*y]&1)<<7);
+				pr = (pc<<1) | (x==1 ? pc&1 : (planes[a][x+1+2*y]&0x80)>>7);
+				ful = ~(pl^pu) & (pl^pb) & (pu^pr);
+				pul = (ful & pu) | (~ful & pc);
+				fur = ~(pu^pr) & (pu^pl) & (pr^pb);
+				pur = (fur & pr) | (~fur & pc);
+				fbl = ~(pb^pl) & (pb^pr) & (pl^pu);
+				pbl = (fbl & pl) | (~fbl & pc);
+				fbr = ~(pr^pb) & (pr^pu) & (pb^pl);
+				pbr = (fbr & pb) | (~fbr & pc);
+				planes2[a][2*x+4*2*y] = (pul&0x80) | ((pul&0x40)>>1)  | ((pul&0x20)>>2) | ((pul&0x10)>>3)
+					| ((pur&0x80)>>1) | ((pur&0x40)>>2)  | ((pur&0x20)>>3) | ((pur&0x10)>>4);
+				planes2[a][2*x+1+4*2*y] = ((pul&0x8)<<4) | ((pul&0x4)<<3)  | ((pul&0x2)<<2) | ((pul&0x1)<<1)
+					| ((pur&0x8)<<3) | ((pur&0x4)<<2)  | ((pur&0x2)<<1) | (pur&0x1);
+				planes2[a][2*x+4*(2*y+1)] =  (pbl&0x80) | ((pbl&0x40)>>1)  | ((pbl&0x20)>>2) | ((pbl&0x10)>>3)
+					| ((pbr&0x80)>>1) | ((pbr&0x40)>>2)  | ((pbr&0x20)>>3) | ((pbr&0x10)>>4);
+				planes2[a][2*x+1+4*(2*y+1)] = ((pbl&0x8)<<4) | ((pbl&0x4)<<3)  | ((pbl&0x2)<<2) | ((pbl&0x1)<<1)
+					| ((pbr&0x8)<<3) | ((pbr&0x4)<<2)  | ((pbr&0x2)<<1) | (pbr&0x1);
+			}
+		}
+	}
+	NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
+		initWithBitmapDataPlanes:planes
+		pixelsWide:16
+		pixelsHigh:16
+		bitsPerSample:1
+		samplesPerPixel:2
+		hasAlpha:YES
+		isPlanar:YES
+		colorSpaceName:NSDeviceWhiteColorSpace
+		bitmapFormat:0
+		bytesPerRow:2
+		bitsPerPixel:0];
+	NSBitmapImageRep *rep2 = [[NSBitmapImageRep alloc]
+		initWithBitmapDataPlanes:planes2
+		pixelsWide:32
+		pixelsHigh:32
+		bitsPerSample:1
+		samplesPerPixel:2
+		hasAlpha:YES
+		isPlanar:YES
+		colorSpaceName:NSDeviceWhiteColorSpace
+		bitmapFormat:0
+		bytesPerRow:4
+		bitsPerPixel:0];
+	NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)];
+	[img addRepresentation:rep2];
+	[img addRepresentation:rep];
+	currentCursor = [[NSCursor alloc] initWithImage:img hotSpot:NSMakePoint(-cursor.offset.x, -cursor.offset.y)];
+	unlock(&cursor.lk);
+
+	dispatch_async(dispatch_get_main_queue(), ^(void){
+		[[myview window] invalidateCursorRectsForView:myview];
+	});
+}
+
+void
+mouseset(Point p)
+{
+	dispatch_async(dispatch_get_main_queue(), ^(void){@autoreleasepool{
+		NSPoint s;
+
+		if([[myview window] isKeyWindow]){
+			s = NSMakePoint(p.x, p.y);
+			LOG(@"-> pixel  %g %g", s.x, s.y);
+			s = [[myview window] convertPointFromBacking:s];
+			LOG(@"-> point  %g %g", s.x, s.y);
+			s = [myview convertPoint:s toView:nil];
+			LOG(@"-> window %g %g", s.x, s.y);
+			s = [[myview window] convertPointToScreen: s];
+			LOG(@"(%g, %g) <- toScreen", s.x, s.y);
+			s.y = NSScreen.screens[0].frame.size.height - s.y;
+			LOG(@"(%g, %g) <- setmouse", s.x, s.y);
+			CGWarpMouseCursorPosition(s);
+			CGAssociateMouseAndMouseCursorPosition(true);
+		}
+	}});
+}
+
+@implementation AppDelegate
+{
+	NSWindow *_window;
+}
+
+static void
+mainproc(void *aux)
+{
+	cpubody();
+}
+
+- (void) applicationDidFinishLaunching:(NSNotification *)aNotification
+{
+	LOG(@"BEGIN");
+
+	NSMenu *sm = [NSMenu new];
+	[sm addItemWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
+	[sm addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
+	[sm addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
+	NSMenu *m = [NSMenu new];
+	[m addItemWithTitle:@"DEVDRAW" action:NULL keyEquivalent:@""];
+	[m setSubmenu:sm forItem:[m itemWithTitle:@"DEVDRAW"]];
+	[NSApp setMainMenu:m];
+
+	const NSWindowStyleMask Winstyle = NSWindowStyleMaskTitled
+		| NSWindowStyleMaskClosable
+		| NSWindowStyleMaskMiniaturizable
+		| NSWindowStyleMaskResizable;
+
+	NSRect r = [[NSScreen mainScreen] visibleFrame];
+
+	r.size.width = r.size.width*3/4;
+	r.size.height = r.size.height*3/4;
+	r = [NSWindow contentRectForFrameRect:r styleMask:Winstyle];
+
+	_window = [[NSWindow alloc] initWithContentRect:r styleMask:Winstyle
+		backing:NSBackingStoreBuffered defer:NO];
+	[_window setTitle:@"drawcpu"];
+	[_window center];
+	[_window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+	[_window setContentMinSize:NSMakeSize(64,64)];
+	[_window setOpaque:YES];
+	[_window setRestorable:NO];
+	[_window setAcceptsMouseMovedEvents:YES];
+	[_window setDelegate:self];
+
+	myview = [DrawtermView new];
+	[_window setContentView:myview];
+
+	[NSEvent setMouseCoalescingEnabled:NO];
+	setcursor();
+
+	[_window makeKeyAndOrderFront:self];
+	[NSApp activateIgnoringOtherApps:YES];
+
+	LOG(@"launch mainproc");
+	kproc("mainproc", mainproc, 0);
+	ksleep(&rend, isready, 0);
+}
+
+- (NSApplicationPresentationOptions) window:(NSWindow *)window
+		willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
+{
+	NSApplicationPresentationOptions o;
+	o = proposedOptions;
+	o &= ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar);
+	o |= NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+	return o;
+}
+
+- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
+{
+	return YES;
+}
+
+- (void) windowDidBecomeKey:(id)arg
+{
+	NSPoint p;
+	p = [_window convertPointToBacking:[_window mouseLocationOutsideOfEventStream]];
+	absmousetrack(p.x, [myview convertSizeToBacking:myview.frame.size].height - p.y, 0, ticks());
+}
+
+- (void) windowDidResignKey:(id)arg
+{
+	[myview clearMods];
+}
+
+@end
+
+@implementation DrawtermView
+{
+	NSMutableString *_tmpText;
+	NSRange _markedRange;
+	NSRange _selectedRange;
+	NSRect _lastInputRect;	// The view is flipped, this is not.
+	BOOL _tapping;
+	NSUInteger _tapFingers;
+	NSUInteger _tapTime;
+	BOOL _breakcompose;
+	NSEventModifierFlags _mods;
+}
+
+- (id) initWithFrame:(NSRect)fr
+{
+	LOG(@"BEGIN");
+	self = [super initWithFrame:fr];
+	[self setWantsLayer:YES];
+	[self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
+	[self setAllowedTouchTypes:NSTouchTypeMaskDirect|NSTouchTypeMaskIndirect];
+	_tmpText = [[NSMutableString alloc] initWithCapacity:2];
+	_markedRange = NSMakeRange(NSNotFound, 0);
+	_selectedRange = NSMakeRange(0, 0);
+	_breakcompose = NO;
+	_mods = 0;
+	LOG(@"END");
+	return self;
+}
+
+- (CALayer *) makeBackingLayer
+{
+	return [DrawLayer layer];
+}
+
+- (BOOL)wantsUpdateLayer
+{
+	return YES;
+}
+
+- (BOOL)isOpaque
+{
+	return YES;
+}
+
+- (BOOL)isFlipped
+{
+	return YES;
+}
+
+static uint
+evkey(uint v)
+{
+	switch(v){
+	case '\r': return '\n';
+	case 127: return '\b';
+	case NSUpArrowFunctionKey: return Kup;
+	case NSDownArrowFunctionKey: return Kdown;
+	case NSLeftArrowFunctionKey: return Kleft;
+	case NSRightArrowFunctionKey: return Kright;
+	case NSF1FunctionKey: return KF|1;
+	case NSF2FunctionKey: return KF|2;
+	case NSF3FunctionKey: return KF|3;
+	case NSF4FunctionKey: return KF|4;
+	case NSF5FunctionKey: return KF|5;
+	case NSF6FunctionKey: return KF|6;
+	case NSF7FunctionKey: return KF|7;
+	case NSF8FunctionKey: return KF|8;
+	case NSF9FunctionKey: return KF|9;
+	case NSF10FunctionKey: return KF|10;
+	case NSF11FunctionKey: return KF|11;
+	case NSF12FunctionKey: return KF|12;
+	case NSInsertFunctionKey: return Kins;
+	case NSDeleteFunctionKey: return Kdel;
+	case NSHomeFunctionKey: return Khome;
+	case NSEndFunctionKey: return Kend;
+	case NSPageUpFunctionKey: return Kpgup;
+	case NSPageDownFunctionKey: return Kpgdown;
+	case NSScrollLockFunctionKey: return Kscroll;
+	case NSBeginFunctionKey:
+	case NSF13FunctionKey:
+	case NSF14FunctionKey:
+	case NSF15FunctionKey:
+	case NSF16FunctionKey:
+	case NSF17FunctionKey:
+	case NSF18FunctionKey:
+	case NSF19FunctionKey:
+	case NSF20FunctionKey:
+	case NSF21FunctionKey:
+	case NSF22FunctionKey:
+	case NSF23FunctionKey:
+	case NSF24FunctionKey:
+	case NSF25FunctionKey:
+	case NSF26FunctionKey:
+	case NSF27FunctionKey:
+	case NSF28FunctionKey:
+	case NSF29FunctionKey:
+	case NSF30FunctionKey:
+	case NSF31FunctionKey:
+	case NSF32FunctionKey:
+	case NSF33FunctionKey:
+	case NSF34FunctionKey:
+	case NSF35FunctionKey:
+	case NSPrintScreenFunctionKey:
+	case NSPauseFunctionKey:
+	case NSSysReqFunctionKey:
+	case NSBreakFunctionKey:
+	case NSResetFunctionKey:
+	case NSStopFunctionKey:
+	case NSMenuFunctionKey:
+	case NSUserFunctionKey:
+	case NSSystemFunctionKey:
+	case NSPrintFunctionKey:
+	case NSClearLineFunctionKey:
+	case NSClearDisplayFunctionKey:
+	case NSInsertLineFunctionKey:
+	case NSDeleteLineFunctionKey:
+	case NSInsertCharFunctionKey:
+	case NSDeleteCharFunctionKey:
+	case NSPrevFunctionKey:
+	case NSNextFunctionKey:
+	case NSSelectFunctionKey:
+	case NSExecuteFunctionKey:
+	case NSUndoFunctionKey:
+	case NSRedoFunctionKey:
+	case NSFindFunctionKey:
+	case NSHelpFunctionKey:
+	case NSModeSwitchFunctionKey: return 0;
+	default: return v;
+	}
+}
+
+- (void)keyDown:(NSEvent*)event {
+	[self interpretKeyEvents:[NSArray arrayWithObject:event]];
+	[self resetLastInputRect];
+}
+
+- (void)flagsChanged:(NSEvent*)event {
+	NSEventModifierFlags x;
+	NSUInteger u;
+
+	x = [event modifierFlags];
+	u = [NSEvent pressedMouseButtons];
+	u = (u&~6) | (u&4)>>1 | (u&2)<<1;
+	if((x & ~_mods & NSEventModifierFlagShift) != 0)
+		kbdkey(Kshift, 1);
+	if((x & ~_mods & NSEventModifierFlagControl) != 0){
+		if(u){
+			u |= 1;
+			[self sendmouse:u];
+			return;
+		}else
+			kbdkey(Kctl, 1);
+	}
+	if((x & ~_mods & NSEventModifierFlagOption) != 0){
+		if(u){
+			u |= 2;
+			[self sendmouse:u];
+			return;
+		}else
+			kbdkey(Kalt, 1);
+	}
+	if((x & NSEventModifierFlagCommand) != 0)
+		if(u){
+			u |= 4;
+			[self sendmouse:u];
+		}
+	if((x & ~_mods & NSEventModifierFlagCapsLock) != 0)
+		kbdkey(Kcaps, 1);
+	if((~x & _mods & NSEventModifierFlagShift) != 0)
+		kbdkey(Kshift, 0);
+	if((~x & _mods & NSEventModifierFlagControl) != 0)
+		kbdkey(Kctl, 0);
+	if((~x & _mods & NSEventModifierFlagOption) != 0){
+		kbdkey(Kalt, 0);
+		if(_breakcompose){
+			kbdkey(Kalt, 1);
+			kbdkey(Kalt, 0);
+			_breakcompose = NO;
+		}
+	}
+	if((~x & _mods & NSEventModifierFlagCapsLock) != 0)
+		kbdkey(Kcaps, 0);
+	_mods = x;
+}
+
+- (void) clearMods {
+	if((_mods & NSEventModifierFlagShift) != 0){
+		kbdkey(Kshift, 0);
+		_mods ^= NSEventModifierFlagShift;
+	}
+	if((_mods & NSEventModifierFlagControl) != 0){
+		kbdkey(Kctl, 0);
+		_mods ^= NSEventModifierFlagControl;
+	}
+	if((_mods & NSEventModifierFlagOption) != 0){
+		kbdkey(Kalt, 0);
+		_mods ^= NSEventModifierFlagOption;
+	}
+}
+
+- (void) mouseevent:(NSEvent*)event
+{
+	NSPoint p;
+	Point q;
+	NSUInteger u;
+	NSEventModifierFlags m;
+
+	p = [self.window convertPointToBacking:[self.window mouseLocationOutsideOfEventStream]];
+	u = [NSEvent pressedMouseButtons];
+	q.x = p.x;
+	q.y = p.y;
+	if(!ptinrect(q, gscreen->clipr)) return;
+	u = (u&~6) | (u&4)>>1 | (u&2)<<1;
+	if(u == 1){
+		m = [event modifierFlags];
+		if(m & NSEventModifierFlagOption){
+			_breakcompose = 1;
+			u = 2;
+		}else if(m & NSEventModifierFlagCommand)
+			u = 4;
+	}
+	absmousetrack(p.x, [self convertSizeToBacking:self.frame.size].height - p.y, u, ticks());
+	if(u && _lastInputRect.size.width && _lastInputRect.size.height)
+		[self resetLastInputRect];
+}
+
+- (void) sendmouse:(NSUInteger)u
+{
+	mousetrack(0, 0, u, ticks());
+	if(u && _lastInputRect.size.width && _lastInputRect.size.height)
+		[self resetLastInputRect];
+}
+
+- (void) mouseDown:(NSEvent*)event { [self mouseevent:event]; }
+- (void) mouseDragged:(NSEvent*)event { [self mouseevent:event]; }
+- (void) mouseUp:(NSEvent*)event { [self mouseevent:event]; }
+- (void) mouseMoved:(NSEvent*)event { [self mouseevent:event]; }
+- (void) rightMouseDown:(NSEvent*)event { [self mouseevent:event]; }
+- (void) rightMouseDragged:(NSEvent*)event { [self mouseevent:event]; }
+- (void) rightMouseUp:(NSEvent*)event { [self mouseevent:event]; }
+- (void) otherMouseDown:(NSEvent*)event { [self mouseevent:event]; }
+- (void) otherMouseDragged:(NSEvent*)event { [self mouseevent:event]; }
+- (void) otherMouseUp:(NSEvent*)event { [self mouseevent:event]; }
+
+- (void) scrollWheel:(NSEvent*)event{
+	NSInteger s = [event scrollingDeltaY];
+	if(s > 0)
+		[self sendmouse:8];
+	else if(s < 0)
+		[self sendmouse:16];
+}
+
+- (void)magnifyWithEvent:(NSEvent*)e{
+	if(fabs([e magnification]) > 0.02)
+		[[self window] toggleFullScreen:nil];
+}
+
+- (void)touchesBeganWithEvent:(NSEvent*)e
+{
+	_tapping = YES;
+	_tapFingers = [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count;
+	_tapTime = ticks();
+}
+
+- (void)touchesMovedWithEvent:(NSEvent*)e
+{
+	_tapping = NO;
+}
+
+- (void)touchesEndedWithEvent:(NSEvent*)e
+{
+	if(_tapping
+		&& [e touchesMatchingPhase:NSTouchPhaseTouching inView:nil].count == 0
+		&& ticks() - _tapTime < 250){
+		switch(_tapFingers){
+		case 3:
+			[self sendmouse:2];
+			[self sendmouse:0];
+			break;
+		case 4:
+			[self sendmouse:2];
+			[self sendmouse:1];
+			[self sendmouse:0];
+			break;
+		}
+		_tapping = NO;
+	}
+}
+
+- (void)touchesCancelledWithEvent:(NSEvent*)e
+{
+	_tapping = NO;
+}
+
+- (BOOL) acceptsFirstResponder
+{
+	return TRUE;
+}
+
+- (void) resetCursorRects
+{
+	[super resetCursorRects];
+	lock(&cursor.lk);
+	[self addCursorRect:self.bounds cursor:currentCursor];
+	unlock(&cursor.lk);
+}
+
+- (void) reshape
+{
+	NSSize s = [self convertSizeToBacking:self.frame.size];
+	LOG(@"%g %g", s.width, s.height);
+	if(gscreen != nil){
+		screenresize(Rect(0, 0, s.width, s.height));
+	}
+}
+
+- (void)windowDidResize:(NSNotification *)notification
+{
+	if(![self inLiveResize])
+		[self reshape];
+}
+
+- (void)viewDidEndLiveResize
+{
+	LOG();
+	[super viewDidEndLiveResize];
+	[self reshape];
+}
+
+- (void)viewDidChangeBackingProperties
+{
+	LOG();
+	[super viewDidChangeBackingProperties];
+	[self reshape];
+}
+
+static void
+keystroke(Rune r)
+{
+	kbdkey(r, 1);
+	kbdkey(r, 0);
+}
+
+// conforms to protocol NSTextInputClient
+- (BOOL)hasMarkedText
+{
+	return _markedRange.location != NSNotFound;
+}
+- (NSRange)markedRange
+{
+	return _markedRange;
+}
+- (NSRange)selectedRange
+{
+	return _selectedRange;
+}
+- (void)setMarkedText:(id)string
+	selectedRange:(NSRange)sRange
+	replacementRange:(NSRange)rRange
+{
+	NSString *str;
+
+	[self clearInput];
+
+	if([string isKindOfClass:[NSAttributedString class]])
+		str = [string string];
+	else
+		str = string;
+
+	if(rRange.location == NSNotFound){
+		if(_markedRange.location != NSNotFound){
+			rRange = _markedRange;
+		}else{
+			rRange = _selectedRange;
+		}
+	}
+
+	if(str.length == 0){
+		[_tmpText deleteCharactersInRange:rRange];
+		[self unmarkText];
+	}else{
+		_markedRange = NSMakeRange(rRange.location, str.length);
+		[_tmpText replaceCharactersInRange:rRange withString:str];
+	}
+	_selectedRange.location = rRange.location + sRange.location;
+	_selectedRange.length = sRange.length;
+
+	if(_tmpText.length){
+		for(uint i = 0; i <= _tmpText.length; ++i){
+			if(i == _markedRange.location)
+				keystroke('[');
+			if(_selectedRange.length){
+				if(i == _selectedRange.location)
+					keystroke('{');
+				if(i == NSMaxRange(_selectedRange))
+					keystroke('}');
+				}
+			if(i == NSMaxRange(_markedRange))
+				keystroke(']');
+			if(i < _tmpText.length)
+				keystroke([_tmpText characterAtIndex:i]);
+		}
+		uint l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
+			+ (_selectedRange.length > 0);
+		for(uint i = 0; i < l; ++i)
+			keystroke(Kleft);
+	}
+}
+- (void)unmarkText
+{
+	[_tmpText deleteCharactersInRange:NSMakeRange(0, [_tmpText length])];
+	_markedRange = NSMakeRange(NSNotFound, 0);
+	_selectedRange = NSMakeRange(0, 0);
+}
+- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText
+{
+	return @[];
+}
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)r
+	actualRange:(NSRangePointer)actualRange
+{
+	NSRange sr;
+	NSAttributedString *s;
+
+	sr = NSMakeRange(0, [_tmpText length]);
+	sr = NSIntersectionRange(sr, r);
+	if(actualRange)
+		*actualRange = sr;
+	if(sr.length)
+		s = [[NSAttributedString alloc]
+			initWithString:[_tmpText substringWithRange:sr]];
+	return s;
+}
+- (void)insertText:(id)s
+	replacementRange:(NSRange)r
+{
+	NSUInteger i;
+	NSUInteger len;
+
+	[self clearInput];
+
+	len = [s length];
+	for(i = 0; i < len; ++i)
+		keystroke([s characterAtIndex:i]);
+	[_tmpText deleteCharactersInRange:NSMakeRange(0, _tmpText.length)];
+	_markedRange = NSMakeRange(NSNotFound, 0);
+	_selectedRange = NSMakeRange(0, 0);
+}
+- (NSUInteger)characterIndexForPoint:(NSPoint)point
+{
+	return 0;
+}
+- (NSRect)firstRectForCharacterRange:(NSRange)r
+	actualRange:(NSRangePointer)actualRange
+{
+	if(actualRange)
+		*actualRange = r;
+	return [[self window] convertRectToScreen:_lastInputRect];
+}
+- (void)doCommandBySelector:(SEL)s
+{
+	NSEvent *e;
+	uint c, k;
+
+	e = [NSApp currentEvent];
+	c = [[e charactersIgnoringModifiers] characterAtIndex:0];
+	k = evkey(c);
+	if(k>0)
+		keystroke(k);
+}
+
+// Helper for managing input rect approximately
+- (void)resetLastInputRect
+{
+	_lastInputRect.origin.x = 0.0;
+	_lastInputRect.origin.y = 0.0;
+	_lastInputRect.size.width = 0.0;
+	_lastInputRect.size.height = 0.0;
+}
+
+- (void)enlargeLastInputRect:(NSRect)r
+{
+	r.origin.y = [self bounds].size.height - r.origin.y - r.size.height;
+	_lastInputRect = NSUnionRect(_lastInputRect, r);
+}
+
+- (void)clearInput
+{
+	if(_tmpText.length){
+		uint l = 1 + _tmpText.length - NSMaxRange(_selectedRange)
+			+ (_selectedRange.length > 0);
+		for(uint i = 0; i < l; ++i)
+			keystroke(Kright);
+		l = _tmpText.length+2+2*(_selectedRange.length > 0);
+		for(uint i = 0; i < l; ++i)
+			keystroke(Kbs);
+	}
+}
+@end
+
+@implementation DrawLayer
+{
+	id<MTLCommandQueue> _commandQueue;
+}
+
+- (id) init {
+	LOG();
+	self = [super init];
+	self.device = MTLCreateSystemDefaultDevice();
+	self.pixelFormat = MTLPixelFormatBGRA8Unorm;
+	self.framebufferOnly = YES;
+	self.opaque = YES;
+
+	// We use a default transparent layer on top of the CAMetalLayer.
+	// This seems to make fullscreen applications behave.
+	{
+		CALayer *stub = [CALayer layer];
+		stub.frame = CGRectMake(0, 0, 1, 1);
+		[stub setNeedsDisplay];
+		[self addSublayer:stub];
+	}
+
+	_commandQueue = [self.device newCommandQueue];
+
+	return self;
+}
+
+- (void) display
+{
+	id<MTLCommandBuffer> cbuf;
+	id<MTLBlitCommandEncoder> blit;
+
+	cbuf = [_commandQueue commandBuffer];
+
+@autoreleasepool{
+	id<CAMetalDrawable> drawable = [self nextDrawable];
+	if(!drawable){
+		LOG(@"display couldn't get drawable");
+		[self setNeedsDisplay];
+		return;
+	}
+
+	blit = [cbuf blitCommandEncoder];
+	[blit copyFromTexture:_texture
+		sourceSlice:0
+		sourceLevel:0
+		sourceOrigin:MTLOriginMake(0, 0, 0)
+		sourceSize:MTLSizeMake(_texture.width, _texture.height, _texture.depth)
+		toTexture:drawable.texture
+		destinationSlice:0
+		destinationLevel:0
+		destinationOrigin:MTLOriginMake(0, 0, 0)];
+	[blit endEncoding];
+
+	[cbuf presentDrawable:drawable];
+	drawable = nil;
+}
+	[cbuf addCompletedHandler:^(id<MTLCommandBuffer> cmdBuff){
+		if(cmdBuff.error){
+			NSLog(@"command buffer finished with error: %@",
+				cmdBuff.error.localizedDescription);
+		}else{
+			LOG(@"command buffer finished");
+		}
+	}];
+	[cbuf commit];
+}
+
+@end
--- /dev/null
+++ b/gui-win32/Makefile
@@ -1,0 +1,15 @@
+ROOT=..
+include ../Make.config
+LIB=libgui.a
+
+OFILES=\
+	screen.$O\
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/gui-win32/screen.c
@@ -1,0 +1,689 @@
+#define _WIN32_WINNT 0x0500
+#include	<windows.h>
+
+#undef Rectangle
+#define Rectangle _Rectangle
+
+#include "u.h"
+#include "lib.h"
+#include "kern/dat.h"
+#include "kern/fns.h"
+#include "error.h"
+#include "user.h"
+#include <draw.h>
+#include <memdraw.h>
+#include "screen.h"
+#include "keyboard.h"
+
+Memimage	*gscreen;
+
+static int depth;
+static int dibtype;
+
+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 int readybit;
+static Rendez	rend;
+
+static int
+isready(void*a)
+{
+	return readybit;
+}
+
+void
+screeninit(void)
+{
+	int dx, dy;
+	ulong chan;
+
+	FreeConsole();
+	memimageinit();
+
+	if(depth == 0)
+		depth = GetDeviceCaps(GetDC(NULL), BITSPIXEL);
+	switch(depth){
+	case 32:
+		dibtype = DIB_RGB_COLORS;
+		depth = 32;
+		chan = XRGB32;
+		break;
+	case 24:
+		dibtype = DIB_RGB_COLORS;
+		depth = 24;
+		chan = RGB24;
+		break;
+	case 16:
+		dibtype = DIB_RGB_COLORS;
+		depth = 16;
+		chan = RGB15;	/* [sic] */
+		break;
+	case 8:
+	default:
+		dibtype = DIB_PAL_COLORS;
+		depth = 8;
+		depth = 8;
+		chan = CMAP8;
+		break;
+	}
+	dx = GetDeviceCaps(GetDC(NULL), HORZRES);
+	dy = GetDeviceCaps(GetDC(NULL), VERTRES);
+	screensize(Rect(0,0,dx,dy), chan);
+	kproc("winscreen", winproc, 0);
+	ksleep(&rend, isready, 0);
+}
+
+void
+screensize(Rectangle r, ulong chan)
+{
+	Memimage *i;
+
+	if((i = allocmemimage(r, chan)) == nil)
+		return;
+	if(gscreen != nil)
+		freememimage(gscreen);
+	gscreen = i;
+	gscreen->clipr = ZR;
+}
+
+Memdata*
+attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
+{
+	*r = gscreen->clipr;
+	*chan = gscreen->chan;
+	*depth = gscreen->depth;
+	*width = gscreen->width;
+	*softscreen = 1;
+
+	gscreen->data->ref++;
+	return gscreen->data;
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+	int dx, dy;
+	HDC hdc;
+
+	/*
+	 * 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->clipr) == 0)
+		return;
+	
+	lock(&gdilock);
+
+	hdc = GetDC(window);
+	SelectPalette(hdc, palette, 0);
+	RealizePalette(hdc);
+
+	dx = r.max.x - r.min.x;
+	dy = r.max.y - r.min.y;
+
+	bmi->bmiHeader.biWidth = (gscreen->width*sizeof(ulong)*8)/gscreen->depth;
+	bmi->bmiHeader.biHeight = -dy;	/* - => origin upper left */
+
+	SetDIBitsToDevice(hdc,
+		r.min.x, r.min.y,
+		dx, dy,
+		r.min.x, 0,
+		0, dy,
+		byteaddr(gscreen, Pt(0, r.min.y)), bmi,
+		dibtype);
+
+	ReleaseDC(window, hdc);
+
+	GdiFlush();
+ 
+	unlock(&gdilock);
+}
+
+static void
+winproc(void *a)
+{
+	WNDCLASS wc;
+	MSG msg;
+
+	inst = GetModuleHandle(NULL);
+
+	paletteinit();
+	bmiinit();
+
+	wc.style = 0;
+	wc.lpfnWndProc = WindowProc;
+	wc.cbClsExtra = 0;
+	wc.cbWndExtra = 0;
+	wc.hInstance = inst;
+	wc.hIcon = LoadIcon(inst, MAKEINTRESOURCE(101));
+	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+	wc.hbrBackground = GetStockObject(WHITE_BRUSH);
+	wc.lpszMenuName = 0;
+	wc.lpszClassName = L"9pmgraphics";
+	RegisterClass(&wc);
+
+	window = CreateWindowEx(
+		0,			/* extended style */
+		L"9pmgraphics",		/* class */
+		L"drawcpu 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);
+
+	terminit();
+
+	readybit = 1;
+	wakeup(&rend);
+
+	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;
+}
+
+void
+togglefull(HWND hwnd)
+{
+	static int full;
+	static LONG style, exstyle;
+	static WINDOWPLACEMENT pl;
+	MONITORINFO mi;
+	
+	full = !full;
+	if(full){
+		SendMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
+		style = GetWindowLong(hwnd, GWL_STYLE);
+		exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
+		pl.length = sizeof(WINDOWPLACEMENT);
+		GetWindowPlacement(hwnd, &pl);
+		SetWindowLong(hwnd, GWL_STYLE, style & ~(WS_CAPTION | WS_THICKFRAME));
+		SetWindowLong(hwnd, GWL_EXSTYLE, exstyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
+		mi.cbSize = sizeof(MONITORINFO);
+		GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), &mi);
+		SetWindowPos(hwnd, NULL, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+	}else{
+		SetWindowLong(hwnd, GWL_STYLE, style);
+		SetWindowLong(hwnd, GWL_EXSTYLE, exstyle);
+		SetWindowPlacement(hwnd, &pl);
+	}
+}
+
+Rune vk2rune[256] = {
+[VK_CANCEL] Kbreak,
+[VK_CAPITAL] Kcaps,
+[VK_CONTROL] Kctl,
+[VK_DELETE] Kdel,
+[VK_DOWN] Kdown,
+[VK_END] Kend,
+[VK_F1] KF|1,KF|2,KF|3,KF|4,KF|5,KF|6,KF|7,KF|8,KF|9,KF|10,KF|11,KF|12,
+[VK_HOME] Khome,
+[VK_INSERT] Kins,
+[VK_LEFT] Kleft,
+[VK_MENU] Kalt,
+[VK_NEXT] Kpgdown,
+[VK_NUMLOCK] Knum,
+[VK_PRINT] Kprint,
+[VK_PRIOR] Kpgup,
+[VK_RIGHT] Kright,
+[VK_RMENU] Kaltgr,
+[VK_SCROLL] Kscroll,
+[VK_SHIFT] Kshift,
+[VK_UP] Kup,
+};
+		
+
+LRESULT CALLBACK
+WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+	static Rune scdown[256];
+	PAINTSTRUCT paint;
+	HDC hdc;
+	LONG x, y, b;
+	int i;
+	RECT winr;
+	Rectangle r;
+	Rune k;
+
+	b = 0;
+
+	switch(msg) {
+	case WM_CREATE:
+		if(GetClientRect(hwnd, &winr) == 0)
+			break;
+		gscreen->clipr = Rect(0, 0, winr.right - winr.left, winr.bottom - winr.top);
+		rectclip(&gscreen->clipr, gscreen->r);
+		break;
+
+	case WM_SETCURSOR:
+		/* User set */
+		if(hcursor != NULL) {
+			SetCursor(hcursor);
+			return 1;
+		}
+		return DefWindowProc(hwnd, msg, wparam, lparam);
+	case WM_MOUSEWHEEL:
+		if ((int)(wparam & 0xFFFF0000)>0)
+			b |=8;
+		else
+			b |=16;
+		mousetrack(0, 0, b, ticks());
+		break;
+	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);
+		if(wparam & MK_LBUTTON)
+			b |= 1;
+		if(wparam & MK_MBUTTON)
+			b |= 2;
+		if(wparam & MK_RBUTTON)
+			b |= 4;
+		absmousetrack(x, y, b, ticks());
+		break;
+
+	case WM_CHAR:
+		k = wparam;
+		if(k == '\n')
+			k = '\r';
+		else if(k == '\r')
+			k = '\n';
+		if(0){
+	case WM_SYSKEYDOWN:
+	case WM_KEYDOWN:
+			k = vk2rune[wparam&0xFF];
+		}
+		if(k == 0)
+			break;
+		i = (lparam>>16)&0xFF;
+		scdown[i] = k;
+		kbdkey(k, 1);
+		break;
+	case WM_SYSKEYUP:
+	case WM_KEYUP:
+		if(wparam == VK_PAUSE)
+			togglefull(hwnd);
+		i = (lparam>>16)&0xFF;
+		k = scdown[i];
+		if(k != 0){
+			scdown[i] = 0;
+			kbdkey(k, 0);
+		}
+		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_SIZE:
+		if(GetClientRect(hwnd, &winr) == 0)
+			break;
+		screenresize(Rect(0, 0, winr.right - winr.left, winr.bottom - winr.top));
+		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, (WPARAM)window, 0);
+}
+
+void
+cursorarrow(void)
+{
+	if(hcursor != 0) {
+		DestroyCursor(hcursor);
+		hcursor = 0;
+	}
+	SetCursor(LoadCursor(0, IDC_ARROW));
+	PostMessage(window, WM_SETCURSOR, (WPARAM)window, 0);
+}
+
+
+void
+setcolor(ulong index, ulong red, ulong green, ulong blue)
+{
+}
+
+
+char*
+clipreadunicode(HANDLE h)
+{
+	wchar_t *p;
+	int n;
+	char *q;
+
+	p = GlobalLock(h);
+	n = WideCharToMultiByte(CP_UTF8, 0, p, -1, 0, 0, 0, 0);
+	q = malloc(n+1);
+	WideCharToMultiByte(CP_UTF8, 0, p, -1, q, n, 0, 0);
+	GlobalUnlock(h);
+
+	return q;
+}
+
+char*
+clipreadutf(HANDLE h)
+{
+	char *p;
+
+	p = GlobalLock(h);
+	p = strdup(p);
+	GlobalUnlock(h);
+	
+	return p;
+}
+
+char*
+clipread(void)
+{
+	HANDLE h;
+	char *p, *q, *r;
+
+	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();
+		return strdup("");
+	}
+
+	for(q = r = p; *q != 0; q++)
+		if(*q != '\r')
+			*r++ = *q;
+	*r = 0;
+	
+	CloseClipboard();
+	return p;
+}
+
+static char *
+addcr(char *buf, int *lp)
+{
+	int nlen;
+	char *r, *p, *q;
+
+	nlen = 0;
+	for(p = buf; *p != 0; p++){
+		if(*p == '\n')
+			nlen++;
+		nlen++;
+	}
+	*lp = nlen;
+	r = malloc(nlen + 1);
+	if(r == nil)
+		panic("malloc: %r");
+	q = r;
+	for(p = buf; *p != 0; p++){
+		if(*p == '\n')
+			*q++ = '\r';
+		*q++ = *p;
+	}
+	*q = 0;
+	return r;
+}
+
+int
+clipwrite(char *buf)
+{
+	HANDLE h;
+	char *p;
+	wchar_t *rp;
+	char *crbuf;
+	int n;
+
+	if(!OpenClipboard(window)) {
+		oserror();
+		return -1;
+	}
+
+	if(!EmptyClipboard()) {
+		oserror();
+		CloseClipboard();
+		return -1;
+	}
+
+	crbuf = addcr(buf, &n);
+
+	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, (n+1)*sizeof(rp[0]));
+	if(h == NULL)
+		panic("out of memory");
+	rp = GlobalLock(h);
+	MultiByteToWideChar(CP_UTF8, 0, crbuf, -1, rp, n+1);
+	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, crbuf, n);
+	p[n] = 0;
+	GlobalUnlock(h);
+	
+	SetClipboardData(CF_TEXT, h);
+
+	CloseClipboard();
+	free(crbuf);
+	return n;
+}
+
+void
+guimain(void)
+{
+	cpubody();
+}
--- /dev/null
+++ b/gui-wl/Makefile
@@ -1,0 +1,58 @@
+ROOT=..
+include ../Make.config
+LIB=libgui.a
+
+PROTO_DIR=`pkg-config --variable=pkgdatadir wayland-protocols`
+WLR_DIR=`pkg-config --variable=pkgdatadir wlr-protocols`
+XDG_SHELL=$(PROTO_DIR)/stable/xdg-shell/xdg-shell.xml
+XDG_DECO=$(PROTO_DIR)/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml
+XDG_PRIMSEL=$(PROTO_DIR)/unstable/primary-selection/primary-selection-unstable-v1.xml
+WLR_VIRTUAL_POINTER=$(WLR_DIR)/unstable/wlr-virtual-pointer-unstable-v1.xml
+
+HFILES=\
+	xdg-shell-protocol.h\
+	xdg-decoration-protocol.h\
+	xdg-primary-selection-protocol.h\
+	wlr-virtual-pointer.h\
+	wl-inc.h\
+
+OFILES=\
+	xdg-shell-protocol.$O\
+	xdg-decoration-protocol.$O\
+	xdg-primary-selection-protocol.$O\
+	wlr-virtual-pointer.$O\
+	wl-cb.$O\
+	wl-screen.$O\
+	wl-util.$O\
+
+wlr-virtual-pointer.c: 
+	wayland-scanner private-code < $(WLR_VIRTUAL_POINTER) > $@ || { rm -f $@; exit 1; }
+
+wlr-virtual-pointer.h: 
+	wayland-scanner client-header < $(WLR_VIRTUAL_POINTER) > $@ || { rm -f $@; exit 1; }
+
+xdg-shell-protocol.c:
+	wayland-scanner private-code < $(XDG_SHELL) > $@ || { rm -f $@; exit 1; }
+
+xdg-shell-protocol.h:
+	wayland-scanner client-header < $(XDG_SHELL) > $@ || { rm -f $@; exit 1; }
+
+xdg-decoration-protocol.c:
+	wayland-scanner private-code < $(XDG_DECO) > $@ || { rm -f $@; exit 1; }
+
+xdg-decoration-protocol.h:
+	wayland-scanner client-header < $(XDG_DECO) > $@ || { rm -f $@; exit 1; }
+
+xdg-primary-selection-protocol.c:
+	wayland-scanner private-code < $(XDG_PRIMSEL) > $@ || { rm -f $@; exit 1; }
+
+xdg-primary-selection-protocol.h:
+	wayland-scanner client-header < $(XDG_PRIMSEL) > $@ || { rm -f $@; exit 1; }
+
+wl-cb.$O: $(HFILES)
+
+default: $(LIB)
+$(LIB): $(HFILES) $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
--- /dev/null
+++ b/gui-wl/wl-cb.c
@@ -1,0 +1,877 @@
+#define _POSIX_C_SOURCE 200809L
+#include <sys/mman.h>
+#include <wayland-client.h>
+#include <wayland-client-protocol.h>
+#include <linux/input-event-codes.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <xkbcommon/xkbcommon.h>
+#include "xdg-shell-protocol.h"
+#include "xdg-decoration-protocol.h"
+#include "xdg-primary-selection-protocol.h"
+#include "wlr-virtual-pointer.h"
+
+#include "u.h"
+#include "lib.h"
+#include "kern/dat.h"
+#include "kern/fns.h"
+#include "error.h"
+#include "user.h"
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include "screen.h"
+#include "wl-inc.h"
+
+#undef close
+#undef send
+#undef pipe
+#undef write
+#undef read
+#undef time
+
+static void
+xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
+{
+	Wlwin *wl;
+
+	wl = data;
+	xdg_surface_ack_configure(xdg_surface, serial);
+	wl_surface_commit(wl->surface);
+}
+
+const struct xdg_surface_listener xdg_surface_listener = {
+	.configure = xdg_surface_handle_configure,
+};
+
+static void
+xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel)
+{
+	wlclose(data);
+}
+
+static void
+xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states)
+{
+	Wlwin *wl;
+	enum xdg_toplevel_state state;
+	int i;
+
+	wl = data;
+	if(width == 0 || height == 0 || (width == wl->dx && height == wl->dy))
+		return;
+	wlresize(wl, width, height);
+
+	wl->maximized = 0;
+	for(i = 0; i < states->size; i++){
+		state = ((enum xdg_toplevel_state *)states->data)[i];
+		switch (state){
+		case XDG_TOPLEVEL_STATE_MAXIMIZED:
+			wl->maximized = 1;
+			return;
+		}
+	}
+}
+
+const struct xdg_toplevel_listener xdg_toplevel_listener = {
+	.configure = xdg_toplevel_handle_configure,
+	.close = xdg_toplevel_handle_close,
+};
+
+static const struct wl_callback_listener wl_surface_frame_listener;
+
+static void
+wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time)
+{
+	Wlwin *wl;
+
+	wl = data;
+	wl_callback_destroy(cb);
+	cb = wl_surface_frame(wl->surface);
+	wl_callback_add_listener(cb, &wl_surface_frame_listener, wl);
+	wlflush(wl);
+}
+
+static void
+keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size)
+{
+	static struct xkb_keymap *keymap = nil;
+	char *keymap_string;
+	Wlwin *wl;
+
+	wl = data;
+	keymap_string = mmap(nil, size, PROT_READ, MAP_SHARED, fd, 0);
+	xkb_keymap_unref(keymap);
+	keymap = xkb_keymap_new_from_string(wl->xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
+	munmap(keymap_string, size);
+	close(fd);
+	xkb_state_unref(wl->xkb_state);
+	wl->xkb_state = xkb_state_new(keymap);
+}
+
+static void
+keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys)
+{
+	Wlwin *wl;
+
+	wl = data;
+	qlock(&wl->clip.lk);
+	wl->clip.serial = serial;
+	qunlock(&wl->clip.lk);
+}
+
+static struct {
+	Rendez z;
+	QLock lk;
+	int active;
+	long keytime;
+	int32_t key;
+	int32_t rate;
+	int32_t delay;
+} repeatstate;
+
+static int
+isactive(void *arg)
+{
+	return repeatstate.active;
+}
+
+void
+repeatproc(void *_dummy)
+{
+	int ms;
+	long keytime;
+
+	USED(_dummy);
+	for(;;){
+		ksleep(&repeatstate.z, isactive, 0);
+		qlock(&repeatstate.lk);
+		keytime = repeatstate.keytime;
+		qunlock(&repeatstate.lk);
+		osmsleep(repeatstate.delay);
+
+repeat:
+		qlock(&repeatstate.lk);
+		if(repeatstate.active == 0 || keytime != repeatstate.keytime){
+			qunlock(&repeatstate.lk);
+			continue;
+		}
+		ms = 1000/repeatstate.rate;
+		kbdkey(repeatstate.key, 0);
+		kbdkey(repeatstate.key, 1);
+		qunlock(&repeatstate.lk);
+		osmsleep(ms);
+		goto repeat;
+	}
+}
+
+static void
+keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay)
+{
+	qlock(&repeatstate.lk);
+	repeatstate.rate = rate;
+	repeatstate.delay = delay;
+	qunlock(&repeatstate.lk);
+}
+
+static void
+keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface)
+{
+	Wlwin *wl;
+
+	wl = data;
+	kbdkey(Kshift, 0);
+	kbdkey(Kmod4, 0);
+	kbdkey(Kctl, 0);
+	kbdkey(Kalt, 0);
+	if(wl->alt != Aunpress){
+		kbdkey(Kalt, 1);
+		kbdkey(Kalt, 0);
+		wl->alt = Aunpress;
+	}
+	qlock(&repeatstate.lk);
+	repeatstate.active = 0;
+	repeatstate.key = 0;
+	qunlock(&repeatstate.lk);
+}
+
+static void
+keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
+{
+	Wlwin *wl;
+	uint32_t utf32;
+	int repeat;
+
+	wl = data;
+	xkb_keysym_t keysym = xkb_state_key_get_one_sym(wl->xkb_state, key+8);
+	switch(keysym) {
+	case XKB_KEY_Return:
+		utf32 = '\n';
+		break;
+	case XKB_KEY_Tab:
+		utf32 = '\t';
+		break;
+	case XKB_KEY_Up:
+		utf32 = Kup;
+		break;
+	case XKB_KEY_Down:
+		utf32 = Kdown;
+		break;
+	case XKB_KEY_Left:
+		utf32 = Kleft;
+		break;
+	case XKB_KEY_Right:
+		utf32 = Kright;
+		break;
+	case XKB_KEY_Page_Up:
+		utf32 = Kpgup;
+		break;
+	case XKB_KEY_Page_Down:
+		utf32 = Kpgdown;
+		break;
+	case XKB_KEY_Control_L:
+	case XKB_KEY_Control_R:
+		utf32 = Kctl;
+		break;
+	case XKB_KEY_Alt_R:
+		utf32 = Kaltgr;
+		break;
+	case XKB_KEY_Alt_L:
+		utf32 = Kalt;
+		break;
+	case XKB_KEY_Shift_L:
+	case XKB_KEY_Shift_R:
+		utf32 = Kshift;
+		break;
+	case XKB_KEY_Super_L:
+	case XKB_KEY_Super_R:
+		utf32 = Kmod4;
+		break;
+	case XKB_KEY_End:
+		utf32 = Kend;
+		break;
+	case XKB_KEY_Begin:
+		utf32 = Khome;
+		break;
+	case XKB_KEY_Insert:
+		utf32 = Kins;
+		break;
+	case XKB_KEY_F1:
+	case XKB_KEY_F2:
+	case XKB_KEY_F3:
+	case XKB_KEY_F4:
+	case XKB_KEY_F5:
+	case XKB_KEY_F6:
+	case XKB_KEY_F7:
+	case XKB_KEY_F8:
+	case XKB_KEY_F9:
+	case XKB_KEY_F10:
+	case XKB_KEY_F11:
+	case XKB_KEY_F12:
+		utf32 = KF|(keysym - XKB_KEY_F1 + 1);
+		break;
+	case XKB_KEY_XF86AudioPrev:
+		utf32 = Ksbwd;
+		break;
+	case XKB_KEY_XF86AudioNext:
+		utf32 = Ksfwd;
+		break;
+	case XKB_KEY_XF86AudioPlay:
+		utf32 = Kpause;
+		break;
+	case XKB_KEY_XF86AudioLowerVolume:
+		utf32 = Kvoldn;
+		break;
+	case XKB_KEY_XF86AudioRaiseVolume:
+		utf32 = Kvolup;
+		break;
+	case XKB_KEY_XF86AudioMute:
+		utf32 = Kmute;
+		break;
+
+	/* Japanese layout; see /sys/lib/kbmap/jp */
+	case XKB_KEY_Muhenkan:
+		utf32 = 0x0c; /* ^l */
+		break;
+	case XKB_KEY_Henkan:
+		utf32 = 0x1c; /* ^\ */
+		break;
+	case XKB_KEY_Hiragana:
+		utf32 = 0x0e; /* ^n */
+		break;
+	case XKB_KEY_Katakana:
+		utf32 = 0x0b; /* ^k */
+		break;
+	case XKB_KEY_Hiragana_Katakana:
+		/* board may not maintain kana state */
+		if(xkb_state_mod_name_is_active(wl->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0)
+			utf32 = 0x0b;
+		else
+			utf32 = 0x0e;
+		break;
+	default:
+		utf32 = xkb_keysym_to_utf32(keysym);
+		break;
+	}
+	if(utf32 == 0)
+		return;
+
+	if(state == 1){
+		if(utf32 == Kalt){
+			if(wl->alt == Aunpress)
+				wl->alt = Apress;
+			else
+				wl->alt = Aunpress;
+		} else {
+			switch(wl->alt){
+			case Apress:
+			case Aenter1:
+				wl->alt++;
+				break;
+			case Aenter2:
+				wl->alt = Aunpress;
+			}
+		}
+	}
+	repeat = state && utf32 != Kctl && utf32 != Kshift && utf32 != Kalt && utf32 != Kmod4;
+	kbdkey(utf32, state);
+	qlock(&repeatstate.lk);
+	repeatstate.active = repeat;
+	repeatstate.keytime = time;
+	repeatstate.key = utf32;
+	qunlock(&repeatstate.lk);
+	wakeup(&repeatstate.z);
+}
+
+static void
+keyboard_modifiers (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
+{
+	Wlwin *wl;
+
+	wl = data;
+	xkb_state_update_mask(wl->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
+}
+
+static const struct wl_callback_listener wl_surface_frame_listener = {
+	.done = wl_surface_frame_done,
+};
+
+static struct wl_keyboard_listener keyboard_listener = {
+	.keymap = keyboard_keymap,
+	.enter = keyboard_enter,
+	.leave = keyboard_leave,
+	.key = keyboard_key,
+	.modifiers = keyboard_modifiers,
+	.repeat_info = keyboard_repeat_info,
+};
+
+enum{
+	P9Mouse1 = 1,
+	P9Mouse2 = 2,
+	P9Mouse3 = 4,
+};
+
+static int
+csd_handle_mouse(Wlwin *wl, uint32_t button, uint32_t serial)
+{
+	if(ptinrect(wl->mouse.xy, wl->csd_rects.button_close)){
+		wlclose(wl);
+		return 1;
+	}
+	if(ptinrect(wl->mouse.xy, wl->csd_rects.button_maximize)){
+		wltogglemaximize(wl);
+		return 1;
+	}
+	if(ptinrect(wl->mouse.xy, wl->csd_rects.button_minimize)){
+		wlminimize(wl);
+		return 1;
+	}
+	if(ptinrect(wl->mouse.xy, wl->csd_rects.bar)){
+		switch (button) {
+		case BTN_LEFT: wlmove(wl, serial); break;
+		case BTN_RIGHT: wlmenu(wl, serial); break;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+static void
+pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
+{
+	Wlwin *wl;
+	int m;
+
+	wl = data;
+	switch(button){
+	case BTN_LEFT: m = P9Mouse1; break;
+	case BTN_MIDDLE: m = P9Mouse2; break;
+	case BTN_RIGHT: m = P9Mouse3; break;
+	default: m = 0; break;
+	}
+
+	if(state)
+		wl->mouse.buttons |= m;
+	else
+		wl->mouse.buttons &= ~m;
+
+	wl->mouse.msec = time;
+	if(state && wl->client_side_deco && csd_handle_mouse(wl, button, serial))
+		return;
+
+	absmousetrack(wl->mouse.xy.x, wl->mouse.xy.y, wl->mouse.buttons, wl->mouse.msec);
+}
+
+static void
+pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+	Wlwin *wl;
+
+	wl = data;
+	wl->mouse.xy.x = surface_x / 256;
+	wl->mouse.xy.y = surface_y / 256;
+	wl->mouse.msec = time;
+	absmousetrack(wl->mouse.xy.x, wl->mouse.xy.y, wl->mouse.buttons, wl->mouse.msec);
+}
+
+static void
+pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+	Wlwin *wl;
+
+	wl = data;
+	wl->pointerserial = serial;
+	pointer_handle_motion(data, wl_pointer, wl->mouse.msec, surface_x, surface_y);
+	setcursor();
+}
+
+static void
+pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface)
+{
+}
+
+static void
+pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+	Wlwin *wl;
+	int buttons;
+
+	if(axis == 1)
+		return; /* Horizontal scroll */
+	wl = data;
+	wl->mouse.msec = time;
+	/* p9 expects a scroll event to work like a button, a set and a release */
+	buttons = wl->mouse.buttons & ~24;
+	absmousetrack(wl->mouse.xy.x, wl->mouse.xy.y, buttons | (value > 0 ? 16 : 8), wl->mouse.msec);
+	absmousetrack(wl->mouse.xy.x, wl->mouse.xy.y, buttons, wl->mouse.msec);
+}
+
+static const struct wl_pointer_listener pointer_listener = {
+	.enter = pointer_handle_enter,
+	.leave = pointer_handle_leave,
+	.motion = pointer_handle_motion,
+	.button = pointer_handle_button,
+	.axis = pointer_handle_axis,
+};
+
+static void
+seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t capabilities)
+{
+	Wlwin *wl;
+	int pointer, keyboard;
+
+	wl = data;
+	pointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
+	if(pointer && wl->pointer == nil){
+		wl->pointer = wl_seat_get_pointer(seat);
+		wl_pointer_add_listener(wl->pointer, &pointer_listener, wl);
+	}else if(!pointer && wl->pointer != nil){
+		wl_pointer_release(wl->pointer);
+		wl->pointer = nil;
+	}
+
+	keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
+	if(keyboard && wl->keyboard == nil){
+		wl->keyboard = wl_seat_get_keyboard(seat);
+		wl_keyboard_add_listener(wl->keyboard, &keyboard_listener, wl);
+	}else if(!keyboard && wl->keyboard != nil){
+		wl_keyboard_release(wl->keyboard);
+		wl->keyboard = nil;
+	}
+}
+
+static void
+seat_handle_name(void *data, struct wl_seat *seat, const char *name)
+{
+}
+
+static const struct wl_seat_listener seat_listener = {
+	.capabilities = seat_handle_capabilities,
+	.name = seat_handle_name,
+};
+
+static void
+data_source_handle_send(void *data, struct wl_data_source *source, const char *mime_type, int fd)
+{
+	ulong n;
+	ulong pos;
+	ulong len;
+	Wlwin *wl;
+
+	if(strcmp(mime_type, "text/plain;charset=utf-8") != 0)
+		return;
+
+	wl = data;
+	qlock(&wl->clip.lk);
+	len = strlen(wl->clip.content);
+	for(pos = 0; (n = write(fd, wl->clip.content+pos, len-pos)) > 0 && pos < len; pos += n)
+		;
+	wl->clip.posted = 0;
+	close(fd);
+	qunlock(&wl->clip.lk);
+}
+
+static void
+data_source_handle_cancelled(void *data, struct wl_data_source *source)
+{
+	Wlwin *wl;
+
+	wl = data;
+	qlock(&wl->clip.lk);
+	wl->clip.posted = 0;
+	qunlock(&wl->clip.lk);
+	wl_data_source_destroy(source);
+}
+
+static const struct wl_data_source_listener data_source_listener = {
+	.send = data_source_handle_send,
+	.cancelled = data_source_handle_cancelled,
+};
+
+static void
+primsel_source_handle_send(void *data, struct zwp_primary_selection_source_v1 *source, const char *mime_type, int fd)
+{
+	ulong n;
+	ulong pos;
+	ulong len;
+	Wlwin *wl;
+
+	if(strcmp(mime_type, "text/plain;charset=utf-8") != 0)
+		return;
+
+	wl = data;
+	qlock(&wl->clip.lk);
+	len = strlen(wl->clip.content);
+	for(pos = 0; (n = write(fd, wl->clip.content+pos, len-pos)) > 0 && pos < len; pos += n)
+		;
+	wl->clip.primsel_posted = 0;
+	close(fd);
+	qunlock(&wl->clip.lk);
+}
+
+static void
+primsel_source_handle_cancelled(void *data, struct zwp_primary_selection_source_v1 *source)
+{
+	Wlwin *wl;
+
+	wl = data;
+	qlock(&wl->clip.lk);
+	wl->clip.primsel_posted = 0;
+	qunlock(&wl->clip.lk);
+	zwp_primary_selection_source_v1_destroy(source);
+}
+
+static const struct zwp_primary_selection_source_v1_listener primsel_source_listener = {
+	.send = primsel_source_handle_send,
+	.cancelled = primsel_source_handle_cancelled,
+};
+
+static void
+data_device_drop_enter(void* data, struct wl_data_device* wl_data_device, uint serial, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer* id)
+{
+
+}
+
+static void
+data_device_drop_motion(void* data, struct wl_data_device* wl_data_device, uint time, wl_fixed_t x, wl_fixed_t y)
+{
+
+}
+
+static void
+data_device_drop_leave(void* data, struct wl_data_device* wl_data_device)
+{
+
+}
+
+static void
+data_device_handle_data_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer)
+{
+}
+
+static void
+data_device_drop_drop(void* data, struct wl_data_device* wl_data_device)
+{
+}
+
+static void
+data_device_handle_selection(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer)
+{
+	Wlwin *wl;
+	ulong n;
+	ulong size;
+	ulong pos;
+	int fds[2];
+
+	// An application has set the clipboard contents
+	if(offer == nil)
+		return;
+
+	wl = data;
+	pipe2(fds, O_CLOEXEC);
+	wl_data_offer_receive(offer, "text/plain;charset=utf-8", fds[1]);
+	close(fds[1]);
+
+	wl_display_roundtrip(wl->display);
+
+	qlock(&wl->clip.lk);
+	size = 8192;
+	wl->clip.content = realloc(wl->clip.content, size+1);
+	memset(wl->clip.content, 0, size+1);
+	for(pos = 0; (n = read(fds[0], wl->clip.content+pos, size-pos)) > 0;){
+		pos += n;
+		if(pos >= size){
+			size *= 2;
+			wl->clip.content = realloc(wl->clip.content, size+1);
+			memset(wl->clip.content+pos, 0, (size-pos)+1);
+		}
+	}
+	close(fds[0]);
+	qunlock(&wl->clip.lk);
+	wl_data_offer_destroy(offer);
+}
+
+static const struct wl_data_device_listener data_device_listener = {
+	.data_offer = data_device_handle_data_offer,
+	.selection = data_device_handle_selection,
+	.leave = data_device_drop_leave,
+	.motion = data_device_drop_motion,
+	.enter = data_device_drop_enter,
+	.drop = data_device_drop_drop,
+};
+
+static void
+xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
+{
+	xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+	.ping = xdg_wm_base_ping,
+};
+
+static void
+zxdg_toplevel_decoration_v1_handle_configure(void *data, struct zxdg_toplevel_decoration_v1 *deco, uint32_t mode)
+{
+	Wlwin *wl = data;
+	int csd = mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
+	if(csd == wl->client_side_deco){
+		return;
+	}
+
+	wl->client_side_deco = csd;
+	wlresize(wl, wl->dx, wl->dy);
+}
+
+static const struct zxdg_toplevel_decoration_v1_listener zxdg_toplevel_decoration_v1_listener = {
+	.configure = zxdg_toplevel_decoration_v1_handle_configure,
+};
+
+static void
+mode(void *data, struct wl_output*, uint, int x, int y, int)
+{
+	Wlwin *wl;
+
+	wl = data;
+	if(x >= wl->monx && y >= wl->mony){
+		wl->monx = x;
+		wl->mony = y;
+	}
+}
+static void done(void*, struct wl_output*){}
+static void scale(void*, struct wl_output*, int){}
+static void geometry(void*, struct wl_output*, int, int, int, int, int, const char*, const char*, int){}
+
+static const struct wl_output_listener output_listener = {
+	.geometry = geometry,
+	.mode = mode,
+	.done = done,
+	.scale = scale,
+};
+
+static void
+handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
+{
+	Wlwin *wl;
+	struct wl_output *out;
+
+	wl = data;
+	if(strcmp(interface, wl_shm_interface.name) == 0){
+		wl->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
+	} else if(strcmp(interface, wl_output_interface.name) == 0){
+		out = wl_registry_bind(registry, name, &wl_output_interface, 2);
+		wl_output_add_listener(out, &output_listener, wl);
+	} else if(strcmp(interface, wl_seat_interface.name) == 0){
+		//We don't support multiseat
+		if(wl->seat != nil)
+			return;
+		wl->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4);
+		wl_seat_add_listener(wl->seat, &seat_listener, wl);
+	} else if(strcmp(interface, wl_compositor_interface.name) == 0){
+		wl->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
+	} else if(strcmp(interface, xdg_wm_base_interface.name) == 0){
+		wl->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
+		xdg_wm_base_add_listener(wl->xdg_wm_base, &xdg_wm_base_listener, wl);
+	} else if(strcmp(interface, wl_data_device_manager_interface.name) == 0){
+		wl->data_device_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3);
+	} else if(strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0){
+		wl->decoman = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1);
+	} else if(strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0){
+		wl->primsel = wl_registry_bind(registry, name, &zwp_primary_selection_device_manager_v1_interface, 1);
+	} else if(strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name) == 0){
+		wl->vpmgr = wl_registry_bind(registry, name, &zwlr_virtual_pointer_manager_v1_interface, 1);
+	}
+}
+
+static void
+handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
+{
+}
+
+const struct wl_registry_listener registry_listener = {
+	.global = handle_global,
+	.global_remove = handle_global_remove,
+};
+
+void
+wlsetcb(Wlwin *wl)
+{
+	struct wl_registry *registry;
+	struct xdg_surface *xdg_surface;
+	struct wl_callback *cb;
+	struct zxdg_toplevel_decoration_v1 *deco;
+
+	//Wayland doesn't do keyboard repeat, but also may
+	//not tell us what the user would like, so we
+	//pick some sane defaults.
+	repeatstate.delay = 200;
+	repeatstate.rate = 20;
+	kproc("keyboard repeat", repeatproc, 0);
+
+	registry = wl_display_get_registry(wl->display);
+	wl_registry_add_listener(registry, &registry_listener, wl);
+	wl_display_roundtrip(wl->display);
+	wl->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+
+	if(wl->shm == nil || wl->compositor == nil || wl->xdg_wm_base == nil || wl->seat == nil)
+		panic("required wayland capabilities not met");
+
+	if(wl->vpmgr != nil)
+		wl->vpointer = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(wl->vpmgr, wl->seat);
+
+	wlallocbuffer(wl);
+	wl->surface = wl_compositor_create_surface(wl->compositor);
+
+	xdg_surface = xdg_wm_base_get_xdg_surface(wl->xdg_wm_base, wl->surface);
+	wl->xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+	xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, wl);
+	xdg_toplevel_add_listener(wl->xdg_toplevel, &xdg_toplevel_listener, wl);
+
+	wl->client_side_deco = wl->decoman == nil;
+	if(wl->decoman != nil){
+		deco = zxdg_decoration_manager_v1_get_toplevel_decoration(wl->decoman, wl->xdg_toplevel);
+		zxdg_toplevel_decoration_v1_add_listener(wl->decoman, &zxdg_toplevel_decoration_v1_listener, wl);
+		zxdg_toplevel_decoration_v1_set_mode(deco, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+	}
+
+	wl_surface_commit(wl->surface);
+	wl_display_roundtrip(wl->display);
+
+	xdg_toplevel_set_app_id(wl->xdg_toplevel, "drawcpu");
+
+	cb = wl_surface_frame(wl->surface);
+	wl_callback_add_listener(cb, &wl_surface_frame_listener, wl);
+
+	if(wl->data_device_manager != nil && wl->seat != nil){
+		wl->data_device = wl_data_device_manager_get_data_device(wl->data_device_manager, wl->seat);
+		wl_data_device_add_listener(wl->data_device, &data_device_listener, wl);
+		if(wl->primsel != nil)
+			wl->primsel_device = zwp_primary_selection_device_manager_v1_get_device(wl->primsel, wl->seat);
+		else
+			iprint("primary selection not available, clipboard will not work\n");
+	}
+}
+
+void
+wlsettitle(Wlwin *wl, char *s)
+{
+	xdg_toplevel_set_title(wl->xdg_toplevel, s);
+}
+
+void
+wlsetsnarf(Wlwin *wl, char *s)
+{
+	struct wl_data_source *source;
+	struct zwp_primary_selection_source_v1 *psource;
+
+	qlock(&wl->clip.lk);
+	free(wl->clip.content);
+	wl->clip.content = strdup(s);
+
+	/* Do we still own the clipboard? */
+	if(wl->clip.posted == 0){
+		source = wl_data_device_manager_create_data_source(wl->data_device_manager);
+		wl_data_source_add_listener(source, &data_source_listener, wl);
+		wl_data_source_offer(source, "text/plain;charset=utf-8");
+		wl_data_device_set_selection(wl->data_device, source, wl->clip.serial);
+		wl->clip.posted = 1;
+	}
+
+	/* Primary selection */
+	if(wl->clip.primsel_posted == 0){
+		psource = zwp_primary_selection_device_manager_v1_create_source(wl->primsel);
+		zwp_primary_selection_source_v1_add_listener(psource, &primsel_source_listener, wl);
+		zwp_primary_selection_source_v1_offer(psource, "text/plain;charset=utf-8");
+		zwp_primary_selection_device_v1_set_selection(wl->primsel_device, psource, wl->clip.serial);
+		wl->clip.primsel_posted = 1;
+	}
+
+	qunlock(&wl->clip.lk);
+}
+
+char*
+wlgetsnarf(Wlwin *wl)
+{
+	char *s;
+	qlock(&wl->clip.lk);
+	s = strdup(wl->clip.content != nil ? wl->clip.content : "");
+	qunlock(&wl->clip.lk);
+	return s;
+}
+
+void
+wlsetmouse(Wlwin *wl, Point p)
+{
+	Point delta;
+	if(wl->vpointer == nil)
+		return;
+
+	delta.x = p.x - wl->mouse.xy.x;
+	delta.y = p.y - wl->mouse.xy.y;
+	wl->mouse.xy = p;
+	zwlr_virtual_pointer_v1_motion(wl->vpointer,  time(nil) * 1000, delta.x * 256, delta.y * 256);
+	zwlr_virtual_pointer_v1_frame(wl->vpointer);
+}
--- /dev/null
+++ b/gui-wl/wl-inc.h
@@ -1,0 +1,117 @@
+typedef struct Wlwin Wlwin;
+typedef struct Clipboard Clipboard;
+typedef struct Csd Csd;
+
+/* The contents of the clipboard
+ * are not stored in the compositor.
+ * Instead we signal that we have content
+ * and the compositor gives us a pipe
+ * to the program that wants it when
+ * the content is pasted. */
+struct Clipboard {
+	QLock lk;
+	char *content;
+
+	/* Wayland requires that in order
+	 * to put data in to the clipboard
+	 * you must be the focused application.
+	 * So we must provide the serial we get
+	 * on keyboard.enter. */
+	u32int serial;
+
+	/* Because we dont actually cough
+	 * up the buffer until someone else
+	 * asks, we can change the contents
+	 * locally without a round trip.
+	 * Posted stores if we already made
+	 * our round trip */
+	int posted;
+	int primsel_posted;
+};
+
+struct Mouse {
+	Point xy;
+	int buttons;
+	ulong msec;
+};
+
+enum{
+	Aunpress,
+	Apress,
+	Aenter1,
+	Aenter2,
+};
+
+enum CsdSizes {
+	csd_bar_height = 24,
+	csd_button_width = 16,
+};
+
+struct Csd {
+	Rectangle bar;
+	Rectangle button_close;
+	Rectangle button_maximize;
+	Rectangle button_minimize;
+};
+
+struct Wlwin {
+	int dx;
+	int dy;
+	int monx;
+	int mony;
+	Mouse mouse;
+	Clipboard clip;
+	Rectangle r;
+	int dirty;
+	int alt; /* Kalt state */
+	int maximized;
+
+	/* Wayland State */
+	int runing;
+	int poolsize;
+	int pointerserial;
+	void *shm_data;
+	struct wl_compositor *compositor;
+	struct wl_display *display;
+	struct wl_surface *surface;
+	struct wl_surface *cursorsurface;
+	struct xdg_wm_base *xdg_wm_base;
+	struct xdg_toplevel *xdg_toplevel;
+	struct wl_shm_pool *pool;
+	struct wl_buffer *screenbuffer;
+	struct wl_buffer *cursorbuffer;
+	struct wl_shm *shm;
+	struct wl_seat *seat;
+	struct wl_data_device_manager *data_device_manager;
+	struct wl_data_device *data_device;
+	struct wl_pointer *pointer;
+	struct wl_keyboard *keyboard;
+	/* Keyboard state */
+	struct xkb_state *xkb_state;
+	struct xkb_context *xkb_context;
+
+	struct zxdg_decoration_manager_v1 *decoman;
+	int client_side_deco;
+	Csd csd_rects;
+
+	struct zwp_primary_selection_device_manager_v1 *primsel;
+	struct zwp_primary_selection_device_v1 *primsel_device;
+
+	struct zwlr_virtual_pointer_manager_v1 *vpmgr;
+	struct zwlr_virtual_pointer_v1 *vpointer;
+};
+
+void wlallocbuffer(Wlwin*);
+void wlsetcb(Wlwin*);
+void wlsettitle(Wlwin*, char*);
+char* wlgetsnarf(Wlwin*);
+void wlsetsnarf(Wlwin*, char*);
+void wlsetmouse(Wlwin*, Point);
+void wldrawcursor(Wlwin*, Cursorinfo*);
+void wlresize(Wlwin*, int, int);
+void wlflush(Wlwin*);
+void wlclose(Wlwin*);
+void wltogglemaximize(Wlwin*);
+void wlminimize(Wlwin*);
+void wlmove(Wlwin*, uint32_t);
+void wlmenu(Wlwin*, uint32_t);
--- /dev/null
+++ b/gui-wl/wl-screen.c
@@ -1,0 +1,286 @@
+#define _POSIX_C_SOURCE 200809L
+#include <sys/mman.h>
+#include <wayland-client.h>
+#include <wayland-client-protocol.h>
+#include <linux/input-event-codes.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <xkbcommon/xkbcommon.h>
+#include "xdg-shell-protocol.h"
+
+#include "u.h"
+#include "lib.h"
+#include "kern/dat.h"
+#include "kern/fns.h"
+#include "error.h"
+#include "user.h"
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include "screen.h"
+#include "wl-inc.h"
+
+#undef close
+
+static Wlwin *gwin;
+
+Memimage *gscreen;
+
+static Wlwin*
+newwlwin(void)
+{
+	Wlwin *wl;
+
+	wl = mallocz(sizeof *wl, 1);
+	if(wl == nil)
+		panic("malloc Wlwin");
+	wl->dx = 1024;
+	wl->dy = 1024;
+	wl->monx = wl->dx;
+	wl->mony = wl->dy;
+	return wl;
+}
+
+void
+wlclose(Wlwin *wl)
+{
+	wl->runing = 0;
+	exits(nil);
+}
+
+void
+wltogglemaximize(Wlwin *wl)
+{
+	if(wl->maximized)
+		xdg_toplevel_unset_maximized(wl->xdg_toplevel);
+	else
+		xdg_toplevel_set_maximized(wl->xdg_toplevel);
+}
+
+void
+wlminimize(Wlwin *wl)
+{
+	xdg_toplevel_set_minimized(wl->xdg_toplevel);
+}
+
+void
+wlmove(Wlwin *wl, uint32_t serial)
+{
+	xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial);
+}
+
+void
+wlmenu(Wlwin *wl, uint32_t serial)
+{
+	xdg_toplevel_show_window_menu(wl->xdg_toplevel, wl->seat, serial, wl->mouse.xy.x, wl->mouse.xy.y);
+}
+
+static void
+wlupdatecsdrects(Wlwin *wl)
+{
+	Point offset;
+	Rectangle button;
+
+	if(!wl->client_side_deco) {
+		memset(&wl->csd_rects, 0, sizeof wl->csd_rects);
+		return;
+	}
+
+	wl->csd_rects.bar = Rect(0, 0, wl->dx, csd_bar_height);
+
+	offset = Pt(csd_button_width + 4, 0);
+	button = Rect(0, 4, csd_button_width, csd_button_width + 4);
+	button = rectsubpt(button, offset);
+
+	wl->csd_rects.button_close = button = rectaddpt(button, Pt(wl->dx, 0));
+	wl->csd_rects.button_maximize = button = rectsubpt(button, offset);
+	wl->csd_rects.button_minimize = rectsubpt(button, offset);
+}
+
+static void
+wlfillrect(Wlwin *wl, Rectangle rect, uint32_t color)
+{
+	Point p;
+	uint32_t *data = wl->shm_data;
+
+	for(p.y = rect.min.y; p.y < rect.max.y; p.y++)
+		for(p.x = rect.min.x; p.x < rect.max.x; p.x++)
+			data[p.y * wl->dx + p.x] = color;
+}
+
+static void
+wldrawcsd(Wlwin *wl)
+{
+	if(!wl->client_side_deco)
+		return;
+	wlfillrect(wl, wl->csd_rects.bar, 0xAAAAAA);
+	wlfillrect(wl, wl->csd_rects.button_close, DRed >> 8);
+	wlfillrect(wl, wl->csd_rects.button_maximize, DGreen >> 8);
+	wlfillrect(wl, wl->csd_rects.button_minimize, DYellow >> 8);
+}
+
+void
+wlflush(Wlwin *wl)
+{
+	Point p;
+
+	wl_surface_attach(wl->surface, wl->screenbuffer, 0, 0);
+	if(wl->dirty){
+		p.x = wl->r.min.x;
+		for(p.y = wl->r.min.y; p.y < wl->r.max.y; p.y++)
+			memcpy(wl->shm_data+(p.y*wl->dx+p.x)*4, byteaddr(gscreen, p), Dx(wl->r)*4);
+		wl_surface_damage(wl->surface, p.x, wl->r.min.y, Dx(wl->r), Dy(wl->r));
+		wl->dirty = 0;
+	}
+	wl_surface_commit(wl->surface);
+}
+
+void  _screenresize(Rectangle);
+
+void
+wlresize(Wlwin *wl, int x, int y)
+{
+	Rectangle r;
+
+	wl->dx = x;
+	wl->dy = y;
+	wlupdatecsdrects(wl);
+
+	qlock(&drawlock);
+	wlallocbuffer(wl);
+	r = Rect(0, wl->csd_rects.bar.max.y, wl->dx, wl->dy);
+	gscreen = allocmemimage(r, XRGB32);
+	gscreen->clipr = ZR;
+	qunlock(&drawlock);
+
+	screenresize(r);
+
+	qlock(&drawlock);
+	wl->dirty = 1;
+	wl->r = r;
+	wlflush(wl);
+	wldrawcsd(wl);
+	qunlock(&drawlock);
+}
+
+void
+dispatchproc(void *a)
+{
+	Wlwin *wl;
+	wl = a;
+	while(wl->runing)
+		wl_display_dispatch(wl->display);
+}
+
+static Wlwin*
+wlattach(char *label)
+{
+	Rectangle r;
+	Wlwin *wl;
+
+	wl = newwlwin();
+	gwin = wl;
+	wl->display = wl_display_connect(nil);
+	if(wl->display == nil)
+		panic("could not connect to display");
+
+	memimageinit();
+	wlsetcb(wl);
+	wlupdatecsdrects(wl);
+	wlflush(wl);
+	wlsettitle(wl, label);
+
+	r = Rect(0, wl->csd_rects.bar.max.y, wl->dx, wl->dy);
+	gscreen = allocmemimage(r, XRGB32);
+	gscreen->clipr = r;
+	gscreen->r = r;
+	rectclip(&(gscreen->clipr), gscreen->r);
+
+	wl->runing = 1;
+	kproc("wldispatch", dispatchproc, wl);
+	qlock(&drawlock);
+	terminit();
+	wlflush(wl);
+	wldrawcsd(wl);
+	qunlock(&drawlock);
+	return wl;
+}
+
+void
+screeninit(void)
+{
+	wlattach("drawcpu");
+}
+
+void
+guimain(void)
+{
+	cpubody();
+}
+
+Memdata*
+attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
+{
+	*r = gscreen->clipr;
+	*chan = gscreen->chan;
+	*depth = gscreen->depth;
+	*width = gscreen->width;
+	*softscreen = 1;
+
+	gscreen->data->ref++;
+	return gscreen->data;
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+	gwin->dirty = 1;
+	gwin->r = r;
+	wlflush(gwin);
+}
+
+void
+screensize(Rectangle r, ulong chan)
+{
+	flushmemscreen(r);
+}
+
+void
+setcursor(void)
+{
+	qlock(&drawlock);
+	wldrawcursor(gwin, &cursor);
+	qunlock(&drawlock);
+}
+
+void
+mouseset(Point p)
+{
+	wlsetmouse(gwin, p);
+}
+
+char*
+clipread(void)
+{
+	return wlgetsnarf(gwin);
+}
+
+int
+clipwrite(char *data)
+{
+	wlsetsnarf(gwin, data);
+	return strlen(data);
+}
+
+void
+getcolor(ulong i, ulong *r, ulong *g, ulong *b)
+{
+}
+
+void
+setcolor(ulong index, ulong red, ulong green, ulong blue)
+{
+}
--- /dev/null
+++ b/gui-wl/wl-util.c
@@ -1,0 +1,138 @@
+#define _POSIX_C_SOURCE 200809L
+#include <sys/mman.h>
+#include <wayland-client.h>
+#include <wayland-client-protocol.h>
+#include <linux/input-event-codes.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <xkbcommon/xkbcommon.h>
+#include "xdg-shell-protocol.h"
+
+#include "u.h"
+#include "lib.h"
+#include "kern/dat.h"
+#include "kern/fns.h"
+#include "error.h"
+#include "user.h"
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include "screen.h"
+#include "wl-inc.h"
+
+#undef getenv
+#undef close
+
+static int
+wlcreateshm(off_t size)
+{
+	char name[] = "/drawcpu--XXXXXX";
+	char *dir, *path;
+	int fd;
+
+	if((dir = getenv("XDG_RUNTIME_DIR")) == nil)
+		panic("XDG_RUNTIME_DIR not set");
+
+	path = malloc(strlen(dir) + sizeof(name) + 1);
+	strcpy(path, dir);
+	strcat(path, name);
+
+	if((fd = mkostemp(path, O_CLOEXEC)) >= 0)
+		unlink(path);
+	free(path);
+
+	return fd;
+}
+
+void
+wlallocpool(Wlwin *wl)
+{
+	int screensize, cursorsize;
+	int depth;
+	int fd;
+
+	if(wl->pool != nil)
+		wl_shm_pool_destroy(wl->pool);
+
+	depth = 4;
+	screensize = wl->monx * wl->mony * depth;
+	cursorsize = 16 * 16 * depth;
+
+	fd = wlcreateshm(screensize+cursorsize);
+	if(fd < 0)
+		panic("could not mk_shm_fd");
+	ftruncate(fd, screensize+cursorsize);
+
+	wl->shm_data = mmap(nil, screensize+cursorsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if(wl->shm_data == MAP_FAILED)
+		panic("could not mmap shm_data");
+
+	wl->pool = wl_shm_create_pool(wl->shm, fd, screensize+cursorsize);
+	wl->poolsize = screensize+cursorsize;
+	close(fd);
+}
+
+void
+wlallocbuffer(Wlwin *wl)
+{
+	int depth;
+	int size;
+
+	depth = 4;
+	size = wl->dx * wl->dy * depth;
+	if(wl->pool == nil || size+(16*16*depth) > wl->poolsize)
+		wlallocpool(wl);
+
+	assert(size+(16*16*depth) <= wl->poolsize);
+
+	if(wl->screenbuffer != nil)
+		wl_buffer_destroy(wl->screenbuffer);
+	if(wl->cursorbuffer != nil)
+		wl_buffer_destroy(wl->cursorbuffer);
+
+	wl->screenbuffer = wl_shm_pool_create_buffer(wl->pool, 0, wl->dx, wl->dy, wl->dx*4, WL_SHM_FORMAT_XRGB8888);
+	wl->cursorbuffer = wl_shm_pool_create_buffer(wl->pool, size, 16, 16, 16*4, WL_SHM_FORMAT_ARGB8888);
+}
+
+enum {
+	White = 0xFFFFFFFF,
+	Black = 0xFF000000,
+	Green = 0xFF00FF00,
+	Transparent = 0x00000000,
+};
+
+void
+wldrawcursor(Wlwin *wl, Cursorinfo *c)
+{
+	int i, j;
+	int pos, mask;
+	u32int *buf;
+	uint16_t clr[16], set[16];
+
+	buf = wl->shm_data+(wl->dx*wl->dy*4);
+	for(i=0,j=0; i < 16; i++,j+=2){
+		clr[i] = c->clr[j]<<8 | c->clr[j+1];
+		set[i] = c->set[j]<<8 | c->set[j+1];
+	}
+	for(i=0; i < 16; i++){
+		for(j = 0; j < 16; j++){
+			pos = i*16 + j;
+			mask = (1<<16) >> j;
+
+			buf[pos] = Transparent;
+			if(clr[i] & mask)
+				buf[pos] = White;
+			if(set[i] & mask)
+				buf[pos] = Black;
+		}
+	}
+	if(wl->cursorsurface == nil)
+		wl->cursorsurface = wl_compositor_create_surface(wl->compositor);
+	wl_surface_attach(wl->cursorsurface, wl->cursorbuffer, 0, 0);
+	wl_surface_damage(wl->cursorsurface, 0, 0, 16, 16);
+	wl_surface_commit(wl->cursorsurface);
+	wl_pointer_set_cursor(wl->pointer, wl->pointerserial, wl->cursorsurface, -c->offset.x, -c->offset.y);
+}
--- /dev/null
+++ b/gui-x11/Makefile
@@ -1,0 +1,15 @@
+ROOT=..
+include ../Make.config
+LIB=libgui.a
+
+OFILES=\
+	x11.$O\
+	keysym2ucs-x11.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+x11.$O:	../glenda-t.xbm
+
--- /dev/null
+++ b/gui-x11/keysym2ucs-x11.c
@@ -1,0 +1,863 @@
+/* $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 */
+
+  { 0xfe50, 0x0060 }, /* dead grave */
+  { 0xfe51, 0x00b4 }, /* dead acute */
+  { 0xfe52, 0x005e }, /* dead circumflex */
+  { 0xfe53, 0x007e }, /* dead tilde */
+
+};
+
+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/x11.c
@@ -1,0 +1,1260 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include "screen.h"
+
+typedef struct Cursor Cursor;
+
+#undef	long
+#undef  ulong
+#define	Font		XFont
+#define	Screen	XScreen
+#define	Display	XDisplay
+#define	Cursor	XCursor
+
+#undef	getenv
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <X11/keysym.h>
+#include <X11/XKBlib.h>
+#include "keysym2ucs.h"
+
+#undef	Font
+#undef	Screen
+#undef	Display
+#undef	Cursor
+#define	long	int
+#define ulong	p9_ulong
+
+/* 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)
+
+static	XDisplay*	xdisplay;	/* used holding draw lock */
+static int		xtblbit;
+static int 		plan9tox11[256]; /* Values for mapping between */
+static int 		x11toplan9[256]; /* X11 and Plan 9 */
+static	GC		xgccopy;
+static	ulong		xscreenchan;
+static	Drawable	xscreenid;
+static	XImage*		xscreenimage;
+static	Visual		*xvis;
+
+extern char		*geometry;	/* defined in main.c */
+
+#include "../glenda-t.xbm"
+
+Memimage*
+xallocmemimage(Rectangle r, ulong chan, int pmid, XImage **X)
+{
+	Memimage *m;
+	XImage *xi;
+	int offset;
+	int d;
+
+	m = allocmemimage(r, chan);
+	if(m == nil)
+		return nil;
+	if(chan != GREY1 && chan != xscreenchan)
+		return m;
+
+	d = m->depth;
+	if(d == 24)
+		offset = r.min.x&(4-1);
+	else
+		offset = r.min.x&(31/d);
+	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;
+	}
+
+	/*
+	 * 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;
+	XInitImage(xi);
+	XFlush(xdisplay);
+
+	*X = xi;
+
+	return m;
+}
+
+/*
+ * X11 window management and kernel hooks.
+ * Oh, how I loathe this code!
+ */
+
+static XColor			map[256];	/* Plan 9 colormap array */
+static XColor			map7[128];	/* Plan 9 colormap array */
+static uchar			map7to8[128][2];
+static Colormap			xcmap;		/* Default shared colormap  */
+
+/* for copy/paste, lifted from plan9ports */
+static Atom clipboard;
+static Atom utf8string;
+static Atom targets;
+static Atom text;
+static Atom compoundtext;
+static Atom wmpid;
+static Atom wmdelete;
+
+static	Drawable	xdrawable;
+static	void		xexpose(XEvent*);
+static	void		xresize(XEvent*);
+static	void		xmouse(XEvent*);
+static	void		xkeyboard(XEvent*);
+static	void		xmapping(XEvent*);
+static	void		xdestroy(XEvent*);
+static	void		xselect(XEvent*, XDisplay*);
+static	void		xproc(void*);
+static	void		initmap(XDisplay*, int, Visual*);
+static	GC		creategc(Drawable);
+static	void		graphicscmap(XColor*);
+static	int		xscreendepth;
+static	XDisplay*	xkmcon;	/* used only in xproc */
+static	XDisplay*	xsnarfcon;	/* used holding clip.lk */
+
+static	int	putsnarf, assertsnarf;
+
+Memimage *gscreen;
+
+static int
+shutup(XDisplay *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(XDisplay *d)
+{
+	screenputs = 0;
+	panic("x error");
+	return -1;
+}
+
+
+void
+flushmemscreen(Rectangle r)
+{
+	int x, y;
+	uchar *p;
+
+	assert(!canqlock(&drawlock));
+	if(rectclip(&r, gscreen->clipr) == 0)
+		return;
+
+	if(xtblbit && gscreen->chan == CMAP8)
+		for(y=r.min.y; y<r.max.y; y++)
+			for(x=r.min.x, p=byteaddr(gscreen, Pt(x,y)); x<r.max.x; x++, p++)
+				*p = plan9tox11[*p];
+
+	XPutImage(xdisplay, xscreenid, xgccopy, xscreenimage, r.min.x, r.min.y, r.min.x, r.min.y, Dx(r), Dy(r));
+
+	if(xtblbit && gscreen->chan == CMAP8)
+		for(y=r.min.y; y<r.max.y; y++)
+			for(x=r.min.x, p=byteaddr(gscreen, Pt(x,y)); x<r.max.x; x++, p++)
+				*p = x11toplan9[*p];
+
+	XCopyArea(xdisplay, xscreenid, xdrawable, xgccopy, r.min.x, r.min.y, Dx(r), Dy(r), r.min.x, r.min.y);
+	XFlush(xdisplay);
+}
+
+void
+screeninit(void)
+{
+	int i, n, x, y;
+	char *argv[2];
+	Rectangle r;
+	XWMHints hints;
+	XVisualInfo xvi;
+	int screen;
+	XTextProperty name;
+	XClassHint classhints;
+	XSizeHints normalhints;
+	XSetWindowAttributes attrs;
+	XPixmapFormatValues *pfmt;
+	Pixmap icon_pixmap;
+	unsigned long pid;
+
+	memimageinit();
+
+	xdisplay = XOpenDisplay(NULL);
+	if(xdisplay == 0)
+		panic("XOpenDisplay: %r [DISPLAY=%s]", getenv("DISPLAY"));
+
+	XSetErrorHandler(shutup);
+	XSetIOErrorHandler(panicshutup);
+
+	xkmcon = XOpenDisplay(NULL);
+	if(xkmcon == 0)
+		panic("XOpenDisplay: %r [DISPLAY=%s]", getenv("DISPLAY"));
+
+	XkbSetDetectableAutoRepeat(xkmcon, True, NULL);
+
+	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);
+
+	xsnarfcon = XOpenDisplay(NULL);
+	if(xsnarfcon == 0)
+		panic("XOpenDisplay: %r [DISPLAY=%s]", getenv("DISPLAY"));
+
+	screen = DefaultScreen(xdisplay);
+	xscreendepth = DefaultDepth(xdisplay, screen);
+	if(XMatchVisualInfo(xdisplay, screen, 16, TrueColor, &xvi)
+	|| XMatchVisualInfo(xdisplay, screen, 16, DirectColor, &xvi)){
+		xvis = xvi.visual;
+		xscreendepth = 16;
+		xtblbit = 1;
+	}
+	else if(XMatchVisualInfo(xdisplay, screen, 24, TrueColor, &xvi)
+	|| XMatchVisualInfo(xdisplay, screen, 24, DirectColor, &xvi)){
+		xvis = xvi.visual;
+		xscreendepth = 24;
+		xtblbit = 1;
+	}
+	else if(XMatchVisualInfo(xdisplay, screen, 8, PseudoColor, &xvi)
+	|| XMatchVisualInfo(xdisplay, screen, 8, StaticColor, &xvi)){
+		if(xscreendepth > 8)
+			panic("can't deal with colormapped depth %d screens", xscreendepth);
+		xvis = xvi.visual;
+		xscreendepth = 8;
+	}
+	else{
+		if(xscreendepth != 8)
+			panic("can't deal with depth %d screens", xscreendepth);
+		xvis = DefaultVisual(xdisplay, screen);
+	}
+
+	/*
+	 * 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("unknown screen pixel format");
+
+	initmap(xdisplay, screen, xvis);
+
+	x = y = 0;
+	r = ZR;
+	if(geometry != nil)
+		XParseGeometry(geometry, &x, &y, (unsigned int*)&r.max.x, (unsigned int*)&r.max.y);
+
+	if(r.max.x == 0)
+		r.max.x = WidthOfScreen(ScreenOfDisplay(xdisplay, screen))*3/4;
+	if(r.max.y == 0)
+		r.max.y = HeightOfScreen(ScreenOfDisplay(xdisplay, screen))*3/4;
+
+	attrs.colormap = xcmap;
+	attrs.background_pixel = 0;
+	attrs.border_pixel = 0;
+	/* attrs.override_redirect = 1;*/ /* WM leave me alone! |CWOverrideRedirect */
+	xdrawable = XCreateWindow(xkmcon, RootWindow(xdisplay, screen), x, y, Dx(r), Dy(r), 0,
+		xscreendepth, InputOutput, xvis, CWBackPixel|CWBorderPixel|CWColormap, &attrs);
+
+	/* load the given bitmap data and create an X pixmap containing it. */
+	icon_pixmap = XCreateBitmapFromData(xkmcon, RootWindow(xdisplay, screen),
+		(char *)glenda_t_bits, glenda_t_width, glenda_t_height);
+
+	/*
+	 * set up property as required by ICCCM
+	 */
+	if((name.value = (uchar*)getenv("WM_NAME")) == nil)
+		name.value = (uchar*)"drawcpu";
+	name.encoding = XA_STRING;
+	name.format = 8;
+	name.nitems = strlen((char*)name.value);
+	normalhints.flags = USSize;
+	normalhints.width = Dx(r);
+	normalhints.height = Dy(r);
+	hints.flags = IconPixmapHint |InputHint|StateHint;
+	hints.input = 1;
+	hints.initial_state = NormalState;
+	hints.icon_pixmap = icon_pixmap;
+
+	classhints.res_name = "drawcpu";
+	if((classhints.res_class = getenv("WM_CLASS")) == nil)
+		classhints.res_class = "Drawterm";
+	argv[0] = "drawcpu";
+	argv[1] = nil;
+	XSetWMProperties(xkmcon, 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(xkmcon);
+	if ((wmpid = XInternAtom(xdisplay, "_NET_WM_PID", False)) != None) {
+		pid = (unsigned long) getpid();
+		XChangeProperty(xkmcon, xdrawable,
+			wmpid, /* Atom property */
+			XA_CARDINAL, /* Atom type */
+			32, /* int format, 32 really is "long" */
+			PropModeReplace, /* int mode */
+			(uchar *)&pid, /* unsigned char * data */
+			1); /* int nelements */
+		XFlush(xkmcon);
+	}
+
+	/*
+	 * put the window on the screen
+	 */
+	wmdelete = XInternAtom(xkmcon, "WM_DELETE_WINDOW", True);
+	XSetWMProtocols(xkmcon, xdrawable, &wmdelete, 1);
+	XMapWindow(xkmcon, xdrawable);
+	XFlush(xkmcon);
+
+	screensize(r, xscreenchan);
+	if(gscreen == nil)
+		panic("screensize failed");
+
+	gscreen->clipr = r;
+	kproc("xscreen", xproc, nil);
+
+	qlock(&drawlock);
+	terminit();
+	flushmemscreen(gscreen->clipr);
+	qunlock(&drawlock);
+}
+
+void
+screensize(Rectangle r, ulong chan)
+{
+	Drawable pix;
+	Memimage *mi;
+	XImage *xi;
+	GC gc;
+
+	pix = XCreatePixmap(xdisplay, xdrawable, Dx(r), Dy(r), xscreendepth);
+	if(pix == 0)
+		return;
+
+	gc = creategc(pix);
+	if(gc == NULL){
+		XFreePixmap(xdisplay, pix);
+		return;
+	}
+
+	mi = xallocmemimage(r, chan, pix, &xi);
+	if(mi == nil){
+		XFreeGC(xdisplay, gc);
+		XFreePixmap(xdisplay, pix);
+		return;
+	}
+
+	if(gscreen != nil){
+		xscreenimage->data = NULL;	/* free'd by freememimage() */
+		XDestroyImage(xscreenimage);
+		freememimage(gscreen);
+
+		XFreeGC(xdisplay, xgccopy);
+		XFreePixmap(xdisplay, xscreenid);
+	}
+
+	xscreenimage = xi;
+	xscreenid = pix;
+	xgccopy = gc;
+
+	gscreen = mi;
+	gscreen->clipr = ZR;
+}
+
+Memdata*
+attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
+{
+	*r = gscreen->clipr;
+	*chan = gscreen->chan;
+	*depth = gscreen->depth;
+	*width = gscreen->width;
+	*softscreen = 1;
+
+	gscreen->data->ref++;
+	return gscreen->data;
+}
+
+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(Point xy)
+{
+	qlock(&drawlock);
+	XWarpPointer(xdisplay, None, xdrawable, 0, 0, 0, 0, xy.x, xy.y);
+	XFlush(xdisplay);
+	qunlock(&drawlock);
+}
+
+static XCursor xcursor;
+
+void
+setcursor(void)
+{
+	XCursor 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]);
+	}
+
+	qlock(&drawlock);
+	fg = map[0];
+	bg = map[255];
+	xsrc = XCreateBitmapFromData(xdisplay, xdrawable, (char*)src, 16, 16);
+	xmask = XCreateBitmapFromData(xdisplay, xdrawable, (char*)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);
+	qunlock(&drawlock);
+}
+
+void
+cursorarrow(void)
+{
+	qlock(&drawlock);
+	if(xcursor != 0){
+		XFreeCursor(xdisplay, xcursor);
+		xcursor = 0;
+	}
+	XUndefineCursor(xdisplay, xdrawable);
+	XFlush(xdisplay);
+	qunlock(&drawlock);
+}
+
+static void
+xproc(void *arg)
+{
+	ulong mask;
+	XEvent event;
+
+	mask = 	KeyPressMask|
+		KeyReleaseMask|
+		ButtonPressMask|
+		ButtonReleaseMask|
+		PointerMotionMask|
+		Button1MotionMask|
+		Button2MotionMask|
+		Button3MotionMask|
+		Button4MotionMask|
+		Button5MotionMask|
+		ExposureMask|
+		EnterWindowMask|
+		LeaveWindowMask|
+		FocusChangeMask|
+		StructureNotifyMask;
+
+	XSelectInput(xkmcon, xdrawable, mask);
+	for(;;) {
+		XNextEvent(xkmcon, &event);
+		xselect(&event, xkmcon);
+		xkeyboard(&event);
+		xmouse(&event);
+		xresize(&event);
+		xexpose(&event);
+		xmapping(&event);
+		xdestroy(&event);
+	}
+}
+
+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 drawcpu colormap as a private colormap for this
+ * application.  Drawterm gets the best colors here when it has the cursor focus.
+ */
+static void
+initmap(XDisplay *xdisplay, int screen, Visual *xvis)
+{
+	XColor c;
+	int i;
+	ulong p, pp;
+	char buf[30];
+
+	xcmap = DefaultColormap(xdisplay, screen);
+	if(xvis->class == StaticColor)
+		return;
+
+	graphicscmap(map);
+	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.
+		 */
+		if(xvis != DefaultVisual(xdisplay, screen))
+			xcmap = XCreateColormap(xdisplay, RootWindow(xdisplay, screen), xvis, AllocNone);
+		c = map[19];
+		/* find out index into colormap for our RGB */
+		if(!XAllocColor(xdisplay, xcmap, &c))
+			panic("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, RootWindow(xdisplay, screen), 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))
+					panic("can't alloc colors in default map, don't use -7");
+				plan9tox11[map7to8[i][0]] = c.pixel;
+				plan9tox11[map7to8[i][1]] = c.pixel;
+				x11toplan9[c.pixel] = map7to8[i][0];
+			}
+		}
+	}
+	else
+		panic("unsupported visual class %d", xvis->class);
+}
+
+static void
+xdestroy(XEvent *e)
+{
+	XDestroyWindowEvent *xe;
+	XClientMessageEvent *ce;
+
+	switch(e->type){
+	case ClientMessage:
+		/* Handle WM_DELETE_WINDOW */
+		ce = (XClientMessageEvent*)e;
+		if(ce->window == xdrawable && ce->data.l[0] == wmdelete)
+			exit(0);
+	case DestroyNotify:
+		xe = (XDestroyWindowEvent*)e;
+		if(xe->window == xdrawable)
+			exit(0);
+	}
+}
+
+static void
+xresize(XEvent *e)
+{
+	if(e->type != ConfigureNotify)
+		return;
+	screenresize(Rect(0, 0, ((XConfigureEvent*)e)->width, ((XConfigureEvent*)e)->height));
+}
+
+static void
+xmapping(XEvent *e)
+{
+	XMappingEvent *xe;
+
+	if(e->type != MappingNotify)
+		return;
+	xe = (XMappingEvent*)e;
+	USED(xe);
+}
+
+
+/*
+ * 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)
+{
+	Rectangle 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;
+
+	qlock(&drawlock);
+	flushmemscreen(r);
+	qunlock(&drawlock);
+}
+
+static void
+xkeyboard(XEvent *e)
+{
+	static int altdown;
+	static int shiftdown;
+	static int superdown;
+	KeySym k;
+
+	switch(e->xany.type){
+	case KeyPress:
+	case KeyRelease:
+		break;
+	case FocusIn:
+	case FocusOut:
+		if(altdown){
+			altdown = 0;
+			kbdkey(Kalt, 0);
+			kbdkey(Kalt, 1);
+			kbdkey(Kalt, 0);
+		}
+		if(shiftdown){
+			shiftdown = 0;
+			kbdkey(Kshift, 0);
+		}
+		if(superdown){
+			superdown = 0;
+			kbdkey(Kmod4, 0);
+		}
+		/* wet floor */
+	default:
+		return;
+	}
+
+	XLookupString((XKeyEvent*)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_Super_L:
+		case XK_Super_R:
+			k = Kmod4;
+			break;
+
+		case XK_Shift_L:
+		case XK_Shift_R:
+			k = Kshift;
+			break;
+		case XK_Control_L:
+		case XK_Control_R:
+			k = Kctl;
+			break;
+		case XK_Shift_Lock:
+		case XK_Caps_Lock:
+			k = Kcaps;
+			break;
+		case XK_Scroll_Lock:
+			k = Kscroll;
+			break;
+
+		case XK_F1:
+		case XK_F2:
+		case XK_F3:
+		case XK_F4:
+		case XK_F5:
+		case XK_F6:
+		case XK_F7:
+		case XK_F8:
+		case XK_F9:
+		case XK_F10:
+		case XK_F11:
+		case XK_F12:
+			k = KF|(k - XK_F1 + 1);
+			break;
+
+		case XK_Meta_L:
+		case XK_Meta_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;
+			}
+			break;
+		}
+	}
+
+	/* 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 != Kalt && k != Kctl)
+		k &= 0x9f;
+	if(k == NoSymbol)
+		return;
+	altdown = e->xany.type == KeyPress && k == Kalt;
+	shiftdown = e->xany.type == KeyPress && k == Kshift;
+	superdown = e->xany.type == KeyPress && k == Kmod4;
+	kbdkey(k, e->xany.type == KeyPress);
+}
+
+static void
+xmouse(XEvent *e)
+{
+	Mousestate ms;
+	XButtonEvent *be;
+	XMotionEvent *me;
+	int s;
+
+	if(putsnarf != assertsnarf){
+		assertsnarf = putsnarf;
+		XSetSelectionOwner(xkmcon, XA_PRIMARY, xdrawable, CurrentTime);
+		if(clipboard != None)
+			XSetSelectionOwner(xkmcon, clipboard, xdrawable, CurrentTime);
+		XFlush(xkmcon);
+	}
+
+	switch(e->type){
+	case ButtonPress:
+		be = (XButtonEvent *)e;
+		/*
+		 * Fake message, just sent to make us announce snarf.
+		 * Apparently state and button are 16 and 8 bits on
+		 * the wire, since they are truncated by the time they
+		 * get to us.
+		 */
+		if(be->send_event
+		&& (~be->state&0xFFFF)==0
+		&& (~be->button&0xFF)==0)
+			return;
+		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;
+
+	absmousetrack(ms.xy.x, ms.xy.y, ms.buttons, ms.msec);
+}
+
+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 */
+}
+
+/*
+ * Cut and paste.  Just couldn't stand to make this simple...
+ */
+
+typedef struct Clip Clip;
+struct Clip
+{
+	char buf[SnarfSize];
+	QLock lk;
+};
+Clip clip;
+
+#undef long	/* sic */
+#undef ulong
+
+static char*
+_xgetsnarf(XDisplay *xd)
+{
+	uchar *data, *xdata;
+	Atom selection, type, prop;
+	unsigned long lastlen;
+	unsigned long dummy, len;
+	int fmt, i;
+	Window w;
+
+	qlock(&clip.lk);
+	/*
+	 * Have we snarfed recently and the X server hasn't caught up?
+	 */
+	if(putsnarf != assertsnarf)
+		goto mine;
+
+	/*
+	 * Is there a primary selection (highlighted text in an xterm)?
+	 */
+	selection = XA_PRIMARY;
+	w = XGetSelectionOwner(xd, XA_PRIMARY);
+	if(w == xdrawable){
+	mine:
+		data = (uchar*)strdup(clip.buf);
+		goto out;
+	}
+
+	/*
+	 * If not, is there a clipboard selection?
+	 */
+	if(w == None && selection != None){
+		selection = clipboard;
+		w = XGetSelectionOwner(xd, selection);
+		if(w == xdrawable)
+			goto mine;
+	}
+
+	/*
+	 * If not, give up.
+	 */
+	if(w == None){
+		data = nil;
+		goto out;
+	}
+
+	/*
+	 * We should be waiting for SelectionNotify here, but it might never
+	 * come, and we have no way to time out.  Instead, we will clear
+	 * local property #1, request our buddy to fill it in for us, and poll
+	 * until he's done or we get tired of waiting.
+	 */
+	prop = 1;
+	XChangeProperty(xd, xdrawable, prop, utf8string, 8, PropModeReplace, (uchar*)"", 0);
+	XConvertSelection(xd, selection, utf8string, prop, xdrawable, CurrentTime);
+	XFlush(xd);
+	lastlen = 0;
+	for(i=0; i<10 || (lastlen!=0 && i<30); i++){
+		usleep(100*1000);
+		XGetWindowProperty(xd, xdrawable, prop, 0, 0, 0, AnyPropertyType,
+			&type, &fmt, &dummy, &len, &data);
+		if(lastlen == len && len > 0)
+			break;
+		lastlen = len;
+	}
+	if(i == 10){
+		data = nil;
+		goto out;
+	}
+	/* get the property */
+	data = nil;
+	XGetWindowProperty(xd, xdrawable, prop, 0, SnarfSize/sizeof(unsigned long), 0,
+		AnyPropertyType, &type, &fmt, &len, &dummy, &xdata);
+	if((type != XA_STRING && type != utf8string) || len == 0){
+		if(xdata)
+			XFree(xdata);
+		data = nil;
+	}else{
+		if(xdata){
+			data = (uchar*)strdup((char*)xdata);
+			XFree(xdata);
+		}else
+			data = nil;
+	}
+out:
+	qunlock(&clip.lk);
+	return (char*)data;
+}
+
+static void
+_xputsnarf(XDisplay *xd, char *data)
+{
+	XButtonEvent e;
+
+	if(strlen(data) >= SnarfSize)
+		return;
+	qlock(&clip.lk);
+	strcpy(clip.buf, data);
+
+	/* leave note for mouse proc to assert selection ownership */
+	putsnarf++;
+
+	/* send mouse a fake event so snarf is announced */
+	memset(&e, 0, sizeof e);
+	e.type = ButtonPress;
+	e.window = xdrawable;
+	e.state = ~0;
+	e.button = ~0;
+	XSendEvent(xd, xdrawable, True, ButtonPressMask, (XEvent*)&e);
+	XFlush(xd);
+	qunlock(&clip.lk);
+}
+
+static void
+xselect(XEvent *e, XDisplay *xd)
+{
+	char *name;
+	XEvent r;
+	XSelectionRequestEvent *xe;
+	Atom a[4];
+
+	if(e->xany.type != SelectionRequest)
+		return;
+
+	memset(&r, 0, sizeof r);
+	xe = (XSelectionRequestEvent*)e;
+if(0) iprint("xselect target=%d requestor=%d property=%d selection=%d\n",
+	xe->target, xe->requestor, xe->property, xe->selection);
+	r.xselection.property = xe->property;
+	if(xe->target == targets){
+		a[0] = XA_STRING;
+		a[1] = utf8string;
+		a[2] = text;
+		a[3] = compoundtext;
+
+		XChangeProperty(xd, xe->requestor, xe->property, XA_ATOM,
+			32, PropModeReplace, (uchar*)a, sizeof a);
+	}else if(xe->target == XA_STRING || xe->target == utf8string || xe->target == text || xe->target == compoundtext){
+	text:
+		/* if the target is STRING we're supposed to reply with Latin1 XXX */
+		qlock(&clip.lk);
+		XChangeProperty(xd, xe->requestor, xe->property, xe->target,
+			8, PropModeReplace, (uchar*)clip.buf, strlen(clip.buf));
+		qunlock(&clip.lk);
+	}else{
+		name = XGetAtomName(xd, xe->target);
+		if(name == nil)
+			iprint("XGetAtomName %d failed\n", xe->target);
+		if(name){
+			if(strcmp(name, "TIMESTAMP") == 0){
+				/* nothing */
+			}else if(strncasecmp(name, "image/", 6) == 0){
+				/* nothing */
+			}else if(strcasecmp(name, "text/html") == 0){
+				/* nothing */
+			}else if(strcasecmp(name, "text/plain") == 0 || strcasecmp(name, "text/plain;charset=UTF-8") == 0){
+				goto text;
+			}else if(0)
+				iprint("cannot handle selection request for '%s' (%d)\n", name, (int)xe->target);
+		}
+		r.xselection.property = None;
+	}
+
+	r.xselection.display = xe->display;
+	/* r.xselection.property filled above */
+	r.xselection.target = xe->target;
+	r.xselection.type = SelectionNotify;
+	r.xselection.requestor = xe->requestor;
+	r.xselection.time = xe->time;
+	r.xselection.send_event = True;
+	r.xselection.selection = xe->selection;
+	XSendEvent(xd, xe->requestor, False, 0, &r);
+	XFlush(xd);
+}
+
+char*
+clipread(void)
+{
+	return _xgetsnarf(xsnarfcon);
+}
+
+int
+clipwrite(char *buf)
+{
+	_xputsnarf(xsnarfcon, buf);
+	return 0;
+}
+
+void
+guimain(void)
+{
+	cpubody();
+}
--- /dev/null
+++ b/include/9windows.h
@@ -1,0 +1,21 @@
+#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 */
+#ifdef MSVC
+#pragma warning( disable : 4245 4305 4244 4102 4761 4090 4028 4024)
+#endif
+
+typedef __int64		p9_vlong;
+typedef	unsigned __int64 p9_uvlong;
+typedef uintptr_t uintptr;
--- /dev/null
+++ b/include/auth.h
@@ -1,0 +1,146 @@
+#ifdef PLAN9
+#pragma	src	"/sys/src/libauth"
+#pragma	lib	"libauth.a"
+#endif
+
+/*
+ * 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+1];	/* +1 for NUL in auth_rpc.c */
+	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*);
+
+extern	int	login(char*, char*, char*);
+
+typedef struct Attr Attr;
+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*);
+#ifdef VARARGCK
+#pragma varargck type "A" Attr*
+#endif
+
+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);
+#ifdef VARARGCK
+#pragma varargck argpos auth_proxy 3
+#pragma varargck argpos auth_challenge 1
+#pragma varargck argpos auth_respond 8
+#pragma varargck argpos auth_getuserpasswd 2
+#endif
--- /dev/null
+++ b/include/authsrv.h
@@ -1,0 +1,217 @@
+/*
+ * 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;
+
+typedef struct	Authkey		Authkey;
+
+enum
+{
+	ANAMELEN=	28,	/* name max size in previous proto */
+	AERRLEN=	64,	/* errstr max size in previous proto */
+	DOMLEN=		48,	/* authentication domain name length */
+	DESKEYLEN=	7,	/* encrypt/decrypt des key length */
+	AESKEYLEN=	16,	/* encrypt/decrypt aes key length */
+
+	CHALLEN=	8,	/* plan9 sk1 challenge length */
+	NETCHLEN=	16,	/* max network challenge length (used in AS protocol) */
+	CONFIGLEN=	14,
+	SECRETLEN=	32,	/* secret max size */
+	PASSWDLEN=	28,	/* password max size */
+
+	NONCELEN=	32,
+
+	KEYDBOFF=	8,	/* bytes of random data at key file's start */
+	OKEYDBLEN=	ANAMELEN+DESKEYLEN+4+2,	/* old key file entry length */
+	KEYDBLEN=	OKEYDBLEN+SECRETLEN,	/* key file entry length */
+	OMD5LEN=	16,
+
+	/* AuthPAK constants */
+	PAKKEYLEN=	32,
+	PAKSLEN=	(448+7)/8,	/* ed448 scalar */
+	PAKPLEN=	4*PAKSLEN,	/* point in extended format X,Y,Z,T */
+	PAKHASHLEN=	2*PAKPLEN,	/* hashed points PM,PN */
+	PAKXLEN=	PAKSLEN,	/* random scalar secret key */ 
+	PAKYLEN=	PAKSLEN,	/* decaf encoded public key */
+};
+
+/* 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) */
+	AuthPAK=19,	/* authenticated diffie hellman key agreement */
+	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 */
+	uchar	key[NONCELEN];		/* nonce key */
+
+	char	form;			/* (not transmitted) format (0 = des, 1 = ccpoly) */
+};
+#define	MAXTICKETLEN	(12+CHALLEN+2*ANAMELEN+NONCELEN+16)
+
+struct Authenticator
+{
+	char	num;			/* replay protection */
+	char	chal[CHALLEN];		/* server/client challenge */
+	uchar	rand[NONCELEN];		/* server/client nonce */
+};
+#define	MAXAUTHENTLEN	(12+CHALLEN+NONCELEN+16)
+
+struct Passwordreq
+{
+	char	num;
+	char	old[PASSWDLEN];
+	char	new[PASSWDLEN];
+	char	changesecret;
+	char	secret[SECRETLEN];	/* new secret */
+};
+#define	MAXPASSREQLEN	(12+2*PASSWDLEN+1+SECRETLEN+16)
+
+struct	OChapreply
+{
+	uchar	id;
+	char	uid[ANAMELEN];
+	char	resp[OMD5LEN];
+};
+#define OCHAPREPLYLEN	(1+ANAMELEN+OMD5LEN)
+
+struct	OMSchapreply
+{
+	char	uid[ANAMELEN];
+	char	LMresp[24];		/* Lan Manager response */
+	char	NTresp[24];		/* NT response */
+};
+#define OMSCHAPREPLYLEN	(ANAMELEN+24+24)
+
+struct	Authkey
+{
+	char	des[DESKEYLEN];		/* DES key from password */
+	uchar	aes[AESKEYLEN];		/* AES key from password */
+	uchar	pakkey[PAKKEYLEN];	/* shared key from AuthPAK exchange (see authpak_finish()) */
+	uchar	pakhash[PAKHASHLEN];	/* secret hash from AES key and user name (see authpak_hash()) */
+};
+
+/*
+ *  convert to/from wire format
+ */
+extern	int	convT2M(Ticket*, char*, int, Authkey*);
+extern	int	convM2T(char*, int, Ticket*, Authkey*);
+extern	int	convA2M(Authenticator*, char*, int, Ticket*);
+extern	int	convM2A(char*, int, Authenticator*, Ticket*);
+extern	int	convTR2M(Ticketreq*, char*, int);
+extern	int	convM2TR(char*, int, Ticketreq*);
+extern	int	convPR2M(Passwordreq*, char*, int, Ticket*);
+extern	int	convM2PR(char*, int, Passwordreq*, Ticket*);
+
+/*
+ *  convert ascii password to auth key
+ */
+extern	void	passtokey(Authkey*, char*);
+
+extern	void	passtodeskey(char key[DESKEYLEN], char *p);
+extern	void	passtoaeskey(uchar key[AESKEYLEN], char *p);
+
+/*
+ *  Nvram interface
+ */
+enum {
+	NVread		= 0,	/* just read */
+	NVwrite		= 1<<0,	/* always prompt and rewrite nvram */
+	NVwriteonerr	= 1<<1,	/* prompt and rewrite nvram when corrupt */
+	NVwritemem	= 1<<2,	/* don't prompt, write nvram from argument */
+};
+
+/* storage layout */
+struct Nvrsafe
+{
+	char	machkey[DESKEYLEN];	/* file server's authid's des key */
+	uchar	machsum;
+	char	authkey[DESKEYLEN];	/* authid's des key from password */
+	uchar	authsum;
+	/*
+	 * file server config string of device holding full configuration;
+	 * secstore key on non-file-servers.
+	 */
+	char	config[CONFIGLEN];
+	uchar	configsum;
+	char	authid[ANAMELEN];	/* auth userid, e.g., bootes */
+	uchar	authidsum;
+	char	authdom[DOMLEN];	/* auth domain, e.g., cs.bell-labs.com */
+	uchar	authdomsum;
+
+	uchar	aesmachkey[AESKEYLEN];
+	uchar	aesmachsum;
+};
+
+extern	uchar	nvcsum(void*, int);
+extern	int	readnvram(Nvrsafe*, int);
+extern	char*	readcons(char*, char*, int);
+
+/*
+ *  call up auth server
+ */
+extern	int	authdial(char *netroot, char *authdom);
+
+/*
+ *  exchange messages with auth server
+ */
+extern	int	_asgetpakkey(int, Ticketreq*, Authkey*);
+extern	int	_asgetticket(int, Ticketreq*, char*, int);
+extern	int	_asrequest(int, Ticketreq*);
+extern	int	_asgetresp(int, Ticket*, Authenticator*, Authkey *);
+extern	int	_asrdresp(int, char*, int);
+
+/*
+ *  AuthPAK protocol
+ */
+typedef struct PAKpriv PAKpriv;
+struct PAKpriv
+{
+	int	isclient;
+	uchar	x[PAKXLEN];
+	uchar	y[PAKYLEN];
+};
+
+extern	void	authpak_hash(Authkey *k, char *u);
+extern	void	authpak_new(PAKpriv *p, Authkey *k, uchar y[PAKYLEN], int isclient);
+extern	int	authpak_finish(PAKpriv *p, Authkey *k, uchar y[PAKYLEN]);
--- /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,512 @@
+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;
+
+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),
+	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),
+	BGR24	= CHAN3(CBlue, 8, CGreen, 8, CRed, 8),
+	ABGR32	= CHAN4(CAlpha, 8, CBlue, 8, CGreen, 8, CRed, 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*);
+extern int		badrect(Rectangle);
+
+/*
+ * 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*);
+
+/*
+ * 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	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);
+
+extern	ulong	drawld2chan[];
+extern	void	drawsetdebug(int);
--- /dev/null
+++ b/include/dtos.h
@@ -1,0 +1,15 @@
+#if defined(linux) || defined(IRIX) || defined(SOLARIS) || defined(OSF1) || defined(__FreeBSD__) || defined(__APPLE__) || defined(__NetBSD__) || defined(__sun) || defined(sun) || defined(__OpenBSD__) || defined(__DragonFly__)
+#	include "unix.h"
+#	ifdef __APPLE__
+#		define panic dt_panic
+#	endif
+#elif defined(WINDOWS)
+#	include "9windows.h"
+#	define main mymain
+#else
+#	error "Define an OS"
+#endif
+
+#ifdef IRIX
+typedef int socklen_t;
+#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)	((u32int)((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/ip.h
@@ -1,0 +1,38 @@
+enum 
+{
+	IPaddrlen=	16,
+	IPv4addrlen=	4,
+	IPv4off=	12,
+};
+
+uchar*	defmask(uchar*);
+void	maskip(uchar*, uchar*, uchar*);
+int	eipfmt(Fmt*);
+int	isv4(uchar*);
+vlong	parseip(uchar*, char*);
+vlong	parseipmask(uchar*, char*, int);
+vlong	parseipandmask(uchar*, uchar*, char*, char*);
+char*	v4parseip(uchar*, char*);
+
+void	hnputv(void*, uvlong);
+void	hnputl(void*, uint);
+void	hnputs(void*, ushort);
+uvlong	nhgetv(void*);
+uint	nhgetl(void*);
+ushort	nhgets(void*);
+
+int	v6tov4(uchar*, uchar*);
+void	v4tov6(uchar*, uchar*);
+
+#define	ipcmp(x, y) memcmp(x, y, IPaddrlen)
+#define	ipmove(x, y) memmove(x, y, IPaddrlen)
+
+extern uchar IPv4bcast[IPaddrlen];
+extern uchar IPv4bcastobs[IPaddrlen];
+extern uchar IPv4allsys[IPaddrlen];
+extern uchar IPv4allrouter[IPaddrlen];
+extern uchar IPnoaddr[IPaddrlen];
+extern uchar v4prefix[IPaddrlen];
+extern uchar IPallbits[IPaddrlen];
+
+#define CLASS(p) ((*(uchar*)(p))>>6)
--- /dev/null
+++ b/include/keyboard.h
@@ -1,0 +1,55 @@
+enum {
+	KF=	0xF000,	/* Rune: beginning of private Unicode space */
+	Spec=	0xF800,
+	PF=	Spec|0x20,	/* num pad function key */
+	Kview=	Spec|0x00,	/* view (shift window up) */
+	/* KF|1, KF|2, ..., KF|0xC is *respectively* F1, F2, ..., F12 */
+	Khome=	KF|0x0D,
+	Kup=	KF|0x0E,
+	Kdown=	Kview,
+	Kpgup=	KF|0x0F,
+	Kprint=	KF|0x10,
+	Kleft=	KF|0x11,
+	Kright=	KF|0x12,
+	Kpgdown=	KF|0x13,
+	Kins=	KF|0x14,
+
+	Kalt=	KF|0x15,
+	Kshift=	KF|0x16,
+	Kctl=	KF|0x17,
+
+	Kend=	KF|0x18,
+	Kscroll=	KF|0x19,
+	Kscrolloneup=	KF|0x20,
+	Kscrollonedown=	KF|0x21,
+
+	/* multimedia keys - no refunds */
+	Ksbwd=	KF|0x22,	/* skip backwards */
+	Ksfwd=	KF|0x23,	/* skip forward */
+	Kpause=	KF|0x24,	/* play/pause */
+	Kvoldn=	KF|0x25,	/* volume decrement */
+	Kvolup=	KF|0x26,	/* volume increment */
+	Kmute=	KF|0x27,	/* (un)mute */
+	Kbrtdn=	KF|0x28,	/* brightness decrement */
+	Kbrtup=	KF|0x29,	/* brightness increment */
+
+	Ksoh=	0x01,
+	Kstx=	0x02,
+	Ketx=	0x03,
+	Keof=	0x04,
+	Kenq=	0x05,
+	Kack=	0x06,
+	Kbs=	0x08,
+	Knack=	0x15,
+	Ketb=	0x17,
+	Kdel=	0x7f,
+	Kesc=	0x1b,
+
+	Kbreak=	Spec|0x61,
+	Kcaps=	Spec|0x64,
+	Knum=	Spec|0x65,
+	Kmiddle=	Spec|0x66,
+	Kaltgr=	Spec|0x67,
+	Kmod4=	Spec|0x68,
+	Kmouse=	Spec|0x100,
+};
--- /dev/null
+++ b/include/lib.h
@@ -1,0 +1,311 @@
+/* avoid name conflicts */
+#define accept	 pm_accept
+#define listen   pm_listen
+#define announce pm_announce
+#define sleep	 ksleep
+#define wakeup	 kwakeup
+#ifdef strtod
+#undef strtod
+#endif
+#define strtod		fmtstrtod
+
+/* 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 strtoll libstrtoll
+#undef timeradd
+#define timeradd	xtimeradd
+#define gmtime	libgmtime
+
+
+#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 int	Rune;
+typedef unsigned int	p9_u32int;
+typedef unsigned long long p9_u64int;
+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 u64int	p9_u64int
+
+/* #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 SET(x)		((x)=0)
+#define	USED(x)		(void)(x)
+
+enum
+{
+	UTFmax		= 4,		/* maximum bytes per rune */
+	Runesync	= 0x80,		/* cannot represent part of a UTF sequence (<) */
+	Runeself	= 0x80,		/* rune and UTF sequences are the same (<) */
+	Runeerror	= 0xFFFD,	/* decoding error in UTF */
+	Runemax		= 0x10FFFF,	/* 21-bit rune */
+	Runemask	= 0x1FFFFF,	/* bits used by runes (see grep) */
+};
+
+/*
+ * 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
+{
+#ifdef PTHREAD
+	int init;
+	pthread_mutex_t mutex;
+#else
+	int key;
+#endif
+} 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,
+	FmtLDouble	= FmtFlag << 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       Rune*	runestrchr(Rune*, Rune);
+extern       long	runestrlen(Rune*);
+extern       Rune*	runestrstr(Rune*, Rune*);
+
+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	int	fmtstrcpy(Fmt*, char*);
+extern	int	fmtprint(Fmt*, char*, ...);
+extern	int	fmtvprint(Fmt*, char*, va_list);
+extern	void*	mallocz(ulong, int);
+
+extern	uintptr	getcallerpc(void*);
+extern	char*	cleanname(char*);
+extern	void	sysfatal(char*, ...);
+extern	char*	strecpy(char*, char*, char*);
+
+extern	int	tokenize(char*, char**, int);
+extern	int	getfields(char*, char**, int, int, char*);
+extern	char*	utfecpy(char*, char*, char*);
+extern	int	tas(int*);
+extern	void	quotefmtinstall(void);
+extern	int	dec64(uchar*, int, char*, int);
+extern	int	enc64(char*, int, uchar*, int);
+extern	int	dec32(uchar*, int, char*, int);
+extern	int	enc32(char*, int, uchar*, int);
+extern	int	dec16(uchar*, int, char*, int);
+extern	int	enc16(char*, int, uchar*, int);
+extern	int	dec64chr(int);
+extern	int	enc64chr(int);
+extern	int	dec32chr(int);
+extern	int	enc32chr(int);
+extern	int	dec16chr(int);
+extern	int	enc16chr(int);
+extern	int	encodefmt(Fmt*);
+void		hnputs(void *p, unsigned short v);
+extern	int	dofmt(Fmt*, char*);
+extern	double	__NaN(void);
+extern	int	__isNaN(double);
+extern	double	strtod(const char*, char**);
+extern	vlong	strtoll(const char *, char **, int);
+extern	int	utfnlen(char*, long);
+extern	double	__Inf(int);
+extern	int	__isInf(double, int);
+
+extern int (*fmtdoquote)(int);
+
+
+/*
+ * Time-of-day
+ */
+
+typedef
+struct Tm
+{
+	int	sec;
+	int	min;
+	int	hour;
+	int	mday;
+	int	mon;
+	int	year;
+	int	wday;
+	int	yday;
+	char	zone[4];
+	int	tzoff;
+} Tm;
+extern	Tm*	gmtime(long);
--- /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,589 @@
+#ifndef _MPINT
+typedef struct mpint mpint;
+#endif
+
+/*
+ * AES definitions
+ */
+
+enum
+{
+	AESbsize=	16,
+	AESmaxkey=	32,
+	AESmaxrounds=	14
+};
+
+typedef struct AESstate AESstate;
+struct AESstate
+{
+	ulong	setup;
+	ulong	offset;
+	int	rounds;
+	int	keybytes;
+	void	*ekey;				/* expanded encryption round key */
+	void	*dkey;				/* expanded decryption round key */
+	uchar	key[AESmaxkey];			/* unexpanded key */
+	uchar	ivec[AESbsize];			/* initialization vector */
+	uchar	storage[512];			/* storage for expanded keys */
+};
+
+/* block ciphers */
+extern void (*aes_encrypt)(ulong rk[], int Nr, uchar pt[16], uchar ct[16]);
+extern void (*aes_decrypt)(ulong rk[], int Nr, uchar ct[16], uchar pt[16]);
+
+void	setupAESstate(AESstate *s, uchar key[], int nkey, uchar *ivec);
+
+void	aesCBCencrypt(uchar *p, int len, AESstate *s);
+void	aesCBCdecrypt(uchar *p, int len, AESstate *s);
+void	aesCFBencrypt(uchar *p, int len, AESstate *s);
+void	aesCFBdecrypt(uchar *p, int len, AESstate *s);
+void	aesOFBencrypt(uchar *p, int len, AESstate *s);
+
+void	aes_xts_encrypt(AESstate *tweak, AESstate *ecb, uvlong sectorNumber, uchar *input, uchar *output, ulong len);
+void	aes_xts_decrypt(AESstate *tweak, AESstate *ecb, uvlong sectorNumber, uchar *input, uchar *output, ulong len);
+
+typedef struct AESGCMstate AESGCMstate;
+struct AESGCMstate
+{
+	AESstate a;
+
+	ulong	H[4];
+	ulong	M[16][256][4];
+};
+
+void	setupAESGCMstate(AESGCMstate *s, uchar *key, int keylen, uchar *iv, int ivlen);
+void	aesgcm_setiv(AESGCMstate *s, uchar *iv, int ivlen);
+void	aesgcm_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], AESGCMstate *s);
+int	aesgcm_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], AESGCMstate *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*);
+
+/*
+ * Chacha definitions
+ */
+
+enum
+{
+	ChachaBsize=	64,
+	ChachaKeylen=	256/8,
+	ChachaIVlen=	96/8,
+	XChachaIVlen=	192/8,
+};
+
+typedef struct Chachastate Chachastate;
+struct Chachastate
+{
+	union{
+		u32int	input[16];
+		struct {
+			u32int	constant[4];
+			u32int	key[8];
+			u32int	counter;
+			u32int	iv[3];
+		};
+	};
+	u32int	xkey[8];
+	int	rounds;
+	int	ivwords;
+};
+
+void	setupChachastate(Chachastate*, uchar*, ulong, uchar*, ulong, int);
+void	chacha_setiv(Chachastate *, uchar*);
+void	chacha_setblock(Chachastate*, u64int);
+void	chacha_encrypt(uchar*, ulong, Chachastate*);
+void	chacha_encrypt2(uchar*, uchar*, ulong, Chachastate*);
+
+void	hchacha(uchar h[32], uchar *key, ulong keylen, uchar nonce[16], int rounds);
+
+void	ccpoly_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs);
+int	ccpoly_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs);
+
+/*
+ * Salsa definitions
+ */
+enum
+{
+	SalsaBsize=	64,
+	SalsaKeylen=	256/8,
+	SalsaIVlen=	64/8,
+	XSalsaIVlen=	192/8,
+};
+
+typedef struct Salsastate Salsastate;
+struct Salsastate
+{
+	u32int	input[16];
+	u32int	xkey[8];
+	int	rounds;
+	int	ivwords;
+};
+
+void	setupSalsastate(Salsastate*, uchar*, ulong, uchar*, ulong, int);
+void	salsa_setiv(Salsastate*, uchar*);
+void	salsa_setblock(Salsastate*, u64int);
+void	salsa_encrypt(uchar*, ulong, Salsastate*);
+void	salsa_encrypt2(uchar*, uchar*, ulong, Salsastate*);
+
+void	salsa_core(u32int in[16], u32int out[16], int rounds);
+
+void	hsalsa(uchar h[32], uchar *key, ulong keylen, uchar nonce[16], int rounds);
+
+/*
+ * 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[32], uchar[8], 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[3][32], uchar[8], 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 */
+	SHA2_224dlen=	28,	/* SHA-224 digest length */
+	SHA2_256dlen=	32,	/* SHA-256 digest length */
+	SHA2_384dlen=	48,	/* SHA-384 digest length */
+	SHA2_512dlen=	64,	/* SHA-512 digest length */
+	MD4dlen=	16,	/* MD4 digest length */
+	MD5dlen=	16,	/* MD5 digest length */
+	RIPEMD160dlen=	20,	/* RIPEMD-160 digest length */
+	Poly1305dlen=	16,	/* Poly1305 digest length */
+
+	Hmacblksz	= 64,	/* in bytes; from rfc2104 */
+};
+
+typedef struct DigestState DigestState;
+struct DigestState
+{
+	uvlong	len;
+	union {
+		u32int	state[16];
+		u64int	bstate[8];
+	};
+	uchar	buf[256];
+	int	blen;
+	char	malloced;
+	char	seeded;
+};
+typedef struct DigestState SHAstate;	/* obsolete name */
+typedef struct DigestState SHA1state;
+typedef struct DigestState SHA2_224state;
+typedef struct DigestState SHA2_256state;
+typedef struct DigestState SHA2_384state;
+typedef struct DigestState SHA2_512state;
+typedef struct DigestState MD5state;
+typedef struct DigestState MD4state;
+
+DigestState*	md4(uchar*, ulong, uchar*, DigestState*);
+DigestState*	md5(uchar*, ulong, uchar*, DigestState*);
+DigestState*	ripemd160(uchar *, ulong, uchar *, DigestState *);
+DigestState*	sha1(uchar*, ulong, uchar*, DigestState*);
+DigestState*	sha2_224(uchar*, ulong, uchar*, DigestState*);
+DigestState*	sha2_256(uchar*, ulong, uchar*, DigestState*);
+DigestState*	sha2_384(uchar*, ulong, uchar*, DigestState*);
+DigestState*	sha2_512(uchar*, ulong, uchar*, DigestState*);
+DigestState*	hmac_x(uchar *p, ulong len, uchar *key, ulong klen,
+			uchar *digest, DigestState *s,
+			DigestState*(*x)(uchar*, ulong, uchar*, DigestState*),
+			int xlen);
+DigestState*	hmac_md5(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+DigestState*	hmac_sha1(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+DigestState*	hmac_sha2_224(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+DigestState*	hmac_sha2_256(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+DigestState*	hmac_sha2_384(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+DigestState*	hmac_sha2_512(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+
+DigestState*	poly1305(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+
+/*
+ * 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 n-bit probable prime */
+void	gensafeprime(mpint *p, mpint *alpha, int n, int accuracy); /* prime & generator */
+void	genstrongprime(mpint *p, int n, int accuracy); /* generate 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;
+typedef struct PEMChain PEMChain;
+
+/* 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 */
+};
+
+struct PEMChain{
+	PEMChain*next;
+	uchar	*pem;
+	int	pemlen;
+};
+
+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);
+RSApub*		asn1toRSApub(uchar*, int);
+RSApriv*	asn1toRSApriv(uchar*, int);
+void		asn1dump(uchar *der, int len);
+uchar*		decodePEM(char *s, char *type, int *len, char **new_s);
+PEMChain*	decodepemchain(char *s, char *type);
+uchar*		X509rsagen(RSApriv *priv, char *subj, ulong valid[2], int *certlen);
+uchar*		X509rsareq(RSApriv *priv, char *subj, int *certlen);
+char*		X509rsaverify(uchar *cert, int ncert, RSApub *pk);
+char*		X509rsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, RSApub *pk);
+
+void		X509dump(uchar *cert, int ncert);
+
+mpint*		pkcs1padbuf(uchar *buf, int len, mpint *modulus, int blocktype);
+int		pkcs1unpadbuf(uchar *buf, int len, mpint *modulus, int blocktype);
+int		asn1encodeRSApub(RSApub *pk, uchar *buf, int len);
+int		asn1encodedigest(DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*),
+			uchar *digest, uchar *buf, int len);
+
+
+/*
+ * 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);	/* deprecated */
+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);	/* opub not checked for consistency! */
+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	hash[SHA2_256dlen];
+	uchar	len;
+} Thumbprint;
+
+typedef struct TLSconn{
+	char	dir[40];	/* connection directory */
+	uchar	*cert;	/* certificate (local on input, remote on output) */
+	uchar	*sessionID;
+	uchar	*psk;
+	int	certlen;
+	int	sessionIDlen;
+	int	psklen;
+	int	(*trace)(char*fmt, ...);
+	PEMChain*chain;	/* optional extra certificate evidence for servers to present */
+	char	*sessionType;
+	uchar	*sessionKey;
+	int	sessionKeylen;
+	char	*sessionConst;
+	char	*serverName;
+	char	*pskID;
+} TLSconn;
+
+/* tlshand.c */
+int tlsClient(int fd, TLSconn *c);
+int tlsServer(int fd, TLSconn *c);
+
+/* thumb.c */
+Thumbprint* initThumbprints(char *ok, char *crl, char *tag);
+void	freeThumbprints(Thumbprint *ok);
+int	okThumbprint(uchar *hash, int len, Thumbprint *ok);
+int	okCertificate(uchar *cert, int len, Thumbprint *ok);
+
+/* readcert.c */
+uchar	*readcert(char *filename, int *pcertlen);
+PEMChain*readcertchain(char *filename);
+
+typedef struct ECpoint{
+	int inf;
+	mpint *x;
+	mpint *y;
+	mpint *z;	/* nil when using affine coordinates */
+} ECpoint;
+
+typedef ECpoint ECpub;
+typedef struct ECpriv{
+	ECpoint a;
+	mpint *d;
+} ECpriv;
+
+typedef struct ECdomain{
+	mpint *p;
+	mpint *a;
+	mpint *b;
+	ECpoint G;
+	mpint *n;
+	mpint *h;
+} ECdomain;
+
+void	ecdominit(ECdomain *, void (*init)(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h));
+void	ecdomfree(ECdomain *);
+
+void	ecassign(ECdomain *, ECpoint *old, ECpoint *new);
+void	ecadd(ECdomain *, ECpoint *a, ECpoint *b, ECpoint *s);
+void	ecmul(ECdomain *, ECpoint *a, mpint *k, ECpoint *s);
+ECpoint*	strtoec(ECdomain *, char *, char **, ECpoint *);
+ECpriv*	ecgen(ECdomain *, ECpriv*);
+int	ecverify(ECdomain *, ECpoint *);
+int	ecpubverify(ECdomain *, ECpub *);
+void	ecdsasign(ECdomain *, ECpriv *, uchar *, int, mpint *, mpint *);
+int	ecdsaverify(ECdomain *, ECpub *, uchar *, int, mpint *, mpint *);
+void	base58enc(uchar *, char *, int);
+int	base58dec(char *, uchar *, int);
+
+ECpub*	ecdecodepub(ECdomain *dom, uchar *, int);
+int	ecencodepub(ECdomain *dom, ECpub *, uchar *, int);
+void	ecpubfree(ECpub *);
+
+ECpub*	X509toECpub(uchar *cert, int ncert, char *name, int nname, ECdomain *dom);
+char*	X509ecdsaverify(uchar *cert, int ncert, ECdomain *dom, ECpub *pub);
+char*	X509ecdsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, ECdomain *dom, ECpub *pub);
+
+/* curves */
+void	secp256r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h);
+void	secp256k1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h);
+void	secp384r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h);
+
+/*
+ * Diffie-Hellman key exchange
+ */
+
+typedef struct DHstate DHstate;
+struct DHstate
+{
+	mpint	*g;	/* base g */
+	mpint	*p;	/* large prime */
+	mpint	*q;	/* subgroup prime */
+	mpint	*x;	/* random secret */
+	mpint	*y;	/* public key y = g**x % p */
+};
+
+/* generate new public key: y = g**x % p */
+mpint* dh_new(DHstate *dh, mpint *p, mpint *q, mpint *g);
+
+/* calculate shared key: k = y**x % p */
+mpint* dh_finish(DHstate *dh, mpint *y);
+
+/* Curve25519 elliptic curve, public key function */
+void curve25519(uchar mypublic[32], uchar secret[32], uchar basepoint[32]);
+
+/* Curve25519 diffie hellman */
+void curve25519_dh_new(uchar x[32], uchar y[32]);
+int curve25519_dh_finish(uchar x[32], uchar y[32], uchar z[32]);
+
+/* password-based key derivation function 2 (rfc2898) */
+void pbkdf2_x(uchar *p, ulong plen, uchar *s, ulong slen, ulong rounds, uchar *d, ulong dlen,
+	DigestState* (*x)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*), int xlen);
+
+/* scrypt password-based key derivation function */
+char* scrypt(uchar *p, ulong plen, uchar *s, ulong slen,
+	ulong N, ulong R, ulong P,
+	uchar *d, ulong dlen);
+
+/* hmac-based key derivation function (rfc5869) */
+void hkdf_x(uchar *salt, ulong nsalt, uchar *info, ulong ninfo, uchar *key, ulong nkey, uchar *d, ulong dlen,
+	DigestState* (*x)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*), int xlen);
+
+/* timing safe memcmp() */
+int tsmemcmp(void*, void*, ulong);
--- /dev/null
+++ b/include/memdraw.h
@@ -1,0 +1,171 @@
+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];
+};
+
+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*	allocmemimaged(Rectangle, ulong, Memdata*);
+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 ulong*	wordaddr(Memimage*, Point);
+extern uchar*	byteaddr(Memimage*, Point);
+extern int		drawclipnorepl(Memimage*, Rectangle*, Memimage*, Point*, Memimage*, Point*, Rectangle*, Rectangle*);
+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 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 int	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*);
--- /dev/null
+++ b/include/memlayer.h
@@ -1,0 +1,53 @@
+#ifdef PLAN9
+#pragma src "/sys/src/libmemlayer"
+#pragma lib "libmemlayer.a"
+#endif
+
+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,176 @@
+#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,	/* static constant */
+	MPnorm=		0x02,	/* normalization status */
+	MPtimesafe=	0x04,	/* request time invariant computation */
+	MPfield=	0x08,	/* this mpint is a field modulus */
+
+	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 */
+mpint*	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);
+/* return uniform random [0..n-1] */
+mpint*	mpnrand(mpint *n, 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**);
+void	mptolel(mpint *b, uchar *p, int n);
+mpint*	betomp(uchar*, uint, mpint*);	/* byte array, big-endian */
+int	mptobe(mpint*, uchar*, uint, uchar**);
+void	mptober(mpint *b, uchar *p, int n);
+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 */
+
+/* logical operations */
+void	mpand(mpint *b1, mpint *b2, mpint *res);
+void	mpbic(mpint *b1, mpint *b2, mpint *res);
+void	mpor(mpint *b1, mpint *b2, mpint *res);
+void	mpnot(mpint *b, mpint *res);
+void	mpxor(mpint *b1, mpint *b2, mpint *res);
+void	mptrunc(mpint *b, int n, mpint *res);
+void	mpxtend(mpint *b, int n, mpint *res);
+void	mpasr(mpint *b, int shift, mpint *res);
+
+/* modular arithmetic, time invariant when 0≤b1≤m-1 and 0≤b2≤m-1 */
+void	mpmodadd(mpint *b1, mpint *b2, mpint *m, mpint *sum);	/* sum = b1+b2 % m */
+void	mpmodsub(mpint *b1, mpint *b2, mpint *m, mpint *diff);	/* diff = b1-b2 % m */
+void	mpmodmul(mpint *b1, mpint *b2, mpint *m, mpint *prod);	/* prod = b1*b2 % 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);
+
+/* res = s != 0 ? b1 : b2 */
+void	mpsel(int s, mpint *b1, mpint *b2, mpint *res);
+
+/* 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);
+void	mpvectsmul(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);
+int	mpvectscmp(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*);
+
+/* fast field arithmetic */
+typedef struct Mfield	Mfield;
+
+struct Mfield
+{
+	mpint	m;
+	int	(*reduce)(Mfield*, mpint*, mpint*);
+};
+
+mpint *mpfield(mpint*);
+
+Mfield *gmfield(mpint*);
+Mfield *cnfield(mpint*);
--- /dev/null
+++ b/include/u.h
@@ -1,0 +1,27 @@
+#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
+#undef iounit
--- /dev/null
+++ b/include/unix.h
@@ -1,0 +1,35 @@
+#undef _FORTIFY_SOURCE	/* stupid ubuntu warnings */
+#define __BSD_VISIBLE 1 /* FreeBSD 5.x */
+#define _BSD_SOURCE 1
+#define _NETBSD_SOURCE 1	/* NetBSD */
+#define _SVID_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#if !defined(__APPLE__) && !defined(__OpenBSD__)
+#	define _XOPEN_SOURCE 1000
+#	define _XOPEN_SOURCE_EXTENDED 1
+#endif
+#define _LARGEFILE64_SOURCE 1
+#define _FILE_OFFSET_BITS 64
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stddef.h>
+#include <time.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+#ifdef PTHREAD
+#include <pthread.h>
+#endif
+
+typedef long long		p9_vlong;
+typedef unsigned long long p9_uvlong;
+typedef uintptr_t uintptr;
--- /dev/null
+++ b/include/user.h
@@ -1,0 +1,103 @@
+/* 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
+#undef sleep
+#define	sleep	osmsleep
+#define iounit	sysiounit
+#define getenv	sysgetenv
+
+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	void	werrstr(char* ,...);
+
+extern	Dir	*dirstat(char*);
+extern	Dir	*dirfstat(int);
+extern	int	dirwstat(char*, Dir*);
+extern	int	dirfwstat(int, Dir*);
+extern	long	dirread(int, Dir*, long);
+extern	ulong	iounit(int);
+
+extern	int	lfdfd(int);
+
+/*
+ *  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;
+
+extern	ulong	truerand(void);
+extern	int	pushssl(int, char*, char*, char*, int*);
+extern	long	pread(int, void*, long, vlong);
+extern	long	pwrite(int, void*, long, vlong);
+extern	void*	rendezvous(void*, void*);
+extern	int	kproc(char*, void(*)(void*), void*);
+extern	int	getpid(void);
+extern	void	panic(char*, ...);
+extern	void	sleep(int);
+extern	void	osyield(void);
+extern	void	setmalloctag(void*, uintptr);
+extern	void	setrealloctag(void*, uintptr);
+extern	int	errstr(char*, uint);
+extern	int	rerrstr(char*, uint);
+extern	int	encrypt(void*, void*, int);
+extern	int	decrypt(void*, void*, int);
+extern	void	qlock(QLock*);
+extern	void	qunlock(QLock*);
+extern	long	time(long*);
+extern	vlong	nsec(void);
+extern	void	lock(Lock*);
+extern	void	unlock(Lock*);
+extern	int	iprint(char*, ...);
+extern	int	atexit(void (*)(void));
+extern	void	exits(char*);
+extern	char*	getenv(char*);
+
+
+#define IOUNIT			32768	/* default buffer size for 9p io */
--- /dev/null
+++ b/kern/Makefile
@@ -1,0 +1,50 @@
+ROOT=..
+include ../Make.config
+LIB=libkern.a
+
+OFILES=\
+	alloc.$O\
+	allocb.$O\
+	chan.$O\
+	data.$O\
+	dev.$O\
+	devaudio.$O\
+	devaudio-$(AUDIO).$O\
+	devcmd.$O\
+	devcons.$O\
+	devdraw.$O\
+	devenv.$O\
+	devfs-$(OS).$O\
+	devip.$O\
+	devip-$(OS).$O\
+	devkbd.$O\
+	devlfd-$(OS).$O\
+	devmnt.$O\
+	devmouse.$O\
+	devpipe.$O\
+	devroot.$O\
+	devssl.$O\
+	devtls.$O\
+	devtab.$O\
+	error.$O\
+	parse.$O\
+	pgrp.$O\
+	procinit.$O\
+	rwlock.$O\
+	sleep.$O\
+	stub.$O\
+	sysfile.$O\
+	qio.$O\
+	qlock.$O\
+	term.$O\
+	waserror.$O\
+	$(OS).$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/kern/alloc.c
@@ -1,0 +1,40 @@
+#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);
+}
+
+enum {
+	SECMAGIC = 0x5ECA110C,
+};
+
+void*
+secalloc(ulong n)
+{
+	void *p = mallocz(n+sizeof(ulong)*2, 1);
+	((ulong*)p)[0] = SECMAGIC;
+	((ulong*)p)[1] = n;
+	return (ulong*)p+2;
+}
+
+void
+secfree(void *p)
+{
+	if(p != nil){
+		assert(((ulong*)p)[-2] == SECMAGIC);
+		memset(p, 0, ((ulong*)p)[-1]);
+		free((ulong*)p-2);
+	}
+}
--- /dev/null
+++ b/kern/allocb.c
@@ -1,0 +1,131 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Hdrspc		= 64,		/* leave room for high-level headers */
+	Tlrspc		= 16,		/* extra room at the end for pad/crc/mac */
+	Bdead		= 0x51494F42,	/* "QIOB" */
+};
+
+static Block*
+_allocb(int size)
+{
+	Block *b;
+	uintptr addr;
+
+	size += Tlrspc;
+	if((b = mallocz(sizeof(Block)+size+Hdrspc, 0)) == nil)
+		return nil;
+
+	b->next = nil;
+	b->list = nil;
+	b->free = nil;
+	b->flag = 0;
+
+	/* align start of data portion by rounding up */
+	addr = (uintptr)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 = (uintptr)b->lim;
+	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.
+	 */
+	if(up == nil)
+		panic("allocb without up: %#p", getcallerpc(&size));
+	if((b = _allocb(size)) == nil)
+		panic("allocb: no memory for %d bytes", size);
+	setmalloctag(b, getcallerpc(&size));
+
+	return b;
+}
+
+Block*
+iallocb(int size)
+{
+	Block *b;
+
+	if((b = _allocb(size)) == nil)
+		panic("iallocb: no memory for %d bytes", size);
+	setmalloctag(b, getcallerpc(&size));
+	b->flag = BINTR;
+
+	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 != nil) {
+		b->free(b);
+		return;
+	}
+
+	/* 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 %#p", msg, b);
+	if(b->base == dead || b->lim == dead || b->next == dead
+	  || b->rp == dead || b->wp == dead){
+		print("checkb: base %#p lim %#p next %#p\n",
+			b->base, b->lim, b->next);
+		print("checkb: rp %#p wp %#p\n", b->rp, b->wp);
+		panic("checkb dead: %s", msg);
+	}
+
+	if(b->base > b->lim)
+		panic("checkb 0 %s %#p %#p", msg, b->base, b->lim);
+	if(b->rp < b->base)
+		panic("checkb 1 %s %#p %#p", msg, b->base, b->rp);
+	if(b->wp < b->base)
+		panic("checkb 2 %s %#p %#p", msg, b->base, b->wp);
+	if(b->rp > b->lim)
+		panic("checkb 3 %s %#p %#p", msg, b->rp, b->lim);
+	if(b->wp > b->lim)
+		panic("checkb 4 %s %#p %#p", msg, b->wp, b->lim);
+}
--- /dev/null
+++ b/kern/chan.c
@@ -1,0 +1,1641 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	PATHSLOP	= 20,
+	PATHMSLOP	= 20,
+};
+
+static struct Chanalloc
+{
+	Lock	lk;
+	int	fid;
+	Chan	*free;
+	Chan	*list;
+}chanalloc;
+
+typedef struct Elemlist Elemlist;
+
+struct Elemlist
+{
+	char	*aname;	/* original name */
+	char	*name;	/* copy of name, so '/' can be overwritten */
+	int	nelems;
+	char	**elems;
+	int	*off;
+	int	mustbedir;
+	int	nerror;
+	int	prefix;
+};
+
+char*
+chanpath(Chan *c)
+{
+	if(c == nil)
+		return "<nil chan>";
+	if(c->path == nil)
+		return "<nil path>";
+	if(c->path->s == nil)
+		return "<nil path.s>";
+	return c->path->s;
+}
+
+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%p", 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 < ns){
+		memmove(s, t, nt);
+		s[nt] = '\0';
+		return;
+	}
+	/* too long, truncate */
+	nt = ns-1;
+	memmove(s, t, nt);
+	s[nt] = '\0';
+	/* append ... if there is space */
+	ns -= 4;
+	if(ns < 0)
+		return;
+	/* 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;
+
+	n = strlen(s);
+	/* if it's a user, we can wait for memory; if not, something's very wrong */
+	if(up != nil)
+		t = smalloc(n+1);
+	else{
+		t = malloc(n+1);
+		if(t == nil)
+			panic("kstrdup: no memory");
+	}
+	setmalloctag(t, getcallerpc(&p));
+	memmove(t, s, n);
+	t[n] = '\0';
+	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 != nil){
+		chanalloc.free = c->next;
+		c->next = nil;
+	} else {
+		unlock(&chanalloc.lk);
+		c = smalloc(sizeof(Chan));
+		lock(&chanalloc.lk);
+		c->link = chanalloc.list;
+		chanalloc.list = c;
+	}
+	if(c->fid == 0)
+		c->fid = ++chanalloc.fid;
+	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->devoffset = 0;
+	c->iounit = 0;
+	c->umh = nil;
+	c->umc = nil;
+	c->uri = 0;
+	c->dri = 0;
+	c->dirrock = nil;
+	c->nrock = 0;
+	c->mrock = 0;
+	c->ismtpt = 0;
+	c->mux = nil;
+	c->aux = nil;
+	c->mchan = nil;
+	memset(&c->mqid, 0, sizeof(c->mqid));
+	c->path = nil;
+
+	return c;
+}
+
+Path*
+newpath(char *s)
+{
+	int i;
+	Path *p;
+
+	p = smalloc(sizeof(Path));
+	i = strlen(s);
+	p->len = i;
+	p->alen = i+PATHSLOP;
+	p->s = smalloc(p->alen);
+	memmove(p->s, s, i+1);
+	p->ref.ref = 1;
+
+	/*
+	 * Cannot use newpath for arbitrary names because the mtpt 
+	 * array will not be populated correctly.  The names #/ and / are
+	 * allowed, but other names with / in them draw warnings.
+	 */
+	if(strchr(s, '/') != nil && strcmp(s, "#/") != 0 && strcmp(s, "/") != 0)
+		print("newpath: %s from %#p\n", s, getcallerpc(&s));
+
+	p->mlen = 1;
+	p->malen = PATHMSLOP;
+	p->mtpt = smalloc(p->malen*sizeof p->mtpt[0]);
+	return p;
+}
+
+static Path*
+copypath(Path *p)
+{
+	int i;
+	Path *pp;
+	
+	pp = smalloc(sizeof(Path));
+	pp->ref.ref = 1;
+	
+	pp->len = p->len;
+	pp->alen = p->alen;
+	pp->s = smalloc(p->alen);
+	memmove(pp->s, p->s, p->len+1);
+	
+	pp->mlen = p->mlen;
+	pp->malen = p->malen;
+	pp->mtpt = smalloc(p->malen*sizeof pp->mtpt[0]);
+	for(i=0; i<pp->mlen; i++){
+		pp->mtpt[i] = p->mtpt[i];
+		if(pp->mtpt[i] != nil)
+			incref(&pp->mtpt[i]->ref);
+	}
+
+	return pp;
+}
+
+void
+pathclose(Path *p)
+{
+	int i;
+
+	if(p == nil || decref(&p->ref))
+		return;
+	for(i=0; i<p->mlen; i++)
+		if(p->mtpt[i] != nil)
+			cclose(p->mtpt[i]);
+	free(p->mtpt);
+	free(p->s);
+	free(p);
+}
+
+/*
+ * In place, rewrite name to compress multiple /, eliminate ., and process ..
+ * (Really only called to remove a trailing .. that has been added.
+ * Otherwise would need to update n->mtpt as well.)
+ */
+static void
+fixdotdotname(Path *p)
+{
+	char *r;
+
+	if(p->s[0] == '#'){
+		r = strchr(p->s, '/');
+		if(r == nil)
+			return;
+		cleanname(r);
+
+		/*
+		 * The correct name is #i rather than #i/,
+		 * but the correct name of #/ is #/.
+		 */
+		if(strcmp(r, "/")==0 && p->s[1] != '/')
+			*r = '\0';
+	}else
+		cleanname(p->s);
+	p->len = strlen(p->s);
+}
+
+static Path*
+uniquepath(Path *p)
+{
+	Path *new;
+	
+	if(p->ref.ref > 1){
+		/* copy on write */
+		new = copypath(p);
+		pathclose(p);
+		p = new;
+	}
+	return p;
+}
+
+static Path*
+addelem(Path *p, char *s, Chan *from)
+{
+	char *t;
+	int a, i;
+	Chan *c, **tt;
+
+	if(s[0]=='.' && s[1]=='\0')
+		return p;
+
+	p = uniquepath(p);
+
+	i = strlen(s);
+	a = p->len+1+i+1;
+	if(a > p->alen){
+		a += PATHSLOP;
+		t = smalloc(a);
+		memmove(t, p->s, p->len+1);
+		free(p->s);
+		p->s = t;
+		p->alen = a;
+	}
+	/* don't insert extra slash if one is present */
+	if(p->len>0 && p->s[p->len-1]!='/' && s[0]!='/')
+		p->s[p->len++] = '/';
+	memmove(p->s+p->len, s, i+1);
+	p->len += i;
+	if(isdotdot(s)){
+		fixdotdotname(p);
+		if(p->mlen > 1 && (c = p->mtpt[--p->mlen]) != nil){
+			p->mtpt[p->mlen] = nil;
+			cclose(c);
+		}
+	}else{
+		if(p->mlen >= p->malen){
+			p->malen = p->mlen+1+PATHMSLOP;
+			tt = smalloc(p->malen*sizeof tt[0]);
+			memmove(tt, p->mtpt, p->mlen*sizeof tt[0]);
+			free(p->mtpt);
+			p->mtpt = tt;
+		}
+		p->mtpt[p->mlen++] = from;
+		if(from != nil)
+			incref(&from->ref);
+	}
+	return p;
+}
+
+void
+chanfree(Chan *c)
+{
+	c->flag = CFREE;
+
+	if(c->dirrock != nil){
+		free(c->dirrock);
+		c->dirrock = nil;
+		c->nrock = 0;
+		c->mrock = 0;
+	}
+	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;
+	}
+
+	pathclose(c->path);
+	c->path = nil;
+
+	lock(&chanalloc.lk);
+	c->next = chanalloc.free;
+	chanalloc.free = c;
+	unlock(&chanalloc.lk);
+}
+
+void
+cclose(Chan *c)
+{
+	if(c == nil || c->ref.ref < 1 || c->flag&CFREE)
+		panic("cclose %#p", 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;
+	}
+
+	if(c->umh != nil){	//BUG
+		print("cunique umh != nil from %#p\n", getcallerpc(&c));
+		putmhead(c->umh);
+		c->umh = nil;
+	}
+
+	return c;
+}
+
+int
+eqqid(Qid a, Qid b)
+{
+	return a.path==b.path && a.vers==b.vers;
+}
+
+int
+eqchan(Chan *a, Chan *b, int skipvers)
+{
+	if(a->qid.path != b->qid.path)
+		return 0;
+	if(!skipvers && 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 skipvers)
+{
+	if(a->qid.path != qid.path)
+		return 0;
+	if(!skipvers && 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);
+	return mh;
+}
+
+/*
+ * 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 in cunmount() and closepgrp().
+ * 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 == nil)
+		return;
+	if(decref(&m->ref))
+		return;
+	assert(m->mount == nil);
+	cclose(m->from);
+	free(m);
+}
+
+int
+cmount(Chan *new, Chan *old, int flag, char *spec)
+{
+	int order;
+	Mhead *m, **l, *mh;
+	Mount *nm, *f, *um;
+	Pgrp *pg;
+
+	if(old->umh != nil)
+		print("cmount: unexpected umh, caller %#p\n", getcallerpc(&new));
+
+	if(QTDIR & (old->qid.type^new->qid.type))
+		error(Emount);
+
+	order = flag&MORDER;
+
+	if((old->qid.type&QTDIR) == 0 && order != MREPL)
+		error(Emount);
+
+	nm = newmount(new, flag, spec);
+	mh = new->umh;
+	if(mh != nil) {
+		rlock(&mh->lock);
+		if(waserror()) {
+			runlock(&mh->lock);
+			mountfree(nm);
+			nexterror();
+		}
+		um = mh->mount;
+		if(um != nil){
+			/*
+			 * 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 allows 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.
+			 */
+			if((flag&MCREATE) != 0 && (um->next != nil || (um->mflag&MCREATE) == 0))
+				error(Emount);
+
+			/*
+			 *  copy a union when binding it onto a directory
+			 */
+			f = nm;
+			for(um = um->next; um != nil; um = um->next){
+				f->next = newmount(um->to, order==MREPL? MAFTER: order, um->spec);
+				f = f->next;
+			}
+		}
+		runlock(&mh->lock);
+		poperror();
+	}
+
+	pg = up->pgrp;
+	wlock(&pg->ns);
+	l = &MOUNTH(pg, old->qid);
+	for(m = *l; m != nil; 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);
+		/*
+		 *  if this is a union mount, add the old
+		 *  node to the mount chain.
+		 */
+		if(order != MREPL)
+			m->mount = newmount(old, 0, nil);
+		*l = m;
+	}
+	wlock(&m->lock);
+	um = m->mount;
+	if(um != nil && order == MAFTER){
+		for(f = um; f->next != nil; f = f->next)
+			;
+		f->next = nm;
+		um = nil;
+	} else {
+		if(order != MREPL){
+			for(f = nm; f->next != nil; f = f->next)
+				;
+			f->next = um;
+			um = nil;
+		}
+		m->mount = nm;
+	}
+	order = nm->mountid;
+	wunlock(&m->lock);
+	wunlock(&pg->ns);
+
+	mountfree(um);
+
+	return order;
+}
+
+void
+cunmount(Chan *mnt, Chan *mounted)
+{
+	Pgrp *pg;
+	Mhead *m, **l;
+	Mount *f, **p;
+
+	if(mnt->umh != nil)	/* 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 != nil; m = m->hash){
+		if(eqchan(m->from, mnt, 1))
+			break;
+		l = &m->hash;
+	}
+
+	if(m == nil){
+		wunlock(&pg->ns);
+		error(Eunmount);
+	}
+
+	wlock(&m->lock);
+	f = m->mount;
+	if(mounted == nil){
+		*l = m->hash;
+		m->mount = nil;
+		wunlock(&m->lock);
+		wunlock(&pg->ns);
+		mountfree(f);
+		putmhead(m);
+		return;
+	}
+	for(p = &m->mount; f != nil; f = f->next){
+		if(eqchan(f->to, mounted, 1) ||
+		  (f->to->mchan != nil && eqchan(f->to->mchan, mounted, 1))){
+			*p = f->next;
+			f->next = nil;
+			if(m->mount == nil){
+				*l = m->hash;
+				wunlock(&m->lock);
+				wunlock(&pg->ns);
+				mountfree(f);
+				putmhead(m);
+				return;
+			}
+			wunlock(&m->lock);
+			wunlock(&pg->ns);
+			mountfree(f);
+			return;
+		}
+		p = &f->next;
+	}
+	wunlock(&m->lock);
+	wunlock(&pg->ns);
+	error(Eunion);
+}
+
+Chan*
+cclone(Chan *c)
+{
+	Chan *nc;
+	Walkqid *wq;
+
+	if(c == nil || c->ref.ref < 1 || c->flag&CFREE)
+		panic("cclone: %#p", getcallerpc(&c));
+	wq = devtab[c->type]->walk(c, nil, nil, 0);
+	if(wq == nil)
+		error("clone failed");
+	nc = wq->clone;
+	free(wq);
+	if((nc->path = c->path) != nil)
+		incref(&c->path->ref);
+	return nc;
+}
+
+/* also used by sysfile.c:/^mountfix */
+int
+findmount(Chan **cp, Mhead **mp, int type, int dev, Qid qid)
+{
+	Chan *to;
+	Pgrp *pg;
+	Mhead *m;
+
+	pg = up->pgrp;
+	rlock(&pg->ns);
+	for(m = MOUNTH(pg, qid); m != nil; m = m->hash){
+		if(eqchantdqid(m->from, type, dev, qid, 1)){
+			if(mp != nil)
+				incref(&m->ref);
+			rlock(&m->lock);
+			to = m->mount->to;
+			incref(&to->ref);
+			runlock(&m->lock);
+			runlock(&pg->ns);
+			if(mp != nil){
+				putmhead(*mp);
+				*mp = m;
+			}
+			if(*cp != nil)
+				cclose(*cp);
+			*cp = to;
+			return 1;
+		}
+	}
+	runlock(&pg->ns);
+	return 0;
+}
+
+/*
+ * Calls findmount but also updates path.
+ */
+static int
+domount(Chan **cp, Mhead **mp, Path **path)
+{
+	Chan **lc, *from;
+	Path *p;
+
+	if(findmount(cp, mp, (*cp)->type, (*cp)->dev, (*cp)->qid) == 0)
+		return 0;
+
+	if(path != nil){
+		p = *path;
+		p = uniquepath(p);
+		if(p->mlen <= 0)
+			print("domount: path %s has mlen==%d\n", p->s, p->mlen);
+		else{
+			from = (*mp)->from;
+			incref(&from->ref);
+			lc = &p->mtpt[p->mlen-1];
+			if(*lc != nil)
+				cclose(*lc);
+			*lc = from;
+		}
+		*path = p;
+	}
+	return 1;
+}
+
+/*
+ * If c is the right-hand-side of a mount point, returns the left hand side.
+ * Changes name to reflect the fact that we've uncrossed the mountpoint,
+ * so name had better be ours to change!
+ */
+static Chan*
+undomount(Chan *c, Path *path)
+{
+	Chan *nc;
+
+	if(path->ref.ref != 1 || path->mlen == 0)
+		print("undomount: path %s ref %ld mlen %d caller %#p\n",
+			path->s, path->ref, path->mlen, getcallerpc(&c));
+
+	if(path->mlen > 0 && (nc = path->mtpt[path->mlen-1]) != nil){
+		cclose(c);
+		path->mtpt[path->mlen-1] = nil;
+		c = nc;
+	}
+	return c;
+}
+
+/*
+ * Call dev walk but catch errors.
+ */
+static Walkqid*
+ewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	Walkqid *wq;
+
+	if(waserror())
+		return nil;
+	wq = devtab[c->type]->walk(c, nc, name, nname);
+	poperror();
+	return wq;
+}
+
+/*
+ * 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, didmount, dotdot, i, n, nhave, ntry, type;
+	Chan *c, *nc, *mtpt;
+	Path *path;
+	Mhead *mh, *nmh;
+	Mount *f;
+	Walkqid *wq;
+
+	c = *cp;
+	incref(&c->ref);
+	path = c->path;
+	incref(&path->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.
+	 *
+	 * Each time through the loop:
+	 *
+	 *	If didmount==0, c is on the undomount side of the mount point.
+	 *	If didmount==1, c is on the domount side of the mount point.
+	 * 	Either way, c's full path is path.
+	 */
+	didmount = 0;
+	for(nhave=0; nhave<nnames; nhave+=n){
+		if((c->qid.type&QTDIR) == 0){
+			if(nerror)
+				*nerror = nhave;
+			pathclose(path);
+			cclose(c);
+			kstrcpy(up->errstr, Enotdir, ERRMAX);
+			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 && !didmount)
+			domount(&c, &mh, &path);
+		
+		type = c->type;
+		dev = c->dev;
+
+		if((wq = ewalk(c, nil, names+nhave, ntry)) == nil){
+			/* try a union mount, if any */
+			if(mh != nil && !nomount){
+				/*
+				 * mh->mount->to == c, so start at mh->mount->next
+				 */
+				rlock(&mh->lock);
+				if((f = mh->mount) != nil)
+					f = f->next;
+				for(; f != nil; f = f->next)
+					if((wq = ewalk(f->to, nil, names+nhave, ntry)) != nil){
+						type = f->to->type;
+						dev = f->to->dev;
+						break;
+					}
+				runlock(&mh->lock);
+			}
+			if(wq == nil){
+				cclose(c);
+				pathclose(path);
+				if(nerror)
+					*nerror = nhave+1;
+				putmhead(mh);
+				return -1;
+			}
+		}
+
+		didmount = 0;
+		if(dotdot){
+			assert(wq->nqid == 1);
+			assert(wq->clone != nil);
+
+			path = addelem(path, "..", nil);
+			nc = undomount(wq->clone, path);
+			nmh = nil;
+			n = 1;
+		}else{
+			nc = nil;
+			nmh = nil;
+			if(!nomount){
+				for(i=0; i<wq->nqid && i<ntry-1; i++){
+					if(findmount(&nc, &nmh, type, dev, wq->qid[i])){
+						didmount = 1;
+						break;
+					}
+				}
+			}
+			if(nc == nil){	/* no mount points along path */
+				if(wq->clone == nil){
+					cclose(c);
+					pathclose(path);
+					if(wq->nqid == 0 || (wq->qid[wq->nqid-1].type&QTDIR) != 0){
+						if(nerror)
+							*nerror = nhave+wq->nqid+1;
+						kstrcpy(up->errstr, Edoesnotexist, ERRMAX);
+					}else{
+						if(nerror)
+							*nerror = nhave+wq->nqid;
+						kstrcpy(up->errstr, Enotdir, ERRMAX);
+					}
+					free(wq);
+					putmhead(mh);
+					return -1;
+				}
+				n = wq->nqid;
+				nc = wq->clone;
+			}else{		/* stopped early, at a mount point */
+				assert(didmount);
+				if(wq->clone != nil){
+					cclose(wq->clone);
+					wq->clone = nil;
+				}
+				n = i+1;
+			}
+			for(i=0; i<n; i++){
+				mtpt = nil;
+				if(i==n-1 && nmh!=nil)
+					mtpt = nmh->from;
+				path = addelem(path, names[nhave+i], mtpt);
+			}
+		}
+		cclose(c);
+		c = nc;
+		putmhead(mh);
+		mh = nmh;
+		free(wq);
+	}
+	putmhead(mh);
+	c = cunique(c);
+
+	pathclose(c->path);
+	c->path = path;
+
+	cclose(*cp);
+	*cp = c;
+	if(nerror)
+		*nerror = nhave;
+	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 != nil; f = f->next){
+		if((f->mflag&MCREATE) != 0){
+			nc = cclone(f->to);
+			runlock(&m->lock);
+			poperror();
+			cclose(c);
+			return nc;
+		}
+	}
+	error(Enocreate);
+	return 0;
+}
+
+void
+saveregisters(void)
+{
+}
+
+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+1)*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 *aname, Elemlist *e)
+{
+	char *name, *slash;
+
+	kstrdup(&e->name, aname);
+	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->off[e->nelems] = name+strlen(name) - e->name;
+			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;
+	}
+}
+
+static void
+namelenerror(char *aname, int len, char *err)
+{
+	char *ename, *name, *next;
+	int i, errlen;
+
+	/*
+	 * If the name is short enough, just use the whole thing.
+	 */
+	errlen = strlen(err);
+	if(len < ERRMAX/3 || len+errlen < 2*ERRMAX/3)
+		snprint(up->genbuf, sizeof up->genbuf, "%.*s", 
+			utfnlen(aname, len), aname);
+	else{
+		/*
+		 * Print a suffix of the name, but try to get a little info.
+		 */
+		ename = aname+len;
+		next = ename;
+		do{
+			name = next;
+			if(next == aname)
+				break;
+			while(next > aname)
+				if(*--next == '/')
+					break;
+			len = ename-next;
+		}while(len < ERRMAX/3 || len + errlen < 2*ERRMAX/3);
+
+		/*
+		 * If the name is ridiculously long, chop it.
+		 */
+		if(name == ename){
+			name = ename-ERRMAX/4;
+			if(name <= aname)
+				panic("bad math in namelenerror");
+			/* walk out of current UTF sequence */
+			for(i=0; (*name&0xC0)==0x80 && i<UTFmax; i++)
+				name++;
+		}
+		snprint(up->genbuf, sizeof up->genbuf, "...%.*s",
+			utfnlen(name, ename-name), name);
+	}				
+	snprint(up->errstr, ERRMAX, "%#q %s", up->genbuf, err);
+	nexterror();
+}
+
+void
+nameerror(char *name, char *err)
+{
+	namelenerror(name, strlen(name), err);
+}
+
+/*
+ * 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, Aremove, or Aaccess 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 or Amount does not guarantee this.
+ *
+ * Under certain circumstances, opening Aaccess will cause
+ * an unnecessary clone in order to get a cunique Chan so it
+ * can attach the correct name.  Sysstat and sys_stat need the
+ * correct name so they can rewrite the stat info.
+ */
+Chan*
+namec(char *aname, int amode, int omode, ulong perm)
+{
+	int len, n, t, nomount;
+	Chan *c;
+	Chan *volatile cnew;
+	Path *volatile path;
+	Elemlist e;
+	Rune r;
+	Mhead *m;
+	char *createerr, tmperrbuf[ERRMAX];
+	char *name;
+
+	if(aname[0] == '\0')
+		error("empty file name");
+	aname = validnamedup(aname, 1);
+	if(waserror()){
+		free(aname);
+		nexterror();
+	}
+	name = aname;
+
+	/*
+	 * 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;
+		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;
+	}
+
+	e.aname = aname;
+	e.prefix = name - aname;
+	e.name = nil;
+	e.elems = nil;
+	e.off = nil;
+	e.nelems = 0;
+	e.nerror = 0;
+	if(waserror()){
+		cclose(c);
+		free(e.name);
+		free(e.elems);
+		/*
+		 * Prepare nice error, showing first e.nerror elements of name.
+		 */
+		if(e.nerror == 0)
+			nexterror();
+		strcpy(tmperrbuf, up->errstr);
+		if(e.off[e.nerror]==0)
+			print("nerror=%d but off=%d\n",
+				e.nerror, e.off[e.nerror]);
+		len = e.prefix+e.off[e.nerror];
+		free(e.off);
+		namelenerror(aname, len, tmperrbuf);
+	}
+
+	/*
+	 * Build a list of elements in the name.
+	 */
+	parsename(name, &e);
+
+	/*
+	 * On create, ....
+	 */
+	if(amode == Acreate){
+		/* perm must have DMDIR if last element is / or /. */
+		if(e.mustbedir && !(perm&DMDIR)){
+			e.nerror = e.nelems;
+			error("create without DMDIR");
+		}
+
+		/* 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, &e.nerror) < 0){
+		if(e.nerror < 0 || e.nerror > e.nelems){
+			print("namec %s walk error nerror=%d\n", aname, e.nerror);
+			e.nerror = 0;
+		}
+		nexterror();
+	}
+
+	if(e.mustbedir && (c->qid.type&QTDIR) == 0)
+		error("not a directory");
+
+	if(amode == Aopen && (omode&3) == OEXEC && (c->qid.type&QTDIR) != 0)
+		error("cannot exec directory");
+
+	switch(amode){
+	case Abind:
+		/* no need to maintain path - cannot dotdot an Abind */
+		m = nil;
+		if(!nomount)
+			domount(&c, &m, nil);
+		if(waserror()){
+			putmhead(m);
+			nexterror();
+		}
+		c = cunique(c);
+		c->umh = m;
+		poperror();
+		break;
+
+	case Aaccess:
+	case Aremove:
+	case Aopen:
+	Open:
+		/* save&update the name; domount might change c */
+		path = c->path;
+		incref(&path->ref);
+		if(waserror()){
+			pathclose(path);
+			nexterror();
+		}
+		m = nil;
+		if(!nomount)
+			domount(&c, &m, &path);
+		if(waserror()){
+			putmhead(m);
+			nexterror();
+		}
+		/* our own copy to open or remove */
+		c = cunique(c);
+		poperror();
+
+		/* now it's our copy anyway, we can put the name back */
+		pathclose(c->path);
+		c->path = path;
+		poperror();
+
+		/* record whether c is on a mount point */
+		c->ismtpt = m!=nil;
+
+		switch(amode){
+		case Aaccess:
+		case Aremove:
+			putmhead(m);
+			break;
+
+		case Aopen:
+		case Acreate:
+			/* only save the mount head if it's a multiple element union */
+			if(m != nil) {
+				rlock(&m->lock);
+				if(m->mount != nil && m->mount->next != nil) {
+					c->umh = m;
+					runlock(&m->lock);
+				} else {
+					runlock(&m->lock);
+					putmhead(m);
+				}
+			}
+
+			/* save registers else error() in open has wrong value of c saved */
+			saveregisters();
+
+			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) == 0)
+			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++;
+		e.nerror++;
+		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);
+			pathclose(cnew->path);
+			cnew->path = c->path;
+			incref(&cnew->path->ref);
+
+			cnew = 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;
+			putmhead(m);
+			cclose(c);
+			c = cnew;
+			c->path = addelem(c->path, e.elems[e.nelems-1], nil);
+			break;
+		}
+
+		/* create failed */
+		cclose(cnew);
+		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;
+
+	default:
+		panic("unknown namec access %d", amode);
+	}
+
+	/* 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);
+	poperror();	/* e c */
+	free(aname);
+	poperror();	/* aname */
+
+	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,
+	/*BKS*/	1, 1, 1, 1, 1, 1, 1, 1,
+	/*DLE*/	1, 1, 1, 1, 1, 1, 1, 1,
+	/*CAN*/	1, 1, 1, 1, 1, 1, 1, 1,
+	['/']	1,
+	[0x7f]	1,
+};
+
+/*
+ * 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.
+ *
+ * The parameter dup flags whether the string should be copied
+ * out of user space before being scanned the second time.
+ * (Otherwise a malicious thread could remove the NUL, causing us
+ * to access unchecked addresses.) 
+ */
+static char*
+validname0(char *aname, int slashok, int dup, uintptr pc)
+{
+	char *ename, *name, *s;
+	int c, n;
+	Rune r;
+
+	name = aname;
+	ename = memchr(name, 0, (1<<16));
+
+	if(ename==nil || ename-name>=(1<<16))
+		error(Etoolong);
+
+	s = nil;
+	if(dup){
+		n = ename-name;
+		s = smalloc(n+1);
+		memmove(s, name, n);
+		s[n] = 0;
+		aname = s;
+		name = s;
+		setmalloctag(s, pc);
+	}
+	
+	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);
+				free(s);
+				error(up->genbuf);
+			}
+			name++;
+		}
+	}
+	return s;
+}
+
+void
+validname(char *aname, int slashok)
+{
+	validname0(aname, slashok, 0, getcallerpc(&aname));
+}
+
+char*
+validnamedup(char *aname, int slashok)
+{
+	return validname0(aname, slashok, 1, getcallerpc(&aname));
+}
+
+void
+isdir(Chan *c)
+{
+	if(c->qid.type & QTDIR)
+		return;
+	error(Enotdir);
+}
+
+
+enum
+{
+	DIRSIZE	= STATFIXLEN + 16 * 4		/* enough for encoded stat buf + some reasonable strings */
+};
+
+Dir*
+dirchanstat(Chan *c)
+{
+	Dir *d;
+	uchar *buf;
+	int n, nd, i;
+
+	nd = DIRSIZE;
+	for(i=0; i<2; i++){	/* should work by the second try */
+		d = smalloc(sizeof(Dir) + BIT16SZ + nd);
+		if(waserror()){
+			free(d);
+			nexterror();
+		}
+		buf = (uchar*)&d[1];
+		n = devtab[c->type]->stat(c, buf, BIT16SZ+nd);
+		if(n < BIT16SZ)
+			error(Eshortstat);
+		nd = GBIT16(buf);	/* upper bound on size of Dir + strings */
+		if(nd <= n){
+			if(convM2D(buf, n, d, (char*)&d[1]) == 0)
+				error(Eshortstat);
+			poperror();
+			return d;
+		}
+		/* else sizeof(Dir)+BIT16SZ+nd is plenty */
+		free(d);
+		poperror();
+	}
+	error(Eshortstat);
+	return nil;
+}
--- /dev/null
+++ b/kern/dat.h
@@ -1,0 +1,484 @@
+#define	KNAMELEN		28	/* max length of name held in kernel */
+
+#define	BLOCKALIGN		8
+
+typedef struct Block	Block;
+typedef struct Chan	Chan;
+typedef struct Cmdbuf	Cmdbuf;
+typedef struct Cmdtab	Cmdtab;
+typedef struct Conf	Conf;
+typedef struct Dev	Dev;
+typedef struct Dirtab	Dirtab;
+typedef struct Egrp	Egrp;
+typedef struct Evalue	Evalue;
+typedef struct Fgrp	Fgrp;
+typedef struct DevConf	DevConf;
+typedef struct Label	Label;
+typedef struct Log	Log;
+typedef struct Logflag	Logflag;
+typedef struct Mount	Mount;
+typedef struct Mntrpc	Mntrpc;
+typedef struct Mntwalk	Mntwalk;
+typedef struct Mnt	Mnt;
+typedef struct Mhead	Mhead;
+typedef struct Path	Path;
+typedef struct Pgrps	Pgrps;
+typedef struct Pgrp	Pgrp;
+typedef struct Proc	Proc;
+typedef struct Queue	Queue;
+typedef struct Ref	Ref;
+typedef struct Rendez	Rendez;
+typedef struct Rgrp	Rgrp;
+typedef struct RWlock	RWlock;
+typedef struct Waitq	Waitq;
+typedef struct Walkqid	Walkqid;
+typedef struct Kmesg	Kmesg;
+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	pipeqsize;	/* size in bytes of pipe queues */
+};
+
+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 */
+/*	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;
+	Lock	lk;
+	Chan*	next;			/* allocation */
+	Chan*	link;
+	vlong	offset;			/* in fd */
+	vlong	devoffset;		/* in underlying device; see read */
+	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 */
+	uchar*	dirrock;		/* directory entry rock for translations */
+	int	nrock;
+	int	mrock;
+	QLock	rockqlock;
+	int	ismtpt;
+	Mnt*	mux;			/* Mnt for clients using me for messages */
+	union {
+		void*	aux;
+		ulong	mid;		/* for ns in devproc */
+	};
+	Chan*	mchan;			/* channel to mounted server */
+	Qid	mqid;			/* qid of root of mount point */
+	Path*	path;
+};
+
+struct Path
+{
+	Ref	ref;
+	char	*s;
+	Chan	**mtpt;			/* mtpt history */
+	int	len;			/* strlen(s) */
+	int	alen;			/* allocated length of s */
+	int	mlen;			/* number of path elements */
+	int	malen;			/* allocated length of mtpt */
+};
+
+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);
+	Chan*	(*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 */
+};
+
+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 */
+};
+#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;
+	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;
+
+	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 */
+
+	void*	rendtag;	/* Tag for rendezvous */
+	void*	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	int	cpuserver;
+extern	Dev*	devtab[];
+extern  char	*eve;
+extern	char	hostdomain[];
+extern  Queue*	kbdq;
+extern  Queue*	kprintoq;
+extern	char*	statename[];
+extern	char	*sysname;
+extern	uint	qiomaxatomic;
+extern	Conf	conf;
+
+/*
+ *  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())
+
+/*
+ * Log console output so it can be retrieved via /dev/kmesg.
+ * This is good for catching boot-time messages after the fact.
+ */
+struct Kmesg {
+	Lock lk;
+	uint n;
+	char buf[16384];
+};
+
+extern Kmesg kmesg;
--- /dev/null
+++ b/kern/data.c
@@ -1,0 +1,17 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+Conf conf = 
+{
+	1,	/* processors */
+	100,	/* processes */
+	0,	/* size in bytes of pipe queues */
+};
+
+char *eve = "eve";
+ulong kerndate;
+int cpuserver;
+char hostdomain[] = "drawcpu.net";
--- /dev/null
+++ b/kern/dev.c
@@ -1,0 +1,461 @@
+#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 %#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)
+{
+	int n;
+	Chan *c;
+	char *buf;
+
+	c = newchan();
+	mkqid(&c->qid, 0, 0, QTDIR);
+	c->type = devno(tc, 0);
+	if(spec == nil)
+		spec = "";
+	n = 1+UTFmax+strlen(spec)+1;
+	buf = smalloc(n);
+	snprint(buf, n, "#%C%s", tc, spec);
+	c->path = newpath(buf);
+	free(buf);
+	return c;
+}
+
+
+Chan*
+devclone(Chan *c)
+{
+	Chan *nc;
+
+	if(c->flag & COPEN)
+		panic("clone of open file type %C", 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->aux = c->aux;
+	nc->mqid = c->mqid;
+	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 = (nc == nil);
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	if(waserror()){
+		if(alloc && wq->clone != nil)
+			cclose(wq->clone);
+		free(wq);
+		return nil;
+	}
+	if(alloc){
+		nc = devclone(c);
+		nc->type = 0;	/* device doesn't know about this channel yet */
+	}
+	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 != nil){
+		/* 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->path == nil)
+					elem = "???";
+				else if(strcmp(c->path->s, "/") == 0)
+					elem = "/";
+				else
+					for(elem=p=c->path->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;
+			}
+
+			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;
+		}
+	}
+}
+
+long
+devdirread(Chan *c, char *d, long n, Dirtab *tab, int ntab, Devgen *gen)
+{
+	long m, dsz;
+	Dir dir;
+
+	for(m=0; m<n; c->dri++) {
+		switch((*gen)(c, nil, tab, ntab, c->dri, &dir)){
+		case -1:
+			return m;
+
+		case 0:
+			break;
+
+		case 1:
+			dsz = convD2M(&dir, (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;
+}
+
+Chan*
+devcreate(Chan *c, char *name, int mode, ulong perm)
+{
+	USED(c);
+	USED(name);
+	USED(mode);
+	USED(perm);
+
+	error(Eperm);
+	return 0;
+}
+
+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/devaudio-alsa.c
@@ -1,0 +1,108 @@
+/*
+ * ALSA
+ */
+#include <alsa/asoundlib.h>
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+enum
+{
+	Channels = 2,
+	Rate = 44100,
+	Bits = 16,
+};
+
+static snd_pcm_t *playback;
+static snd_pcm_t *capture;
+static int speed = Rate;
+
+/* maybe this should return -1 instead of sysfatal */
+void
+audiodevopen(void)
+{
+	if(snd_pcm_open(&playback, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0)
+		error("snd_pcm_open playback");
+
+	if(snd_pcm_set_params(playback, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, speed, 1, 500000) < 0)
+		error("snd_pcm_set_params playback");
+
+	if(snd_pcm_prepare(playback) < 0)
+		error("snd_pcm_prepare playback");
+
+	if(snd_pcm_open(&capture, "default", SND_PCM_STREAM_CAPTURE, 0) < 0)
+		error("snd_pcm_open capture");
+
+	if(snd_pcm_set_params(capture, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, speed, 1, 500000) < 0)
+		error("snd_pcm_set_params capture");
+
+	if(snd_pcm_prepare(capture) < 0)
+		error("snd_pcm_prepare capture");
+}
+
+void
+audiodevclose(void)
+{
+	snd_pcm_drain(playback);
+	snd_pcm_close(playback);
+
+	snd_pcm_close(capture);
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+	if(what == Vspeed){
+		speed = left;
+		return;
+	}
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+	if(what == Vspeed){
+		*left = *right = speed;
+		return;
+	}
+
+	*left = *right = 100;
+}
+
+int
+audiodevwrite(void *v, int n)
+{
+	snd_pcm_sframes_t frames;
+	int tot, m;
+
+	for(tot = 0; tot < n; tot += m){
+		do {
+			frames = snd_pcm_writei(playback, v+tot, (n-tot)/4);
+		} while(frames == -EAGAIN);
+		if (frames < 0)
+			frames = snd_pcm_recover(playback, frames, 0);
+		if (frames < 0)
+			error((char*)snd_strerror(frames));
+		m = frames*4;
+	}
+
+	return tot;
+}
+
+int
+audiodevread(void *v, int n)
+{
+	snd_pcm_sframes_t frames;
+
+	do {
+		frames = snd_pcm_readi(capture, v, n/4);
+	} while(frames == -EAGAIN);
+
+	if (frames < 0)
+		error((char*)snd_strerror(frames));
+
+	return frames*4;
+}
--- /dev/null
+++ b/kern/devaudio-none.c
@@ -1,0 +1,49 @@
+/*
+ * Linux and BSD
+ */
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+/* maybe this should return -1 instead of sysfatal */
+void
+audiodevopen(void)
+{
+	error("no audio support");
+}
+
+void
+audiodevclose(void)
+{
+	error("no audio support");
+}
+
+int
+audiodevread(void *a, int n)
+{
+	error("no audio support");
+	return -1;
+}
+
+int
+audiodevwrite(void *a, int n)
+{
+	error("no audio support");
+	return -1;
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+	error("no audio support");
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+	error("no audio support");
+}
+
--- /dev/null
+++ b/kern/devaudio-pipewire.c
@@ -1,0 +1,189 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+#undef long
+#undef ulong
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+
+static struct {
+	Lock lk;
+	Rendez w;
+	int init;
+	struct pw_main_loop *loop;
+	struct pw_stream *output;
+
+	char buf[2*2*44100/10]; /* 1/10th sec */
+	int written; /* 0 means empty buffer */
+} pwstate;
+
+static char *argv[] = { "drawcpu" };
+static int argc = 1;
+
+static void
+on_process(void *data)
+{
+	struct pw_buffer *b;
+	struct spa_buffer *buf;
+	int16_t *dst;
+	int n;
+
+	lock(&pwstate.lk);
+	if(pwstate.written == sizeof(pwstate.buf))
+		wakeup(&pwstate.w);
+	if(pwstate.written == 0){
+		unlock(&pwstate.lk);
+		return;
+	}
+
+	if((b = pw_stream_dequeue_buffer(pwstate.output)) == nil)
+		return;
+	buf = b->buffer;
+	dst = buf->datas[0].data;
+
+	n = pwstate.written;
+	if(n > buf->datas[0].maxsize)
+		n = buf->datas[0].maxsize;
+	memcpy(dst, pwstate.buf, n);
+	buf->datas[0].chunk->offset = 0;
+	buf->datas[0].chunk->stride = sizeof(int16_t) * 2;
+	buf->datas[0].chunk->size = n;
+	pwstate.written -= n;
+	if(pwstate.written > 0)
+		memmove(pwstate.buf, pwstate.buf+n, pwstate.written);
+
+	pw_stream_queue_buffer(pwstate.output, b);
+	unlock(&pwstate.lk);
+}
+
+static const struct pw_stream_events stream_events = {
+	PW_VERSION_STREAM_EVENTS,
+	.process = on_process,
+};
+
+static void
+pwproc(void *arg)
+{
+	struct pw_main_loop *loop;
+
+	loop = arg;
+	pw_main_loop_run(loop);
+	pexit("", 0);
+}
+
+void
+audiodevopen(void)
+{
+	const struct spa_pod *params[1];
+	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(pwstate.buf, sizeof(pwstate.buf));
+	int err;
+
+	lock(&pwstate.lk);
+	pwstate.written = 0;
+	if(pwstate.init == 0){
+		pw_init(&argc, (char***)&argv);
+		pwstate.init++;
+		pwstate.loop = pw_main_loop_new(NULL);
+		if(pwstate.loop == NULL)
+			sysfatal("could not create loop");
+	}
+
+	pwstate.output = pw_stream_new_simple(
+		pw_main_loop_get_loop(pwstate.loop),
+		"drawcpu",
+		pw_properties_new(
+			PW_KEY_NODE_NAME, "drawcpu",
+			PW_KEY_MEDIA_TYPE, "Audio",
+			PW_KEY_MEDIA_CATEGORY, "Playback",
+			PW_KEY_MEDIA_ROLE, "Music",
+			NULL),
+		&stream_events,
+		NULL);
+
+	if(pwstate.output == NULL){
+		unlock(&pwstate.lk);
+		error("could not create pipewire output");
+		return;
+	}
+	params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+		&SPA_AUDIO_INFO_RAW_INIT(
+			.format = SPA_AUDIO_FORMAT_S16_LE,
+			.channels = 2,
+			.rate = 44100 ));
+
+	err = pw_stream_connect(pwstate.output,
+		PW_DIRECTION_OUTPUT,
+		PW_ID_ANY,
+		PW_STREAM_FLAG_AUTOCONNECT |
+		PW_STREAM_FLAG_MAP_BUFFERS |
+		PW_STREAM_FLAG_RT_PROCESS,
+		params, 1);
+
+	unlock(&pwstate.lk);
+	if(err < 0){
+		error("could not connect pipewire stream");
+		return;
+	}
+
+	kproc("pipewire main loop", pwproc, pwstate.loop);
+}
+
+void
+audiodevclose(void)
+{
+	pw_main_loop_quit(pwstate.loop);
+	pw_stream_destroy(pwstate.output);
+}
+
+int
+audiodevread(void *a, int n)
+{
+	error("no record support");
+	return -1;
+}
+
+static int
+canwrite(void *arg)
+{
+	return pwstate.written < sizeof(pwstate.buf);
+}
+
+int
+audiodevwrite(void *a, int n)
+{
+	int w, x, max;
+	char *p;
+
+	w = n;
+	for(p = a; n > 0; p += x, n -= x){
+		lock(&pwstate.lk);
+		max = sizeof(pwstate.buf) - pwstate.written;
+		x = n > max ? max : n;
+		if(x < 1){
+			unlock(&pwstate.lk);
+			sleep(&pwstate.w, canwrite, 0);
+		}else{
+			memmove(pwstate.buf+pwstate.written, p, x);
+			pwstate.written += x;
+			unlock(&pwstate.lk);
+		}
+	}
+	return w;
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+	error("no volume support");
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+	error("no volume support");
+}
+
--- /dev/null
+++ b/kern/devaudio-sndio.c
@@ -1,0 +1,78 @@
+#include	<sndio.h>
+
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+enum
+{
+	Channels = 2,
+	Rate = 44100,
+	Bits = 16,
+};
+
+static struct sio_hdl *hdl;
+static struct sio_par par;
+
+void
+audiodevopen(void)
+{
+	hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
+	if(hdl == NULL){
+		error("sio_open failed");
+		return;
+	}
+
+	sio_initpar(&par);
+
+	par.bits = Bits;
+	par.pchan = Channels;
+	par.rate = Rate;
+	par.appbufsz = 288000;
+
+	if(!sio_setpar(hdl, &par) || !sio_start(hdl)){
+		sio_close(hdl);
+		error("sio_setpar/sio_start failed");
+		return;
+	}
+}
+
+void
+audiodevclose(void)
+{
+	sio_close(hdl);
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+	USED(what);
+	USED(left);
+	USED(right);
+	error("not supported");
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+	USED(what);
+	USED(left);
+	USED(right);
+	error("not supported");
+}
+
+int
+audiodevwrite(void *v, int n)
+{
+	return sio_write(hdl, v, n);
+}
+
+int
+audiodevread(void *v, int n)
+{
+	error("no reading");
+	return -1;
+}
--- /dev/null
+++ b/kern/devaudio-sun.c
@@ -1,0 +1,268 @@
+/*
+ * Sun
+ */
+#include <sys/ioctl.h>
+#include <sys/audio.h>
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+enum
+{
+	Channels = 2,
+	Rate = 44100,
+	Bits = 16,
+};
+
+static char* afn = 0;
+static char* cfn = 0;
+static int afd = -1;
+static int cfd = -1;
+static int speed = Rate;
+static int needswap = -1;
+
+static void
+audiodevinit(void)
+{
+	uchar *p;
+	ushort leorder;
+
+	if ((afn = getenv("AUDIODEV")) == nil)
+		afn = "/dev/audio";
+	cfn = (char*)malloc(strlen(afn) + 3 + 1);
+	if(cfn == nil)
+		panic("out of memory");
+	strcpy(cfn, afn);
+	strcat(cfn, "ctl");
+
+	/*
+	 * Plan 9 /dev/audio is always little endian;
+	 * solaris /dev/audio seems to expect native byte order,
+	 * so on big endian machine (like sparc) we have to swap.
+	 */
+	leorder = (ushort) 0x0100;
+	p = (uchar*)&leorder;
+	if (p[0] == 0 && p[1] == 1) {
+		/* little-endian: nothing to do */
+		needswap = 0;
+	} else {
+		/* big-endian: translate Plan 9 little-endian */
+		needswap = 1;
+	}
+}
+
+/* maybe this should return -1 instead of sysfatal */
+void
+audiodevopen(void)
+{
+	audio_info_t info;
+	struct audio_device ad;
+
+	if (afn == nil || cfn == nil)
+		audiodevinit();
+	if((afd = open(afn, O_WRONLY)) < 0)
+		goto err;
+	if(cfd < 0 && (cfd = open(cfn, O_RDWR)) < 0)
+		goto err;
+
+	AUDIO_INITINFO(&info);
+	info.play.precision = Bits;
+	info.play.channels = Channels;
+	info.play.sample_rate = speed;
+	info.play.encoding = AUDIO_ENCODING_LINEAR;
+	if(ioctl(afd, AUDIO_SETINFO, &info) < 0)
+		goto err;
+
+	return;
+
+err:
+	if(afd >= 0)
+		close(afd);
+	afd = -1;
+	if(cfd >= 0)
+		close(cfd);
+	cfd = -1;
+	oserror();
+}
+
+void
+audiodevclose(void)
+{
+	if(afd >= 0)
+		close(afd);
+	if(cfd >= 0)
+		close(cfd);
+	afd = -1;
+	cfd = -1;
+}
+
+static double
+fromsun(double val, double min, double max)
+{
+	return (val-min) / (max-min);
+}
+
+static double
+tosun(double val, double min, double max)
+{
+	return val*(max-min) + min;
+}
+
+static void
+setvolbal(double left, double right)
+{
+	audio_info_t info;
+	double vol, bal;
+
+	if (left < 0 || right < 0) {
+		/* should not happen */
+		return;
+	} else if (left == right) {
+		vol = tosun(left/100.0, AUDIO_MIN_GAIN, AUDIO_MAX_GAIN);
+		bal = AUDIO_MID_BALANCE;
+	} else if (left < right) {
+		vol = tosun(right/100.0, AUDIO_MIN_GAIN, AUDIO_MAX_GAIN);
+		bal = tosun(1.0 - left/right, AUDIO_MID_BALANCE, AUDIO_RIGHT_BALANCE);
+	} else if (right < left) {
+		vol = tosun(left/100.0, AUDIO_MIN_GAIN, AUDIO_MAX_GAIN);
+		bal = tosun(1.0 - right/left, AUDIO_MID_BALANCE, AUDIO_LEFT_BALANCE);
+	}
+	AUDIO_INITINFO(&info);
+	info.play.gain = (long)(vol+0.5);
+	info.play.balance = (long)(bal+0.5);
+	if(ioctl(cfd, AUDIO_SETINFO, &info) < 0)
+		oserror();
+}
+
+static void
+getvolbal(int *left, int *right)
+{
+	audio_info_t info;
+	double gain, bal, vol, l, r;
+
+	AUDIO_INITINFO(&info);
+	if (ioctl(cfd, AUDIO_GETINFO, &info) < 0)
+		oserror();
+
+	gain = info.play.gain;
+	bal = info.play.balance;
+	vol = fromsun(gain, AUDIO_MIN_GAIN, AUDIO_MAX_GAIN) * 100.0;
+
+	if (bal == AUDIO_MID_BALANCE) {
+		l = r = vol;
+	} else if (bal < AUDIO_MID_BALANCE) {
+		l = vol;
+		r = vol * (1.0 - fromsun(bal, AUDIO_MID_BALANCE, AUDIO_LEFT_BALANCE));
+	} else {
+		r = vol;
+		l = vol * (1.0 - fromsun(bal, AUDIO_MID_BALANCE, AUDIO_RIGHT_BALANCE));
+	}
+	*left = (long)(l+0.5);
+	*right = (long)(r+0.5);
+	return;
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+	audio_info_t info;
+	ulong x;
+	int l, r;
+	
+	if (afn == nil || cfn == nil)
+		audiodevinit();
+	if(cfd < 0 && (cfd = open(cfn, O_RDWR)) < 0) {
+		cfd = -1;
+		oserror();
+	}
+
+	if(what == Vspeed){
+		x = left;
+		AUDIO_INITINFO(&info);
+		info.play.sample_rate = x;
+		if(ioctl(cfd, AUDIO_SETINFO, &info) < 0)
+			oserror();
+		speed = x;
+		return;
+	}
+	if(what == Vaudio){
+		getvolbal(&l, &r);
+		if (left < 0)
+			setvolbal(l, right);
+		else if (right < 0)
+			setvolbal(left, r);
+		else 
+			setvolbal(left, right);
+		return;
+	}
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+	audio_info_t info;
+
+	if (afn == nil || cfn == nil)
+		audiodevinit();
+	if(cfd < 0 && (cfd = open(cfn, O_RDWR)) < 0) {
+		cfd = -1;
+		oserror();
+	}
+	switch(what) {
+	case Vspeed:
+		*left = *right = speed;
+		break;
+	case Vaudio:
+		getvolbal(left, right);
+		break;
+	case Vtreb:
+	case Vbass:
+		*left = *right = 50;
+		break;
+	default:
+		*left = *right = 0;
+	}
+}
+
+
+static uchar *buf = 0;
+static int nbuf = 0;
+
+int
+audiodevwrite(void *v, int n)
+{
+	int i, m, tot;
+	uchar *p;
+
+	if (needswap) {
+		if (nbuf < n) {
+			buf = (uchar*)erealloc(buf, n);
+			if(buf == nil)
+				panic("out of memory");
+			nbuf = n;
+		}
+
+		p = (uchar*)v;
+		for(i=0; i+1<n; i+=2) {
+			buf[i] = p[i+1];
+			buf[i+1] = p[i];
+		}
+		p = buf;
+	} else
+		p = (uchar*)v;
+	
+	for(tot=0; tot<n; tot+=m)
+		if((m = write(afd, p+tot, n-tot)) <= 0)
+			oserror();
+	return tot;
+}
+
+int
+audiodevread(void *v, int n)
+{
+	error("no reading");
+	return -1;
+}
--- /dev/null
+++ b/kern/devaudio-unix.c
@@ -1,0 +1,183 @@
+/*
+ * Linux and BSD
+ */
+#include <sys/ioctl.h>
+#ifdef __linux__
+#include <linux/soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+enum
+{
+	Channels = 2,
+	Rate = 44100,
+	Bits = 16,
+	Bigendian = 1,
+};
+
+static int afd = -1;
+static int cfd= -1;
+static int speed;
+
+/* maybe this should return -1 instead of sysfatal */
+void
+audiodevopen(void)
+{
+	int t;
+	ulong ul;
+
+	afd = -1;
+	cfd = -1;
+	if((afd = open("/dev/dsp", OWRITE)) < 0)
+		goto err;
+	if((cfd = open("/dev/mixer", ORDWR)) < 0)
+		goto err;
+	
+	t = Bits;
+	if(ioctl(afd, SNDCTL_DSP_SAMPLESIZE, &t) < 0)
+		goto err;
+	
+	t = Channels-1;
+	if(ioctl(afd, SNDCTL_DSP_STEREO, &t) < 0)
+		goto err;
+	
+	speed = Rate;
+	ul = Rate;
+	if(ioctl(afd, SNDCTL_DSP_SPEED, &ul) < 0)
+		goto err;
+
+	return;
+
+err:
+	if(afd >= 0)
+		close(afd);
+	afd = -1;
+	oserror();
+}
+
+void
+audiodevclose(void)
+{
+	close(afd);
+	close(cfd);
+	afd = -1;
+	cfd = -1;
+}
+
+static struct {
+	int id9;
+	int id;
+} names[] = {
+	Vaudio,	SOUND_MIXER_VOLUME,
+	Vbass, 		SOUND_MIXER_BASS,
+	Vtreb, 		SOUND_MIXER_TREBLE,
+	Vline, 		SOUND_MIXER_LINE,
+	Vpcm, 		SOUND_MIXER_PCM,
+	Vsynth, 		SOUND_MIXER_SYNTH,
+	Vcd, 		SOUND_MIXER_CD,
+	Vmic, 		SOUND_MIXER_MIC,
+//	"record", 		SOUND_MIXER_RECLEV,
+//	"mix",		SOUND_MIXER_IMIX,
+//	"pcm2",		SOUND_MIXER_ALTPCM,
+	Vspeaker,	SOUND_MIXER_SPEAKER
+//	"line1",		SOUND_MIXER_LINE1,
+//	"line2",		SOUND_MIXER_LINE2,
+//	"line3",		SOUND_MIXER_LINE3,
+//	"digital1",	SOUND_MIXER_DIGITAL1,
+//	"digital2",	SOUND_MIXER_DIGITAL2,
+//	"digital3",	SOUND_MIXER_DIGITAL3,
+//	"phonein",		SOUND_MIXER_PHONEIN,
+//	"phoneout",		SOUND_MIXER_PHONEOUT,
+//	"radio",		SOUND_MIXER_RADIO,
+//	"video",		SOUND_MIXER_VIDEO,
+//	"monitor",	SOUND_MIXER_MONITOR,
+//	"igain",		SOUND_MIXER_IGAIN,
+//	"ogain",		SOUND_MIXER_OGAIN,
+};
+
+static int
+lookname(int id9)
+{
+	int i;
+	
+	for(i=0; i<nelem(names); i++)
+		if(names[i].id9 == id9)
+			return names[i].id;
+	return -1;
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+	int id;
+	ulong x;
+	int can, v;
+	
+	if(cfd < 0)
+		error("audio device not open");
+	if(what == Vspeed){
+		x = left;
+		if(ioctl(afd, SNDCTL_DSP_SPEED, &x) < 0)
+			oserror();
+		speed = x;
+		return;
+	}
+	if((id = lookname(what)) < 0)
+		return;
+	if(ioctl(cfd, SOUND_MIXER_READ_DEVMASK, &can) < 0)
+		can = ~0;
+	if(!(can & (1<<id)))
+		return;
+	v = left | (right<<8);
+	if(ioctl(cfd, MIXER_WRITE(id), &v) < 0)
+		oserror();
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+	int id;
+	int can, v;
+	
+	if(cfd < 0)
+		error("audio device not open");
+	if(what == Vspeed){
+		*left = *right = speed;
+		return;
+	}
+	if((id = lookname(what)) < 0)
+		return;
+	if(ioctl(cfd, SOUND_MIXER_READ_DEVMASK, &can) < 0)
+		can = ~0;
+	if(!(can & (1<<id)))
+		return;
+	if(ioctl(cfd, MIXER_READ(id), &v) < 0)
+		oserror();
+	*left = v&0xFF;
+	*right = (v>>8)&0xFF;
+}
+
+int
+audiodevwrite(void *v, int n)
+{
+	int m, tot;
+	
+	for(tot=0; tot<n; tot+=m)
+		if((m = write(afd, (uchar*)v+tot, n-tot)) <= 0)
+			oserror();
+	return tot;
+}
+
+int
+audiodevread(void *v, int n)
+{
+	error("no reading");
+	return -1;
+}
--- /dev/null
+++ b/kern/devaudio-win32.c
@@ -1,0 +1,111 @@
+#include	<windows.h>
+#include	<mmsystem.h>
+
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+enum
+{
+	Channels = 2,
+	Rate = 44100,
+	Bits = 16,
+};
+
+typedef struct Waveblock Waveblock;
+struct Waveblock {
+	WAVEHDR	h;
+	uchar	s[2048];
+};
+
+static HWAVEOUT waveout;
+static Waveblock blk[16];
+static uint blkidx;
+
+void
+audiodevopen(void)
+{
+	WAVEFORMATEX f;
+
+	memset(&f, 0, sizeof(f));
+	f.nSamplesPerSec = Rate;
+	f.wBitsPerSample = Bits;
+	f.nChannels = Channels;
+	f.cbSize = 0;
+	f.wFormatTag = WAVE_FORMAT_PCM;
+	f.nBlockAlign = (f.wBitsPerSample/8) * f.nChannels;
+	f.nAvgBytesPerSec = f.nBlockAlign * f.nSamplesPerSec;
+	if(waveOutOpen(&waveout, WAVE_MAPPER, &f, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR)
+		oserror();
+}
+
+void
+audiodevclose(void)
+{
+	waveOutClose(waveout);
+	waveout = 0;
+}
+
+void
+audiodevsetvol(int what, int left, int right)
+{
+	DWORD v;
+
+	//Windows uses a 0-0xFFFF scale, plan9 uses 0-100
+	v = right*0xFFFF/100;
+	v = (v<<16)|(left*0xFFFF/100);
+	if(waveOutSetVolume(waveout, v) != MMSYSERR_NOERROR)
+		oserror();
+}
+
+void
+audiodevgetvol(int what, int *left, int *right)
+{
+	DWORD v;
+
+	if(waveOutGetVolume(waveout, &v) != MMSYSERR_NOERROR)
+		oserror();
+	*left = (v&0xFFFF)*100/0xFFFF;
+	*right = ((v>>16)&0xFFFF)*100/0xFFFF;
+}
+
+int
+audiodevwrite(void *v, int n)
+{
+	Waveblock *b;
+	int m;
+
+	m = 0;
+	while(n > sizeof(b->s)){
+		audiodevwrite(v, sizeof(b->s));
+		v = (uchar*)v + sizeof(b->s);
+		n -= sizeof(b->s);
+		m += sizeof(b->s);
+	}
+
+	b = &blk[blkidx++ % nelem(blk)];
+	if(b->h.dwFlags & WHDR_PREPARED){
+		while(waveOutUnprepareHeader(waveout, &b->h, sizeof(b->h)) == WAVERR_STILLPLAYING)
+			osmsleep(50);
+	}
+	memmove(b->s, v, n);
+	b->h.lpData = (void*)b->s;
+	b->h.dwBufferLength = n;
+	waveOutPrepareHeader(waveout, &b->h, sizeof(b->h));
+	waveOutWrite(waveout, &b->h, sizeof(b->h));
+
+	return m + n;
+}
+
+int
+audiodevread(void *v, int n)
+{
+	USED(v);
+	USED(n);
+
+	error("no reading");
+	return -1;
+}
--- /dev/null
+++ b/kern/devaudio.c
@@ -1,0 +1,377 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"devaudio.h"
+
+enum
+{
+	Qdir		= 0,
+	Qaudio,
+	Qvolume,
+
+	Aclosed		= 0,
+	Aread,
+	Awrite,
+
+	Speed		= 44100,
+	Ncmd		= 50,		/* max volume command words */
+};
+
+Dirtab
+audiodir[] =
+{
+	".",	{Qdir, 0, QTDIR},		0,	DMDIR|0555,
+	"audio",	{Qaudio},		0,	0666,
+	"volume",	{Qvolume},		0,	0666,
+};
+
+static	struct
+{
+	QLock	lk;
+	Rendez	vous;
+	int	amode;		/* Aclosed/Aread/Awrite for /audio */
+} audio;
+
+#define aqlock(a) qlock(&(a)->lk)
+#define aqunlock(a) qunlock(&(a)->lk)
+
+static	struct
+{
+	char*	name;
+	int	flag;
+	int	ilval;		/* initial values */
+	int	irval;
+} volumes[] =
+{
+	"audio",	Fout, 		50,	50,
+	"synth",	Fin|Fout,	0,	0,
+	"cd",		Fin|Fout,	0,	0,
+	"line",	Fin|Fout,	0,	0,
+	"mic",	Fin|Fout|Fmono,	0,	0,
+	"speaker",	Fout|Fmono,	0,	0,
+
+	"treb",		Fout, 		50,	50,
+	"bass",		Fout, 		50,	50,
+
+	"speed",	Fin|Fout|Fmono,	Speed,	Speed,
+
+	"pcm",		Fout, 		50,	50,
+	0
+};
+
+static	char	Emode[]		= "illegal open mode";
+static	char	Evolume[]	= "illegal volume specifier";
+
+static	void
+resetlevel(void)
+{
+	int i;
+
+	for(i=0; volumes[i].name; i++)
+		audiodevsetvol(i, volumes[i].ilval, volumes[i].irval);
+}
+
+static void
+audioinit(void)
+{
+}
+
+static Chan*
+audioattach(char *param)
+{
+	return devattach('A', param);
+}
+
+static Walkqid*
+audiowalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
+}
+
+static int
+audiostat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, audiodir, nelem(audiodir), devgen);
+}
+
+static Chan*
+audioopen(Chan *c, int omode)
+{
+	int amode;
+
+	switch((ulong)c->qid.path) {
+	default:
+		error(Eperm);
+		break;
+
+	case Qvolume:
+	case Qdir:
+		break;
+
+	case Qaudio:
+		amode = Awrite;
+		if((omode&7) == OREAD)
+			amode = Aread;
+		aqlock(&audio);
+		if(waserror()){
+			aqunlock(&audio);
+			nexterror();
+		}
+		if(audio.amode != Aclosed)
+			error(Einuse);
+		audiodevopen();
+		audio.amode = amode;
+		poperror();
+		aqunlock(&audio);
+		break;
+	}
+	c = devopen(c, omode, audiodir, nelem(audiodir), devgen);
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+
+	return c;
+}
+
+static void
+audioclose(Chan *c)
+{
+	switch((ulong)c->qid.path) {
+	default:
+		error(Eperm);
+		break;
+
+	case Qdir:
+	case Qvolume:
+		break;
+
+	case Qaudio:
+		if(c->flag & COPEN) {
+			aqlock(&audio);
+			audiodevclose();
+			audio.amode = Aclosed;
+			aqunlock(&audio);
+		}
+		break;
+	}
+}
+
+static long
+audioread(Chan *c, void *v, long n, vlong off)
+{
+	int liv, riv, lov, rov;
+	long m;
+	char buf[300];
+	int j;
+	ulong offset = off;
+	char *a;
+
+	a = v;
+	switch((ulong)c->qid.path) {
+	default:
+		error(Eperm);
+		break;
+
+	case Qdir:
+		return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
+
+	case Qaudio:
+		if(audio.amode != Aread)
+			error(Emode);
+		aqlock(&audio);
+		if(waserror()){
+			aqunlock(&audio);
+			nexterror();
+		}
+		n = audiodevread(v, n);
+		poperror();
+		aqunlock(&audio);
+		break;
+
+	case Qvolume:
+		j = 0;
+		buf[0] = 0;
+		for(m=0; volumes[m].name; m++){
+			lov = -1;
+			rov = -1;
+			audiodevgetvol(m, &lov, &rov);
+			if(lov < 0 && rov < 0)
+				continue;
+			liv = lov;
+			riv = rov;
+			j += snprint(buf+j, sizeof(buf)-j, "%s", volumes[m].name);
+			if((volumes[m].flag & Fmono) || (liv==riv && lov==rov)){
+				if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) && liv==lov)
+					j += snprint(buf+j, sizeof(buf)-j, " %d", liv);
+				else{
+					if(volumes[m].flag & Fin)
+						j += snprint(buf+j, sizeof(buf)-j,
+							" in %d", liv);
+					if(volumes[m].flag & Fout)
+						j += snprint(buf+j, sizeof(buf)-j,
+							" out %d", lov);
+				}
+			}else{
+				if((volumes[m].flag&(Fin|Fout))==(Fin|Fout) &&
+				    liv==lov && riv==rov)
+					j += snprint(buf+j, sizeof(buf)-j,
+						" left %d right %d",
+						liv, riv);
+				else{
+					if(volumes[m].flag & Fin)
+						j += snprint(buf+j, sizeof(buf)-j,
+							" in left %d right %d",
+							liv, riv);
+					if(volumes[m].flag & Fout)
+						j += snprint(buf+j, sizeof(buf)-j,
+							" out left %d right %d",
+							lov, rov);
+				}
+			}
+			j += snprint(buf+j, sizeof(buf)-j, "\n");
+		}
+		return readstr(offset, a, n, buf);
+	}
+	return n;
+}
+
+static long
+audiowrite(Chan *c, void *vp, long n, vlong off)
+{
+	long m;
+	int i, v, left, right, in, out;
+	Cmdbuf *cb;
+
+	USED(off);
+	switch((ulong)c->qid.path) {
+	default:
+		error(Eperm);
+		break;
+
+	case Qvolume:
+		v = Vaudio;
+		left = 1;
+		right = 1;
+		in = 1;
+		out = 1;
+		cb = parsecmd(vp, n);
+		if(waserror()){
+			free(cb);
+			nexterror();
+		}
+
+		for(i = 0; i < cb->nf; i++){
+			/*
+			 * a number is volume
+			 */
+			if(cb->f[i][0] >= '0' && cb->f[i][0] <= '9') {
+				m = strtoul(cb->f[i], 0, 10);
+				USED(in);
+				if(!out)
+					goto cont0;
+				if(left && right)
+					audiodevsetvol(v, m, m);
+				else if(left)
+					audiodevsetvol(v, m, -1);
+				else if(right)
+					audiodevsetvol(v, -1, m);
+				goto cont0;
+			}
+
+			for(m=0; volumes[m].name; m++) {
+				if(strcmp(cb->f[i], volumes[m].name) == 0) {
+					v = m;
+					in = 1;
+					out = 1;
+					left = 1;
+					right = 1;
+					goto cont0;
+				}
+			}
+
+			if(strcmp(cb->f[i], "reset") == 0) {
+				resetlevel();
+				goto cont0;
+			}
+			if(strcmp(cb->f[i], "in") == 0) {
+				in = 1;
+				out = 0;
+				goto cont0;
+			}
+			if(strcmp(cb->f[i], "out") == 0) {
+				in = 0;
+				out = 1;
+				goto cont0;
+			}
+			if(strcmp(cb->f[i], "left") == 0) {
+				left = 1;
+				right = 0;
+				goto cont0;
+			}
+			if(strcmp(cb->f[i], "right") == 0) {
+				left = 0;
+				right = 1;
+				goto cont0;
+			}
+			error(Evolume);
+			break;
+		cont0:;
+		}
+		free(cb);
+		poperror();
+		break;
+
+	case Qaudio:
+		if(audio.amode != Awrite)
+			error(Emode);
+		aqlock(&audio);
+		if(waserror()){
+			aqunlock(&audio);
+			nexterror();
+		}
+		n = audiodevwrite(vp, n);
+		poperror();
+		aqunlock(&audio);
+		break;
+	}
+	return n;
+}
+
+void
+audioswab(uchar *a, uint n)
+{
+	ulong *p, *ep, b;
+
+	p = (ulong*)a;
+	ep = p + (n>>2);
+	while(p < ep) {
+		b = *p;
+		b = (b>>24) | (b<<24) |
+			((b&0xff0000) >> 8) |
+			((b&0x00ff00) << 8);
+		*p++ = b;
+	}
+}
+
+Dev audiodevtab = {
+	'A',
+	"audio",
+
+	devreset,
+	audioinit,
+	devshutdown,
+	audioattach,
+	audiowalk,
+	audiostat,
+	audioopen,
+	devcreate,
+	audioclose,
+	audioread,
+	devbread,
+	audiowrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/kern/devaudio.h
@@ -1,0 +1,25 @@
+enum
+{
+	Fmono		= 1,
+	Fin		= 2,
+	Fout		= 4,
+
+	Vaudio		= 0,
+	Vsynth,
+	Vcd,
+	Vline,
+	Vmic,
+	Vspeaker,
+	Vtreb,
+	Vbass,
+	Vspeed,
+	Vpcm,
+	Nvol,
+};
+
+void	audiodevopen(void);
+void	audiodevclose(void);
+int	audiodevread(void*, int);
+int	audiodevwrite(void*, int);
+void	audiodevgetvol(int, int*, int*);
+void	audiodevsetvol(int, int, int);
--- /dev/null
+++ b/kern/devcmd.c
@@ -1,0 +1,699 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Qtopdir,	/* top level directory */
+	Qcmd,
+	Qclonus,
+	Qconvdir,
+	Qconvbase,
+	Qdata = Qconvbase,
+	Qstderr,
+	Qctl,
+	Qstatus,
+	Qwait,
+
+	Debug=0	/* to help debug os.c */
+};
+#define TYPE(x) 	((ulong)(x).path & 0xf)
+#define CONV(x) 	(((ulong)(x).path >> 4)&0xfff)
+#define QID(c, y) 	(((c)<<4) | (y))
+
+typedef struct Conv	Conv;
+struct Conv
+{
+	int	x;
+	int	inuse;
+	Chan*	fd[3];		/* stdin, stdout, and stderr */
+	int	count[3];	/* number of readers on stdin/stdout/stderr */
+	int	perm;
+	char*	owner;
+	char*	state;
+	Cmdbuf*	cmd;
+	char*	dir;
+	QLock	l;	/* protects state changes */
+	Queue*	waitq;
+	void*	child;
+	char*	error;	/* on start up */
+	int	nice;
+	short	killonclose;
+	short	killed;
+	Rendez	startr;
+};
+
+static struct
+{
+	QLock	l;
+	int	nc;
+	int	maxconv;
+	Conv**	conv;
+} cmd;
+
+static	Conv*	cmdclone(char*);
+static	void	cmdproc(void*);
+
+static int
+cmd3gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+
+	cv = cmd.conv[CONV(c->qid)];
+	switch(i){
+	default:
+		return -1;
+	case Qdata:
+		mkqid(&q, QID(CONV(c->qid), Qdata), 0, QTFILE);
+		devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qstderr:
+		mkqid(&q, QID(CONV(c->qid), Qstderr), 0, QTFILE);
+		devdir(c, q, "stderr", 0, cv->owner, 0444, dp);
+		return 1;
+	case Qctl:
+		mkqid(&q, QID(CONV(c->qid), Qctl), 0, QTFILE);
+		devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qstatus:
+		mkqid(&q, QID(CONV(c->qid), Qstatus), 0, QTFILE);
+		devdir(c, q, "status", 0, cv->owner, 0444, dp);
+		return 1;
+	case Qwait:
+		mkqid(&q, QID(CONV(c->qid), Qwait), 0, QTFILE);
+		devdir(c, q, "wait", 0, cv->owner, 0444, dp);
+		return 1;
+	}
+}
+
+static int
+cmdgen(Chan *c, char *name, Dirtab *d, int nd, int s, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+
+	USED(name);
+	USED(nd);
+	USED(d);
+
+	if(s == DEVDOTDOT){
+		switch(TYPE(c->qid)){
+		case Qtopdir:
+		case Qcmd:
+			mkqid(&q, QID(0, Qtopdir), 0, QTDIR);
+			devdir(c, q, "#C", 0, eve, DMDIR|0555, dp);
+			break;
+		case Qconvdir:
+			mkqid(&q, QID(0, Qcmd), 0, QTDIR);
+			devdir(c, q, "cmd", 0, eve, DMDIR|0555, dp);
+			break;
+		default:
+			panic("cmdgen %llux", c->qid.path);
+		}
+		return 1;
+	}
+
+	switch(TYPE(c->qid)) {
+	case Qtopdir:
+		if(s >= 1)
+			return -1;
+		mkqid(&q, QID(0, Qcmd), 0, QTDIR);
+		devdir(c, q, "cmd", 0, "cmd", DMDIR|0555, dp);
+		return 1;
+	case Qcmd:
+		if(s < cmd.nc) {
+			cv = cmd.conv[s];
+			mkqid(&q, QID(s, Qconvdir), 0, QTDIR);
+			sprint(up->genbuf, "%d", s);
+			devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
+			return 1;
+		}
+		s -= cmd.nc;
+		if(s == 0){
+			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
+			devdir(c, q, "clone", 0, "cmd", 0666, dp);
+			return 1;
+		}
+		return -1;
+	case Qclonus:
+		if(s == 0){
+			mkqid(&q, QID(0, Qclonus), 0, QTFILE);
+			devdir(c, q, "clone", 0, "cmd", 0666, dp);
+			return 1;
+		}
+		return -1;
+	case Qconvdir:
+		return cmd3gen(c, Qconvbase+s, dp);
+	case Qdata:
+	case Qstderr:
+	case Qctl:
+	case Qstatus:
+	case Qwait:
+		return cmd3gen(c, TYPE(c->qid), dp);
+	}
+	return -1;
+}
+
+static void
+cmdinit(void)
+{
+	cmd.maxconv = 1000;
+	cmd.conv = mallocz(sizeof(Conv*)*(cmd.maxconv+1), 1);
+	/* cmd.conv is checked by cmdattach, below */
+}
+
+static Chan *
+cmdattach(char *spec)
+{
+	Chan *c;
+
+	if(cmd.conv == nil)
+		error(Enomem);
+	c = devattach('C', spec);
+	mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
+	return c;
+}
+
+static Walkqid*
+cmdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, 0, 0, cmdgen);
+}
+
+static int
+cmdstat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, 0, 0, cmdgen);
+}
+
+static Chan *
+cmdopen(Chan *c, int omode)
+{
+	int perm;
+	Conv *cv;
+	char *user;
+
+	perm = 0;
+	omode = openmode(omode);
+	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 Qcmd:
+	case Qconvdir:
+	case Qstatus:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qclonus:
+		qlock(&cmd.l);
+		if(waserror()){
+			qunlock(&cmd.l);
+			nexterror();
+		}
+		cv = cmdclone(up->user);
+		poperror();
+		qunlock(&cmd.l);
+		if(cv == 0)
+			error(Enodev);
+		mkqid(&c->qid, QID(cv->x, Qctl), 0, QTFILE);
+		break;
+	case Qdata:
+	case Qstderr:
+	case Qctl:
+	case Qwait:
+		qlock(&cmd.l);
+		cv = cmd.conv[CONV(c->qid)];
+		qlock(&cv->l);
+		if(waserror()){
+			qunlock(&cv->l);
+			qunlock(&cmd.l);
+			nexterror();
+		}
+		user = up->user;
+		if((perm & (cv->perm>>6)) != perm) {
+			if(strcmp(user, cv->owner) != 0 ||
+		 	  (perm & cv->perm) != perm)
+				error(Eperm);
+		}
+		switch(TYPE(c->qid)){
+		case Qdata:
+			if(omode == OWRITE || omode == ORDWR)
+				cv->count[0]++;
+			if(omode == OREAD || omode == ORDWR)
+				cv->count[1]++;
+			break;
+		case Qstderr:
+			if(omode != OREAD)
+				error(Eperm);
+			cv->count[2]++;
+			break;
+		case Qwait:
+			if(cv->waitq == nil)
+				cv->waitq = qopen(1024, Qmsg, nil, 0);
+			break;
+		}
+		cv->inuse++;
+		if(cv->inuse == 1) {
+			cv->state = "Open";
+			kstrdup(&cv->owner, user);
+			cv->perm = 0660;
+			cv->nice = 0;
+		}
+		poperror();
+		qunlock(&cv->l);
+		qunlock(&cmd.l);
+		break;
+	}
+	c->mode = omode;
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+static void
+closeconv(Conv *c)
+{
+	kstrdup(&c->owner, "cmd");
+	kstrdup(&c->dir, ".");
+	c->perm = 0666;
+	c->state = "Closed";
+	c->killonclose = 0;
+	c->killed = 0;
+	c->nice = 0;
+	free(c->cmd);
+	c->cmd = nil;
+	if(c->waitq != nil){
+		qfree(c->waitq);
+		c->waitq = nil;
+	}
+	free(c->error);
+	c->error = nil;
+}
+
+static void
+cmdfdclose(Conv *c, int fd)
+{
+	if(--c->count[fd] == 0 && c->fd[fd] != nil){
+		cclose(c->fd[fd]);
+		c->fd[fd] = nil;
+	}
+}
+
+static void
+cmdclose(Chan *c)
+{
+	Conv *cc;
+	int r;
+
+	if((c->flag & COPEN) == 0)
+		return;
+
+	switch(TYPE(c->qid)) {
+	case Qctl:
+	case Qdata:
+	case Qstderr:
+	case Qwait:
+		cc = cmd.conv[CONV(c->qid)];
+		qlock(&cc->l);
+		if(TYPE(c->qid) == Qdata){
+			if(c->mode == OWRITE || c->mode == ORDWR)
+				cmdfdclose(cc, 0);
+			if(c->mode == OREAD || c->mode == ORDWR)
+				cmdfdclose(cc, 1);
+		}else if(TYPE(c->qid) == Qstderr)
+			cmdfdclose(cc, 2);
+
+		r = --cc->inuse;
+		if(cc->child != nil){
+			if(!cc->killed)
+			if(r == 0 || (cc->killonclose && TYPE(c->qid) == Qctl)){
+				oscmdkill(cc->child);
+				cc->killed = 1;
+			}
+		}else if(r == 0)
+			closeconv(cc);
+
+		qunlock(&cc->l);
+		break;
+	}
+}
+
+static long
+cmdread(Chan *ch, void *a, long n, vlong offset)
+{
+	Conv *c;
+	char *p, *cmds;
+	int fd;
+
+	USED(offset);
+
+	p = a;
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qcmd:
+	case Qtopdir:
+	case Qconvdir:
+		return devdirread(ch, a, n, 0, 0, cmdgen);
+	case Qctl:
+		sprint(up->genbuf, "%ld", CONV(ch->qid));
+		return readstr(offset, p, n, up->genbuf);
+	case Qstatus:
+		c = cmd.conv[CONV(ch->qid)];
+		cmds = "";
+		if(c->cmd != nil)
+			cmds = c->cmd->f[1];
+		snprint(up->genbuf, sizeof(up->genbuf), "cmd/%d %d %s %q %q\n",
+			c->x, c->inuse, c->state, c->dir, cmds);
+		return readstr(offset, p, n, up->genbuf);
+	case Qdata:
+	case Qstderr:
+		fd = 1;
+		if(TYPE(ch->qid) == Qstderr)
+			fd = 2;
+		c = cmd.conv[CONV(ch->qid)];
+		qlock(&c->l);
+		ch = c->fd[fd];
+		if(ch == nil){
+			qunlock(&c->l);
+			return 0;
+		}
+		incref(&ch->ref);
+		qunlock(&c->l);
+		if(waserror()){
+			cclose(ch);
+			nexterror();
+		}
+		n = devtab[ch->type]->read(ch, a, n, 0);
+		if(n < 0)
+			oserror();
+		poperror();
+		cclose(ch);
+		return n;
+	case Qwait:
+		c = cmd.conv[CONV(ch->qid)];
+		return qread(c->waitq, a, n);
+	}
+}
+
+static int
+cmdstarted(void *a)
+{
+	Conv *c;
+
+	c = a;
+	return c->child != nil || c->error != nil || strcmp(c->state, "Execute") != 0;
+}
+
+enum
+{
+	CMdir,
+	CMexec,
+	CMkill,
+	CMnice,
+	CMkillonclose
+};
+
+static
+Cmdtab cmdtab[] = {
+	CMdir,	"dir",	2,
+	CMexec,	"exec",	0,
+	CMkill,	"kill",	1,
+	CMnice,	"nice",	0,
+	CMkillonclose, "killonclose", 0,
+};
+
+static long
+cmdwrite(Chan *ch, void *a, long n, vlong offset)
+{
+	int i, r;
+	Conv *c;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	USED(offset);
+
+	switch(TYPE(ch->qid)) {
+	default:
+		error(Eperm);
+	case Qctl:
+		c = cmd.conv[CONV(ch->qid)];
+		cb = parsecmd(a, n);
+		if(waserror()){
+			free(cb);
+			nexterror();
+		}
+		ct = lookupcmd(cb, cmdtab, nelem(cmdtab));
+		switch(ct->index){
+		case CMdir:
+			kstrdup(&c->dir, cb->f[1]);
+			break;
+		case CMexec:
+			poperror();	/* cb */
+			qlock(&c->l);
+			if(waserror()){
+				qunlock(&c->l);
+				free(cb);
+				nexterror();
+			}
+			if(c->child != nil || c->cmd != nil)
+				error(Einuse);
+			for(i = 0; i < nelem(c->fd); i++)
+				if(c->fd[i] != nil)
+					error(Einuse);
+			if(cb->nf < 1)
+				error(Etoosmall);
+			kproc("cmdproc", cmdproc, c);	/* cmdproc held back until unlock below */
+			free(c->cmd);
+			c->cmd = cb;	/* don't free cb */
+			c->state = "Execute";
+			poperror();
+			qunlock(&c->l);
+			while(waserror())
+				;
+			sleep(&c->startr, cmdstarted, c);
+			poperror();
+			if(c->error)
+				error(c->error);
+			return n;	/* avoid free(cb) below */
+		case CMkill:
+			qlock(&c->l);
+			if(waserror()){
+				qunlock(&c->l);
+				nexterror();
+			}
+			if(c->child == nil)
+				error("not started");
+			if(oscmdkill(c->child) < 0)
+				oserror();
+			poperror();
+			qunlock(&c->l);
+			break;
+		case CMnice:
+			c->nice = cb->nf > 1? atoi(cb->f[1]): 1;
+			break;
+		case CMkillonclose:
+			c->killonclose = 1;
+			break;
+		}
+		poperror();
+		free(cb);
+		break;
+	case Qdata:
+		c = cmd.conv[CONV(ch->qid)];
+		qlock(&c->l);
+		ch = c->fd[0];
+		if(ch == nil){
+			qunlock(&c->l);
+			error(Ehungup);
+		}
+		incref(&ch->ref);
+		qunlock(&c->l);
+		if(waserror()){
+			cclose(ch);
+			nexterror();
+		}
+		r = devtab[ch->type]->write(ch, a, n, 0);
+		if(r == 0)
+			error(Ehungup);
+		if(r < 0) {
+			/* XXX perhaps should kill writer "write on closed pipe" here, 2nd time around? */
+			oserror();
+		}
+		poperror();
+		cclose(ch);
+		return r;
+	}
+	return n;
+}
+
+static int
+cmdwstat(Chan *c, uchar *dp, int n)
+{
+	Dir *d;
+	Conv *cv;
+
+	switch(TYPE(c->qid)){
+	default:
+		error(Eperm);
+	case Qctl:
+	case Qdata:
+	case Qstderr:
+		d = malloc(sizeof(*d)+n);
+		if(d == nil)
+			error(Enomem);
+		if(waserror()){
+			free(d);
+			nexterror();
+		}
+		n = convM2D(dp, n, d, (char*)&d[1]);
+		if(n == 0)
+			error(Eshortstat);
+		cv = cmd.conv[CONV(c->qid)];
+		if(!iseve() && strcmp(up->user, cv->owner) != 0)
+			error(Eperm);
+		if(!emptystr(d->uid))
+			kstrdup(&cv->owner, d->uid);
+		if(d->mode != (ulong)~0UL)
+			cv->perm = d->mode & 0777;
+		poperror();
+		free(d);
+		break;
+	}
+	return n;
+}
+
+static Conv*
+cmdclone(char *user)
+{
+	Conv *c, **pp, **ep;
+	int i;
+
+	c = nil;
+	ep = &cmd.conv[cmd.maxconv];
+	for(pp = cmd.conv; pp < ep; pp++) {
+		c = *pp;
+		if(c == nil) {
+			c = malloc(sizeof(Conv));
+			if(c == nil)
+				error(Enomem);
+			qlock(&c->l);
+			c->inuse = 1;
+			c->x = pp - cmd.conv;
+			cmd.nc++;
+			*pp = c;
+			break;
+		}
+		if(canqlock(&c->l)){
+			if(c->inuse == 0 && c->child == nil)
+				break;
+			qunlock(&c->l);
+		}
+	}
+	if(pp >= ep)
+		return nil;
+
+	c->inuse = 1;
+	kstrdup(&c->owner, user);
+	kstrdup(&c->dir, ".");
+	c->perm = 0660;
+	c->state = "Closed";
+	for(i=0; i<nelem(c->fd); i++)
+		c->fd[i] = nil;
+
+	qunlock(&c->l);
+	return c;
+}
+
+static void
+cmdproc(void *a)
+{
+	Conv *c;
+	int n;
+	char status[ERRMAX];
+	void *t;
+
+	c = a;
+	qlock(&c->l);
+	if(Debug)
+		print("f[0]=%q f[1]=%q\n", c->cmd->f[0], c->cmd->f[1]);
+	if(waserror()){
+		if(Debug)
+			print("failed: %q\n", up->errstr);
+		kstrdup(&c->error, up->errstr);
+		c->state = "Done";
+		wakeup(&c->startr);
+		qunlock(&c->l);
+		pexit("cmdproc", 0);
+	}
+	t = oscmd(c->cmd->f+1, c->nice, c->dir, c->fd);
+	if(t == nil)
+		oserror();
+	c->child = t;	/* to allow oscmdkill */
+	poperror();
+	qunlock(&c->l);
+	wakeup(&c->startr);
+	if(Debug)
+		print("started\n");
+	while(waserror()){
+		iprint("XXX %s\n", up->errstr);
+		oscmdkill(t);
+	}
+	n = oscmdwait(t, status, sizeof(status));
+	if(n < 0){
+		oserrstr();
+		n = snprint(status, sizeof(status), "0 0 0 0 %q", up->errstr);
+	}
+	qlock(&c->l);
+	c->child = nil;
+	oscmdfree(t);
+	if(Debug){
+		status[n]=0;
+		print("done %s %s %s: %q\n", chanpath(c->fd[0]), chanpath(c->fd[1]), chanpath(c->fd[2]), status);
+	}
+	if(c->inuse > 0){
+		c->state = "Done";
+		if(c->waitq != nil)
+			qproduce(c->waitq, status, n);
+	}else
+		closeconv(c);
+	qunlock(&c->l);
+	pexit("", 0);
+}
+
+Dev cmddevtab = {
+	'C',
+	"cmd",
+
+	devreset,
+	cmdinit,
+	devshutdown,
+	cmdattach,
+	cmdwalk,
+	cmdstat,
+	cmdopen,
+	devcreate,
+	cmdclose,
+	cmdread,
+	devbread,
+	cmdwrite,
+	devbwrite,
+	devremove,
+	cmdwstat
+};
--- /dev/null
+++ b/kern/devcons.c
@@ -1,0 +1,935 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include 	"keyboard.h"
+
+#include	<authsrv.h>
+
+#undef write
+#undef read
+
+void	(*screenputs)(char*, int) = 0;
+
+Kmesg	kmesg;			/* console messages */
+Queue*	kbdq;			/* unprocessed console input */
+Queue*	lineq;			/* processed console input */
+Queue*	kprintoq;		/* console output, for /dev/kprint */
+int	kprintinuse;		/* test and set whether /dev/kprint is open */
+
+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;
+
+	/* 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 },
+	kbd.istage,
+	kbd.istage,
+	kbd.istage + sizeof(kbd.istage),
+};
+
+char	*sysname;
+vlong	fasthz = 1000;
+
+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, 0, nil);
+	if(lineq == nil)
+		panic("printinit");
+	qnoblock(lineq, 1);
+
+	kbdq = qopen(4*1024, 0, 0, 0);
+	if(kbdq == nil)
+		panic("kbdinit");
+	qnoblock(kbdq, 1);
+}
+
+static void
+kmesgputs(char *str, int n)
+{
+	uint nn, d;
+
+	ilock(&kmesg.lk);
+	/* take the tail of huge writes */
+	if(n > sizeof kmesg.buf){
+		d = n - sizeof kmesg.buf;
+		str += d;
+		n -= d;
+	}
+
+	/* slide the buffer down to make room */
+	nn = kmesg.n;
+	if(nn + n >= sizeof kmesg.buf){
+		d = nn + n - sizeof kmesg.buf;
+		if(d)
+			memmove(kmesg.buf, kmesg.buf+d, sizeof kmesg.buf-d);
+		nn -= d;
+	}
+
+	/* copy the data in */
+	memmove(kmesg.buf+nn, str, n);
+	nn += n;
+	kmesg.n = nn;
+	iunlock(&kmesg.lk);
+}
+
+/*
+ *   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)
+{
+	/*
+	 *  how many different output devices do we need?
+	 */
+	kmesgputs(str, n);
+
+	/*
+	 *  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 != 0)
+		screenputs(str, n);
+	else
+		write(1, str, n);
+}
+
+void
+putstrn(char *str, int n)
+{
+	putstrn0(str, n, 0);
+}
+
+int
+print(char *fmt, ...)
+{
+	int n;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	va_start(arg, fmt);
+	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	putstrn(buf, n);
+
+	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(;;) osyield();
+	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';
+	spllo();
+	putstrn(buf, n+1);
+	while(screenputs != 0)
+		osyield();
+	setterm(0);
+	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 ebuf[128];
+	char *e, *p;
+	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
+echo(char *buf, int n)
+{
+	if(kbd.raw)
+		return;
+	if(screenputs != 0)
+		echoscreen(buf, n);
+	else
+		write(1, buf, n);
+}
+
+static int
+_kbdputc(Queue *q, int c)
+{
+	char buf[UTFmax];
+	Rune r = c;
+	int n;
+
+	if((n = runetochar(buf, &r)) > 0){
+		echo(buf, n);
+		qproduce(q, buf, n);
+	}
+	return 0;
+}
+
+/* _kbdputc, but with compose translation */
+int
+kbdputc(Queue *q, int c)
+{
+	static int collecting, nk;
+	static Rune kc[5];
+	int i;
+
+	switch(c){
+	case 0:
+	case Kcaps:
+	case Knum:
+	case Kshift:
+	case Kaltgr:
+	case Kmod4:
+	case Kctl:
+		/* ignore modifiers; see nextrune() of kbdfs */
+		return 0;
+
+	case Kalt:
+		collecting = 1;
+		nk = 0;
+		return 0;
+	}
+
+	if(!collecting)
+		return _kbdputc(q, c);
+
+	kc[nk++] = c;
+	c = latin1(kc, nk);
+	if(c < -1)  /* need more keystrokes */
+		return 0;
+	if(c != -1) /* valid sequence */
+		_kbdputc(q, c);
+	else
+		for(i=0; i<nk; i++)
+			_kbdputc(q, kc[i]);
+	nk = 0;
+	collecting = 0;
+	return 0;
+}
+
+
+enum{
+	Qdir,
+	Qbintime,
+	Qcons,
+	Qconsctl,
+	Qdrivers,
+	Qkmesg,
+	Qkprint,
+	Qhostdomain,
+	Qhostowner,
+	Qnull,
+	Qosversion,
+	Qrandom,
+	Qreboot,
+	Qshowfile,
+	Qsnarf,
+	Qsysname,
+	Qsysstat,
+	Qtime,
+	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,
+	"drivers",	{Qdrivers},	0,		0444,
+	"hostdomain",	{Qhostdomain},	DOMLEN,		0664,
+	"hostowner",	{Qhostowner},	0,	0664,
+	"kmesg",	{Qkmesg},	0,		0440,
+	"kprint",	{Qkprint, 0, QTEXCL},	0,	DMEXCL|0440,
+	"null",		{Qnull},	0,		0666,
+	"osversion",	{Qosversion},	0,		0444,
+	"random",	{Qrandom},	0,		0444,
+	"reboot",	{Qreboot},	0,		0664,
+	"showfile",	{Qshowfile},	0,	0220,
+	"snarf",	{Qsnarf},		0,		0666,
+	"sysname",	{Qsysname},	0,		0664,
+	"sysstat",	{Qsysstat},	0,		0666,
+	"time",		{Qtime},	NUMSIZE+3*VLNUMSIZE,	0664,
+	"zero",		{Qzero},	0,		0444,
+};
+
+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)
+{
+	randominit();
+}
+
+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 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;
+			if(screenputs == 0)
+				setterm(kbd.raw);
+			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 int
+qreadcons(Queue *q, char *buf, int n)
+{
+	if(screenputs==0 && !qcanread(q))
+		return read(0, buf, n);
+	return qread(q, buf, n);
+}
+
+static long
+consread(Chan *c, void *buf, long n, vlong off)
+{
+	char *b;
+	char tmp[128];		/* must be >= 6*NUMSIZE */
+	char *cbuf = buf;
+	int ch, i, 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 = qreadcons(kbdq, cbuf, n);
+					cbuf += i;
+					n -= i;
+				} while (n>0 && qcanread(kbdq));
+				n = cbuf - (char*)buf;
+			}
+		} else {
+			while(!qcanread(lineq)) {
+				eol = 1;
+				if(qreadcons(kbdq, &kbd.line[kbd.x], 1) == 1){
+					eol = 0;
+					ch = kbd.line[kbd.x];
+					switch(ch){
+					case '\b':
+						if(kbd.x)
+							kbd.x--;
+						break;
+					case 0x15:
+						kbd.x = 0;
+						break;
+					case '\n':
+						kbd.x++;
+					case 0x04:
+						eol = 1;
+						break;
+					default:
+						kbd.x++;
+					}
+				}
+				if(kbd.x == sizeof(kbd.line) || eol){
+					qwrite(lineq, kbd.line, kbd.x);
+					kbd.x = 0;
+				}
+			}
+			n = qread(lineq, buf, n);
+		}
+		qunlock(&kbd.lk);
+		poperror();
+		return n;
+
+	case Qkmesg:
+		/*
+		 * This is unlocked to avoid tying up a process
+		 * that's writing to the buffer.  kmesg.n never 
+		 * gets smaller, so worst case the reader will
+		 * see a slurred buffer.
+		 */
+		if(off >= kmesg.n)
+			n = 0;
+		else{
+			if(off+n > kmesg.n)
+				n = kmesg.n - off;
+			memmove(buf, kmesg.buf+off, n);
+		}
+		return n;
+		
+	case Qkprint:
+		return qread(kprintoq, buf, n);
+
+	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 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 Qsysstat:
+		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;
+	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;
+				if(screenputs == 0)
+					setterm(1);
+				qunlock(&kbd.lk);
+			} else if(strncmp(a, "rawoff", 6) == 0){
+				qlock(&kbd.lk);
+				kbd.raw = 0;
+				kbd.x = 0;
+				if(screenputs == 0)
+					setterm(0);
+				qunlock(&kbd.lk);
+			}
+			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 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:
+			error(Egreg);
+			break;
+		case CMpanic:
+			panic("/dev/reboot");
+		}
+		poperror();
+		free(cb);
+		break;
+
+	case Qshowfile:
+		return showfilewrite(a, 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 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 uvlong uvorder = (uvlong) 0x0001020304050607ULL;
+
+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);
+}
+
+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;
+	ulong sec;
+	char str[7*NUMSIZE];
+
+	sec = seconds();
+	nsec = (vlong)sec*1000000000LL;
+	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)
+{
+	USED(buf);
+	USED(n);
+	error(Egreg);
+	return 0;
+}
+
+/*
+ *  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;
+	uchar *b = (uchar*)buf;
+
+	i = 0;
+	nsec = (ulong)seconds()*1000000000LL;
+	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)
+{
+	USED(buf);
+	USED(n);
+	error(Egreg);
+	return 0;
+}
+
+
+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);
+	write(2, buf, n);
+	if(screenputs != 0)
+		screenputs(buf, n);
+	splx(s);
+
+	return n;
+}
--- /dev/null
+++ b/kern/devdraw.c
@@ -1,0 +1,2130 @@
+#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,
+	Qwinname,
+	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;
+
+struct Draw
+{
+	int		clientid;
+	int		nclient;
+	Client**	client;
+	int		nname;
+	DName*		name;
+	int		vers;
+	int		softscreen;
+};
+
+struct Client
+{
+	Ref		r;
+	DImage*		dimage[NHASH];
+	CScreen*	cscreen;
+	Refresh*	refresh;
+	Rendez		refrend;
+	QLock		refq;
+	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;
+	QLock	drawlock;
+
+static	Memimage	*screenimage;
+static	DImage*	screendimage;
+static	char	screenname[40];
+static	int	screennameid;
+
+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);
+	DImage*	allocdimage(Memimage*);
+
+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 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 void
+dlock(void)
+{
+	qlock(&drawlock);
+}
+
+static void
+dunlock(void)
+{
+	qunlock(&drawlock);
+}
+
+static int
+drawgen(Chan *c, char *n, Dirtab *d, int nd, int s, Dir *dp)
+{
+	int t;
+	Qid q;
+	ulong path;
+	Client *cl;
+
+	USED(n);
+	USED(d);
+	USED(nd);
+
+	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);
+	switch(t){
+	case Qtopdir:
+		if(s == 0){
+			mkqid(&q, Q2nd, 0, QTDIR);
+			devdir(c, q, "draw", 0, eve, 0555, dp);
+			return 1;
+		}
+		if(s == 1){
+	case Qwinname:
+			mkqid(&q, Qwinname, 0, QTFILE);
+			devdir(c, q, "winname", 0, eve, 0444, dp);
+			return 1;
+		}
+		return -1;
+	}
+
+	/*
+	 * Second level contains "new" plus all the clients.
+	 */
+	switch(t){
+	case Q2nd:
+		if(s == 0){
+	case Qnew:
+			mkqid(&q, Qnew, 0, QTFILE);
+			devdir(c, q, "new", 0, eve, 0666, dp);
+			return 1;
+		}
+		if(s <= sdraw.nclient){
+			cl = sdraw.client[s-1];
+			if(cl == nil)
+				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;
+		}
+		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 *i, Rectangle r, void *v)
+{
+	Refx *x;
+	DImage *d;
+	Client *c;
+	Refresh *ref;
+
+	USED(i);
+
+	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 || screenimage == nil || !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;
+	}
+	if(screenimage == nil || dst == nil || (l = dst->layer) == 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(screenimage && flushrect.min.x < flushrect.max.x)
+		flushmemscreen(flushrect);
+	flushrect = Rect(10000, 10000, -10000, -10000);
+}
+
+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;
+}
+
+DImage*
+allocdimage(Memimage *i)
+{
+	DImage *d;
+
+	d = malloc(sizeof(DImage));
+	if(d == 0)
+		return 0;
+	d->ref = 1;
+	d->name = 0;
+	d->vers = 0;
+	d->image = i;
+	d->dscreen = 0;
+	d->nfchar = 0;
+	d->fchar = 0;
+	d->fromname = 0;
+	return d;
+}
+
+Memimage*
+drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen)
+{
+	DImage *d;
+
+	d = allocdimage(i);
+	if(d == 0)
+		return 0;
+	d->id = id;
+	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;
+
+	free(name->name);
+	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) != nil){
+		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;
+	}
+	ds = dimage->dscreen;
+	if(ds){
+		l = dimage->image;
+		if(screenimage && 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) != nil){
+		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) != nil){
+		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, Memimage *rdst, Point p, Memimage *src, Point *sp, DImage *font, int index, int op)
+{
+	FChar *fc;
+	Rectangle r;
+	Point sp1;
+	static Memimage *tmp;
+
+	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;
+
+	/*
+	 * If we're drawing greyscale fonts onto a VGA screen,
+	 * it's very costly to read the screen memory to do the
+	 * alpha blending inside memdraw.  If this is really a stringbg,
+	 * then rdst is the bg image (in main memory) which we can
+	 * refer to for the underlying dst pixels instead of reading dst
+	 * directly.
+	 */
+	if(ishwimage(dst) && !ishwimage(rdst) && font->image->depth > 1){
+		if(tmp == nil || tmp->chan != dst->chan || Dx(tmp->r) < Dx(r) || Dy(tmp->r) < Dy(r)){
+			if(tmp)
+				freememimage(tmp);
+			tmp = allocmemimage(Rect(0,0,Dx(r),Dy(r)), dst->chan);
+			if(tmp == nil)
+				goto fallback;
+		}
+		memdraw(tmp, Rect(0,0,Dx(r),Dy(r)), rdst, r.min, memopaque, ZP, S);
+		memdraw(tmp, Rect(0,0,Dx(r),Dy(r)), src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+		memdraw(dst, r, tmp, ZP, memopaque, ZP, S);
+	}else{
+	fallback:
+		memdraw(dst, r, src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+	}
+
+	p.x += fc->width;
+	sp->x += fc->width;
+	return p;
+}
+
+static DImage*
+makescreenimage(void)
+{
+	int width, depth;
+	ulong chan;
+	DImage *di;
+	Memdata *md;
+	Memimage *i;
+	Rectangle r;
+
+	if((md = attachscreen(&r, &chan, &depth, &width, &sdraw.softscreen)) == nil)
+		return nil;
+	assert(md->ref > 0);
+	if((i = allocmemimaged(r, chan, md)) == nil){
+		if(--md->ref == 0 && md->allocd)
+			free(md);
+		return nil;
+	}
+	i->width = width;
+	i->clipr = r;
+	di = allocdimage(i);
+	if(di == nil){
+		freememimage(i);	/* frees md */
+		return nil;
+	}
+	if(!waserror()){
+		snprint(screenname, sizeof screenname, "noborder.screen.%d", ++screennameid);
+		drawaddname(nil, di, strlen(screenname), screenname);
+		poperror();
+	}
+	return di;
+}
+
+static int
+initscreenimage(void)
+{
+	if(screenimage != nil)
+		return 1;
+
+	screendimage = makescreenimage();
+	if(screendimage == nil)
+		return 0;
+	screenimage = screendimage->image;
+// iprint("initscreenimage %p %p\n", screendimage, screenimage);
+	mouseresize();
+	return 1;
+}
+
+void
+deletescreenimage(void)
+{
+	if(screenimage){
+		/* will be freed via screendimage; disable */
+		screenimage->clipr = ZR;
+		screenimage = nil;
+	}
+	if(screendimage){
+		drawfreedimage(screendimage);
+		screendimage = nil;
+	}
+}
+
+void
+resetscreenimage(void)
+{
+	initscreenimage();
+}
+
+static Chan*
+drawattach(char *spec)
+{
+	if(gscreen == nil)
+		screeninit();
+	dlock();
+	if(!initscreenimage()){
+		dunlock();
+		error("no frame buffer");
+	}
+	dunlock();
+	return devattach('i', spec);
+}
+
+static Walkqid*
+drawwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	if(screenimage == 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;
+	DName *dn;
+	DImage *di;
+
+	if(c->qid.type & QTDIR){
+		c = devopen(c, omode, 0, 0, drawgen);
+		c->iounit = IOUNIT;
+	}
+
+	dlock();
+	if(waserror()){
+		dunlock();
+		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 Qwinname:
+		break;
+
+	case Qnew:
+		break;
+
+	case Qctl:
+		cl = drawclient(c);
+		if(cl->busy)
+			error(Einuse);
+		cl->busy = 1;
+		flushrect = Rect(10000, 10000, -10000, -10000);
+		dn = drawlookupname(strlen(screenname), screenname);
+		if(dn == 0)
+			error("draw: cannot happen 2");
+		if(drawinstall(cl, 0, dn->dimage->image, 0) == 0)
+			error(Edrawmem);
+		di = drawlookup(cl, 0, 0);
+		if(di == 0)
+			error("draw: cannot happen 1");
+		di->vers = dn->vers;
+		di->name = smalloc(strlen(screenname)+1);
+		strcpy(di->name, screenname);
+		di->fromname = dn->dimage;
+		di->fromname->ref++;
+		incref(&cl->r);
+		break;
+
+	case Qcolormap:
+	case Qdata:
+	case Qrefresh:
+		cl = drawclient(c);
+		incref(&cl->r);
+		break;
+	}
+	dunlock();
+	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;
+	dlock();
+	if(waserror()){
+		dunlock();
+		nexterror();
+	}
+
+	cl = drawclient(c);
+	if(QID(c->qid) == Qctl)
+		cl->busy = 0;
+	if((c->flag&COPEN) && (decref(&cl->r)==0)){
+		while((r = cl->refresh) != nil){
+			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);
+	}
+	dunlock();
+	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);
+	if(QID(c->qid) == Qwinname)
+		return readstr(off, a, n, screenname);
+
+	cl = drawclient(c);
+	dlock();
+	if(waserror()){
+		dunlock();
+		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);
+		((char*)a)[n++] = ' ';
+		cl->infoid = -1;
+		break;
+
+	case Qcolormap:
+		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;
+			dunlock();
+			if(waserror()){
+				dlock();
+				nexterror();
+			}
+			qlock(&cl->refq);
+			if(waserror()){
+				qunlock(&cl->refq);
+				nexterror();
+			}
+			sleep(&cl->refrend, drawrefactive, cl);
+			poperror();
+			qunlock(&cl->refq);
+			poperror();
+			dlock();
+		}
+		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;
+		break;
+	}
+	dunlock();
+	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 off)
+{
+	char buf[128], *fields[4], *q;
+	Client *cl;
+	int i, m, red, green, blue, x;
+
+	USED(off);
+
+	if(c->qid.type & QTDIR)
+		error(Eisdir);
+	cl = drawclient(c);
+	dlock();
+	if(waserror()){
+		drawwakeall();
+		dunlock();
+		nexterror();
+	}
+	switch(QID(c->qid)){
+	case Qctl:
+		if(n != 4)
+			error("unknown draw control request");
+		cl->infoid = BGLONG((uchar*)a);
+		break;
+
+	case Qcolormap:
+		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);
+	}
+	dunlock();
+	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){
+		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;
+		case 'z':
+			q += sprint(q, " %.*q", (int)*a, (char*)(a+1));
+			a += 1 + *a;
+			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, *bg, *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 = BGLONG(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("cannot 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);
+			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("cannot use display as font");
+			font = drawlookup(client, dstid, 1);
+			if(font == 0)
+				error(Enodrawimage);
+			if(font->image->layer)
+				error("cannot 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: cannot 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 || (screenimage && 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);
+			bg = dst;
+			if(*a == 'x'){
+				/* paint background */
+				bg = 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, bg, 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, bg, 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[j]->layer == 0)
+					error("images are not windows");
+				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(screenimage && 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;
+
+	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);
+		    }
+	}
+}
--- /dev/null
+++ b/kern/devenv.c
@@ -1,0 +1,426 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Maxenvsize = 16300,
+};
+
+static Egrp	*envgrp(Chan *c);
+static int	envwriteable(Chan *c);
+static void	initunix();
+
+static Egrp	unixegrp;	/* unix environment group */
+
+static Evalue*
+envlookup(Egrp *eg, char *name, ulong qidpath)
+{
+	Evalue *e;
+	int i;
+
+	for(i=0; i<eg->nent; i++){
+		e = eg->ent[i];
+		if(e->qid.path == qidpath || (name && e->name[0]==name[0] && strcmp(e->name, name) == 0))
+			return e;
+	}
+	return nil;
+}
+
+static int
+envgen(Chan *c, char *name, Dirtab *_1, int _2, int s, Dir *dp)
+{
+	Egrp *eg;
+	Evalue *e;
+
+	if(s == DEVDOTDOT){
+		devdir(c, c->qid, "#e", 0, eve, DMDIR|0775, dp);
+		return 1;
+	}
+
+	eg = envgrp(c);
+	rlock(&eg->lk);
+	e = 0;
+	if(name)
+		e = envlookup(eg, name, -1);
+	else if(s < eg->nent)
+		e = eg->ent[s];
+
+	if(e == 0) {
+		runlock(&eg->lk);
+		return -1;
+	}
+
+	/* make sure name string continues to exist after we release lock */
+	kstrcpy(up->genbuf, e->name, sizeof up->genbuf);
+	devdir(c, e->qid, up->genbuf, e->len, eve, 0666, dp);
+	runlock(&eg->lk);
+	return 1;
+}
+
+static Chan*
+envattach(char *spec)
+{
+	Chan *c;
+
+	if(spec && *spec) {
+		error(Ebadarg);
+	}
+	initunix();
+	c = devattach('e', spec);
+	c->aux = &unixegrp;
+	return c;
+}
+
+static Walkqid*
+envwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, 0, 0, envgen);
+}
+
+static int
+envstat(Chan *c, uchar *db, int n)
+{
+	if(c->qid.type & QTDIR)
+		c->qid.vers = envgrp(c)->vers;
+	return devstat(c, db, n, 0, 0, envgen);
+}
+
+static Chan*
+envopen(Chan *c, int omode)
+{
+	Egrp *eg;
+	Evalue *e;
+	int trunc;
+
+	eg = envgrp(c);
+	if(c->qid.type & QTDIR) {
+		if(omode != OREAD)
+			error(Eperm);
+	}
+	else {
+		trunc = omode & OTRUNC;
+		if(omode != OREAD && !envwriteable(c))
+			error(Eperm);
+		if(trunc)
+			wlock(&eg->lk);
+		else
+			rlock(&eg->lk);
+		e = envlookup(eg, nil, c->qid.path);
+		if(e == 0) {
+			if(trunc)
+				wunlock(&eg->lk);
+			else
+				runlock(&eg->lk);
+			error(Enonexist);
+		}
+		if(trunc && e->value) {
+			e->qid.vers++;
+			free(e->value);
+			e->value = 0;
+			e->len = 0;
+		}
+		if(trunc)
+			wunlock(&eg->lk);
+		else
+			runlock(&eg->lk);
+	}
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+static Chan*
+envcreate(Chan *c, char *name, int omode, ulong _)
+{
+	Egrp *eg;
+	Evalue *e;
+	Evalue **ent;
+
+	if(c->qid.type != QTDIR)
+		error(Eperm);
+	if(strlen(name) >= sizeof up->genbuf)
+		error("name too long");			/* protect envgen */
+
+	omode = openmode(omode);
+	eg = envgrp(c);
+
+	wlock(&eg->lk);
+	if(waserror()) {
+		wunlock(&eg->lk);
+		nexterror();
+	}
+
+	if(envlookup(eg, name, -1))
+		error(Eexist);
+
+	e = smalloc(sizeof(Evalue));
+	e->name = smalloc(strlen(name)+1);
+	strcpy(e->name, name);
+
+	if(eg->nent == eg->ment){
+		eg->ment += 32;
+		ent = smalloc(sizeof(eg->ent[0])*eg->ment);
+		if(eg->nent)
+			memmove(ent, eg->ent, sizeof(eg->ent[0])*eg->nent);
+		free(eg->ent);
+		eg->ent = ent;
+	}
+	e->qid.path = ++eg->path;
+	e->qid.vers = 0;
+	eg->vers++;
+	eg->ent[eg->nent++] = e;
+	c->qid = e->qid;
+
+	wunlock(&eg->lk);
+	poperror();
+
+	c->offset = 0;
+	c->mode = omode;
+	c->flag |= COPEN;
+	return c;
+}
+
+static void
+envremove(Chan *c)
+{
+	int i;
+	Egrp *eg;
+	Evalue *e;
+
+	if(c->qid.type & QTDIR)
+		error(Eperm);
+
+	eg = envgrp(c);
+	wlock(&eg->lk);
+	e = 0;
+	for(i=0; i<eg->nent; i++){
+		if(eg->ent[i]->qid.path == c->qid.path){
+			e = eg->ent[i];
+			eg->nent--;
+			eg->ent[i] = eg->ent[eg->nent];
+			eg->vers++;
+			break;
+		}
+	}
+	wunlock(&eg->lk);
+	if(e == 0)
+		error(Enonexist);
+	free(e->name);
+	if(e->value)
+		free(e->value);
+	free(e);
+}
+
+static void
+envclose(Chan *c)
+{
+	/*
+	 * cclose can't fail, so errors from remove will be ignored.
+	 * since permissions aren't checked,
+	 * envremove can't not remove it if its there.
+	 */
+	if(c->flag & CRCLOSE)
+		envremove(c);
+}
+
+static long
+envread(Chan *c, void *a, long n, vlong off)
+{
+	Egrp *eg;
+	Evalue *e;
+	ulong offset = off;
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, a, n, 0, 0, envgen);
+
+	eg = envgrp(c);
+	rlock(&eg->lk);
+	e = envlookup(eg, nil, c->qid.path);
+	if(e == 0) {
+		runlock(&eg->lk);
+		error(Enonexist);
+	}
+
+	if(offset > e->len)	/* protects against overflow converting vlong to ulong */
+		n = 0;
+	else if(offset + n > e->len)
+		n = e->len - offset;
+	if(n <= 0)
+		n = 0;
+	else
+		memmove(a, e->value+offset, n);
+	runlock(&eg->lk);
+	return n;
+}
+
+static long
+envwrite(Chan *c, void *a, long n, vlong off)
+{
+	char *s;
+	ulong len;
+	Egrp *eg;
+	Evalue *e;
+	ulong offset = off;
+
+	if(n <= 0)
+		return 0;
+	if(offset > Maxenvsize || n > (Maxenvsize - offset))
+		error(Etoobig);
+
+	eg = envgrp(c);
+	wlock(&eg->lk);
+	e = envlookup(eg, nil, c->qid.path);
+	if(e == 0) {
+		wunlock(&eg->lk);
+		error(Enonexist);
+	}
+
+	len = offset+n;
+	if(len > e->len) {
+		s = smalloc(len);
+		if(e->value){
+			memmove(s, e->value, e->len);
+			free(e->value);
+		}
+		e->value = s;
+		e->len = len;
+	}
+	memmove(e->value+offset, a, n);
+	e->qid.vers++;
+	eg->vers++;
+	wunlock(&eg->lk);
+	return n;
+}
+
+Dev envdevtab = {
+	'e',
+	"env",
+
+	devreset,
+	devinit,
+	devshutdown,
+	envattach,
+	envwalk,
+	envstat,
+	envopen,
+	envcreate,
+	envclose,
+	envread,
+	devbread,
+	envwrite,
+	devbwrite,
+	envremove,
+	devwstat,
+};
+
+extern char **environ;
+
+static void
+initunix()
+{
+	Egrp *eg = &unixegrp;
+	Evalue **ent, *e;
+	char *eq, **envp, *line;
+	int n;
+
+	wlock(&eg->lk);
+
+	if(eg->path > 0 || eg->ment > 0 || !environ){
+		// already initialized or nothing in environent
+		wunlock(&eg->lk);
+		return;
+	}
+
+	for(envp = environ; *envp != nil; envp++)
+		eg->ment++;
+	ent = smalloc(sizeof(eg->ent[0])*eg->ment);
+	eg->ent = ent;
+
+	for(envp = environ; *envp != nil; envp++){
+		line = *envp;
+		n = strlen(line);
+
+		eq = strchr(line, '=');
+		if(eq == nil)
+			eq = &line[n];
+		e = smalloc(sizeof(Evalue));
+		e->name = smalloc(eq-line+1);
+		strncpy(e->name, line, eq-line);
+
+		if(eq[0] != '\0')
+			eq++;
+		e->len = line+n-eq;
+		e->value = smalloc(e->len);
+		memmove(e->value, eq, e->len);
+
+		e->qid.path = ++eg->path;
+		e->qid.vers = 0;
+		eg->vers++;
+		eg->ent[eg->nent++] = e;
+	}
+
+	wunlock(&eg->lk);
+}
+
+void
+envcpy(Egrp *to, Egrp *from)
+{
+	int i;
+	Evalue *ne, *e;
+
+	rlock(&from->lk);
+	to->ment = (from->nent+31)&~31;
+	to->ent = smalloc(to->ment*sizeof(to->ent[0]));
+	for(i=0; i<from->nent; i++){
+		e = from->ent[i];
+		ne = smalloc(sizeof(Evalue));
+		ne->name = smalloc(strlen(e->name)+1);
+		strcpy(ne->name, e->name);
+		if(e->value){
+			ne->value = smalloc(e->len);
+			memmove(ne->value, e->value, e->len);
+			ne->len = e->len;
+		}
+		ne->qid.path = ++to->path;
+		to->ent[i] = ne;
+	}
+	to->nent = from->nent;
+	runlock(&from->lk);
+}
+
+void
+closeegrp(Egrp *eg)
+{
+	int i;
+	Evalue *e;
+
+	if(decref(&eg->ref) == 0){
+		for(i=0; i<eg->nent; i++){
+			e = eg->ent[i];
+			free(e->name);
+			if(e->value)
+				free(e->value);
+			free(e);
+		}
+		free(eg->ent);
+		free(eg);
+	}
+}
+
+static Egrp*
+envgrp(Chan *c)
+{
+	if(c->aux == nil)
+		return &unixegrp;
+	return c->aux;
+}
+
+static int
+envwriteable(Chan *c)
+{
+	return iseve() || c->aux == nil;
+}
--- /dev/null
+++ b/kern/devfs-posix.c
@@ -1,0 +1,616 @@
+#include	"u.h"
+#include	<sys/types.h>
+#include	<sys/time.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	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+typedef	struct Ufsinfo	Ufsinfo;
+struct Ufsinfo
+{
+	int	mode;
+	int	fd;
+	int	uid;
+	int	gid;
+	DIR*	dir;
+	vlong	offset;
+	QLock	oq;
+	char*	path;
+	char nextname[NAME_MAX];
+};
+
+static	Qid	fsqid(struct stat *);
+static	char*	catpath(char*, char*);
+static	ulong	fsdirread(Chan*, uchar*, int, ulong);
+static	int	fsomode(int);
+
+static char*
+lastelem(char *s)
+{
+	char *t;
+
+	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("/", &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;
+	uif->path = strdup("/");
+
+	c->aux = uif;
+	c->dev = devno++;
+	c->qid.type = QTDIR;
+/*print("fsattach %s\n", chanpath(c));*/
+
+	return c;
+}
+
+static Chan*
+fsclone(Chan *c, Chan *nc)
+{
+	Ufsinfo *uif;
+
+	uif = mallocz(sizeof(Ufsinfo), 1);
+	*uif = *(Ufsinfo*)c->aux;
+	uif->path = strdup(uif->path);
+	nc->aux = uif;
+
+	return nc;
+}
+
+static int
+fswalk1(Chan *c, char *name)
+{
+	struct stat stbuf;
+	Ufsinfo *uif;
+	char *path;
+
+	/*print("** fs walk '%s' -> %s\n", path, name);  */
+
+	uif = c->aux;
+	path = catpath(uif->path, name);
+	if(stat(path, &stbuf) < 0){
+		free(path);
+		return 0;
+	}
+	free(uif->path);
+	uif->path = path;
+
+	uif->mode = stbuf.st_mode;
+	uif->uid = stbuf.st_uid;
+	uif->gid = stbuf.st_gid;
+
+	c->qid = fsqid(&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;
+	Ufsinfo *uif;
+
+	if(n < BIT16SZ)
+		error(Eshortstat);
+
+	uif = c->aux;
+	if(stat(uif->path, &stbuf) < 0)
+		error(strerror(errno));
+
+	d.name = lastelem(uif->path);
+	d.uid = eve;
+	d.gid = eve;
+	d.muid = eve;
+	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)
+{
+	Ufsinfo *uif;
+	int omode;
+
+	omode = openmode(mode);
+
+	uif = c->aux;
+	if(c->qid.type & QTDIR) {
+		if(omode != OREAD)
+			error(Eperm);
+		if((uif->dir = opendir(uif->path)) == NULL)
+			error(strerror(errno));
+	}	
+	else {
+		int m = fsomode(omode & 3);
+		if(mode & OTRUNC)
+			m |= O_TRUNC;
+		if((uif->fd = open(uif->path, m, 0666)) < 0)
+			error(strerror(errno));
+	}
+	uif->offset = 0;
+
+	c->offset = 0;
+	c->mode = omode;
+	c->flag |= COPEN;
+	return c;
+}
+
+static Chan*
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+	struct stat stbuf;
+	Ufsinfo *uif;
+	char *path;
+	DIR *dir;
+	int fd, omode;
+
+	omode = openmode(mode & ~OEXCL);
+
+	uif = c->aux;
+	path = catpath(uif->path, name);
+	if(waserror()){
+		free(path);
+		nexterror();
+	}
+	fd = -1;
+	dir = NULL;
+	if(perm & DMDIR) {
+		if(omode != OREAD)
+			error(Eperm);
+		if(mkdir(path, uif->mode & perm & 0777) < 0){
+			if(errno != EEXIST || mode & OEXCL)
+				error(strerror(errno));
+		}
+		if((dir = opendir(path)) == NULL)
+			error(strerror(errno));
+		if(waserror()){
+			closedir(dir);
+			nexterror();
+		}
+	}
+	else {
+		int m = fsomode(omode & 3) | O_CREAT | O_TRUNC;
+		if(mode & OEXCL)
+			m |= O_EXCL;
+		if((fd = open(path, m, uif->mode & perm & 0777)) < 0)
+			error(strerror(errno));
+		if(waserror()){
+			close(fd);
+			nexterror();
+		}
+	}
+	if(stat(path, &stbuf) < 0)
+		error(strerror(errno));
+	c->qid = fsqid(&stbuf);
+	uif->fd = fd;
+	uif->dir = dir;
+	poperror();
+
+	free(uif->path);
+	uif->path = path;
+	uif->offset = 0;
+	poperror();
+
+	c->offset = 0;
+	c->mode = omode;
+	c->flag |= COPEN;
+	return c;
+}
+
+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);
+		c->flag &= ~COPEN;
+		if(c->flag & CRCLOSE) {
+			devtab[c->type]->remove(c);
+			return;
+		}
+	}
+	free(uif->path);
+	free(uif);
+}
+
+static long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+	int fd;
+	vlong r;
+	Ufsinfo *uif;
+
+/*print("fsread %s\n", chanpath(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;
+	vlong 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;
+	Ufsinfo *uif;
+
+	if(waserror()){
+		fsclose(c);
+		nexterror();
+	}
+	uif = c->aux;
+	if(c->qid.type & QTDIR)
+		n = rmdir(uif->path);
+	else
+		n = remove(uif->path);
+	if(n < 0)
+		error(strerror(errno));
+	poperror();
+	fsclose(c);
+}
+
+int
+fswstat(Chan *c, uchar *buf, int n)
+{
+	Dir d;
+	struct stat stbuf;
+	char strs[NAME_MAX*3];
+	Ufsinfo *uif;
+
+	/*
+	 * wstat is supposed to be atomic.
+	 * we check all the things we can before trying anything.
+	 * still, if we are told to truncate a file and rename it and only
+	 * one works, we're screwed.  in such cases we leave things
+	 * half broken and return an error.  it's hardly perfect.
+	 */
+	if(convM2D(buf, n, &d, strs) != n)
+		error(Ebadstat);
+	
+	uif = c->aux;
+	if(stat(uif->path, &stbuf) < 0)
+		error(strerror(errno));
+
+	/*
+	 * try things in increasing order of harm to the file.
+	 * mtime should come after truncate so that if you
+	 * do both the mtime actually takes effect, but i'd rather
+	 * leave truncate until last.
+	 * (see above comment about atomicity).
+	 */
+	if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) {
+		if(chmod(uif->path, d.mode&0777) < 0)
+			error(strerror(errno));
+		uif->mode &= ~0777;
+		uif->mode |= d.mode&0777;
+	}
+	if(~d.atime != 0 || ~d.mtime != 0){
+		struct timeval t[2];
+		t[0].tv_sec = ~d.atime != 0 ? d.atime : stbuf.st_atime;
+		t[0].tv_usec = 0;
+		t[1].tv_sec = ~d.mtime != 0 ? d.mtime : stbuf.st_mtime;
+		t[1].tv_usec = 0;
+		utimes(uif->path, t);
+	}
+	if(d.name[0] && strcmp(d.name, lastelem(uif->path)) != 0) {
+		char *base, *newpath;
+
+		base = strdup(uif->path);
+		*lastelem(base) = 0;
+		newpath = catpath(base, d.name);
+		free(base);
+		if(waserror()){
+			free(newpath);
+			nexterror();
+		}
+		if(stat(newpath, &stbuf) >= 0)
+			error(Eexist);
+		if(rename(uif->path, newpath) < 0)
+			error(strerror(errno));
+		free(uif->path);
+		uif->path = newpath;
+		poperror();
+	}
+
+/*
+	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;
+	}
+*/
+
+	if((uvlong)d.length != (uvlong)~0 && truncate(uif->path, d.length) < 0)
+		error(strerror(errno));
+
+	return n;
+}
+
+static Qid
+fsqid(struct stat *st)
+{
+	Qid q;
+
+	q.type = 0;
+	if((st->st_mode&S_IFMT) ==  S_IFDIR)
+		q.type = QTDIR;
+
+	q.path = (uvlong)st->st_dev<<32 | st->st_ino;
+	q.vers = st->st_mtime;
+
+	return q;
+}
+
+static char*
+catpath(char *base, char *ext)
+{
+	char *path;
+	int n, m;
+
+	n = strlen(base);
+	m = strlen(ext);
+	path = malloc(n+m+2);
+	memmove(path, base, n);
+	if(n > 0 && path[n-1] != '/')
+		path[n++] = '/';
+	memmove(path+n, ext, m+1);
+	return 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;
+	Ufsinfo *uif;
+
+/*print("fsdirread %s\n", chanpath(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);
+	}
+
+	while(i+BIT16SZ < count) {
+		char *p;
+
+		if(!p9readdir(de, uif))
+			break;
+
+		if(de[0]==0 || isdots(de))
+			continue;
+
+		d.name = de;
+		p = catpath(uif->path, de);
+		if(stat(p, &stbuf) < 0) {
+			/* fprint(2, "dir: bad path %s\n", path); */
+			/* but continue... probably a bad symlink */
+			memset(&stbuf, 0, sizeof stbuf);
+		}
+		free(p);
+
+		d.uid = eve;
+		d.gid = eve;
+		d.muid = eve;
+		d.qid = fsqid(&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, (uchar*)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 OREAD:
+	case OEXEC:
+		return O_RDONLY;
+	case OWRITE:
+		return O_WRONLY;
+	case ORDWR:
+		return O_RDWR;
+	}
+	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/devfs-win32.c
@@ -1,0 +1,820 @@
+#include	<windows.h>
+#include	<sys/types.h>
+#include	<sys/stat.h>
+#include	<fcntl.h>
+
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	<libsec.h>	/* for sha1 in pathhash() */
+
+typedef struct DIR	DIR;
+typedef	struct Ufsinfo	Ufsinfo;
+
+enum
+{
+	TPATH_ROOT	= 0,	// ""
+	TPATH_VOLUME	= 1,	// "C:"
+	TPATH_FILE	= 2,	// "C:\bla"
+};
+
+struct DIR
+{
+	// for FindFileFirst()
+	HANDLE		handle;
+	WIN32_FIND_DATA	wfd;
+
+	// for GetLogicalDriveStrings()
+	wchar_t		*drivebuf;
+	wchar_t		*drivep;
+
+	// dont move to the next item
+	int		keep;
+};
+
+struct Ufsinfo
+{
+	int	mode;
+	HANDLE	fh;
+	DIR*	dir;
+	vlong	offset;
+	QLock	oq;
+	wchar_t	*path;
+};
+
+static	wchar_t *catpath(wchar_t *, char *, wchar_t *);
+static	ulong	fsdirread(Chan*, uchar*, int, vlong);
+static	ulong	fsaccess(int);
+static	ulong	pathtype(wchar_t *);
+static	int	checkvolume(wchar_t *);
+
+static ulong
+unixtime(FILETIME *ft)
+{
+	vlong t;
+	t = ((vlong)ft->dwHighDateTime << 32)|((vlong)ft->dwLowDateTime);
+	t -= 116444736000000000LL;
+	return ((t<0)?(-1 - (-t - 1)) : t)/10000000;
+}
+
+static FILETIME
+filetime(ulong ut)
+{
+	FILETIME ft;
+	vlong t = (vlong)ut * 10000000LL;
+	t += 116444736000000000LL;
+	ft.dwLowDateTime = t;
+	ft.dwHighDateTime = t >> 32;
+	return ft;
+}
+
+static uvlong
+pathhash(wchar_t *p)
+{
+	uchar digest[SHA1dlen];
+	sha1((uchar*)p, wcslen(p)*sizeof(wchar_t), digest, nil);
+	return *(uvlong*)digest;
+}
+
+static ulong
+wfdtodmode(WIN32_FIND_DATA *wfd)
+{
+	int m;
+	m = DMREAD|DMWRITE|DMEXEC;
+	if(wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+		m |= DMDIR;
+	if(wfd->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+		m &= ~DMWRITE;
+	m |= (m & 07)<<3;
+	m |= (m & 07)<<6;
+	return m;
+}
+
+static Qid
+wfdtoqid(wchar_t *path, WIN32_FIND_DATA *wfd)
+{
+	ulong t;
+	WIN32_FIND_DATA f;
+	Qid q;
+
+	t = pathtype(path);
+	switch(t){
+	case TPATH_VOLUME:
+	case TPATH_ROOT:
+		q.type = QTDIR;
+		q.path = pathhash(path);
+		q.vers = 0;
+		break;
+
+	case TPATH_FILE:
+		if(!wfd){
+			HANDLE h;
+			if((h = FindFirstFile(path, &f))==INVALID_HANDLE_VALUE)
+				oserror();
+			FindClose(h);
+			wfd = &f;
+		}
+		q.type = (wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? QTDIR : QTFILE;
+		q.path = pathhash(path);
+		q.vers = unixtime(&wfd->ftLastWriteTime);
+		break;
+	}
+	return q;
+}
+
+static void
+wfdtodir(wchar_t *path, Dir *d, WIN32_FIND_DATA *wfd)
+{
+	extern ulong kerndate;
+	WIN32_FIND_DATA f;
+
+	switch(pathtype(path)){
+	case TPATH_VOLUME:
+	case TPATH_ROOT:
+		wfd = nil;
+		d->mode = 0777 | DMDIR;
+		d->atime = seconds();
+		d->mtime = kerndate;
+		d->length = 0;
+		break;
+
+	case TPATH_FILE:
+		if(wfd == nil){
+			HANDLE h;
+			if((h = FindFirstFile(path, &f))==INVALID_HANDLE_VALUE)
+				oserror();
+			FindClose(h);
+			wfd = &f;
+		}
+		d->mode		= wfdtodmode(wfd);
+		d->atime	= unixtime(&wfd->ftLastAccessTime);
+		d->mtime	= unixtime(&wfd->ftLastWriteTime);
+		d->length	= ((uvlong)wfd->nFileSizeHigh << 32)|((uvlong)wfd->nFileSizeLow);
+		break;
+	}
+	d->qid = wfdtoqid(path, wfd);
+	d->uid = eve;
+	d->gid = eve;
+	d->muid = eve;
+}
+
+static char*
+lastelem(Chan *c)
+{
+	char *s, *t;
+
+	s = chanpath(c);
+	if((t = strrchr(s, '/')) == nil)
+		return s;
+	if(t[1] == 0)
+		return t;
+	return t+1;
+}
+
+static ulong
+pathtype(wchar_t *path)
+{
+	if(path[0] == 0 || path[1] == 0)
+		return TPATH_ROOT;
+	if(path[1] == ':' && path[2] == 0)
+		return TPATH_VOLUME;
+	return TPATH_FILE;
+}
+
+static int
+checkvolume(wchar_t *path)
+{
+	wchar_t vol[MAX_PATH];
+	wchar_t volname[MAX_PATH];
+	wchar_t fsysname[MAX_PATH];
+	DWORD complen;
+	DWORD flags;
+
+	wcscpy(vol, path);
+	wcscat(vol, L"\\");
+	if(!GetVolumeInformation(
+		vol,
+		volname,
+		MAX_PATH,
+		NULL,
+		&complen,
+		&flags,
+		fsysname,
+		MAX_PATH))
+		return 0;
+
+	return 1;	
+}
+
+static wchar_t*
+wstrdup(wchar_t *s)
+{
+	wchar_t *d;
+	long n;
+
+	n = (wcslen(s)+1)*sizeof(wchar_t);
+	d = mallocz(n, 0);
+	memmove(d, s, n);
+	return d;
+}
+	
+static Chan*
+fsattach(char *spec)
+{
+	static int devno;
+	Ufsinfo *uif;
+	Chan *c;
+
+	uif = mallocz(sizeof(Ufsinfo), 1);
+	uif->path = wstrdup(L"");
+
+	c = devattach('U', spec);
+	c->aux = uif;
+	c->dev = devno++;
+	c->qid.type = QTDIR;
+
+	return c;
+}
+
+static Chan*
+fsclone(Chan *c, Chan *nc)
+{
+	Ufsinfo *uif;
+
+	uif = mallocz(sizeof(Ufsinfo), 1);
+	*uif = *(Ufsinfo*)c->aux;
+	uif->path = wstrdup(uif->path);
+	nc->aux = uif;
+
+	return nc;
+}
+
+static int
+fswalk1(Chan *c, char *name)
+{
+	WIN32_FIND_DATA wfd;
+	HANDLE h;
+	wchar_t *p;
+	Ufsinfo *uif;
+	
+	uif = c->aux;
+	p = catpath(uif->path, name, nil);
+	switch(pathtype(p)){
+	case TPATH_VOLUME:
+		if(!checkvolume(p)){
+			free(p);
+			return 0;
+		}
+	case TPATH_ROOT:
+		c->qid = wfdtoqid(p, nil);
+		break;
+
+	case TPATH_FILE:
+		if((h = FindFirstFile(p, &wfd)) == INVALID_HANDLE_VALUE){
+			free(p);
+			return 0;
+		}
+		FindClose(h);
+		c->qid = wfdtoqid(p, &wfd);
+		break;
+	}
+	free(uif->path);
+	uif->path = p;
+	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;
+	Ufsinfo *uif;
+
+	if(n < BIT16SZ)
+		error(Eshortstat);
+	uif = c->aux;
+	d.name = lastelem(c);
+	wfdtodir(uif->path, &d, nil);
+	d.type = 'U';
+	d.dev = c->dev;
+	return convD2M(&d, buf, n);
+}
+
+static Chan*
+fsopen(Chan *c, int mode)
+{
+	wchar_t *p;
+	Ufsinfo *uif;
+	int omode;
+
+	omode = openmode(mode);
+
+	uif = c->aux;
+	if(c->qid.type & QTDIR){
+		DIR *d;
+
+		if(omode != OREAD)
+			error(Eperm);
+		d = malloc(sizeof(*d));
+		if(waserror()){
+			free(d);
+			nexterror();
+		}
+		switch(pathtype(uif->path)){
+		case TPATH_ROOT:
+			d->drivebuf = malloc(sizeof(wchar_t)*MAX_PATH);
+			if(GetLogicalDriveStrings(MAX_PATH-1, d->drivebuf) == 0){
+				free(d->drivebuf);
+				d->drivebuf = nil;
+				oserror();
+			}
+			d->drivep = d->drivebuf;
+			break;
+		case TPATH_VOLUME:
+		case TPATH_FILE:
+			p = catpath(uif->path, "*.*", nil);
+			if((d->handle = FindFirstFile(p, &d->wfd)) == INVALID_HANDLE_VALUE){
+				free(p);
+				oserror();
+			}
+			free(p);
+			break;
+		}
+		d->keep = 1;
+		uif->dir = d;
+		poperror();
+	} else {
+		uif->dir = NULL;
+		if((uif->fh = CreateFile(
+			uif->path,
+			fsaccess(omode & 3),
+			FILE_SHARE_READ | FILE_SHARE_WRITE,
+			NULL,
+			(mode & OTRUNC) ? TRUNCATE_EXISTING : OPEN_EXISTING,
+			FILE_ATTRIBUTE_NORMAL,
+			0)) == INVALID_HANDLE_VALUE)
+			oserror();
+	}
+	uif->offset = 0;
+
+	c->offset = 0;
+	c->mode = omode;
+	c->flag |= COPEN;
+	return c;
+}
+
+static Chan*
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+	wchar_t *newpath;
+	Ufsinfo *uif;
+	DIR *d;
+	HANDLE h;
+	int omode;
+
+	omode = openmode(mode & ~OEXCL);
+
+	uif = c->aux;
+	newpath = catpath(uif->path, name, nil);
+	if(waserror()){
+		free(newpath);
+		nexterror();
+	}
+	d = NULL;
+	h = INVALID_HANDLE_VALUE;
+	if(perm & DMDIR) {
+		wchar_t *p;
+
+		if(omode != OREAD || pathtype(uif->path) == TPATH_ROOT)
+			error(Eperm);
+		if(!CreateDirectory(newpath, NULL)){
+			if(GetLastError() != ERROR_ALREADY_EXISTS || mode & OEXCL)
+				oserror();
+		}
+		d = malloc(sizeof(*d));
+		p = catpath(newpath, "*.*", nil);
+		if((d->handle = FindFirstFile(p, &d->wfd)) == INVALID_HANDLE_VALUE){
+			free(p);
+			free(d);
+			oserror();
+		}
+		free(p);
+		if(waserror()){
+			FindClose(d->handle);
+			free(d);
+			nexterror();
+		}
+		d->keep = 1;
+	} else {
+		if((h = CreateFile(
+			newpath,
+			fsaccess(omode & 3),
+			FILE_SHARE_READ | FILE_SHARE_WRITE,
+			NULL,
+			(mode & OEXCL) ? CREATE_NEW : CREATE_ALWAYS,
+			FILE_ATTRIBUTE_NORMAL,
+			0)) == INVALID_HANDLE_VALUE)
+			oserror();
+		if(waserror()){
+			CloseHandle(h);
+			nexterror();
+		}
+	}
+	c->qid = wfdtoqid(newpath, nil);
+	uif->dir = d;
+	uif->fh = h;
+	poperror();
+	free(uif->path);
+	uif->path = newpath;
+	uif->offset = 0;
+	poperror();
+
+	c->offset = 0;
+	c->mode = omode;
+	c->flag |= COPEN;
+	return c;
+}
+
+
+static void
+fsclose(Chan *c)
+{
+	Ufsinfo *uif;
+
+	uif = c->aux;
+	if(c->flag & COPEN) {
+		if(uif->dir != nil){
+			if(uif->dir->drivebuf != nil){
+				free(uif->dir->drivebuf);
+				uif->dir->drivebuf = nil;
+			} else {
+				FindClose(uif->dir->handle);
+				free(uif->dir);
+			}
+		} else {
+			CloseHandle(uif->fh);
+		}
+		c->flag &= ~COPEN;
+		if(c->flag & CRCLOSE) {
+			devtab[c->type]->remove(c);
+			return;
+		}
+	}
+	free(uif->path);
+	free(uif);
+}
+
+static long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+	HANDLE fh;
+	DWORD 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();
+	}
+	fh = uif->fh;
+	if(uif->offset != offset) {
+		LONG high;
+		high = offset>>32;
+		offset = SetFilePointer(fh, (LONG)(offset & 0xFFFFFFFF), &high, FILE_BEGIN);
+		offset |= (vlong)high<<32;
+		uif->offset = offset;
+	}
+	r = 0;
+	if(!ReadFile(fh, va, (DWORD)n, &r, NULL))
+		oserror();
+	n = r;
+	uif->offset += n;
+	qunlock(&uif->oq);
+	poperror();
+	return n;
+}
+
+static long
+fswrite(Chan *c, void *va, long n, vlong offset)
+{
+	HANDLE fh;
+	DWORD w;
+	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();
+	}
+	fh = uif->fh;
+	if(uif->offset != offset) {
+		LONG high;
+		high = offset>>32;
+		offset = SetFilePointer(fh, (LONG)(offset & 0xFFFFFFFF), &high, FILE_BEGIN);
+		offset |= (vlong)high<<32;
+		uif->offset = offset;
+	}
+	w = 0;
+	if(!WriteFile(fh, va, (DWORD)n, &w, NULL))
+		oserror();
+	n = w;
+	uif->offset += n;
+	qunlock(&uif->oq);
+	poperror();
+	return n;
+}
+
+static void
+fsremove(Chan *c)
+{
+	Ufsinfo *uif;
+
+	if(waserror()){
+		fsclose(c);
+		nexterror();
+	}
+	uif = c->aux;
+	if(c->qid.type & QTDIR){
+		if(!RemoveDirectory(uif->path))
+			oserror();
+	} else {
+		if(!DeleteFile(uif->path))
+			oserror();
+	}
+	poperror();
+	fsclose(c);
+}
+
+static int
+fswstat(Chan *c, uchar *buf, int n)
+{
+	char strs[MAX_PATH*3];
+	Ufsinfo *uif;
+	Dir d;
+
+	if (convM2D(buf, n, &d, strs) != n)
+		error(Ebadstat);
+	uif = c->aux;
+	if(pathtype(uif->path) != TPATH_FILE)
+		error(Ebadstat);
+	if(~d.atime != 0 || ~d.mtime != 0){
+		FILETIME ta, *pta = NULL;
+		FILETIME tm, *ptm = NULL;
+		HANDLE h;
+		if(~d.atime != 0){
+			ta = filetime(d.atime);
+			pta = &ta;
+		}
+		if(~d.mtime != 0){
+			tm = filetime(d.mtime);
+			ptm = &tm;
+		}
+		if((h = CreateFile(uif->path,
+			FILE_WRITE_ATTRIBUTES,
+			FILE_SHARE_READ | FILE_SHARE_WRITE,
+			NULL,
+			OPEN_EXISTING,
+			FILE_ATTRIBUTE_NORMAL,
+			0)) != INVALID_HANDLE_VALUE){
+			SetFileTime(h, NULL, pta, ptm);
+			CloseHandle(h);
+		}
+	}
+	/* change name */
+	if(d.name[0]){
+		wchar_t *base, *newpath;
+		int l;
+
+		base = wstrdup(uif->path);
+		if(waserror()){
+			free(base);
+			nexterror();
+		}
+		/* replace last path-element with d.name */
+		l = wcslen(base)-1;
+		if(l <= 0)
+			error(Ebadstat);
+		for(;l>0; l--){
+			if(base[l-1]=='\\')
+				break;
+		}
+		if(l <= 0)
+			error(Ebadstat);
+		base[l] = 0;
+		newpath = catpath(base, d.name, nil);
+		free(base);
+		poperror();
+		if(waserror()){
+			free(newpath);
+			nexterror();
+		}
+		if(wcscmp(uif->path, newpath)!=0){
+			if(!MoveFile(uif->path, newpath))
+				oserror();
+		}
+		free(uif->path);
+		uif->path = newpath;
+		poperror();
+	}
+
+	/* fixme: change attributes */
+	c->qid = wfdtoqid(uif->path, nil);
+	return n;	
+}
+
+static wchar_t*
+catpath(wchar_t *base, char *cext, wchar_t *wext)
+{
+	wchar_t *path;
+	long n, m;
+
+	n = wcslen(base);
+	m = wext!=nil ? wcslen(wext) : strlen(cext)*4;
+	path = malloc((n+m+2)*sizeof(wchar_t));
+	memmove(path, base, n*sizeof(wchar_t));
+	if(n > 0 && path[n-1] != '\\') path[n++] = '\\';
+	if(wext != nil)
+		memmove(path+n, wext, m*sizeof(wchar_t));
+	else
+		m = MultiByteToWideChar(CP_UTF8,0,cext,-1,path+n,m);
+	path[n+m] = 0;
+	return 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 ulong
+fsdirread(Chan *c, uchar *va, int count, vlong offset)
+{
+	int i;
+	ulong t;
+	Dir d;
+	long n;
+	Ufsinfo *uif;
+	char de[MAX_PATH*3];
+	wchar_t *p;
+
+	i = 0;
+	uif = c->aux;
+	errno = 0;
+
+	t = pathtype(uif->path);
+	if(uif->offset != offset) {
+		if(offset != 0)
+			error("bad offset in fsdirread");
+		uif->offset = offset;  /* sync offset */
+		switch(t){
+		case TPATH_ROOT:
+			uif->dir->drivep = uif->dir->drivebuf;
+			break;
+		case TPATH_VOLUME:
+		case TPATH_FILE:
+			FindClose(uif->dir->handle);
+			p = catpath(uif->path, "*.*", nil);
+			uif->dir->handle = FindFirstFile(p, &uif->dir->wfd);
+			free(p);
+			if(uif->dir->handle == INVALID_HANDLE_VALUE)
+				oserror();
+			break;
+		}
+		uif->dir->keep = 1;
+	}
+
+	while(i+BIT16SZ < count) {
+		if(!uif->dir->keep) {
+			switch(t){
+			case TPATH_ROOT:
+				uif->dir->drivep += 4;
+				if(*uif->dir->drivep == 0)
+					goto out;
+				break;
+			case TPATH_VOLUME:
+			case TPATH_FILE:
+				if(!FindNextFile(uif->dir->handle, &uif->dir->wfd))
+					goto out;
+				break;
+			}
+		} else {
+			uif->dir->keep = 0;
+		}
+		if(t == TPATH_ROOT){
+			uif->dir->drivep[2] = 0;
+			WideCharToMultiByte(CP_UTF8,0,uif->dir->drivep,-1,de,sizeof(de),0,0);
+		} else {
+			WideCharToMultiByte(CP_UTF8,0,uif->dir->wfd.cFileName,-1,de,sizeof(de),0,0);
+		}
+		if(de[0]==0 || isdots(de))
+			continue;
+		d.name = de;
+		if(t == TPATH_ROOT){
+			p = catpath(uif->path, nil, uif->dir->drivep);
+			wfdtodir(p, &d, nil);
+		} else {
+			p = catpath(uif->path, nil, uif->dir->wfd.cFileName);
+			wfdtodir(p, &d, &uif->dir->wfd);
+		}
+		free(p);
+		d.type = 'U';
+		d.dev = c->dev;
+		n = convD2M(&d, (uchar*)va+i, count-i);
+		if(n == BIT16SZ){
+			uif->dir->keep = 1;
+			break;
+		}
+		i += n;
+	}
+out:
+	uif->offset += i;
+	return i;
+}
+
+static ulong
+fsaccess(int m)
+{
+	ulong a;
+	a = 0;
+	switch(m & 3){
+	default:
+		error(Eperm);
+		break;
+	case OREAD:
+		a = GENERIC_READ;
+		break;
+	case OWRITE:
+		a = GENERIC_WRITE;
+		break;
+	case ORDWR:
+		a = GENERIC_READ | GENERIC_WRITE;
+		break;
+	}
+	return a;
+}
+
+
+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,259 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "ip.h"
+
+#include "devip.h"
+
+#undef listen
+#undef accept
+#undef bind
+
+static int
+family(unsigned char *addr)
+{
+	if(isv4(addr))
+		return AF_INET;
+	return AF_INET6;
+}
+
+static int
+addrlen(struct sockaddr_storage *ss)
+{
+	switch(ss->ss_family){
+	case AF_INET:
+		return sizeof(struct sockaddr_in);
+	case AF_INET6:
+		return sizeof(struct sockaddr_in6);
+	}
+	return 0;
+}
+
+void
+osipinit(void)
+{
+	char buf[1024];
+	gethostname(buf, sizeof(buf));
+	kstrdup(&sysname, buf);
+
+}
+
+int
+so_socket(int type, unsigned char *addr)
+{
+	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(family(addr), 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 char *raddr, unsigned short rport)
+{
+	struct sockaddr_storage ss;
+
+	memset(&ss, 0, sizeof(ss));
+
+	ss.ss_family = family(raddr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, rport);
+		v6tov4((unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr, raddr);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, rport);
+		memcpy(&((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, raddr, sizeof(struct in6_addr));
+		break;
+	}
+
+	if(connect(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+void
+so_getsockname(int fd, unsigned char *laddr, unsigned short *lport)
+{
+	socklen_t len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	if(getsockname(fd, (struct sockaddr*)&ss, &len) < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(laddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*lport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(laddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*lport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+}
+
+void
+so_listen(int fd)
+{
+	if(listen(fd, 5) < 0)
+		oserror();
+}
+
+int
+so_accept(int fd, unsigned char *raddr, unsigned short *rport)
+{
+	int nfd;
+	socklen_t len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	nfd = accept(fd, (struct sockaddr*)&ss, &len);
+	if(nfd < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(raddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*rport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(raddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*rport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port, unsigned char *addr)
+{
+	int i, one;
+	struct sockaddr_storage ss;
+
+	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(&ss, 0, sizeof(ss));
+			ss.ss_family = family(addr);
+
+			switch(ss.ss_family){
+			case AF_INET:
+				((struct sockaddr_in*)&ss)->sin_port = i;
+				break;
+			case AF_INET6:
+				((struct sockaddr_in6*)&ss)->sin6_port = i;
+				break;
+			}
+
+			if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) >= 0)	
+				return;
+		}
+		oserror();
+	}
+
+	memset(&ss, 0, sizeof(ss));
+	ss.ss_family = family(addr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, port);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, port);
+		break;
+	}
+
+	if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+int
+so_gethostbyname(char *host, char **hostv, int n)
+{
+	char buf[INET6_ADDRSTRLEN];
+	struct addrinfo *r, *p;
+	int i;
+
+	r = NULL;
+	if(getaddrinfo(host, NULL, NULL, &r))
+		return 0;
+	for(i = 0, p = r; i < n && p != NULL; p = p->ai_next){
+		switch (p->ai_family) {
+		default:
+			continue;
+		case AF_INET:
+			inet_ntop(AF_INET, &((struct sockaddr_in*)p->ai_addr)->sin_addr, buf, sizeof(buf));
+			break;
+		case AF_INET6:
+			inet_ntop(AF_INET6, &((struct sockaddr_in6*)p->ai_addr)->sin6_addr, buf, sizeof(buf));
+			break;
+		}
+		hostv[i++] = strdup(buf);
+	}
+	freeaddrinfo(r);
+	return i;
+}
+
+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-win32.c
@@ -1,0 +1,265 @@
+#define  _WIN32_WINNT 0x0501
+#include <winsock2.h>
+#include <windows.h>
+#include <ws2tcpip.h>
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "ip.h"
+
+#include "devip.h"
+
+#ifdef MSVC
+#pragma comment(lib, "wsock32.lib")
+#endif
+
+#undef listen
+#undef accept
+#undef bind
+
+static int
+family(unsigned char *addr)
+{
+	if(isv4(addr))
+		return AF_INET;
+	return AF_INET6;
+}
+
+static int
+addrlen(struct sockaddr_storage *ss)
+{
+	switch(ss->ss_family){
+	case AF_INET:
+		return sizeof(struct sockaddr_in);
+	case AF_INET6:
+		return sizeof(struct sockaddr_in6);
+	}
+	return 0;
+}
+
+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, unsigned char *addr)
+{
+	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(family(addr), type, 0);
+	if(fd < 0)
+		oserror();
+
+	one = 1;
+	if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) > 0){
+		oserrstr();
+		print("setsockopt: %s\n", up->errstr);
+	}
+
+	return fd;
+}
+
+
+void
+so_connect(int fd, unsigned char *raddr, unsigned short rport)
+{
+	struct sockaddr_storage ss;
+
+	memset(&ss, 0, sizeof(ss));
+
+	ss.ss_family = family(raddr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, rport);
+		v6tov4((unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr, raddr);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, rport);
+		memcpy(&((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, raddr, sizeof(struct in6_addr));
+		break;
+	}
+
+	if(connect(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+void
+so_getsockname(int fd, unsigned char *laddr, unsigned short *lport)
+{
+	int len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	if(getsockname(fd, (struct sockaddr*)&ss, &len) < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(laddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*lport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(laddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*lport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+}
+
+void
+so_listen(int fd)
+{
+	if(listen(fd, 5) < 0)
+		oserror();
+}
+
+int
+so_accept(int fd, unsigned char *raddr, unsigned short *rport)
+{
+	int nfd;
+	int len;
+	struct sockaddr_storage ss;
+
+	len = sizeof(ss);
+	nfd = accept(fd, (struct sockaddr*)&ss, &len);
+	if(nfd < 0)
+		oserror();
+
+	switch(ss.ss_family){
+	case AF_INET:
+		v4tov6(raddr, (unsigned char*)&((struct sockaddr_in*)&ss)->sin_addr.s_addr);
+		*rport = nhgets(&((struct sockaddr_in*)&ss)->sin_port);
+		break;
+	case AF_INET6:
+		memcpy(raddr, &((struct sockaddr_in6*)&ss)->sin6_addr.s6_addr, sizeof(struct in6_addr));
+		*rport = nhgets(&((struct sockaddr_in6*)&ss)->sin6_port);
+		break;
+	default:
+		error("not AF_INET or AF_INET6");
+	}
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned short port, unsigned char *addr)
+{
+	int i, one;
+	struct sockaddr_storage ss;
+
+	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(&ss, 0, sizeof(ss));
+			ss.ss_family = family(addr);
+
+			switch(ss.ss_family){
+			case AF_INET:
+				((struct sockaddr_in*)&ss)->sin_port = i;
+				break;
+			case AF_INET6:
+				((struct sockaddr_in6*)&ss)->sin6_port = i;
+				break;
+			}
+
+			if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) >= 0)	
+				return;
+		}
+		oserror();
+	}
+
+	memset(&ss, 0, sizeof(ss));
+	ss.ss_family = family(addr);
+
+	switch(ss.ss_family){
+	case AF_INET:
+		hnputs(&((struct sockaddr_in*)&ss)->sin_port, port);
+		break;
+	case AF_INET6:
+		hnputs(&((struct sockaddr_in6*)&ss)->sin6_port, port);
+		break;
+	}
+
+	if(bind(fd, (struct sockaddr*)&ss, addrlen(&ss)) < 0)
+		oserror();
+}
+
+int
+so_gethostbyname(char *host, char **hostv, int n)
+{
+	char buf[INET6_ADDRSTRLEN];
+	struct addrinfo *r, *p;
+	DWORD l;
+	int i;
+
+	r = NULL;
+	if(getaddrinfo(host, NULL, NULL, &r))
+		return 0;
+	for(i = 0, p = r; i < n && p != NULL; p = p->ai_next){
+		switch (p->ai_family) {
+		default:
+			continue;
+		case AF_INET:
+		case AF_INET6:
+			l = sizeof(buf);
+			WSAAddressToStringA(p->ai_addr, p->ai_addrlen, NULL, buf, &l);
+			break;
+		}
+		hostv[i++] = strdup(buf);
+	}
+	freeaddrinfo(r);
+	return i;
+}
+
+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,870 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "ip.h"
+
+#include "devip.h"
+
+void	csclose(Chan*);
+long	csread(Chan*, void*, long, vlong);
+long	cswrite(Chan*, void*, long, vlong);
+
+void osipinit(void);
+
+enum
+{
+	Qtopdir		= 1,	/* top level directory */
+	Qtopbase,
+	Qcs		= Qtopbase,
+
+	Qprotodir,		/* directory for a protocol */
+	Qprotobase,
+	Qclone		= Qprotobase,
+
+	Qconvdir,		/* directory for a conversation */
+	Qconvbase,
+	Qctl		= Qconvbase,
+	Qdata,
+	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))
+#define ipzero(x)	memset(x, 0, IPaddrlen)
+
+typedef struct Proto	Proto;
+typedef struct Conv	Conv;
+struct Conv
+{
+	int	x;
+	Ref	r;
+	int	sfd;
+	int	perm;
+	char	owner[KNAMELEN];
+	char*	state;
+	uchar	laddr[IPaddrlen];
+	ushort	lport;
+	uchar	raddr[IPaddrlen];
+	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];
+
+static	Conv*	protoclone(Proto*, char*, int);
+static	void	setladdr(Conv*);
+
+static char	network[] = "network";
+
+static int
+ip3gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+	char *p;
+
+	cv = proto[PROTO(c->qid)].conv[CONV(c->qid)];
+	mkqid(&q, QID(PROTO(c->qid), CONV(c->qid), i), 0, QTFILE);
+
+	switch(i) {
+	default:
+		return -1;
+	case Qctl:
+		devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qdata:
+		devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qlisten:
+		devdir(c, q, "listen", 0, cv->owner, cv->perm, dp);
+		return 1;
+	case Qlocal:
+		p = "local";
+		break;
+	case Qremote:
+		p = "remote";
+		break;
+	case Qstatus:
+		p = "status";
+		break;
+	}
+	devdir(c, q, p, 0, cv->owner, 0444, dp);
+	return 1;
+}
+
+static int
+ip2gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+
+	switch(i) {
+	case Qclone:
+		mkqid(&q, QID(PROTO(c->qid), 0, Qclone), 0, QTFILE);
+		devdir(c, q, "clone", 0, network, 0666, dp);
+		return 1;
+	}
+	return -1;
+}
+
+static int
+ip1gen(Chan *c, int i, Dir *dp)
+{
+	Qid q;
+	char *p;
+	int prot;
+	int len = 0;
+
+	prot = 0666;
+	mkqid(&q, QID(0, 0, i), 0, QTFILE);
+	switch(i) {
+	default:
+		return -1;
+	case Qcs:
+		p = "cs";
+		prot = 0664;
+		break;
+	}
+	devdir(c, q, p, len, network, prot, dp);
+	return 1;
+}
+
+static int
+ipgen(Chan *c, char *nname, Dirtab *d, int nd, int s, Dir *dp)
+{
+	Qid q;
+	Conv *cv;
+
+	switch(TYPE(c->qid)) {
+	case Qtopdir:
+		if(s == DEVDOTDOT){
+			mkqid(&q, QID(0, 0, Qtopdir), 0, QTDIR);
+			snprint(up->genbuf, sizeof up->genbuf, "#I%lud", c->dev);
+			devdir(c, q, up->genbuf, 0, network, 0555, dp);
+			return 1;
+		}
+		if(s < np) {
+			mkqid(&q, QID(s, 0, Qprotodir), 0, QTDIR);
+			devdir(c, q, proto[s].name, 0, network, 0555, dp);
+			return 1;
+		}
+		s -= np;
+		return ip1gen(c, s+Qtopbase, dp);
+	case Qcs:
+		return ip1gen(c, TYPE(c->qid), dp);
+	case Qprotodir:
+		if(s == DEVDOTDOT){
+			mkqid(&q, QID(0, 0, Qtopdir), 0, QTDIR);
+			snprint(up->genbuf, sizeof up->genbuf, "#I%lud", c->dev);
+			devdir(c, q, up->genbuf, 0, network, 0555, dp);
+			return 1;
+		}
+		if(s < proto[PROTO(c->qid)].nc) {
+			cv = proto[PROTO(c->qid)].conv[s];
+			snprint(up->genbuf, sizeof up->genbuf, "%d", s);
+			mkqid(&q, QID(PROTO(c->qid), s, Qconvdir), 0, QTDIR);
+			devdir(c, q, up->genbuf, 0, cv->owner, 0555, dp);
+			return 1;
+		}
+		s -= proto[PROTO(c->qid)].nc;
+		return ip2gen(c, s+Qprotobase, dp);
+	case Qclone:
+		return ip2gen(c, TYPE(c->qid), dp);
+	case Qconvdir:
+		if(s == DEVDOTDOT){
+			s = PROTO(c->qid);
+			mkqid(&q, QID(s, 0, Qprotodir), 0, QTDIR);
+			devdir(c, q, proto[s].name, 0, network, 0555, dp);
+			return 1;
+		}
+		return ip3gen(c, s+Qconvbase, dp);
+	case Qctl:
+	case Qdata:
+	case Qlisten:
+	case Qlocal:
+	case Qremote:
+	case Qstatus:
+		return ip3gen(c, TYPE(c->qid), dp);
+	}
+	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;
+	uchar raddr[IPaddrlen];
+	ushort rport;
+	int perm, sfd;
+	Conv *cv, *lcv;
+
+	omode &= 3;
+	perm = 0;
+	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 Qclone:
+		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);
+		}
+		ipmove(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";
+		ipzero(cc->laddr);
+		ipzero(cc->raddr);
+		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[IPaddrlen];
+	char buf[128], *p;
+
+/*print("ipread %s %lux\n", chanpath(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)];
+		ipmove(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)];
+		ipmove(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;
+
+	if(c->sfd == -1)
+		c->sfd = so_socket(c->p->stype, c->laddr);
+
+	so_bind(c->sfd, c->restricted, c->lport, c->laddr);
+}
+
+static void
+setladdrport(Conv *c, char *str)
+{
+	char *p;
+	uchar addr[IPaddrlen];
+
+	p = strchr(str, '!');
+	if(p == 0) {
+		p = str;
+		ipzero(c->laddr);
+	}
+	else {
+		*p++ = 0;
+		parseip(addr, str);
+		ipmove(c->laddr, addr);
+	}
+	if(*p == '*')
+		c->lport = 0;
+	else
+		c->lport = atoi(p);
+
+	setlport(c);
+}
+
+static char*
+setraddrport(Conv *c, char *str)
+{
+	char *p;
+	uchar addr[IPaddrlen];
+
+	p = strchr(str, '!');
+	if(p == 0)
+		return "malformed address";
+	*p++ = 0;
+	parseip(addr, str);
+	ipmove(c->raddr, 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;
+			}
+			if(c->sfd == -1)
+				c->sfd = so_socket(c->p->stype, c->raddr);
+			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;
+	ipzero(c->laddr);
+	ipzero(c->raddr);
+	c->lport = 0;
+	c->rport = 0;
+	c->sfd = nfd;
+
+	unlock(&c->r.lk);
+	unlock(&p->l);
+	poperror();
+	return c;
+}
+
+void
+csclose(Chan *c)
+{
+	free(c->aux);
+	c->aux = nil;
+}
+
+long
+csread(Chan *c, void *a, long n, vlong offset)
+{
+	char *s, *e, *p;
+
+	s = c->aux;
+	if(s == nil)
+		return 0;
+	e = strchr(s, 0);
+	s += offset;
+	if(s >= e)
+		return 0;
+	p = strchr(s, '\n');
+	if(p != nil)
+		p++;
+	else
+		p = e;
+	if(p - s < n)
+		n = p - s;
+	memmove(a, s, n);
+	return n;
+}
+
+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,
+	"rcpu", 17019,
+	"t9fs", 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;
+}
+
+long
+cswrite(Chan *c, void *a, long n, vlong offset)
+{
+	char *f[4], *ips[8];
+	char *s, *ns;
+	uchar ip[IPaddrlen];
+	int i, nf, port, nips;
+
+	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");
+	if(strcmp(f[0], "net") == 0)
+		f[0] = "tcp";
+	if(strcmp(f[1], "*") == 0){
+		ns = smprint("/net/%s/clone %d", f[0], port);
+	} else {
+		if(parseip(ip, f[1]) != -1){
+			ips[0] = smprint("%I", ip);
+			nips = 1;
+		} else {
+			nips = so_gethostbyname(f[1], ips, nelem(ips));
+			if(nips <= 0)
+				error("no translation for host found");
+		}
+		ns = smprint("/net/%s/clone %s!%d", f[0], ips[0], port);
+		free(ips[0]);
+		for(i=1; i<nips; i++){
+			ips[0] = smprint("%s\n/net/%s/clone %s!%d", ns, f[0], ips[i], port);
+			free(ips[i]);
+			free(ns);
+			ns = ips[0];
+		}
+	}
+	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,16 @@
+enum
+{
+	S_TCP,
+	S_UDP
+};
+
+int		so_socket(int, unsigned char*);
+void		so_connect(int, unsigned char*, unsigned short);
+void		so_getsockname(int, unsigned char*, unsigned short*);
+void		so_bind(int, int, unsigned short, unsigned char*);
+void		so_listen(int);
+int		so_send(int, void*, int, int);
+int		so_recv(int, void*, int, int);
+int		so_accept(int, unsigned char*, unsigned short*);
+int		so_getservbyname(char*, char*, char*);
+int		so_gethostbyname(char*, char**, int);
--- /dev/null
+++ b/kern/devkbd.c
@@ -1,0 +1,141 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+static Queue*	keyq;
+static int kbdinuse;
+
+void
+kbdkey(Rune r, int down)
+{
+	char buf[2+UTFmax];
+
+	if(r == 0)
+		return;
+
+	if(!kbdinuse || keyq == nil){
+		if(down)
+			kbdputc(kbdq, r);	/* /dev/cons */
+		return;
+	}
+
+	memset(buf, 0, sizeof buf);
+	buf[0] = down ? 'r' : 'R';
+	qproduce(keyq, buf, 2+runetochar(buf+1, &r));
+}
+
+enum{
+	Qdir,
+	Qkbd,
+};
+
+static Dirtab kbddir[]={
+	".",	{Qdir, 0, QTDIR},	0,		DMDIR|0555,
+	"kbd",		{Qkbd},		0,		0444,
+};
+
+static void
+kbdinit(void)
+{
+	keyq = qopen(4*1024, Qcoalesce, 0, 0);
+	if(keyq == nil)
+		panic("kbdinit");
+	qnoblock(keyq, 1);
+}
+
+static Chan*
+kbdattach(char *spec)
+{
+	return devattach('b', spec);
+}
+
+static Walkqid*
+kbdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name,nname, kbddir, nelem(kbddir), devgen);
+}
+
+static int
+kbdstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, kbddir, nelem(kbddir), devgen);
+}
+
+static Chan*
+kbdopen(Chan *c, int omode)
+{
+	c = devopen(c, omode, kbddir, nelem(kbddir), devgen);
+	switch((ulong)c->qid.path){
+	case Qkbd:
+		if(tas(&kbdinuse) != 0){
+			c->flag &= ~COPEN;
+			error(Einuse);
+		}
+		break;
+	}
+	return c;
+}
+
+static void
+kbdclose(Chan *c)
+{
+	switch((ulong)c->qid.path){
+	case Qkbd:
+		if(c->flag&COPEN)
+			kbdinuse = 0;
+		break;
+	}
+}
+
+static long
+kbdread(Chan *c, void *buf, long n, vlong off)
+{
+	USED(off);
+
+	if(n <= 0)
+		return n;
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, buf, n, kbddir, nelem(kbddir), devgen);
+	case Qkbd:
+		return qread(keyq, buf, n);
+	default:
+		print("kbdread 0x%llux\n", c->qid.path);
+		error(Egreg);
+	}
+	return -1;		/* never reached */
+}
+
+static long
+kbdwrite(Chan *c, void *va, long n, vlong off)
+{
+	USED(c);
+	USED(va);
+	USED(n);
+	USED(off);
+	error(Eperm);
+	return -1;		/* never reached */
+}
+
+Dev kbddevtab = {
+	'b',
+	"kbd",
+
+	devreset,
+	kbdinit,
+	devshutdown,
+	kbdattach,
+	kbdwalk,
+	kbdstat,
+	kbdopen,
+	devcreate,
+	kbdclose,
+	kbdread,
+	devbread,
+	kbdwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/kern/devlfd-posix.c
@@ -1,0 +1,121 @@
+#include	"u.h"
+#include 	<errno.h>
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#undef read
+#undef write
+
+Chan*
+lfdchan(void *fd)
+{
+	Chan *c;
+	
+	c = newchan();
+	c->type = devno('L', 0);
+	c->aux = fd;
+	c->path = newpath("fd");
+	c->mode = ORDWR;
+	c->qid.type = 0;
+	c->qid.path = 0;
+	c->qid.vers = 0;
+	c->dev = 0;
+	c->offset = 0;
+	return c;
+}
+
+int
+lfdfd(int fd)
+{
+	return newfd(lfdchan((void*)(uintptr)fd));
+}
+
+static Chan*
+lfdattach(char *x)
+{
+	USED(x);
+	
+	error(Egreg);
+	return nil;
+}
+
+static Walkqid*
+lfdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	USED(c);
+	USED(nc);
+	USED(name);
+	USED(nname);
+	error(Egreg);
+	return nil;
+}
+
+static int
+lfdstat(Chan *c, uchar *dp, int n)
+{
+	USED(c);
+	USED(dp);
+	USED(n);
+	error(Egreg);
+	return -1;
+}
+
+static Chan*
+lfdopen(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+	error(Egreg);
+	return nil;
+}
+
+static void
+lfdclose(Chan *c)
+{
+	close((int)(uintptr)c->aux);
+}
+
+static long
+lfdread(Chan *c, void *buf, long n, vlong off)
+{
+	USED(off);	/* can't pread on pipes */
+
+	n = read((int)(uintptr)c->aux, buf, n);
+	if(n < 0)
+		oserror();
+	return n;
+}
+
+static long
+lfdwrite(Chan *c, void *buf, long n, vlong off)
+{
+	USED(off);	/* can't pread on pipes */
+
+	n = write((int)(uintptr)c->aux, buf, n);
+	if(n < 0)
+		oserror();
+	return n;
+}
+
+Dev lfddevtab = {
+	'L',
+	"lfd",
+	
+	devreset,
+	devinit,
+	devshutdown,
+	lfdattach,
+	lfdwalk,
+	lfdstat,
+	lfdopen,
+	devcreate,
+	lfdclose,
+	lfdread,
+	devbread,
+	lfdwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/kern/devlfd-win32.c
@@ -1,0 +1,133 @@
+#include	<windows.h>
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+Chan*
+lfdchan(void *fd)
+{
+	Chan *c;
+	
+	c = newchan();
+	c->type = devno('L', 0);
+	c->aux = fd;
+	c->path = newpath("fd");
+	c->mode = ORDWR;
+	c->qid.type = 0;
+	c->qid.path = 0;
+	c->qid.vers = 0;
+	c->dev = 0;
+	c->offset = 0;
+	return c;
+}
+
+/*
+ * only good for stdin/stdout/stderr
+ */
+int
+lfdfd(int fd)
+{
+	HANDLE h;
+
+	switch(fd){
+	case 0: h = GetStdHandle(STD_INPUT_HANDLE); break;
+	case 1: h = GetStdHandle(STD_OUTPUT_HANDLE); break;
+	case 2: h = GetStdHandle(STD_ERROR_HANDLE); break;
+	default:
+		return -1;
+	}
+	if(h == INVALID_HANDLE_VALUE)
+		return -1;
+	return newfd(lfdchan((void*)h));
+}
+
+static Chan*
+lfdattach(char *x)
+{
+	USED(x);
+	
+	error(Egreg);
+	return nil;
+}
+
+static Walkqid*
+lfdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	USED(c);
+	USED(nc);
+	USED(name);
+	USED(nname);
+	
+	error(Egreg);
+	return nil;
+}
+
+static int
+lfdstat(Chan *c, uchar *dp, int n)
+{
+	USED(c);
+	USED(dp);
+	USED(n);
+	error(Egreg);
+	return -1;
+}
+
+static Chan*
+lfdopen(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+	error(Egreg);
+	return nil;
+}
+
+static void
+lfdclose(Chan *c)
+{
+	CloseHandle((HANDLE)c->aux);
+}
+
+static long
+lfdread(Chan *c, void *buf, long n, vlong off)
+{
+	DWORD r;
+
+	USED(off);	/* can't pread on pipes */
+	if(!ReadFile((HANDLE)c->aux, buf, (DWORD)n, &r, NULL))
+		oserror();
+	return r;
+}
+
+static long
+lfdwrite(Chan *c, void *buf, long n, vlong off)
+{
+	DWORD r;
+
+	USED(off);	/* can't pread on pipes */
+	if(!WriteFile((HANDLE)c->aux, buf, (DWORD)n, &r, NULL))
+		oserror();
+	return r;
+}
+
+Dev lfddevtab = {
+	'L',
+	"lfd",
+	
+	devreset,
+	devinit,
+	devshutdown,
+	lfdattach,
+	lfdwalk,
+	lfdstat,
+	lfdopen,
+	devcreate,
+	lfdclose,
+	lfdread,
+	devbread,
+	lfdwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/kern/devmnt.c
@@ -1,0 +1,1138 @@
+#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+32768)
+#define MAXRPC0 (IOHDRSZ+8192)	/* maximum size of Tversion/Rversion pair */
+
+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*	z;		/* Place to hang out */
+	Block*	b;		/* reply blocks */
+	Mntrpc*	flushed;	/* message this one flushes */
+	char	done;		/* Rpc completed */
+};
+
+enum
+{
+	TAGSHIFT = 5,
+	TAGMASK = (1<<TAGSHIFT)-1,
+	NMASK = (64*1024)>>TAGSHIFT,
+};
+
+static struct Mntalloc
+{
+	Lock	lk;
+	Mnt*	list;		/* Mount devices in use */
+	Mnt*	mntfree;	/* Free list */
+	Mntrpc*	rpcfree;
+	ulong	nrpcfree;
+	ulong	nrpcused;
+	ulong	id;
+	u32int	tagmask[NMASK];
+} mntalloc;
+
+static Chan*	mntchan(void);
+static Mnt*	mntchk(Chan*);
+static void	mntdirfix(uchar*, Chan*);
+static Mntrpc*	mntflushalloc(Mntrpc*);
+static Mntrpc*	mntflushfree(Mnt*, Mntrpc*);
+static void	mntfree(Mntrpc*);
+static void	mntgate(Mnt*);
+static void	mntqrm(Mnt*, Mntrpc*);
+static Mntrpc*	mntralloc(Chan*);
+static long	mntrdwr(int, Chan*, void*, long, vlong);
+static int	mntrpcread(Mnt*, Mntrpc*);
+static void	mountio(Mnt*, Mntrpc*);
+static void	mountmux(Mnt*, Mntrpc*);
+static void	mountrpc(Mnt*, Mntrpc*);
+static int	rpcattn(void*);
+
+char	Esbadstat[] = "invalid directory entry received from server";
+char	Enoversion[] = "version not established for mount channel";
+
+
+static void
+mntreset(void)
+{
+	mntalloc.id = 1;
+	mntalloc.tagmask[0] = 1;			/* don't allow 0 as a tag */
+	mntalloc.tagmask[NMASK-1] = 0x80000000;		/* 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] */
+}
+
+/*
+ * Version is not multiplexed: message sent only once per connection.
+ */
+int
+mntversion(Chan *c, char *version, int msize, int returnlen)
+{
+	Fcall f;
+	uchar *msg;
+	Mnt *m;
+	char *v;
+	Queue *q;
+	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(MAXRPC0);
+	if(msg == nil)
+		exhausted("version memory");
+	if(waserror()){
+		free(msg);
+		nexterror();
+	}
+	k = convS2M(&f, msg, MAXRPC0);
+	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 */
+	for(k = 0; k < BIT32SZ || (k < GBIT32(msg) && k < MAXRPC0); k += l){
+		l = devtab[c->type]->read(c, msg+k, MAXRPC0-k, c->offset);
+		if(l <= 0)
+			error("EOF receiving fversion reply");
+		lock(&c->ref.lk);
+		c->offset += l;
+		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");
+	k = strlen(f.version);
+	if(strncmp(f.version, v, k) != 0)
+		error("bad 9P version returned from server");
+	if(returnlen > 0 && returnlen < k)
+		error(Eshort);
+
+	v = nil;
+	kstrdup(&v, f.version);
+	q = qopen(10*MAXRPC, 0, nil, nil);
+	if(q == nil){
+		free(v);
+		exhausted("mount queues");
+	}
+
+	/* now build Mnt associated with this connection */
+	lock(&mntalloc.lk);
+	m = mntalloc.mntfree;
+	if(m != nil)
+		mntalloc.mntfree = m->list;
+	else {
+		unlock(&mntalloc.lk);
+		m = malloc(sizeof(Mnt));
+		if(m == nil) {
+			qfree(q);
+			free(v);
+			exhausted("mount devices");
+		}
+		lock(&mntalloc.lk);
+	}
+	m->list = mntalloc.list;
+	mntalloc.list = m;
+	m->version = v;
+	m->id = mntalloc.id++;
+	m->q = q;
+	m->msize = f.msize;
+	unlock(&mntalloc.lk);
+
+	if(returnlen > 0)
+		memmove(version, f.version, k);	/* length was checked above */
+
+	poperror();	/* msg */
+	free(msg);
+
+	lock(&m->lk);
+	m->queue = nil;
+	m->rip = nil;
+
+	c->flag |= CMSG;
+	c->mux = m;
+	m->c = c;
+	unlock(&m->lk);
+
+	poperror();	/* c */
+	qunlock(&c->umqlock);
+
+	return k;
+}
+
+Chan*
+mntauth(Chan *c, char *spec)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	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(c);
+	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;
+	c->iounit = m->msize-IOHDRSZ;
+
+	poperror();	/* r */
+	mntfree(r);
+
+	poperror();	/* c */
+
+	return c;
+
+}
+
+Chan*
+mntattach(Chan *c, Chan *ac, char *spec, int flags)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	if(ac != nil && ac->mchan != c)
+		error(Ebadusefd);
+
+	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(c);
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+	r->request.type = Tattach;
+	r->request.fid = c->fid;
+	if(ac == nil)
+		r->request.afid = NOFID;
+	else
+		r->request.afid = ac->fid;
+	r->request.uname = up->user;
+	r->request.aname = 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(flags&MCACHE)
+		c->flag |= CCACHE;
+	return c;
+}
+
+static Chan*
+noattach(char *s)
+{
+	USED(s);
+	error(Enoattach);
+	return nil;
+}
+
+static Chan*
+mntchan(void)
+{
+	Chan *c;
+
+	c = devattach('M', 0);
+	lock(&mntalloc.lk);
+	c->dev = mntalloc.id++;
+	unlock(&mntalloc.lk);
+
+	if(c->mchan != nil)
+		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);
+	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;
+		nc->flag |= (c->flag & CCACHE);
+		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);
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+	r->request.type = Tstat;
+	r->request.fid = c->fid;
+	mountrpc(m, r);
+
+	if(r->reply.nstat > n){
+		n = BIT16SZ;
+		PBIT16((uchar*)dp, r->reply.nstat-2);
+	}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);
+	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);
+
+	return c;
+}
+
+static Chan*
+mntopen(Chan *c, int omode)
+{
+	return mntopencreate(Topen, c, nil, omode, 0);
+}
+
+static Chan*
+mntcreate(Chan *c, char *name, int omode, ulong perm)
+{
+	return mntopencreate(Tcreate, c, name, omode, perm);
+}
+
+static void
+mntclunk(Chan *c, int t)
+{
+	Mnt *m;
+	Mntrpc *r;
+
+	m = mntchk(c);
+	r = mntralloc(c);
+	if(waserror()) {
+		mntfree(r);
+		nexterror();
+	}
+	r->request.type = t;
+	r->request.fid = c->fid;
+	mountrpc(m, r);
+	mntfree(r);
+	poperror();
+}
+
+void
+muxclose(Mnt *m)
+{
+	Mnt *f, **l;
+	Mntrpc *r;
+
+	while((r = m->queue) != nil){
+		m->queue = r->list;
+		mntfree(r);
+	}
+	m->id = 0;
+	free(m->version);
+	m->version = nil;
+	qfree(m->q);
+	m->q = nil;
+
+	lock(&mntalloc.lk);
+	l = &mntalloc.list;
+	for(f = *l; f != nil; f = f->list) {
+		if(f == m) {
+			*l = m->list;
+			break;
+		}
+		l = &f->list;
+	}
+	m->list = mntalloc.mntfree;
+	mntalloc.mntfree = m;
+	unlock(&mntalloc.lk);
+}
+
+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);
+	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 dirlen;
+
+	p = buf;
+	n = mntrdwr(Tread, c, p, n, off);
+	if(c->qid.type & QTDIR) {
+		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);
+}
+
+static long
+mntrdwr(int type, Chan *c, void *buf, long n, vlong off)
+{
+	Mnt *m;
+ 	Mntrpc *r;
+	char *uba;
+	ulong cnt, nr, nreq;
+
+	m = mntchk(c);
+	uba = buf;
+	cnt = 0;
+
+	for(;;) {
+		nreq = n;
+		if(nreq > c->iounit)
+			nreq = c->iounit;
+
+		r = mntralloc(c);
+		if(waserror()) {
+			mntfree(r);
+			nexterror();
+		}
+		r->request.type = type;
+		r->request.fid = c->fid;
+		r->request.offset = off;
+		r->request.data = uba;
+		r->request.count = nreq;
+		mountrpc(m, r);
+		nr = r->reply.count;
+		if(nr > nreq)
+			nr = nreq;
+		if(type == Tread)
+			nr = readblist(r->b, (uchar*)uba, nr, 0);
+		mntfree(r);
+		poperror();
+
+		off += nr;
+		uba += nr;
+		cnt += nr;
+		n -= nr;
+		if(nr != nreq || n == 0)
+			break;
+	}
+	return cnt;
+}
+
+static void
+mountrpc(Mnt *m, Mntrpc *r)
+{
+	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;
+		print("mnt: proc %s %lud: mismatch from %s %s rep %#p tag %d fid %d T%d R%d rp %d\n",
+			up->text, up->pid, chanpath(m->c), chanpath(r->c),
+			r, r->request.tag, r->request.fid, r->request.type,
+			r->reply.type, r->reply.tag);
+		error(Emountrpc);
+	}
+}
+
+static void
+mountio(Mnt *m, Mntrpc *r)
+{
+	Block *b;
+	int n;
+
+	while(waserror()) {
+		if(m->rip == up)
+			mntgate(m);
+		if(strcmp(up->errstr, Eintr) != 0 || waserror()){
+			r = mntflushfree(m, r);
+			switch(r->request.type){
+			case Tremove:
+			case Tclunk:
+				/* botch, abandon fid */ 
+				if(strcmp(up->errstr, Ehungup) != 0)
+					r->c->fid = 0;
+			}
+			nexterror();
+		}
+		r = mntflushalloc(r);
+		poperror();
+	}
+
+	lock(&m->lk);
+	r->z = &up->sleep;
+	r->m = m;
+	r->list = m->queue;
+	m->queue = r;
+	unlock(&m->lk);
+
+	/* Transmit a file system rpc */
+	n = sizeS2M(&r->request);
+	b = allocb(n);
+	if(waserror()){
+		freeb(b);
+		nexterror();
+	}
+	n = convS2M(&r->request, b->wp, n);
+	if(n <= 0 || n > m->msize) {
+		print("mountio: proc %s %lud: convS2M returned %d for tag %d fid %d T%d\n",
+			up->text, up->pid, n, r->request.tag, r->request.fid, r->request.type);
+		error(Emountrpc);
+	}
+	b->wp += n;
+	poperror();
+	devtab[m->c->type]->bwrite(m->c, b, 0);
+
+	/* Gate readers onto the mount point one at a time */
+	for(;;) {
+		lock(&m->lk);
+		if(m->rip == nil)
+			break;
+		unlock(&m->lk);
+		sleep(r->z, 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 || qaddlist(m->q, b) == 0)
+			return -1;
+	}
+	return 0;
+}
+
+static 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 ridiculous (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;
+}
+
+static void
+mntgate(Mnt *m)
+{
+	Mntrpc *q;
+
+	lock(&m->lk);
+	m->rip = nil;
+	for(q = m->queue; q != nil; q = q->list) {
+		if(q->done == 0)
+		if(wakeup(q->z))
+			break;
+	}
+	unlock(&m->lk);
+}
+
+static void
+mountmux(Mnt *m, Mntrpc *r)
+{
+	Mntrpc **l, *q;
+	Rendez *z;
+
+	lock(&m->lk);
+	l = &m->queue;
+	for(q = *l; q != nil; q = q->list) {
+		/* look for a reply to a message */
+		if(q->request.tag == r->reply.tag) {
+			*l = q->list;
+			if(q == r) {
+				q->done = 1;
+				unlock(&m->lk);
+				return;
+			}
+			/*
+			 * Completed someone else.
+			 * Trade pointers to receive buffer.
+			 */
+			q->reply = r->reply;
+			q->b = r->b;
+			r->b = nil;
+			z = q->z;
+			// coherence();
+			q->done = 1;
+			wakeup(z);
+			unlock(&m->lk);
+			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
+ */
+static Mntrpc*
+mntflushalloc(Mntrpc *r)
+{
+	Mntrpc *fr;
+
+	fr = mntralloc(r->c);
+	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. Return the original rpc.
+ */
+static Mntrpc*
+mntflushfree(Mnt *m, Mntrpc *r)
+{
+	Mntrpc *fr;
+
+	while(r != nil){
+		fr = r->flushed;
+		if(!r->done){
+			r->reply.type = Rflush;
+			mntqrm(m, r);
+		}
+		if(fr == nil)
+			break;
+		mntfree(r);
+		r = fr;
+	}
+	return r;
+}
+
+static int
+alloctag(void)
+{
+	int i, j;
+	u32int v;
+
+	for(i = 0; i < NMASK; i++){
+		v = mntalloc.tagmask[i];
+		if(v == -1)
+			continue;
+		for(j = 0; (v & 1) != 0; j++)
+			v >>= 1;
+		mntalloc.tagmask[i] |= 1<<j;
+		return i<<TAGSHIFT | j;
+	}
+	panic("no friggin tags left");
+	return NOTAG;
+}
+
+static void
+freetag(int t)
+{
+	mntalloc.tagmask[t>>TAGSHIFT] &= ~(1<<(t&TAGMASK));
+}
+
+static Mntrpc*
+mntralloc(Chan *c)
+{
+	Mntrpc *new;
+
+	if(mntalloc.nrpcfree == 0) {
+	Alloc:
+		new = malloc(sizeof(Mntrpc));
+		if(new == nil)
+			exhausted("mount rpc header");
+		lock(&mntalloc.lk);
+		new->request.tag = alloctag();
+	} else {
+		lock(&mntalloc.lk);
+		new = mntalloc.rpcfree;
+		if(new == nil) {
+			unlock(&mntalloc.lk);
+			goto Alloc;
+		}
+		mntalloc.rpcfree = new->list;
+		mntalloc.nrpcfree--;
+	}
+	mntalloc.nrpcused++;
+	unlock(&mntalloc.lk);
+	new->c = c;
+	new->done = 0;
+	new->flushed = nil;
+	new->b = nil;
+	return new;
+}
+
+static void
+mntfree(Mntrpc *r)
+{
+	freeblist(r->b);
+	lock(&mntalloc.lk);
+	mntalloc.nrpcused--;
+	if(mntalloc.nrpcfree < 32) {
+		r->list = mntalloc.rpcfree;
+		mntalloc.rpcfree = r;
+		mntalloc.nrpcfree++;
+		unlock(&mntalloc.lk);
+		return;
+	}
+	freetag(r->request.tag);
+	unlock(&mntalloc.lk);
+	free(r);
+}
+
+static void
+mntqrm(Mnt *m, Mntrpc *r)
+{
+	Mntrpc **l, *f;
+
+	lock(&m->lk);
+	r->done = 1;
+
+	l = &m->queue;
+	for(f = *l; f != nil; f = f->list) {
+		if(f == r) {
+			*l = r->list;
+			break;
+		}
+		l = &f->list;
+	}
+	unlock(&m->lk);
+}
+
+static 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", chanpath(c));
+
+	m = c->mchan->mux;
+	if(m == nil)
+		print("mntchk 2: nil mux c %s c->mchan %s \n", chanpath(c), chanpath(c->mchan));
+
+	/*
+	 * Was it closed and reused (was error(Eshutdown); now, it cannot 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.
+ */
+static 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);
+}
+
+static int
+rpcattn(void *v)
+{
+	Mntrpc *r;
+
+	r = v;
+	return r->done || r->m->rip == nil;
+}
+
+Dev mntdevtab = {
+	'M',
+	"mnt",
+
+	mntreset,
+	devinit,
+	devshutdown,
+	noattach,
+	mntwalk,
+	mntstat,
+	mntopen,
+	mntcreate,
+	mntclose,
+	mntread,
+	devbread,
+	mntwrite,
+	devbwrite,
+	mntremove,
+	mntwstat,
+};
--- /dev/null
+++ b/kern/devmouse.c
@@ -1,0 +1,391 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	"draw.h"
+#include	"memdraw.h"
+#include	"screen.h"
+
+Mouseinfo	mouse;
+Cursorinfo	cursor;
+Cursorinfo      arrow = {
+	.lk = 0,
+	.offset = { -1, -1 },
+	.clr = { 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
+	  0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
+	  0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
+	  0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
+	},
+	.set = { 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
+	  0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
+	  0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
+	  0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
+	},
+};
+
+static uchar	buttonmap[8] = {0,1,2,3,4,5,6,7,};
+static int	mouseswap;
+static int	scrollswap;
+
+static int	mousechanged(void*);
+
+enum {
+	CMbuttonmap,
+	CMscrollswap,
+	CMswap,
+};
+
+static Cmdtab mousectlmsg[] = 
+{
+	CMbuttonmap,	"buttonmap",	0,
+	CMscrollswap,	"scrollswap",	0,
+	CMswap,		"swap",		1,
+};
+
+enum {
+	Qdir,
+	Qcursor,
+	Qmouse,
+	Qmousectl
+};
+
+Dirtab mousedir[]={
+	".",		{Qdir, 0, QTDIR},	0,	DMDIR|0555,	
+	"cursor",	{Qcursor},		0,	0666,
+	"mouse",	{Qmouse},		0,	0666,
+	"mousectl",	{Qmousectl},		0,	0222,
+};
+
+#define	NMOUSE	(sizeof(mousedir)/sizeof(Dirtab))
+
+static void
+mouseinit(void)
+{
+	cursor = arrow;
+}
+
+static Chan*
+mouseattach(char *spec)
+{
+	setcursor();
+	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:
+		if(tas(&mouse.open) != 0)
+			error(Einuse);
+		mouse.resize = 0;
+		mouse.lastcounter = mouse.state.counter;
+		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:
+		mouse.open = 0;
+		cursor = arrow;
+		setcursor();
+	}
+}
+
+long
+mouseread(Chan *c, void *va, long n, vlong offset)
+{
+	char buf[1+4*12+1];
+	Mousestate m;
+	uchar *p;
+	int b;
+	
+	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(&mouse.lk);
+		if(mouse.ri != mouse.wi)
+			m = mouse.queue[mouse.ri++ % nelem(mouse.queue)];
+		else
+			m = mouse.state;
+		unlock(&mouse.lk);
+
+		b = buttonmap[m.buttons&7];
+		/* put buttons 4 and 5 back in */
+		b |= m.buttons & (3<<3);
+		if (scrollswap) {
+			if (b == 8)
+				b = 16;
+			else if (b == 16)
+				b = 8;
+		}
+		sprint(buf, "m%11d %11d %11d %11ld ",
+			m.xy.x, m.xy.y, b, m.msec);
+
+		mouse.lastcounter = m.counter;
+		if(mouse.resize){
+			mouse.resize = 0;
+			buf[0] = 'r';
+		}
+
+		if(n > 1+4*12)
+			n = 1+4*12;
+		memmove(va, buf, n);
+		return n;
+	}
+	return 0;
+}
+
+static void
+setbuttonmap(char* map)
+{
+	int i, x, one, two, three;
+
+	one = two = three = 0;
+	for(i = 0; i < 3; i++){
+		if(map[i] == 0)
+			error(Ebadarg);
+		if(map[i] == '1'){
+			if(one)
+				error(Ebadarg);
+			one = 1<<i;
+		}
+		else if(map[i] == '2'){
+			if(two)
+				error(Ebadarg);
+			two = 1<<i;
+		}
+		else if(map[i] == '3'){
+			if(three)
+				error(Ebadarg);
+			three = 1<<i;
+		}
+		else
+			error(Ebadarg);
+	}
+	if(map[i])
+		error(Ebadarg);
+
+	memset(buttonmap, 0, 8);
+	for(i = 0; i < 8; i++){
+		x = 0;
+		if(i & 1)
+			x |= one;
+		if(i & 2)
+			x |= two;
+		if(i & 4)
+			x |= three;
+		buttonmap[x] = i;
+	}
+}
+
+long
+mousewrite(Chan *c, void *va, long n, vlong offset)
+{
+	char *p;
+	Point pt;
+	char buf[64];
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	USED(offset);
+
+	p = va;
+	switch((long)c->qid.path){
+	case Qdir:
+		error(Eisdir);
+
+	case Qcursor:
+		if(n < 2*4+2*2*16){
+			cursor = arrow;
+			setcursor();
+		}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;
+
+	case Qmousectl:
+		cb = parsecmd(va,n);
+		if(waserror()){
+			free(cb);
+			nexterror();
+		}
+		ct = lookupcmd(cb, mousectlmsg, nelem(mousectlmsg));
+		switch(ct->index){
+		case CMswap:
+			if(mouseswap)
+				setbuttonmap("123");
+			else
+				setbuttonmap("321");
+			mouseswap ^= 1;
+			break;
+
+		case CMscrollswap:
+			scrollswap ^= 1;
+			break;
+
+		case CMbuttonmap:
+			if(cb->nf==1)
+				setbuttonmap("123");
+			else
+				setbuttonmap(cb->f[1]);
+			break;
+		}
+		free(cb);
+		poperror();
+		return n;
+	}
+
+	error(Egreg);
+	return -1;
+}
+
+/*
+ * notify reader that screen has been resized
+ */
+void
+mouseresize(void)
+{
+	mouse.resize = 1;
+	wakeup(&mouse.r);
+}
+
+int
+mousechanged(void *a)
+{
+	USED(a);
+	return mouse.lastcounter != mouse.state.counter || mouse.resize;
+}
+
+void
+mousetrack(int dx, int dy, int b, ulong msec)
+{
+	absmousetrack(mouse.state.xy.x + dx, mouse.state.xy.y + dy, b, msec);
+}
+
+void
+absmousetrack(int x, int y, int b, ulong msec)
+{
+	int lastb;
+
+	if(gscreen==nil)
+		return;
+
+	if(x < gscreen->clipr.min.x)
+		x = gscreen->clipr.min.x;
+	if(x >= gscreen->clipr.max.x)
+		x = gscreen->clipr.max.x-1;
+	if(y < gscreen->clipr.min.y)
+		y = gscreen->clipr.min.y;
+	if(y >= gscreen->clipr.max.y)
+		y = gscreen->clipr.max.y-1;
+
+	lock(&mouse.lk);
+	mouse.state.xy = Pt(x, y);
+	lastb = mouse.state.buttons;
+	mouse.state.buttons = b;
+	mouse.state.msec = msec;
+	mouse.state.counter++;
+
+	/*
+	 * if the queue fills, don't queue any more events until a
+	 * reader polls the mouse.
+	 */
+	if(b != lastb && (mouse.wi-mouse.ri) < nelem(mouse.queue))
+		mouse.queue[mouse.wi++ % nelem(mouse.queue)] = mouse.state;
+	unlock(&mouse.lk);
+
+	wakeup(&mouse.r);
+}
+
+Dev mousedevtab = {
+	'm',
+	"mouse",
+
+	devreset,
+	mouseinit,
+	devshutdown,
+	mouseattach,
+	mousewalk,
+	mousestat,
+	mouseopen,
+	devcreate,
+	mouseclose,
+	mouseread,
+	devbread,
+	mousewrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+
--- /dev/null
+++ b/kern/devpipe.c
@@ -1,0 +1,396 @@
+#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(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,299 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Qdir = 0,
+	Qboot = 0x1000,
+	Qmnt = 0x2000,
+	Qfactotum,
+
+	Nrootfiles = 32,
+	Nbootfiles = 32,
+	Nmntfiles = 2,
+};
+
+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,
+	"mnt",	{Qmnt, 0, QTDIR},	0,		DMDIR|0555,
+};
+static uchar *rootdata[Nrootfiles];
+static Dirlist rootlist = 
+{
+	0,
+	rootdir,
+	rootdata,
+	3,
+	Nrootfiles
+};
+
+static Dirtab bootdir[Nbootfiles] = {
+	"boot",	{Qboot, 0, QTDIR},	0,		DMDIR|0555,
+};
+static uchar *bootdata[Nbootfiles];
+static Dirlist bootlist =
+{
+	Qboot,
+	bootdir,
+	bootdata,
+	1,
+	Nbootfiles
+};
+
+static uchar *mntdata[Nmntfiles];
+static Dirtab mntdir[Nmntfiles] = {
+	"mnt",	{Qmnt, 0, QTDIR},	0,		DMDIR|0555,
+	"factotum",	{Qfactotum, 0, QTDIR},	0,	DMDIR|0555,
+};
+static Dirlist mntlist =
+{
+	Qmnt,
+	mntdir,
+	mntdata,
+	2,
+	Nmntfiles
+};
+
+/*
+ *  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("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 Qmnt:
+		if(s == DEVDOTDOT){
+			Qid tqiddir = {Qdir, 0, QTDIR};
+			devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+			return 1;
+		}
+		return devgen(c, name, mntlist.dir, mntlist.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};
+			tqiddir.path = c->qid.path&0xF000;
+			devdir(c, tqiddir, "#/", 0, eve, 0555, dp);
+			return 1;
+		}
+		if(s != 0)
+			return -1;
+		switch((int)c->qid.path & 0xF000){
+		case Qdir:
+			t = c->qid.path-1;
+			l = &rootlist;
+			break;
+		case Qboot:
+			t = c->qid.path - Qboot - 1;
+			l = &bootlist;
+			break;
+		case Qmnt:
+			t = c->qid.path - Qmnt - 1;
+			l = &mntlist;
+			break;
+		default:
+			return -1;
+		}
+		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:
+	case Qmnt:
+		return devdirread(c, buf, n, nil, 0, rootgen);
+	}
+
+	if(t&Qboot)
+		l = &bootlist;
+	else if(t&Qmnt)
+		l = &mntlist;
+	else
+		l = &bootlist;
+	t &= 0xFFF;
+	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,1450 @@
+/*
+ *  devssl - secure sockets layer
+ */
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	<libsec.h>
+
+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=	512,	/* max. open ssl conn's; must be a power of 2 */
+};
+
+static	Lock	dslock;
+static	int	dshiwat;
+static	char	*dsname[Maxdstate];
+static	Dstate	*dstate[Maxdstate];
+static	char	*encalgs;
+static	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[] = {
+[Qclonus]	"clone",
+[Qdata]		"data",
+[Qctl]		"ctl",
+[Qsecretin]	"secretin",
+[Qsecretout]	"secretout",
+[Qencalgs]	"encalgs",
+[Qhashalgs]	"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;
+	}
+}
+
+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 ft;
+
+	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 {
+			devpermcheck(s->user, s->perm, omode);
+			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 != -1)
+		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);
+		secfree(s->in.secret);
+		secfree(s->out.secret);
+		secfree(s->in.state);
+		secfree(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);
+			pullblock(&b, s->diglen);
+			len -= 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);
+			pullblock(&b, 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;
+}
+
+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)
+			freeb(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);
+			prng(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)
+{
+	secfree(w->secret);
+	w->secret = secalloc(n);
+	memmove(w->secret, secret, n);
+	w->slen = n;
+}
+
+static void
+initDESkey(OneWay *w)
+{
+	secfree(w->state);
+	w->state = secalloc(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->slen >= 8){
+		memmove(key, w->secret, 8);
+		key[0] &= 0x0f;
+		key[2] &= 0x0f;
+		key[4] &= 0x0f;
+		key[6] &= 0x0f;
+	}
+	initDESkey(w);
+}
+
+static void
+initRC4key(OneWay *w)
+{
+	secfree(w->state);
+	w->state = secalloc(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->slen > 5)
+		w->slen = 5;
+	initRC4key(w);
+}
+
+/*
+ *  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->slen > 16)
+		w->slen = 16;
+	initRC4key(w);
+}
+
+
+typedef struct Hashalg Hashalg;
+struct Hashalg
+{
+	char	*name;
+	int	diglen;
+	DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*);
+};
+
+Hashalg hashtab[] =
+{
+	{ "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*);
+};
+
+static
+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 }
+};
+
+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;
+}
+
+enum {
+	Cfd,
+	Calg,
+	Csin,
+	Csout,
+};
+	
+static
+Cmdtab sslcmds[] = {
+	{Cfd, 	"fd", 	2 },
+	{Calg, 	"alg", 	0 },
+	{Csin, 	"secretin", 	2 },
+	{Csout,	"secretout",	2 },
+};
+
+static long
+sslwrite(Chan *c, void *a, long n, vlong o)
+{
+	Dstate * volatile s;
+	Block * volatile b;
+	int m, t, i;
+	char *p, *e;
+	uchar *x;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	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;
+	}
+
+	cb = parsecmd(a, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	ct = lookupcmd(cb, sslcmds, nelem(sslcmds));
+	switch(ct->index){
+	case Cfd:
+		s->c = buftochan(cb->f[1]);
+
+		/* 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;
+		break;
+	case Calg:
+		if(cb->nf < 2)
+			cmderror(cb, "no algorithms");
+
+		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(cb->f[1], "clear") == 0)
+			break;
+
+		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(i=1; i<cb->nf; i++){
+			p = cb->f[i];
+			if(parsehashalg(p, s) < 0)
+			if(parseencryptalg(p, s) < 0)
+				error("bad algorithm");
+		}
+
+		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;
+		break;
+	case Csin:
+		p = cb->f[1];
+		m = (strlen(p)*3)/2 + 1;
+		x = secalloc(m);
+		t = dec64(x, m, p, strlen(p));
+		memset(p, 0, strlen(p));
+		if(t <= 0){
+			secfree(x);
+			error(Ebadarg);
+		}
+		setsecret(&s->in, x, t);
+		secfree(x);
+		break;
+	case Csout:
+		p = cb->f[1];
+		m = (strlen(p)*3)/2 + 1;
+		x = secalloc(m);
+		t = dec64(x, m, p, strlen(p));
+		memset(p, 0, strlen(p));
+		if(t <= 0){
+			secfree(x);
+			error(Ebadarg);
+		}
+		setsecret(&s->out, x, t);
+		secfree(x);
+		break;
+	}
+	poperror();
+	free(cb);
+
+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(tsmemcmp(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, ORDWR, 1, 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,41 @@
+#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 tlsdevtab;
+extern Dev mousedevtab;
+extern Dev drawdevtab;
+extern Dev ipdevtab;
+extern Dev fsdevtab;
+extern Dev mntdevtab;
+extern Dev lfddevtab;
+extern Dev audiodevtab;
+extern Dev kbddevtab;
+extern Dev cmddevtab;
+extern Dev envdevtab;
+
+Dev *devtab[] = {
+	&rootdevtab,
+	&consdevtab,
+	&pipedevtab,
+	&ssldevtab,
+	&tlsdevtab,
+	&mousedevtab,
+	&drawdevtab,
+	&ipdevtab,
+	&fsdevtab,
+	&mntdevtab,
+	&lfddevtab,
+	&audiodevtab,
+	&kbddevtab,
+	&cmddevtab,
+	&envdevtab,
+	0
+};
+
--- /dev/null
+++ b/kern/devtls.c
@@ -1,0 +1,2380 @@
+/*
+ *  devtls - record layer for transport layer security 1.2 and secure sockets layer 3.0
+ */
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	<libsec.h>
+
+typedef struct OneWay	OneWay;
+typedef struct Secret	Secret;
+typedef struct TlsRec	TlsRec;
+typedef struct TlsErrs	TlsErrs;
+
+enum {
+	Statlen=	1024,		/* max. length of status or stats message */
+	/* buffer limits */
+	MaxRecLen	= 1<<14,	/* max payload length of a record layer message */
+	MaxCipherRecLen	= MaxRecLen + 2048,
+	RecHdrLen	= 5,
+	MaxMacLen	= SHA2_256dlen,
+
+	/* protocol versions we can accept */
+	SSL3Version	= 0x0300,
+	TLS10Version	= 0x0301,
+	TLS11Version	= 0x0302,
+	TLS12Version	= 0x0303,
+	MinProtoVersion	= 0x0300,	/* limits on version we accept */
+	MaxProtoVersion	= 0x03ff,
+
+	/* connection states */
+	SHandshake	= 1 << 0,	/* doing handshake */
+	SOpen		= 1 << 1,	/* application data can be sent */
+	SRClose		= 1 << 2,	/* remote side has closed down */
+	SLClose		= 1 << 3,	/* sent a close notify alert */
+	SAlert		= 1 << 5,	/* sending or sent a fatal alert */
+	SError		= 1 << 6,	/* some sort of error has occured */
+	SClosed		= 1 << 7,	/* it is all over */
+
+	/* record types */
+	RChangeCipherSpec = 20,
+	RAlert,
+	RHandshake,
+	RApplication,
+
+	SSL2ClientHello = 1,
+	HSSL2ClientHello = 9,  /* local convention;  see tlshand.c */
+
+	/* alerts */
+	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,
+	EUnrecognizedName		= 112,
+
+	EMAX = 256
+};
+
+struct Secret
+{
+	char		*encalg;	/* name of encryption alg */
+	char		*hashalg;	/* name of hash alg */
+
+	int		(*aead_enc)(Secret*, uchar*, int, uchar*, uchar*, int);
+	int		(*aead_dec)(Secret*, uchar*, int, uchar*, uchar*, int);
+
+	int		(*enc)(Secret*, uchar*, int);
+	int		(*dec)(Secret*, uchar*, int);
+	int		(*unpad)(uchar*, int, int);
+	DigestState*	(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+
+	int		block;		/* encryption block len, 0 if none */
+	int		maclen;		/* # bytes of record mac / authentication tag */
+	int		recivlen;	/* # bytes of record iv for AEAD ciphers */
+	void		*enckey;
+	uchar		mackey[MaxMacLen];
+};
+
+struct OneWay
+{
+	QLock		io;		/* locks io access */
+	QLock		seclock;	/* locks secret paramaters */
+	u64int		seq;
+	Secret		*sec;		/* cipher in use */
+	Secret		*new;		/* cipher waiting for enable */
+};
+
+struct TlsRec
+{
+	Chan	*c;				/* io channel */
+	int		ref;				/* serialized by tdlock for atomic destroy */
+	int		version;			/* version of the protocol we are speaking */
+	char		verset;			/* version has been set */
+	char		opened;			/* opened command every issued? */
+	char		err[ERRMAX];		/* error message to return to handshake requests */
+	vlong	handin;			/* bytes communicated by the record layer */
+	vlong	handout;
+	vlong	datain;
+	vlong	dataout;
+
+	Lock		statelk;
+	int		state;
+	int		debug;
+
+	/*
+	 * function to genrate authenticated data blob for different
+	 * protocol versions
+	 */
+	int		(*packAAD)(u64int, uchar*, uchar*);
+
+	/* input side -- protected by in.io */
+	OneWay		in;
+	Block		*processed;	/* next bunch of application data */
+	Block		*unprocessed;	/* data read from c but not parsed into records */
+
+	/* handshake queue */
+	Lock		hqlock;			/* protects hqref, alloc & free of handq, hprocessed */
+	int		hqref;
+	Queue		*handq;		/* queue of handshake messages */
+	Block		*hprocessed;	/* remainder of last block read from handq */
+	QLock		hqread;		/* protects reads for hprocessed, handq */
+
+	/* output side */
+	OneWay		out;
+
+	/* protections */
+	char		*user;
+	int		perm;
+};
+
+struct TlsErrs{
+	int	err;
+	int	sslerr;
+	int	tlserr;
+	int	fatal;
+	char	*msg;
+};
+
+static TlsErrs tlserrs[] = {
+	{ECloseNotify,			ECloseNotify,			ECloseNotify,			0, 	"close notify"},
+	{EUnexpectedMessage,	EUnexpectedMessage,	EUnexpectedMessage, 	1, "unexpected message"},
+	{EBadRecordMac,		EBadRecordMac,		EBadRecordMac, 		1, "bad record mac"},
+	{EDecryptionFailed,		EIllegalParameter,		EDecryptionFailed,		1, "decryption failed"},
+	{ERecordOverflow,		EIllegalParameter,		ERecordOverflow,		1, "record too long"},
+	{EDecompressionFailure,	EDecompressionFailure,	EDecompressionFailure,	1, "decompression failed"},
+	{EHandshakeFailure,		EHandshakeFailure,		EHandshakeFailure,		1, "could not negotiate acceptable security parameters"},
+	{ENoCertificate,		ENoCertificate,			ECertificateUnknown,	1, "no appropriate certificate available"},
+	{EBadCertificate,		EBadCertificate,		EBadCertificate,		1, "corrupted or invalid certificate"},
+	{EUnsupportedCertificate,	EUnsupportedCertificate,	EUnsupportedCertificate,	1, "unsupported certificate type"},
+	{ECertificateRevoked,	ECertificateRevoked,		ECertificateRevoked,		1, "revoked certificate"},
+	{ECertificateExpired,		ECertificateExpired,		ECertificateExpired,		1, "expired certificate"},
+	{ECertificateUnknown,	ECertificateUnknown,	ECertificateUnknown,	1, "unacceptable certificate"},
+	{EIllegalParameter,		EIllegalParameter,		EIllegalParameter,		1, "illegal parameter"},
+	{EUnknownCa,			EHandshakeFailure,		EUnknownCa,			1, "unknown certificate authority"},
+	{EAccessDenied,		EHandshakeFailure,		EAccessDenied,		1, "access denied"},
+	{EDecodeError,			EIllegalParameter,		EDecodeError,			1, "error decoding message"},
+	{EDecryptError,			EIllegalParameter,		EDecryptError,			1, "error decrypting message"},
+	{EExportRestriction,		EHandshakeFailure,		EExportRestriction,		1, "export restriction violated"},
+	{EProtocolVersion,		EIllegalParameter,		EProtocolVersion,		1, "protocol version not supported"},
+	{EInsufficientSecurity,	EHandshakeFailure,		EInsufficientSecurity,	1, "stronger security routines required"},
+	{EInternalError,			EHandshakeFailure,		EInternalError,			1, "internal error"},
+	{EUserCanceled,		ECloseNotify,			EUserCanceled,			0, "handshake canceled by user"},
+	{ENoRenegotiation,		EUnexpectedMessage,	ENoRenegotiation,		0, "no renegotiation"},
+};
+
+enum
+{
+	/* max. open tls connections */
+	MaxTlsDevs	= 1024
+};
+
+static	Lock	tdlock;
+static	int	tdhiwat;
+static	int	maxtlsdevs = 128;
+static	TlsRec	**tlsdevs;
+static	char	**trnames;
+static	char	*encalgs;
+static	char	*hashalgs;
+
+enum{
+	Qtopdir		= 1,	/* top level directory */
+	Qprotodir,
+	Qclonus,
+	Qencalgs,
+	Qhashalgs,
+	Qconvdir,		/* directory for a conversation */
+	Qdata,
+	Qctl,
+	Qhand,
+	Qstatus,
+	Qstats,
+};
+
+#define TYPE(x) 	((x).path & 0xf)
+#define CONV(x) 	(((x).path >> 5)&(MaxTlsDevs-1))
+#define QID(c, y) 	(((c)<<5) | (y))
+
+static void	checkstate(TlsRec *, int, int);
+static void	ensure(TlsRec*, Block**, int);
+static void	consume(Block**, uchar*, int);
+static Chan*	buftochan(char*);
+static void	tlshangup(TlsRec*);
+static void	tlsError(TlsRec*, char *);
+static void	alertHand(TlsRec*, char *);
+static TlsRec	*newtls(Chan *c);
+static TlsRec	*mktlsrec(void);
+static DigestState*sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static DigestState*sslmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static DigestState*nomac(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static int	sslPackAAD(u64int, uchar*, uchar*);
+static int	tlsPackAAD(u64int, uchar*, uchar*);
+static void	packMac(Secret*, uchar*, int, uchar*, int, uchar*);
+static void	put64(uchar *p, u64int);
+static void	put32(uchar *p, u32int);
+static void	put24(uchar *p, int);
+static void	put16(uchar *p, int);
+static int	get16(uchar *p);
+static void	tlsSetState(TlsRec *tr, int new, int old);
+static void	rcvAlert(TlsRec *tr, int err);
+static void	sendAlert(TlsRec *tr, int err);
+static void	rcvError(TlsRec *tr, int err, char *msg, ...);
+static int	rc4enc(Secret *sec, uchar *buf, int n);
+static int	des3enc(Secret *sec, uchar *buf, int n);
+static int	des3dec(Secret *sec, uchar *buf, int n);
+static int	aesenc(Secret *sec, uchar *buf, int n);
+static int	aesdec(Secret *sec, uchar *buf, int n);
+static int	ccpoly_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int	ccpoly_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int	aesgcm_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int	aesgcm_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int	noenc(Secret *sec, uchar *buf, int n);
+static int	sslunpad(uchar *buf, int n, int block);
+static int	tlsunpad(uchar *buf, int n, int block);
+static void	freeSec(Secret *sec);
+static char	*tlsstate(int s);
+static void	pdump(int, void*, char*);
+
+static char *tlsnames[] = {
+[Qclonus]		"clone",
+[Qencalgs]	"encalgs",
+[Qhashalgs]	"hashalgs",
+[Qdata]		"data",
+[Qctl]		"ctl",
+[Qhand]		"hand",
+[Qstatus]		"status",
+[Qstats]		"stats",
+};
+
+static int convdir[] = { Qctl, Qdata, Qhand, Qstatus, Qstats };
+
+static int
+tlsgen(Chan *c, char *unused1, Dirtab *unused2, int unused3, int s, Dir *dp)
+{
+	Qid q;
+	TlsRec *tr;
+	char *name, *nm;
+	int perm, t;
+
+	q.vers = 0;
+	q.type = QTFILE;
+
+	t = TYPE(c->qid);
+	switch(t) {
+	case Qtopdir:
+		if(s == DEVDOTDOT){
+			q.path = QID(0, Qtopdir);
+			q.type = QTDIR;
+			devdir(c, q, "#a", 0, eve, 0555, dp);
+			return 1;
+		}
+		if(s > 0)
+			return -1;
+		q.path = QID(0, Qprotodir);
+		q.type = QTDIR;
+		devdir(c, q, "tls", 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 < 3){
+			switch(s) {
+			default:
+				return -1;
+			case 0:
+				q.path = QID(0, Qclonus);
+				break;
+			case 1:
+				q.path = QID(0, Qencalgs);
+				break;
+			case 2:
+				q.path = QID(0, Qhashalgs);
+				break;
+			}
+			perm = 0444;
+			if(TYPE(q) == Qclonus)
+				perm = 0555;
+			devdir(c, q, tlsnames[TYPE(q)], 0, eve, perm, dp);
+			return 1;
+		}
+		s -= 3;
+		if(s >= tdhiwat)
+			return -1;
+		q.path = QID(s, Qconvdir);
+		q.type = QTDIR;
+		lock(&tdlock);
+		tr = tlsdevs[s];
+		if(tr != nil)
+			nm = tr->user;
+		else
+			nm = eve;
+		if((name = trnames[s]) == nil){
+			name = trnames[s] = smalloc(16);
+			sprint(name, "%d", s);
+		}
+		devdir(c, q, name, 0, nm, 0555, dp);
+		unlock(&tdlock);
+		return 1;
+	case Qconvdir:
+		if(s == DEVDOTDOT){
+			q.path = QID(0, Qprotodir);
+			q.type = QTDIR;
+			devdir(c, q, "tls", 0, eve, 0555, dp);
+			return 1;
+		}
+		if(s < 0 || s >= nelem(convdir))
+			return -1;
+		lock(&tdlock);
+		tr = tlsdevs[CONV(c->qid)];
+		if(tr != nil){
+			nm = tr->user;
+			perm = tr->perm;
+		}else{
+			perm = 0;
+			nm = eve;
+		}
+		t = convdir[s];
+		if(t == Qstatus || t == Qstats)
+			perm &= 0444;
+		q.path = QID(CONV(c->qid), t);
+		devdir(c, q, tlsnames[t], 0, nm, perm, dp);
+		unlock(&tdlock);
+		return 1;
+	case Qclonus:
+	case Qencalgs:
+	case Qhashalgs:
+		perm = 0444;
+		if(t == Qclonus)
+			perm = 0555;
+		devdir(c, c->qid, tlsnames[t], 0, eve, perm, dp);
+		return 1;
+	default:
+		lock(&tdlock);
+		tr = tlsdevs[CONV(c->qid)];
+		if(tr != nil){
+			nm = tr->user;
+			perm = tr->perm;
+		}else{
+			perm = 0;
+			nm = eve;
+		}
+		if(t == Qstatus || t == Qstats)
+			perm &= 0444;
+		devdir(c, c->qid, tlsnames[t], 0, nm, perm, dp);
+		unlock(&tdlock);
+		return 1;
+	}
+}
+
+static Chan*
+tlsattach(char *spec)
+{
+	Chan *c;
+
+	c = devattach('a', spec);
+	c->qid.path = QID(0, Qtopdir);
+	c->qid.type = QTDIR;
+	c->qid.vers = 0;
+	return c;
+}
+
+static Walkqid*
+tlswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, nil, 0, tlsgen);
+}
+
+static int
+tlsstat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, nil, 0, tlsgen);
+}
+
+static Chan*
+tlsopen(Chan *c, int omode)
+{
+	TlsRec *tr, **pp;
+	int t;
+
+	t = TYPE(c->qid);
+	switch(t) {
+	default:
+		panic("tlsopen");
+	case Qtopdir:
+	case Qprotodir:
+	case Qconvdir:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qclonus:
+		tr = newtls(c);
+		if(tr == nil)
+			error(Enodev);
+		break;
+	case Qctl:
+	case Qdata:
+	case Qhand:
+	case Qstatus:
+	case Qstats:
+		if((t == Qstatus || t == Qstats) && omode != OREAD)
+			error(Eperm);
+		if(waserror()) {
+			unlock(&tdlock);
+			nexterror();
+		}
+		lock(&tdlock);
+		pp = &tlsdevs[CONV(c->qid)];
+		tr = *pp;
+		if(tr == nil)
+			error("must open connection using clone");
+		devpermcheck(tr->user, tr->perm, omode);
+		if(t == Qhand){
+			if(waserror()){
+				unlock(&tr->hqlock);
+				nexterror();
+			}
+			lock(&tr->hqlock);
+			if(tr->handq != nil)
+				error(Einuse);
+			tr->handq = qopen(2 * MaxCipherRecLen, 0, nil, nil);
+			if(tr->handq == nil)
+				error("cannot allocate handshake queue");
+			tr->hqref = 1;
+			unlock(&tr->hqlock);
+			poperror();
+		}
+		tr->ref++;
+		unlock(&tdlock);
+		poperror();
+		break;
+	case Qencalgs:
+	case Qhashalgs:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	}
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	switch(t){
+	case Qdata:
+		c->iounit = qiomaxatomic;
+		break;
+	case Qhand:
+		c->iounit = MaxRecLen;
+		break;
+	default:
+		c->iounit = 0;
+		break;
+	}
+	return c;
+}
+
+static int
+tlswstat(Chan *c, uchar *dp, int n)
+{
+	Dir *d;
+	TlsRec *tr;
+	int rv;
+
+	d = nil;
+	if(waserror()){
+		free(d);
+		unlock(&tdlock);
+		nexterror();
+	}
+
+	lock(&tdlock);
+	tr = tlsdevs[CONV(c->qid)];
+	if(tr == nil)
+		error(Ebadusefd);
+	if(strcmp(tr->user, up->user) != 0)
+		error(Eperm);
+
+	d = smalloc(n + sizeof *d);
+	rv = convM2D(dp, n, &d[0], (char*) &d[1]);
+	if(rv == 0)
+		error(Eshortstat);
+	if(!emptystr(d->uid))
+		kstrdup(&tr->user, d->uid);
+	if(d->mode != -1)
+		tr->perm = d->mode;
+
+	free(d);
+	poperror();
+	unlock(&tdlock);
+
+	return rv;
+}
+
+static void
+dechandq(TlsRec *tr)
+{
+	lock(&tr->hqlock);
+	if(--tr->hqref == 0){
+		if(tr->handq != nil){
+			qfree(tr->handq);
+			tr->handq = nil;
+		}
+		if(tr->hprocessed != nil){
+			freeb(tr->hprocessed);
+			tr->hprocessed = nil;
+		}
+	}
+	unlock(&tr->hqlock);
+}
+
+static void
+tlsclose(Chan *c)
+{
+	TlsRec *tr;
+	int t;
+
+	t = TYPE(c->qid);
+	switch(t) {
+	case Qctl:
+	case Qdata:
+	case Qhand:
+	case Qstatus:
+	case Qstats:
+		if((c->flag & COPEN) == 0)
+			break;
+
+		tr = tlsdevs[CONV(c->qid)];
+		if(tr == nil)
+			break;
+
+		if(t == Qhand)
+			dechandq(tr);
+
+		lock(&tdlock);
+		if(--tr->ref > 0) {
+			unlock(&tdlock);
+			return;
+		}
+		tlsdevs[CONV(c->qid)] = nil;
+		unlock(&tdlock);
+
+		if(tr->c != nil && !waserror()){
+			checkstate(tr, 0, SOpen|SHandshake|SRClose);
+			sendAlert(tr, ECloseNotify);
+			poperror();
+		}
+		tlshangup(tr);
+		if(tr->c != nil)
+			cclose(tr->c);
+		freeSec(tr->in.sec);
+		freeSec(tr->in.new);
+		freeSec(tr->out.sec);
+		freeSec(tr->out.new);
+		free(tr->user);
+		free(tr);
+		break;
+	}
+}
+
+/*
+ *  make sure we have at least 'n' bytes in list 'l'
+ */
+static void
+ensure(TlsRec *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, MaxCipherRecLen + RecHdrLen, 0);
+		if(bl == 0)
+			error(Ehungup);
+		*l = bl;
+		i = 0;
+		for(b = bl; b; b = b->next){
+			i += BLEN(b);
+			l = &b->next;
+		}
+		if(i == 0)
+			error(Ehungup);
+		sofar += i;
+	}
+if(s->debug) pprint("ensure read %d\n", sofar);
+}
+
+/*
+ *  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(TlsRec *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
+ */
+static Block*
+qgrab(Block **l, int n)
+{
+	Block *bb, *b;
+	int i;
+
+	b = *l;
+	if(BLEN(b) == n){
+		*l = b->next;
+		b->next = nil;
+		return b;
+	}
+
+	i = 0;
+	for(bb = b; bb != nil && i < n; bb = bb->next)
+		i += BLEN(bb);
+	if(i > n)
+		i = n;
+
+	bb = allocb(i);
+	consume(l, bb->wp, i);
+	bb->wp += i;
+	return bb;
+}
+
+static void
+tlsclosed(TlsRec *tr, int new)
+{
+	lock(&tr->statelk);
+	if(tr->state == SOpen || tr->state == SHandshake)
+		tr->state = new;
+	else if((new | tr->state) == (SRClose|SLClose))
+		tr->state = SClosed;
+	unlock(&tr->statelk);
+	alertHand(tr, "close notify");
+}
+
+/*
+ *  read and process one tls record layer message
+ *  must be called with tr->in.io held
+ *  We can't let Eintrs lose data, since doing so will get
+ *  us out of sync with the sender and break the reliablity
+ *  of the channel.  Eintr only happens during the reads in
+ *  consume.  Therefore we put back any bytes consumed before
+ *  the last call to ensure.
+ */
+static void
+tlsrecread(TlsRec *tr)
+{
+	OneWay *volatile in;
+	Block *volatile b;
+	uchar *p, aad[8+RecHdrLen], header[RecHdrLen], hmac[MaxMacLen];
+	int volatile nconsumed;
+	int len, type, ver, unpad_len, aadlen, ivlen;
+	Secret *sec;
+
+	nconsumed = 0;
+	if(waserror()){
+		if(strcmp(up->errstr, Eintr) == 0 && !waserror()){
+			regurgitate(tr, header, nconsumed);
+			poperror();
+		}else
+			tlsError(tr, "channel error");
+		nexterror();
+	}
+	ensure(tr, &tr->unprocessed, RecHdrLen);
+	consume(&tr->unprocessed, header, RecHdrLen);
+if(tr->debug)pprint("consumed %d header\n", RecHdrLen);
+	nconsumed = RecHdrLen;
+
+	if((tr->handin == 0) && (header[0] & 0x80)){
+		/* 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. */
+		len = (get16(header) & ~0x8000) - 3;
+		type = header[2];
+		ver = get16(header + 3);
+		if(type != SSL2ClientHello || len < 22)
+			rcvError(tr, EProtocolVersion, "invalid initial SSL2-like message");
+	}else{  /* normal SSL3 record format */
+		type = header[0];
+		ver = get16(header+1);
+		len = get16(header+3);
+	}
+	if(ver != tr->version && (tr->verset || ver < MinProtoVersion || ver > MaxProtoVersion))
+		rcvError(tr, EProtocolVersion, "devtls expected ver=%x%s, saw (len=%d) type=%x ver=%x '%.12s'",
+			tr->version, tr->verset?"/set":"", len, type, ver, (char*)header);
+	if(len > MaxCipherRecLen || len <= 0)
+		rcvError(tr, ERecordOverflow, "bad record message length %d", len);
+	ensure(tr, &tr->unprocessed, len);
+	nconsumed = 0;
+	poperror();
+
+	/*
+	 * If an Eintr happens after this, we'll get out of sync.
+	 * Make sure nothing we call can sleep.
+	 * Errors are ok, as they kill the connection.
+	 * Luckily, allocb won't sleep, it'll just error out.
+	 */
+	b = nil;
+	if(waserror()){
+		if(b != nil)
+			freeb(b);
+		tlsError(tr, "channel error");
+		nexterror();
+	}
+	b = qgrab(&tr->unprocessed, len);
+if(tr->debug) pprint("consumed unprocessed %d\n", len);
+
+	in = &tr->in;
+	if(waserror()){
+		qunlock(&in->seclock);
+		nexterror();
+	}
+	qlock(&in->seclock);
+	p = b->rp;
+	sec = in->sec;
+	if(sec != nil) {
+		/* to avoid Canvel-Hiltgen-Vaudenay-Vuagnoux attack, all errors here
+		        should look alike, including timing of the response. */
+		if(sec->aead_dec != nil)
+			unpad_len = len;
+		else {
+			unpad_len = (*sec->dec)(sec, p, len);
+if(tr->debug) pprint("decrypted %d\n", unpad_len);
+if(tr->debug) pdump(unpad_len, p, "decrypted:");
+		}
+
+		ivlen = sec->recivlen;
+		if(tr->version >= TLS11Version){
+			if(ivlen == 0)
+				ivlen = sec->block;
+		}
+		len -= ivlen;
+		if(len < 0)
+			rcvError(tr, EDecodeError, "runt record message");
+		unpad_len -= ivlen;
+		p += ivlen;
+
+		if(unpad_len >= sec->maclen)
+			len = unpad_len - sec->maclen;
+
+		/* update length */
+		put16(header+3, len);
+		aadlen = (*tr->packAAD)(in->seq++, header, aad);
+		if(sec->aead_dec != nil) {
+			len = (*sec->aead_dec)(sec, aad, aadlen, p - ivlen, p, unpad_len);
+			if(len < 0)
+				rcvError(tr, EBadRecordMac, "record mac mismatch");
+		} else {
+			packMac(sec, aad, aadlen, p, len, hmac);
+			if(unpad_len < sec->maclen)
+				rcvError(tr, EBadRecordMac, "short record mac");
+			if(tsmemcmp(hmac, p + len, sec->maclen) != 0)
+				rcvError(tr, EBadRecordMac, "record mac mismatch");
+		}
+		b->rp = p;
+		b->wp = p+len;
+	}
+	qunlock(&in->seclock);
+	poperror();
+	if(len < 0)
+		rcvError(tr, EDecodeError, "runt record message");
+
+	switch(type) {
+	default:
+		rcvError(tr, EIllegalParameter, "invalid record message %#x", type);
+		break;
+	case RChangeCipherSpec:
+		if(len != 1 || p[0] != 1)
+			rcvError(tr, EDecodeError, "invalid change cipher spec");
+		qlock(&in->seclock);
+		if(in->new == nil){
+			qunlock(&in->seclock);
+			rcvError(tr, EUnexpectedMessage, "unexpected change cipher spec");
+		}
+		freeSec(in->sec);
+		in->sec = in->new;
+		in->new = nil;
+		in->seq = 0;
+		qunlock(&in->seclock);
+		break;
+	case RAlert:
+		if(len != 2)
+			rcvError(tr, EDecodeError, "invalid alert");
+		if(p[0] == 2)
+			rcvAlert(tr, p[1]);
+		if(p[0] != 1)
+			rcvError(tr, EIllegalParameter, "invalid alert fatal code");
+
+		/*
+		 * propagate non-fatal alerts to handshaker
+		 */
+		switch(p[1]){
+		case ECloseNotify:
+			tlsclosed(tr, SRClose);
+			if(tr->opened)
+				error("tls hungup");
+			error("close notify");
+			break;
+		case ENoRenegotiation:
+			alertHand(tr, "no renegotiation");
+			break;
+		case EUserCanceled:
+			alertHand(tr, "handshake canceled by user");
+			break;
+		case EUnrecognizedName:
+			/* happens in response to SNI, can be ignored. */
+			break;
+		default:
+			rcvError(tr, EIllegalParameter, "invalid alert code");
+		}
+		break;
+	case RHandshake:
+		/*
+		 * don't worry about dropping the block
+		 * qbwrite always queues even if flow controlled and interrupted.
+		 *
+		 * if there isn't any handshaker, ignore the request,
+		 * but notify the other side we are doing so.
+		 */
+		lock(&tr->hqlock);
+		if(tr->handq != nil){
+			tr->hqref++;
+			unlock(&tr->hqlock);
+			if(waserror()){
+				dechandq(tr);
+				nexterror();
+			}
+			b = padblock(b, 1);
+			*b->rp = RHandshake;
+			qbwrite(tr->handq, b);
+			b = nil;
+			poperror();
+			dechandq(tr);
+		}else{
+			unlock(&tr->hqlock);
+			if(tr->verset && tr->version != SSL3Version && !waserror()){
+				sendAlert(tr, ENoRenegotiation);
+				poperror();
+			}
+		}
+		break;
+	case SSL2ClientHello:
+		lock(&tr->hqlock);
+		if(tr->handq != nil){
+			tr->hqref++;
+			unlock(&tr->hqlock);
+			if(waserror()){
+				dechandq(tr);
+				nexterror();
+			}
+			/* Pass the SSL2 format data, so that the handshake code can compute
+				the correct checksums.  HSSL2ClientHello = HandshakeType 9 is
+				unused in RFC2246. */
+			b = padblock(b, 8);
+			b->rp[0] = RHandshake;
+			b->rp[1] = HSSL2ClientHello;
+			put24(&b->rp[2], len+3);
+			b->rp[5] = SSL2ClientHello;
+			put16(&b->rp[6], ver);
+			qbwrite(tr->handq, b);
+			b = nil;
+			poperror();
+			dechandq(tr);
+		}else{
+			unlock(&tr->hqlock);
+			if(tr->verset && tr->version != SSL3Version && !waserror()){
+				sendAlert(tr, ENoRenegotiation);
+				poperror();
+			}
+		}
+		break;
+	case RApplication:
+		if(!tr->opened)
+			rcvError(tr, EUnexpectedMessage, "application message received before handshake completed");
+		if(BLEN(b) > 0){
+			tr->processed = b;
+			b = nil;
+		}
+		break;
+	}
+	if(b != nil)
+		freeb(b);
+	poperror();
+}
+
+/*
+ * got a fatal alert message
+ */
+static void
+rcvAlert(TlsRec *tr, int err)
+{
+	char *s;
+	int i;
+
+	s = "unknown error";
+	for(i=0; i < nelem(tlserrs); i++){
+		if(tlserrs[i].err == err){
+			s = tlserrs[i].msg;
+			break;
+		}
+	}
+if(tr->debug) pprint("rcvAlert: %s\n", s);
+
+	tlsError(tr, s);
+	if(!tr->opened)
+		error(s);
+	error("tls error");
+}
+
+/*
+ * found an error while decoding the input stream
+ */
+static void
+rcvError(TlsRec *tr, int err, char *fmt, ...)
+{
+	char msg[ERRMAX];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(msg, msg+sizeof(msg), fmt, arg);
+	va_end(arg);
+if(tr->debug) pprint("rcvError: %s\n", msg);
+
+	sendAlert(tr, err);
+
+	if(!tr->opened)
+		error(msg);
+	error("tls error");
+}
+
+/*
+ * make sure the next hand operation returns with a 'msg' error
+ */
+static void
+alertHand(TlsRec *tr, char *msg)
+{
+	Block *b;
+	int n;
+
+	lock(&tr->hqlock);
+	if(tr->handq == nil){
+		unlock(&tr->hqlock);
+		return;
+	}
+	tr->hqref++;
+	unlock(&tr->hqlock);
+
+	n = strlen(msg);
+	if(waserror()){
+		dechandq(tr);
+		nexterror();
+	}
+	b = allocb(n + 2);
+	*b->wp++ = RAlert;
+	memmove(b->wp, msg, n + 1);
+	b->wp += n + 1;
+
+	qbwrite(tr->handq, b);
+
+	poperror();
+	dechandq(tr);
+}
+
+static void
+checkstate(TlsRec *tr, int ishand, int ok)
+{
+	int state;
+
+	lock(&tr->statelk);
+	state = tr->state;
+	unlock(&tr->statelk);
+	if(state & ok)
+		return;
+	switch(state){
+	case SHandshake:
+	case SOpen:
+		break;
+	case SError:
+	case SAlert:
+		if(ishand)
+			error(tr->err);
+		error("tls error");
+	case SRClose:
+	case SLClose:
+	case SClosed:
+		error("tls hungup");
+	}
+	error("tls improperly configured");
+}
+
+static Block*
+tlsbread(Chan *c, long n, ulong offset)
+{
+	int ty;
+	Block *b;
+	TlsRec *volatile tr;
+
+	ty = TYPE(c->qid);
+	switch(ty) {
+	default:
+		return devbread(c, n, offset);
+	case Qhand:
+	case Qdata:
+		break;
+	}
+
+	tr = tlsdevs[CONV(c->qid)];
+	if(tr == nil)
+		panic("tlsbread");
+
+	if(waserror()){
+		qunlock(&tr->in.io);
+		nexterror();
+	}
+	qlock(&tr->in.io);
+	if(ty == Qdata){
+		checkstate(tr, 0, SOpen);
+		while(tr->processed == nil)
+			tlsrecread(tr);
+
+		/* return at most what was asked for */
+		b = qgrab(&tr->processed, n);
+if(tr->debug) pprint("consumed processed %zd\n", BLEN(b));
+if(tr->debug) pdump(BLEN(b), b->rp, "consumed:");
+		qunlock(&tr->in.io);
+		poperror();
+		tr->datain += BLEN(b);
+	}else{
+		checkstate(tr, 1, SOpen|SHandshake|SLClose);
+
+		/*
+		 * it's ok to look at state without the lock
+		 * since it only protects reading records,
+		 * and we have that tr->in.io held.
+		 */
+		while(!tr->opened && tr->hprocessed == nil && !qcanread(tr->handq))
+			tlsrecread(tr);
+
+		qunlock(&tr->in.io);
+		poperror();
+
+		if(waserror()){
+			qunlock(&tr->hqread);
+			nexterror();
+		}
+		qlock(&tr->hqread);
+		if(tr->hprocessed == nil){
+			b = qbread(tr->handq, MaxRecLen + 1);
+			if(*b->rp++ == RAlert){
+				kstrcpy(up->errstr, (char*)b->rp, ERRMAX);
+				freeb(b);
+				nexterror();
+			}
+			tr->hprocessed = b;
+		}
+		b = qgrab(&tr->hprocessed, n);
+		poperror();
+		qunlock(&tr->hqread);
+		tr->handin += BLEN(b);
+	}
+
+	return b;
+}
+
+static long
+tlsread(Chan *c, void *a, long n, vlong off)
+{
+	Block *volatile b;
+	Block *nb;
+	uchar *va;
+	int i, ty;
+	char *buf, *s, *e;
+	ulong offset = off;
+	TlsRec * tr;
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, a, n, 0, 0, tlsgen);
+
+	tr = tlsdevs[CONV(c->qid)];
+	ty = TYPE(c->qid);
+	switch(ty) {
+	default:
+		error(Ebadusefd);
+	case Qstatus:
+		buf = smalloc(Statlen);
+		qlock(&tr->in.seclock);
+		qlock(&tr->out.seclock);
+		s = buf;
+		e = buf + Statlen;
+		s = seprint(s, e, "State: %s\n", tlsstate(tr->state));
+		s = seprint(s, e, "Version: %#x\n", tr->version);
+		if(tr->in.sec != nil)
+			s = seprint(s, e, "EncIn: %s\nHashIn: %s\n", tr->in.sec->encalg, tr->in.sec->hashalg);
+		if(tr->in.new != nil)
+			s = seprint(s, e, "NewEncIn: %s\nNewHashIn: %s\n", tr->in.new->encalg, tr->in.new->hashalg);
+		if(tr->out.sec != nil)
+			s = seprint(s, e, "EncOut: %s\nHashOut: %s\n", tr->out.sec->encalg, tr->out.sec->hashalg);
+		if(tr->out.new != nil)
+			s = seprint(s, e, "NewEncOut: %s\nNewHashOut: %s\n", tr->out.new->encalg, tr->out.new->hashalg);
+		if(tr->c != nil)
+			seprint(s, e, "Chan: %s\n", chanpath(tr->c));
+		qunlock(&tr->in.seclock);
+		qunlock(&tr->out.seclock);
+		n = readstr(offset, a, n, buf);
+		free(buf);
+		return n;
+	case Qstats:
+		buf = smalloc(Statlen);
+		s = buf;
+		e = buf + Statlen;
+		s = seprint(s, e, "DataIn: %lld\n", tr->datain);
+		s = seprint(s, e, "DataOut: %lld\n", tr->dataout);
+		s = seprint(s, e, "HandIn: %lld\n", tr->handin);
+		seprint(s, e, "HandOut: %lld\n", tr->handout);
+		n = readstr(offset, a, n, buf);
+		free(buf);
+		return n;
+	case Qctl:
+		buf = smalloc(Statlen);
+		snprint(buf, Statlen, "%llud", CONV(c->qid));
+		n = readstr(offset, a, n, buf);
+		free(buf);
+		return n;
+	case Qdata:
+	case Qhand:
+		b = tlsbread(c, n, offset);
+		break;
+	case Qencalgs:
+		return readstr(offset, a, n, encalgs);
+	case Qhashalgs:
+		return readstr(offset, a, n, hashalgs);
+	}
+
+	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;
+}
+
+/*
+ *  write a block in tls records
+ */
+static void
+tlsrecwrite(TlsRec *tr, int type, Block *b)
+{
+	Block *volatile bb;
+	Block *nb;
+	uchar *p, aad[8+RecHdrLen];
+	OneWay *volatile out;
+	int n, ivlen, maclen, aadlen, pad, ok;
+	Secret *sec;
+
+	out = &tr->out;
+	bb = b;
+	if(waserror()){
+		qunlock(&out->io);
+		if(bb != nil)
+			freeb(bb);
+		nexterror();
+	}
+	qlock(&out->io);
+if(tr->debug)pprint("send %zd\n", BLEN(b));
+if(tr->debug)pdump(BLEN(b), b->rp, "sent:");
+
+
+	if(type == RApplication)
+		checkstate(tr, 0, SOpen);
+
+	ok = SHandshake|SOpen|SRClose;
+	if(type == RAlert)
+		ok |= SAlert;
+	while(bb != nil){
+		checkstate(tr, type != RApplication, ok);
+
+		/*
+		 * get at most one maximal record's input,
+		 * with padding on the front for header and
+		 * back for mac and maximal block padding.
+		 */
+		if(waserror()){
+			qunlock(&out->seclock);
+			nexterror();
+		}
+		qlock(&out->seclock);
+		maclen = 0;
+		pad = 0;
+		ivlen = 0;
+		sec = out->sec;
+		if(sec != nil){
+			maclen = sec->maclen;
+			pad = maclen + sec->block;
+			ivlen = sec->recivlen;
+			if(tr->version >= TLS11Version){
+				if(ivlen == 0)
+					ivlen = sec->block;
+			}
+		}
+		n = BLEN(bb);
+		if(n > MaxRecLen){
+			n = MaxRecLen;
+			nb = allocb(RecHdrLen + ivlen + n + pad);
+			memmove(nb->wp + RecHdrLen + ivlen, bb->rp, n);
+			bb->rp += n;
+		}else{
+			/*
+			 * carefully reuse bb so it will get freed if we're out of memory
+			 */
+			bb = padblock(bb, RecHdrLen + ivlen);
+			if(pad)
+				nb = padblock(bb, -pad);
+			else
+				nb = bb;
+			bb = nil;
+		}
+
+		p = nb->rp;
+		p[0] = type;
+		put16(p+1, tr->version);
+		put16(p+3, n);
+
+		if(sec != nil){
+			aadlen = (*tr->packAAD)(out->seq++, p, aad);
+			if(sec->aead_enc != nil)
+				n = (*sec->aead_enc)(sec, aad, aadlen, p + RecHdrLen, p + RecHdrLen + ivlen, n) + ivlen;
+			else {
+				if(ivlen > 0)
+					prng(p + RecHdrLen, ivlen);
+				packMac(sec, aad, aadlen, p + RecHdrLen + ivlen, n, p + RecHdrLen + ivlen + n);
+				n = (*sec->enc)(sec, p + RecHdrLen, ivlen + n + maclen);
+			}
+			nb->wp = p + RecHdrLen + n;
+
+			/* update length */
+			put16(p+3, n);
+		}
+		if(type == RChangeCipherSpec){
+			if(out->new == nil)
+				error("change cipher without a new cipher");
+			freeSec(out->sec);
+			out->sec = out->new;
+			out->new = nil;
+			out->seq = 0;
+		}
+		qunlock(&out->seclock);
+		poperror();
+
+		/*
+		 * if bwrite error's, we assume the block is queued.
+		 * if not, we're out of sync with the receiver and will not recover.
+		 */
+		if(waserror()){
+			if(strcmp(up->errstr, "interrupted") != 0)
+				tlsError(tr, "channel error");
+			else if(bb != nil)
+				continue;
+			nexterror();
+		}
+		devtab[tr->c->type]->bwrite(tr->c, nb, 0);
+		poperror();
+	}
+	qunlock(&out->io);
+	poperror();
+}
+
+static long
+tlsbwrite(Chan *c, Block *b, ulong offset)
+{
+	int ty;
+	ulong n;
+	TlsRec *tr;
+
+	n = BLEN(b);
+
+	tr = tlsdevs[CONV(c->qid)];
+	if(tr == nil)
+		panic("tlsbwrite");
+
+	ty = TYPE(c->qid);
+	switch(ty) {
+	default:
+		return devbwrite(c, b, offset);
+	case Qhand:
+		tlsrecwrite(tr, RHandshake, b);
+		tr->handout += n;
+		break;
+	case Qdata:
+		tlsrecwrite(tr, RApplication, b);
+		tr->dataout += n;
+		break;
+	}
+
+	return n;
+}
+
+typedef struct Hashalg Hashalg;
+struct Hashalg
+{
+	char	*name;
+	int	maclen;
+	void	(*initkey)(Hashalg *, int, Secret *, uchar*);
+};
+
+static void
+initmd5key(Hashalg *ha, int version, Secret *s, uchar *p)
+{
+	s->maclen = ha->maclen;
+	if(version == SSL3Version)
+		s->mac = sslmac_md5;
+	else
+		s->mac = hmac_md5;
+	memmove(s->mackey, p, ha->maclen);
+}
+
+static void
+initclearmac(Hashalg *ha, int version, Secret *s, uchar *p)
+{
+	s->mac = nomac;
+}
+
+static void
+initsha1key(Hashalg *ha, int version, Secret *s, uchar *p)
+{
+	s->maclen = ha->maclen;
+	if(version == SSL3Version)
+		s->mac = sslmac_sha1;
+	else
+		s->mac = hmac_sha1;
+	memmove(s->mackey, p, ha->maclen);
+}
+
+static void
+initsha2_256key(Hashalg *ha, int version, Secret *s, uchar *p)
+{
+	if(version == SSL3Version)
+		error("sha256 cannot be used with SSL");
+	s->maclen = ha->maclen;
+	s->mac = hmac_sha2_256;
+	memmove(s->mackey, p, ha->maclen);
+}
+
+static Hashalg hashtab[] =
+{
+	{ "clear",	0,		initclearmac, },
+	{ "md5",	MD5dlen,	initmd5key, },
+	{ "sha1",	SHA1dlen,	initsha1key, },
+	{ "sha256",	SHA2_256dlen,	initsha2_256key, },
+	{ 0 }
+};
+
+static Hashalg*
+parsehashalg(char *p)
+{
+	Hashalg *ha;
+
+	for(ha = hashtab; ha->name; ha++)
+		if(strcmp(p, ha->name) == 0)
+			return ha;
+	error("unsupported hash algorithm");
+	return nil;
+}
+
+typedef struct Encalg Encalg;
+struct Encalg
+{
+	char	*name;
+	int	keylen;
+	int	ivlen;
+	void	(*initkey)(Encalg *ea, Secret *, uchar*, uchar*);
+};
+
+static void
+initRC4key(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+	s->enckey = secalloc(sizeof(RC4state));
+	s->enc = rc4enc;
+	s->dec = rc4enc;
+	setupRC4state(s->enckey, p, ea->keylen);
+}
+
+static void
+initDES3key(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+	s->enckey = secalloc(sizeof(DES3state));
+	s->enc = des3enc;
+	s->dec = des3dec;
+	s->block = 8;
+	setupDES3state(s->enckey, (uchar(*)[8])p, iv);
+}
+
+static void
+initAESkey(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+	s->enckey = secalloc(sizeof(AESstate));
+	s->enc = aesenc;
+	s->dec = aesdec;
+	s->block = 16;
+	setupAESstate(s->enckey, p, ea->keylen, iv);
+}
+
+static void
+initccpolykey(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+	s->enckey = secalloc(sizeof(Chachastate));
+	s->aead_enc = ccpoly_aead_enc;
+	s->aead_dec = ccpoly_aead_dec;
+	s->maclen = Poly1305dlen;
+	if(ea->ivlen == 0) {
+		/* older draft version, iv is 64-bit sequence number */
+		setupChachastate(s->enckey, p, ea->keylen, nil, 64/8, 20);
+	} else {
+		/* IETF standard, 96-bit iv xored with sequence number */
+		memmove(s->mackey, iv, ea->ivlen);
+		setupChachastate(s->enckey, p, ea->keylen, iv, ea->ivlen, 20);
+	}
+}
+
+static void
+initaesgcmkey(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+	s->enckey = secalloc(sizeof(AESGCMstate));
+	s->aead_enc = aesgcm_aead_enc;
+	s->aead_dec = aesgcm_aead_dec;
+	s->maclen = 16;
+	s->recivlen = 8;
+	memmove(s->mackey, iv, ea->ivlen);
+	prng(s->mackey + ea->ivlen, s->recivlen);
+	setupAESGCMstate(s->enckey, p, ea->keylen, nil, 0);
+}
+
+static void
+initclearenc(Encalg *ea, Secret *s, uchar *key, uchar *iv)
+{
+	s->enc = noenc;
+	s->dec = noenc;
+}
+
+static Encalg encrypttab[] =
+{
+	{ "clear", 0, 0, initclearenc },
+	{ "rc4_128", 128/8, 0, initRC4key },
+	{ "3des_ede_cbc", 3 * 8, 8, initDES3key },
+	{ "aes_128_cbc", 128/8, 16, initAESkey },
+	{ "aes_256_cbc", 256/8, 16, initAESkey },
+	{ "ccpoly64_aead", 256/8, 0, initccpolykey },
+	{ "ccpoly96_aead", 256/8, 96/8, initccpolykey },
+	{ "aes_128_gcm_aead", 128/8, 4, initaesgcmkey },
+	{ "aes_256_gcm_aead", 256/8, 4, initaesgcmkey },
+	{ 0 }
+};
+
+static Encalg*
+parseencalg(char *p)
+{
+	Encalg *ea;
+
+	for(ea = encrypttab; ea->name; ea++)
+		if(strcmp(p, ea->name) == 0)
+			return ea;
+	error("unsupported encryption algorithm");
+	return nil;
+}
+
+static long
+tlswrite(Chan *c, void *a, long n, vlong off)
+{
+	Encalg *ea;
+	Hashalg *ha;
+	TlsRec *volatile tr;
+	Secret *volatile tos, *volatile toc;
+	Block *volatile b;
+	Cmdbuf *volatile cb;
+	int m, ty;
+	char *p, *e;
+	uchar *volatile x;
+	ulong offset = off;
+
+	tr = tlsdevs[CONV(c->qid)];
+	if(tr == nil)
+		panic("tlswrite");
+
+	ty = TYPE(c->qid);
+	switch(ty){
+	case Qdata:
+	case Qhand:
+		p = a;
+		e = p + n;
+		do{
+			m = e - p;
+			if(m > c->iounit)
+				m = c->iounit;
+
+			b = allocb(m);
+			if(waserror()){
+				freeb(b);
+				nexterror();
+			}
+			memmove(b->wp, p, m);
+			poperror();
+			b->wp += m;
+
+			tlsbwrite(c, b, offset);
+			offset += m;
+
+			p += m;
+		}while(p < e);
+		return n;
+	case Qctl:
+		break;
+	default:
+		error(Ebadusefd);
+		return -1;
+	}
+
+	cb = parsecmd(a, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	if(cb->nf < 1)
+		error("short control request");
+
+	/* mutex with operations using what we're about to change */
+	if(waserror()){
+		qunlock(&tr->in.seclock);
+		qunlock(&tr->out.seclock);
+		nexterror();
+	}
+	qlock(&tr->in.seclock);
+	qlock(&tr->out.seclock);
+
+	if(strcmp(cb->f[0], "fd") == 0){
+		if(cb->nf != 3)
+			error("usage: fd open-fd version");
+		if(tr->c != nil)
+			error(Einuse);
+		m = strtol(cb->f[2], nil, 0);
+		if(m < MinProtoVersion || m > MaxProtoVersion)
+			error("unsupported version");
+		tr->c = buftochan(cb->f[1]);
+		tr->version = m;
+		tlsSetState(tr, SHandshake, SClosed);
+	}else if(strcmp(cb->f[0], "version") == 0){
+		if(cb->nf != 2)
+			error("usage: version vers");
+		if(tr->c == nil)
+			error("must set fd before version");
+		if(tr->verset)
+			error("version already set");
+		m = strtol(cb->f[1], nil, 0);
+		if(m < MinProtoVersion || m > MaxProtoVersion)
+			error("unsupported version");
+		if(m == SSL3Version)
+			tr->packAAD = sslPackAAD;
+		else
+			tr->packAAD = tlsPackAAD;
+		tr->verset = 1;
+		tr->version = m;
+	}else if(strcmp(cb->f[0], "secret") == 0){
+		if(cb->nf != 5)
+			error("usage: secret hashalg encalg isclient secretdata");
+		if(tr->c == nil || !tr->verset)
+			error("must set fd and version before secrets");
+
+		if(tr->in.new != nil){
+			freeSec(tr->in.new);
+			tr->in.new = nil;
+		}
+		if(tr->out.new != nil){
+			freeSec(tr->out.new);
+			tr->out.new = nil;
+		}
+
+		ha = parsehashalg(cb->f[1]);
+		ea = parseencalg(cb->f[2]);
+
+		p = cb->f[4];
+		m = (strlen(p)*3)/2 + 1;
+		x = secalloc(m);
+		tos = secalloc(sizeof(Secret));
+		toc = secalloc(sizeof(Secret));
+		if(waserror()){
+			secfree(x);
+			freeSec(tos);
+			freeSec(toc);
+			nexterror();
+		}
+
+		m = dec64(x, m, p, strlen(p));
+		memset(p, 0, strlen(p));
+		if(m < 2 * ha->maclen + 2 * ea->keylen + 2 * ea->ivlen)
+			error("not enough secret data provided");
+
+		if(!ha->initkey || !ea->initkey)
+			error("misimplemented secret algorithm");
+
+		(*ha->initkey)(ha, tr->version, tos, &x[0]);
+		(*ha->initkey)(ha, tr->version, toc, &x[ha->maclen]);
+		(*ea->initkey)(ea, tos, &x[2 * ha->maclen], &x[2 * ha->maclen + 2 * ea->keylen]);
+		(*ea->initkey)(ea, toc, &x[2 * ha->maclen + ea->keylen], &x[2 * ha->maclen + 2 * ea->keylen + ea->ivlen]);
+
+		if(!tos->aead_enc || !tos->aead_dec || !toc->aead_enc || !toc->aead_dec)
+			if(!tos->mac || !tos->enc || !tos->dec || !toc->mac || !toc->enc || !toc->dec)
+				error("missing algorithm implementations");
+
+		if(strtol(cb->f[3], nil, 0) == 0){
+			tr->in.new = tos;
+			tr->out.new = toc;
+		}else{
+			tr->in.new = toc;
+			tr->out.new = tos;
+		}
+		if(tr->version == SSL3Version){
+			toc->unpad = sslunpad;
+			tos->unpad = sslunpad;
+		}else{
+			toc->unpad = tlsunpad;
+			tos->unpad = tlsunpad;
+		}
+		toc->encalg = ea->name;
+		toc->hashalg = ha->name;
+		tos->encalg = ea->name;
+		tos->hashalg = ha->name;
+
+		secfree(x);
+		poperror();
+	}else if(strcmp(cb->f[0], "changecipher") == 0){
+		if(cb->nf != 1)
+			error("usage: changecipher");
+		if(tr->out.new == nil)
+			error("cannot change cipher spec without setting secret");
+
+		qunlock(&tr->in.seclock);
+		qunlock(&tr->out.seclock);
+		poperror();
+		free(cb);
+		poperror();
+
+		/*
+		 * the real work is done as the message is written
+		 * so the stream is encrypted in sync.
+		 */
+		b = allocb(1);
+		*b->wp++ = 1;
+		tlsrecwrite(tr, RChangeCipherSpec, b);
+		return n;
+	}else if(strcmp(cb->f[0], "opened") == 0){
+		if(cb->nf != 1)
+			error("usage: opened");
+		if(tr->in.sec == nil || tr->out.sec == nil)
+			error("cipher must be configured before enabling data messages");
+		lock(&tr->statelk);
+		if(tr->state != SHandshake && tr->state != SOpen){
+			unlock(&tr->statelk);
+			error("cannot enable data messages");
+		}
+		tr->state = SOpen;
+		unlock(&tr->statelk);
+		tr->opened = 1;
+	}else if(strcmp(cb->f[0], "alert") == 0){
+		if(cb->nf != 2)
+			error("usage: alert n");
+		m = strtol(cb->f[1], nil, 0);
+	Hangup:
+		if(tr->c == nil)
+			error("must set fd before sending alerts");
+		qunlock(&tr->in.seclock);
+		qunlock(&tr->out.seclock);
+		poperror();
+		free(cb);
+		poperror();
+
+		sendAlert(tr, m);
+
+		if(m == ECloseNotify)
+			tlsclosed(tr, SLClose);
+
+		return n;
+	} else if(strcmp(cb->f[0], "hangup") == 0){
+		m = ECloseNotify;
+		goto Hangup;
+	} else if(strcmp(cb->f[0], "debug") == 0){
+		if(cb->nf == 2){
+			if(strcmp(cb->f[1], "on") == 0)
+				tr->debug = 1;
+			else
+				tr->debug = 0;
+		} else
+			tr->debug = 1;
+	} else
+		error(Ebadarg);
+
+	qunlock(&tr->in.seclock);
+	qunlock(&tr->out.seclock);
+	poperror();
+	free(cb);
+	poperror();
+
+	return n;
+}
+
+static void
+tlsinit(void)
+{
+	struct Encalg *e;
+	struct Hashalg *h;
+	int n;
+	char *cp;
+
+	fmtinstall('H', encodefmt);
+
+	tlsdevs = smalloc(sizeof(TlsRec*) * maxtlsdevs);
+	trnames = smalloc((sizeof *trnames) * maxtlsdevs);
+
+	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 tlsdevtab = {
+	'a',
+	"tls",
+
+	devreset,
+	tlsinit,
+	devshutdown,
+	tlsattach,
+	tlswalk,
+	tlsstat,
+	tlsopen,
+	devcreate,
+	tlsclose,
+	tlsread,
+	tlsbread,
+	tlswrite,
+	tlsbwrite,
+	devremove,
+	tlswstat,
+};
+
+/* 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, ORDWR, 1, 1);	/* error check and inc ref */
+	return c;
+}
+
+static void
+sendAlert(TlsRec *tr, int err)
+{
+	Block *b;
+	int i, fatal;
+	char *msg;
+
+if(tr->debug)pprint("sendAlert %d\n", err);
+	fatal = 1;
+	msg = "tls unknown alert";
+	for(i=0; i < nelem(tlserrs); i++) {
+		if(tlserrs[i].err == err) {
+			msg = tlserrs[i].msg;
+			if(tr->version == SSL3Version)
+				err = tlserrs[i].sslerr;
+			else
+				err = tlserrs[i].tlserr;
+			fatal = tlserrs[i].fatal;
+			break;
+		}
+	}
+
+	if(!waserror()){
+		b = allocb(2);
+		*b->wp++ = fatal + 1;
+		*b->wp++ = err;
+		if(fatal)
+			tlsSetState(tr, SAlert, SOpen|SHandshake|SRClose);
+		tlsrecwrite(tr, RAlert, b);
+		poperror();
+	}
+	if(fatal)
+		tlsError(tr, msg);
+}
+
+static void
+tlsError(TlsRec *tr, char *msg)
+{
+	int s;
+
+if(tr->debug)pprint("tlsError %s\n", msg);
+	lock(&tr->statelk);
+	s = tr->state;
+	tr->state = SError;
+	if(s != SError){
+		strncpy(tr->err, msg, ERRMAX - 1);
+		tr->err[ERRMAX - 1] = '\0';
+	}
+	unlock(&tr->statelk);
+	if(s != SError)
+		alertHand(tr, msg);
+}
+
+static void
+tlsSetState(TlsRec *tr, int new, int old)
+{
+	lock(&tr->statelk);
+	if(tr->state & old)
+		tr->state = new;
+	unlock(&tr->statelk);
+}
+
+/* hand up a digest connection */
+static void
+tlshangup(TlsRec *tr)
+{
+	Block *b;
+
+	qlock(&tr->in.io);
+	for(b = tr->processed; b; b = tr->processed){
+		tr->processed = b->next;
+		freeb(b);
+	}
+	if(tr->unprocessed != nil){
+		freeb(tr->unprocessed);
+		tr->unprocessed = nil;
+	}
+	qunlock(&tr->in.io);
+
+	tlsSetState(tr, SClosed, ~0);
+}
+
+static TlsRec*
+newtls(Chan *ch)
+{
+	TlsRec **pp, **ep, **np;
+	char **nmp;
+	int t, newmax;
+
+	if(waserror()) {
+		unlock(&tdlock);
+		nexterror();
+	}
+	lock(&tdlock);
+	ep = &tlsdevs[maxtlsdevs];
+	for(pp = tlsdevs; pp < ep; pp++)
+		if(*pp == nil)
+			break;
+	if(pp >= ep) {
+		if(maxtlsdevs >= MaxTlsDevs) {
+			unlock(&tdlock);
+			poperror();
+			return nil;
+		}
+		newmax = 2 * maxtlsdevs;
+		if(newmax > MaxTlsDevs)
+			newmax = MaxTlsDevs;
+		np = smalloc(sizeof(TlsRec*) * newmax);
+		memmove(np, tlsdevs, sizeof(TlsRec*) * maxtlsdevs);
+		tlsdevs = np;
+		pp = &tlsdevs[maxtlsdevs];
+		memset(pp, 0, sizeof(TlsRec*)*(newmax - maxtlsdevs));
+
+		nmp = smalloc(sizeof *nmp * newmax);
+		memmove(nmp, trnames, sizeof *nmp * maxtlsdevs);
+		trnames = nmp;
+
+		maxtlsdevs = newmax;
+	}
+	*pp = mktlsrec();
+	if(pp - tlsdevs >= tdhiwat)
+		tdhiwat++;
+	t = TYPE(ch->qid);
+	if(t == Qclonus)
+		t = Qctl;
+	ch->qid.path = QID(pp - tlsdevs, t);
+	ch->qid.vers = 0;
+	unlock(&tdlock);
+	poperror();
+	return *pp;
+}
+
+static TlsRec *
+mktlsrec(void)
+{
+	TlsRec *tr;
+
+	tr = mallocz(sizeof(*tr), 1);
+	if(tr == nil)
+		error(Enomem);
+	tr->state = SClosed;
+	tr->ref = 1;
+	kstrdup(&tr->user, up->user);
+	tr->perm = 0660;
+	return tr;
+}
+
+static char*
+tlsstate(int s)
+{
+	switch(s){
+	case SHandshake:
+		return "Handshaking";
+	case SOpen:
+		return "Established";
+	case SRClose:
+		return "RemoteClosed";
+	case SLClose:
+		return "LocalClosed";
+	case SAlert:
+		return "Alerting";
+	case SError:
+		return "Errored";
+	case SClosed:
+		return "Closed";
+	}
+	return "Unknown";
+}
+
+static void
+freeSec(Secret *s)
+{
+	if(s == nil)
+		return;
+	secfree(s->enckey);
+	secfree(s);
+}
+
+static int
+noenc(Secret *sec, uchar *buf, int n)
+{
+	return n;
+}
+
+static int
+rc4enc(Secret *sec, uchar *buf, int n)
+{
+	rc4(sec->enckey, buf, n);
+	return n;
+}
+
+static int
+tlsunpad(uchar *buf, int n, int block)
+{
+	int pad, nn;
+
+	pad = buf[n - 1];
+	nn = n - 1 - pad;
+	if(nn <= 0 || n % block)
+		return -1;
+	while(--n > nn)
+		if(pad != buf[n - 1])
+			return -1;
+	return nn;
+}
+
+static int
+sslunpad(uchar *buf, int n, int block)
+{
+	int pad, nn;
+
+	pad = buf[n - 1];
+	nn = n - 1 - pad;
+	if(nn <= 0 || n % block)
+		return -1;
+	return nn;
+}
+
+static int
+blockpad(uchar *buf, int n, int block)
+{
+	int pad, nn;
+
+	nn = n + block;
+	nn -= nn % block;
+	pad = nn - (n + 1);
+	while(n < nn)
+		buf[n++] = pad;
+	return nn;
+}
+		
+static int
+des3enc(Secret *sec, uchar *buf, int n)
+{
+	n = blockpad(buf, n, 8);
+	des3CBCencrypt(buf, n, sec->enckey);
+	return n;
+}
+
+static int
+des3dec(Secret *sec, uchar *buf, int n)
+{
+	des3CBCdecrypt(buf, n, sec->enckey);
+	return (*sec->unpad)(buf, n, 8);
+}
+
+static int
+aesenc(Secret *sec, uchar *buf, int n)
+{
+	n = blockpad(buf, n, 16);
+	aesCBCencrypt(buf, n, sec->enckey);
+	return n;
+}
+
+static int
+aesdec(Secret *sec, uchar *buf, int n)
+{
+	aesCBCdecrypt(buf, n, sec->enckey);
+	return (*sec->unpad)(buf, n, 16);
+}
+
+static void
+ccpoly_aead_setiv(Secret *sec, uchar seq[8])
+{
+	uchar iv[ChachaIVlen];
+	Chachastate *cs;
+	int i;
+
+	cs = (Chachastate*)sec->enckey;
+	if(cs->ivwords == 2){
+		chacha_setiv(cs, seq);
+		return;
+	}
+
+	memmove(iv, sec->mackey, ChachaIVlen);
+	for(i=0; i<8; i++)
+		iv[i+(ChachaIVlen-8)] ^= seq[i];
+
+	chacha_setiv(cs, iv);
+
+	memset(iv, 0, sizeof(iv));
+}
+
+static int
+ccpoly_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+	USED(reciv);
+	ccpoly_aead_setiv(sec, aad);
+	ccpoly_encrypt(data, len, aad, aadlen, data+len, sec->enckey);
+	return len + sec->maclen;
+}
+
+static int
+ccpoly_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+	USED(reciv);
+	len -= sec->maclen;
+	if(len < 0)
+		return -1;
+	ccpoly_aead_setiv(sec, aad);
+	if(ccpoly_decrypt(data, len, aad, aadlen, data+len, sec->enckey) != 0)
+		return -1;
+	return len;
+}
+
+static int
+aesgcm_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+	uchar iv[12];
+	int i;
+
+	memmove(iv, sec->mackey, 4+8);
+	for(i=0; i<8; i++) iv[4+i] ^= aad[i];
+	memmove(reciv, iv+4, 8);
+	aesgcm_setiv(sec->enckey, iv, 12);
+	memset(iv, 0, sizeof(iv));
+	aesgcm_encrypt(data, len, aad, aadlen, data+len, sec->enckey);
+	return len + sec->maclen;
+}
+
+static int
+aesgcm_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+	uchar iv[12];
+
+	len -= sec->maclen;
+	if(len < 0)
+		return -1;
+	memmove(iv, sec->mackey, 4);
+	memmove(iv+4, reciv, 8);
+	aesgcm_setiv(sec->enckey, iv, 12);
+	memset(iv, 0, sizeof(iv));
+	if(aesgcm_decrypt(data, len, aad, aadlen, data+len, sec->enckey) != 0)
+		return -1;
+	return len;
+}
+
+
+static DigestState*
+nomac(uchar *p, ulong len, uchar *key, ulong keylen, uchar *digest, DigestState *s)
+{
+	return nil;
+}
+
+/*
+ * sslmac: mac calculations for ssl 3.0 only; tls 1.0 uses the standard hmac.
+ */
+static DigestState*
+sslmac_x(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s,
+	DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, int padlen)
+{
+	int i;
+	uchar pad[48], innerdigest[20];
+
+	if(xlen > sizeof(innerdigest)
+	|| padlen > sizeof(pad))
+		return nil;
+
+	if(klen>64)
+		return nil;
+
+	/* first time through */
+	if(s == nil){
+		for(i=0; i<padlen; i++)
+			pad[i] = 0x36;
+		s = (*x)(key, klen, nil, nil);
+		s = (*x)(pad, padlen, nil, s);
+		if(s == nil)
+			return nil;
+	}
+
+	s = (*x)(p, len, nil, s);
+	if(digest == nil)
+		return s;
+
+	/* last time through */
+	for(i=0; i<padlen; i++)
+		pad[i] = 0x5c;
+	(*x)(nil, 0, innerdigest, s);
+	s = (*x)(key, klen, nil, nil);
+	s = (*x)(pad, padlen, nil, s);
+	(*x)(innerdigest, xlen, digest, s);
+	return nil;
+}
+
+static DigestState*
+sslmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+	return sslmac_x(p, len, key, klen, digest, s, sha1, SHA1dlen, 40);
+}
+
+static DigestState*
+sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+	return sslmac_x(p, len, key, klen, digest, s, md5, MD5dlen, 48);
+}
+
+static int
+sslPackAAD(u64int seq, uchar *hdr, uchar *aad)
+{
+	put64(aad, seq);
+	aad[8] = hdr[0];
+	aad[9] = hdr[3];
+	aad[10] = hdr[4];
+	return 11;
+}
+
+static int
+tlsPackAAD(u64int seq, uchar *hdr, uchar *aad)
+{
+	put64(aad, seq);
+	aad[8] = hdr[0];
+	aad[9] = hdr[1];
+	aad[10] = hdr[2];
+	aad[11] = hdr[3];
+	aad[12] = hdr[4];
+	return 13;
+}
+
+static void
+packMac(Secret *sec, uchar *aad, int aadlen, uchar *body, int bodylen, uchar *mac)
+{
+	DigestState *s;
+
+	s = (*sec->mac)(aad, aadlen, sec->mackey, sec->maclen, nil, nil);
+	(*sec->mac)(body, bodylen, sec->mackey, sec->maclen, mac, s);
+}
+
+static void
+put32(uchar *p, u32int x)
+{
+	p[0] = x>>24;
+	p[1] = x>>16;
+	p[2] = x>>8;
+	p[3] = x;
+}
+
+static void
+put64(uchar *p, u64int x)
+{
+	put32(p, x >> 32);
+	put32(p+4, 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 int
+get16(uchar *p)
+{
+	return (p[0]<<8)|p[1];
+}
+
+static char *charmap = "0123456789abcdef";
+
+static void
+pdump(int len, void *a, char *tag)
+{
+	uchar *p;
+	int i;
+	char buf[65+32];
+	char *q;
+
+	p = a;
+	strcpy(buf, tag);
+	while(len > 0){
+		q = buf + strlen(tag);
+		for(i = 0; len > 0 && i < 32; i++){
+			if(*p >= ' ' && *p < 0x7f){
+				*q++ = ' ';
+				*q++ = *p;
+			} else {
+				*q++ = charmap[*p>>4];
+				*q++ = charmap[*p & 0xf];
+			}
+			len--;
+			p++;
+		}
+		*q = 0;
+
+		if(len > 0)
+			pprint("%s...\n", buf);
+		else
+			pprint("%s\n", buf);
+	}
+}
--- /dev/null
+++ b/kern/error.c
@@ -1,0 +1,51 @@
+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";
+char Etoolong[] = "name too long";
--- /dev/null
+++ b/kern/error.h
@@ -1,0 +1,51 @@
+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 */
+extern char Etoolong[];		/* name too long */
--- /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);
+	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,229 @@
+#define	ROUND(s, sz)	(((s)+((sz)-1))&~((sz)-1))
+
+Block*		adjustblock(Block*, int);
+Block*		allocb(int);
+int		blocklen(Block*);
+char*		chanpath(Chan*);
+int		cangetc(void*);
+int		canlock(Lock*);
+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		checkb(Block*, char*);
+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*);
+void		cmderror(Cmdbuf*, char*);
+int		cmount(Chan*, Chan*, int, char*);
+Block*		concatblock(Block*);
+Block*		copyblock(Block*, int);
+void		cunmount(Chan*, Chan*);
+int		decref(Ref*);
+Chan*		devattach(int, char*);
+Block*		devbread(Chan*, long, ulong);
+long		devbwrite(Chan*, Block*, ulong);
+Chan*		devclone(Chan*);
+int		devconfig(int, char *, DevConf *);
+Chan*		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);
+Dir*		dirchanstat(Chan*);
+void		drawcmap(void);
+Fgrp*		dupfgrp(Fgrp*);
+int		emptystr(char*);
+void		envcpy(Egrp*, Egrp*);
+int		eqchan(Chan*, Chan*, int);
+int		eqqid(Qid, Qid);
+void		error(char*);
+void		exhausted(char*);
+void		exit(int);
+Chan*		fdtochan(int, int, int, int);
+void		free(void*);
+void		freeb(Block*);
+void		freeblist(Block*);
+uintptr		getmalloctag(void*);
+uintptr		getrealloctag(void*);
+void		gotolabel(Label*);
+char*		getconfenv(void);
+long		hostdomainwrite(char*, int);
+long		hostownerwrite(char*, int);
+Block*		iallocb(int);
+void		ilock(Lock*);
+void		iunlock(Lock*);
+int		incref(Ref*);
+int		iprint(char*, ...);
+void		isdir(Chan*);
+int		iseve(void);
+#define	islo()	(0)
+int		kbdputc(Queue*, int);
+void		kbdkey(Rune, int);
+int		kproc(char*, void(*)(void*), void*);
+void		ksetenv(char*, char*, int);
+void		kstrcpy(char*, char*, int);
+void		kstrdup(char**, char*);
+long		latin1(Rune*, int);
+Chan*		lfdchan(void *);
+void		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*		mallocz(ulong, int);
+#define		malloc kmalloc
+void*		malloc(ulong);
+void		mkqid(Qid*, vlong, ulong, int);
+Chan*		mntauth(Chan*, char*);
+void		mntdump(void);
+long		mntversion(Chan*, char*, int, int);
+Chan*		mntattach(Chan*, Chan*, char*, int);
+void		mountfree(Mount*);
+void		muxclose(Mnt*);
+Chan*		namec(char*, int, int, ulong);
+Chan*		newchan(void);
+int		newfd(Chan*);
+Mhead*		newmhead(Chan*);
+Mount*		newmount(Chan*, int, char*);
+Path*		newpath(char*);
+Pgrp*		newpgrp(void);
+Rgrp*		newrgrp(void);
+Proc*		newproc(void);
+char*		nextelem(char*, char*);
+void		nexterror(void);
+int		openmode(ulong);
+void*		oscmd(char**, int, char*, Chan**);
+int		oscmdwait(void*, char*, int);
+int		oscmdkill(void*);
+void		oscmdfree(void*);
+void		oserrstr(void);
+void		oserror(void);
+void		osexit(void);
+Block*		packblock(Block*);
+Block*		padblock(Block*, int);
+void		panic(char*, ...);
+Cmdbuf*		parsecmd(char *a, int n);
+void		pathclose(Path*);
+void		pexit(char*, int);
+void		printinit(void);
+int		procindex(ulong);
+void		pgrpcpy(Pgrp*, Pgrp*);
+void		pgrpnote(ulong, char*, long, int);
+Pgrp*		pgrptab(int);
+#define		poperror()		up->nerrlab--
+int		postnote(Proc*, int, char*, int);
+int		pprint(char*, ...);
+int		procfdprint(Chan*, int, int, char*, int);
+void		procinit0(void);
+Proc*		proctab(int);
+void		procwired(Proc*, int);
+int		pullblock(Block**, int);
+Block*		pullupblock(Block*, int);
+Block*		pullupqueue(Queue*, int);
+void		putmhead(Mhead*);
+void		putstr(char*);
+void		putstrn(char*, int);
+Label*	pwaserror(void);
+long		readblist(Block*, uchar*, long, ulong);
+int		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);
+void		randominit(void);
+ulong		randomread(void*, ulong);
+int		readnum(ulong, char*, ulong, ulong, int);
+int		readstr(ulong, char*, ulong, char*);
+int		return0(void*);
+void		rlock(RWlock*);
+void		runlock(RWlock*);
+extern void		(*screenputs)(char*, int);
+void*		secalloc(ulong);
+void		secfree(void*);
+long		seconds(void);
+int		setlabel(Label*);
+void		setmalloctag(void*, uintptr);
+void		setrealloctag(void*, uintptr);
+long		showfilewrite(char*, int);
+char*		skipslash(char*);
+void		sleep(Rendez*, int(*)(void*), void*);
+void*		smalloc(ulong);
+int		splhi(void);
+int		spllo(void);
+void		splx(int);
+Block*		trimblock(Block*, int, int);
+long		unionread(Chan*, void*, long);
+void		unlock(Lock*);
+#define	validaddr(a, b, c)
+void		validname(char*, int);
+char*		validnamedup(char*, int);
+void		validstat(uchar*, int);
+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		osyield(void);
+void		osmsleep(int);
+ulong	ticks(void);
+void	osproc(Proc*);
+void	osnewproc(Proc*);
+void	procsleep(void);
+void	procwakeup(Proc*);
+void	osinit(void);
+void	screeninit(void);
+extern	void	terminit(void);
+extern	void	setterm(int);
--- /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/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,214 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+static Ref mountid;
+
+Pgrp*
+newpgrp(void)
+{
+	Pgrp *p;
+
+	p = smalloc(sizeof(Pgrp));
+	p->ref.ref = 1;
+	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;
+	Mount *m;
+
+	if(decref(&p->ref))
+		return;
+
+	e = &p->mnthash[MNTHASH];
+	for(h = p->mnthash; h < e; h++) {
+		while((f = *h) != nil){
+			*h = f->hash;
+			wlock(&f->lock);
+			m = f->mount;
+			f->mount = nil;
+			wunlock(&f->lock);
+			mountfree(m);
+			putmhead(f);
+		}
+	}
+	free(p);
+}
+
+void
+pgrpinsert(Mount **order, Mount *m)
+{
+	Mount *f;
+
+	m->order = nil;
+	if(*order == nil) {
+		*order = m;
+		return;
+	}
+	for(f = *order; f != nil; 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)
+{
+	Mount *n, *m, **link, *order;
+	Mhead *f, **tom, **l, *mh;
+	int i;
+
+	wlock(&to->ns);
+	rlock(&from->ns);
+	order = nil;
+	tom = to->mnthash;
+	for(i = 0; i < MNTHASH; i++) {
+		l = tom++;
+		for(f = from->mnthash[i]; f != nil; f = f->hash) {
+			rlock(&f->lock);
+			mh = newmhead(f->from);
+			*l = mh;
+			l = &mh->hash;
+			link = &mh->mount;
+			for(m = f->mount; m != nil; m = m->next) {
+				n = smalloc(sizeof(Mount));
+				n->mountid = m->mountid;
+				n->mflag = m->mflag;
+				n->to = m->to;
+				incref(&n->to->ref);
+				if(m->spec != nil)
+					kstrdup(&n->spec, m->spec);
+				pgrpinsert(&order, n);
+				*link = n;
+				link = &n->next;
+			}
+			runlock(&f->lock);
+		}
+	}
+	/*
+	 * Allocate mount ids in the same sequence as the parent group
+	 */
+	for(m = order; m != nil; m = m->order)
+		m->mountid = incref(&mountid);
+	runlock(&from->ns);
+	wunlock(&to->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 == nil){
+		unlock(&f->ref.lk);
+		free(new);
+		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]) != nil){
+			incref(&c->ref);
+			new->fd[i] = c;
+		}
+	}
+	unlock(&f->ref.lk);
+
+	return new;
+}
+
+void
+closefgrp(Fgrp *f)
+{
+	int i;
+	Chan *c;
+
+	if(f == nil || decref(&f->ref))
+		return;
+
+	for(i = 0; i <= f->maxfd; i++)
+		if((c = f->fd[i]) != nil){
+			f->fd[i] = nil;
+			cclose(c);
+		}
+
+	free(f->fd);
+	free(f);
+}
+
+Mount*
+newmount(Chan *to, int flag, char *spec)
+{
+	Mount *m;
+
+	m = smalloc(sizeof(Mount));
+	m->to = to;
+	incref(&to->ref);
+	m->mountid = incref(&mountid);
+	m->mflag = flag;
+	if(spec != nil)
+		kstrdup(&m->spec, spec);
+
+	return m;
+}
+
+void
+mountfree(Mount *m)
+{
+	Mount *f;
+
+	while((f = m) != nil) {
+		m = m->next;
+		cclose(f->to);
+		free(f->spec);
+		free(f);
+	}
+}
--- /dev/null
+++ b/kern/posix.c
@@ -1,0 +1,326 @@
+/*
+ * Posix generic OS implementation for drawcpu.
+ */
+
+#include "u.h"
+
+#ifndef _XOPEN_SOURCE	/* for Apple and OpenBSD; not sure if needed */
+#define _XOPEN_SOURCE 500
+#endif
+
+#include <pthread.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <termios.h>
+
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Oproc Oproc;
+struct Oproc
+{
+	int nsleep;
+	int nwakeup;
+	pthread_mutex_t mutex;
+	pthread_cond_t cond;
+};
+
+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");
+
+	signal(SIGPIPE, SIG_IGN);
+}
+
+void
+osnewproc(Proc *p)
+{
+	Oproc *op;
+	pthread_mutexattr_t attr;
+
+	op = (Oproc*)p->oproc;
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+	pthread_mutex_init(&op->mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+	pthread_cond_init(&op->cond, 0);
+}
+
+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;
+
+	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 *vp)
+{
+	Proc *p;
+
+	p = vp;
+	if(pthread_setspecific(prdakey, p))
+		panic("cannot setspecific");
+	(*p->fn)(p->arg);
+	pexit("", 0);
+	return 0;
+}
+
+void
+osproc(Proc *p)
+{
+	pthread_t pid;
+
+	if(pthread_create(&pid, nil, tramp, p)){
+		oserrstr();
+		panic("osproc: %r");
+	}
+	sched_yield();
+}
+
+void
+osexit(void)
+{
+	pthread_setspecific(prdakey, 0);
+	pthread_exit(0);
+}
+
+void
+procsleep(void)
+{
+	Proc *p;
+	Oproc *op;
+
+	p = up;
+	op = (Oproc*)p->oproc;
+	pthread_mutex_lock(&op->mutex);
+	op->nsleep++;
+	while(op->nsleep > op->nwakeup)
+		pthread_cond_wait(&op->cond, &op->mutex);
+	pthread_mutex_unlock(&op->mutex);
+}
+
+void
+procwakeup(Proc *p)
+{
+	Oproc *op;
+
+	op = (Oproc*)p->oproc;
+	pthread_mutex_lock(&op->mutex);
+	op->nwakeup++;
+	if(op->nwakeup == op->nsleep)
+		pthread_cond_signal(&op->cond);
+	pthread_mutex_unlock(&op->mutex);
+}
+
+#undef chdir
+#undef pipe
+#undef fork
+#undef close
+void*
+oscmd(char **argv, int nice, char *dir, Chan **fd)
+{
+	int p[3][2];
+	int i, pid;
+
+	for(i = 0; i<3; i++){
+		if(pipe(p[i]) < 0){
+			while(--i >= 0){
+				close(p[i][0]);
+				close(p[i][1]);
+			}
+			oserror();
+		}
+	}
+	if(waserror()){
+		for(i = 0; i < 3; i++){
+			close(p[i][0]);
+			close(p[i][1]);
+		}
+		nexterror();
+	}
+	pid = fork();
+	if(pid == -1)
+		oserror();
+	if(pid == 0){
+		setsid();
+		dup2(p[0][0], 0);
+		dup2(p[1][1], 1);
+		dup2(p[2][1], 2);
+		for(i = 3; i < 1000; i++)
+			close(i);
+		if(chdir(dir) < 0){
+			perror("chdir");
+			_exit(1);
+		}
+		execvp(argv[0], argv);
+		perror("exec");
+		_exit(1);
+	}
+	poperror();
+	close(p[0][0]);
+	close(p[1][1]);
+	close(p[2][1]);
+
+	fd[0] = lfdchan((void*)(uintptr)p[0][1]);
+	fd[1] = lfdchan((void*)(uintptr)p[1][0]);
+	fd[2] = lfdchan((void*)(uintptr)p[2][0]);
+
+	return (void*)(uintptr)pid;
+}
+
+int
+oscmdwait(void *c, char *status, int nstatus)
+{
+	int pid = (int)(uintptr)c;
+	int s = -1;
+
+	if(waitpid(pid, &s, 0) < 0)
+		return -1;
+	if(WIFEXITED(s)){
+		if((s = WEXITSTATUS(s)) == 0)
+			return snprint(status, nstatus, "%d 0 0 0 ''", pid);
+		return snprint(status, nstatus, "%d 0 0 0 'exit: %d'", pid, s);
+	}
+	if(WIFSIGNALED(s)){
+		switch(s = WTERMSIG(s)){
+		case SIGTERM:
+		case SIGKILL:
+			return snprint(status, nstatus, "%d 0 0 0 killed", pid);
+		}
+		return snprint(status, nstatus, "%d 0 0 0 'signal: %d'", pid, s);
+	}
+	return snprint(status, nstatus, "%d 0 0 0 'odd status: 0x%x'", pid, s);
+}
+
+int
+oscmdkill(void *c)
+{
+	int pid = (int)(uintptr)c;
+	return kill(pid, SIGTERM);
+}
+
+void
+oscmdfree(void *c)
+{
+	USED(c);
+}
+
+static 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;
+}
+
+long
+showfilewrite(char *a, int n)
+{
+	error("not implemented");
+	return -1;
+}
+
+void
+setterm(int raw)
+{
+	struct termios t;
+
+	if(tcgetattr(0, &t) < 0)
+		return;
+	if(raw)
+		t.c_lflag &= ~(ECHO|ICANON);
+	else
+		t.c_lflag |= (ECHO|ICANON);
+	tcsetattr(0, TCSAFLUSH, &t);
+}
--- /dev/null
+++ b/kern/procinit.c
@@ -1,0 +1,92 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+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);
+	pathclose(up->slash->path);
+	up->slash->path = newpath("/");
+	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, "drawcpu");
+	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 != nil)
+		incref(&p->rgrp->ref);
+	p->pgrp = up->pgrp;
+	if(up->pgrp != nil)
+		incref(&up->pgrp->ref);
+	p->fgrp = up->fgrp;
+	if(p->fgrp != nil)
+		incref(&p->fgrp->ref);
+	strecpy(p->text, p->text+sizeof p->text, name);
+
+	osproc(p);
+	return p->pid;
+}
+
+void
+pexit(char *msg, int freemem)
+{
+	Proc *p = up;
+
+	USED(msg);
+	USED(freemem);
+
+	if(p->pgrp != nil){
+		closepgrp(p->pgrp);
+		p->pgrp = nil;
+	}
+	if(p->rgrp != nil){
+		closergrp(p->rgrp);
+		p->rgrp = nil;
+	}
+	if(p->fgrp != nil){
+		closefgrp(p->fgrp);
+		p->fgrp = nil;
+	}
+
+	cclose(p->dot);
+	cclose(p->slash);
+
+	free(p);
+	osexit();
+}
--- /dev/null
+++ b/kern/qio.c
@@ -1,0 +1,1406 @@
+#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;
+
+#define QDEBUG	if(0)
+
+/*
+ *  IO queues
+ */
+typedef struct Queue	Queue;
+
+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;
+
+/*
+ *  free a list of blocks
+ */
+void
+freeblist(Block *b)
+{
+	Block *next;
+
+	for(; b != nil; b = next){
+		next = b->next;
+		b->next = nil;
+		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 0");
+	if(size >= 0){
+		if(bp->rp - bp->base >= size){
+			bp->rp -= size;
+			return bp;
+		}
+		n = BLEN(bp);
+		nbp = allocb(size+n);
+		nbp->rp += size;
+		nbp->wp = nbp->rp;
+		memmove(nbp->wp, bp->rp, n);
+		nbp->wp += n;
+		nbp->rp -= size;
+	} else {
+		size = -size;
+		if(bp->lim - bp->wp >= size)
+			return bp;
+		n = BLEN(bp);
+		nbp = allocb(n+size);
+		memmove(nbp->wp, bp->rp, n);
+		nbp->wp += n;
+	}
+	nbp->next = bp->next;
+	freeb(bp);
+	padblockcnt++;
+	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 != nil) {
+		len += BLEN(bp);
+		bp = bp->next;
+	}
+	return len;
+}
+
+/*
+ * return count of space in blocks
+ */
+int
+blockalloclen(Block *bp)
+{
+	int len;
+
+	len = 0;
+	while(bp != nil) {
+		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;
+
+	if(bp->next == nil)
+		return bp;
+	len = blocklen(bp);
+	concatblockcnt += len;
+	return pullupblock(bp, len);
+}
+
+/*
+ *  make sure the first block has at least n bytes
+ */
+Block*
+pullupblock(Block *bp, int n)
+{
+	Block *nbp;
+	int i;
+
+	/*
+	 *  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) != nil){
+		pullupblockcnt++;
+		i = BLEN(nbp);
+		if(i > n) {
+			memmove(bp->wp, nbp->rp, n);
+			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, called from %#p\n",
+					getcallerpc(&bp));
+				i = 0;
+			}
+			memmove(bp->wp, nbp->rp, i);
+			bp->wp += i;
+			bp->next = nbp->next;
+			nbp->next = nil;
+			freeb(nbp);
+			n -= i;
+			if(n == 0){
+				QDEBUG checkb(bp, "pullupblock 2");
+				return bp;
+			}
+		}
+	}
+	freeb(bp);
+	return nil;
+}
+
+/*
+ *  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 != nil) {
+		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 != nil; 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;
+	}
+	QDEBUG checkb(b, "qget");
+	q->bfirst = b->next;
+	b->next = nil;
+	q->len -= BALLOC(b);
+	q->dlen -= BLEN(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);
+
+	return b;
+}
+
+/*
+ *  throw away the next 'len' bytes in the queue
+ */
+int
+qdiscard(Queue *q, int len)
+{
+	Block *b, *tofree = nil;
+	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;
+			q->len -= BALLOC(b);
+			q->dlen -= BLEN(b);
+
+			/* remember to free this */
+			b->next = tofree;
+			tofree = 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);
+
+	if(tofree != nil)
+		freeblist(tofree);
+
+	return sofar;
+}
+
+/*
+ *  Interrupt level copy out of a queue, return # bytes copied.
+ */
+int
+qconsume(Queue *q, void *vp, int len)
+{
+	Block *b, *tofree = nil;
+	int n, dowakeup;
+	uchar *p = vp;
+
+	/* sync with qwrite */
+	ilock(&q->lk);
+
+	for(;;) {
+		b = q->bfirst;
+		if(b == nil){
+			q->state |= Qstarve;
+			len = -1;
+			goto out;
+		}
+		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;
+	};
+
+	consumecnt += n;
+	if(n < len)
+		len = n;
+	memmove(p, b->rp, len);
+	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;
+		q->len -= BALLOC(b);
+		q->dlen -= BLEN(b);
+
+		/* remember to free this */
+		b->next = tofree;
+		tofree = b;
+	}
+
+out:
+	/* 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 len, dowakeup;
+
+	/* sync with qread */
+	dowakeup = 0;
+	ilock(&q->lk);
+	if(q->len >= q->limit){
+		iunlock(&q->lk);
+		freeblist(b);
+		return -1;
+	}
+	if(q->state & Qclosed){
+		iunlock(&q->lk);
+		freeblist(b);
+		return 0;
+	}
+
+	len = qaddlist(q, b);
+
+	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 len, dowakeup;
+
+	/* sync with qread */
+	dowakeup = 0;
+	ilock(&q->lk);
+
+	if(q->state & Qclosed){
+		iunlock(&q->lk);
+		freeblist(b);
+		return 0;
+	}
+
+	len = qaddlist(q, b);
+
+	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; (nbp = *l) != nil; l = &(*l)->next){
+		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;
+
+	b = iallocb(len);
+	if(b == nil)
+		return 0;
+
+	/* 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);
+		freeb(b);
+		return -1;
+	}
+	producecnt += len;
+
+	/* save in buffer */
+	memmove(b->wp, p, len);
+	b->wp += len;
+	qaddlist(q, b);
+
+	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)
+{
+	Block *b;
+
+	b = allocb(len);
+	ilock(&q->lk);
+	b->wp += readblist(q->bfirst, b->wp, len, offset);
+	iunlock(&q->lk);
+	return b;
+}
+
+/*
+ *  called by non-interrupt code
+ */
+Queue*
+qopen(int limit, int msg, void (*kick)(void*), void *arg)
+{
+	Queue *q;
+
+	q = malloc(sizeof(Queue));
+	if(q == nil)
+		return nil;
+
+	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 == nil)
+		return nil;
+
+	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 != nil;
+}
+
+/*
+ *  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, return bytes added
+ */
+int
+qaddlist(Queue *q, Block *b)
+{
+	int len, dlen;
+
+	QDEBUG checkb(b, "qaddlist 1");
+
+	/* queue the block */
+	if(q->bfirst != nil)
+		q->blast->next = b;
+	else
+		q->bfirst = b;
+
+	len = BALLOC(b);
+	dlen = BLEN(b);
+	while(b->next != nil){
+		b = b->next;
+		QDEBUG checkb(b, "qaddlist 2");
+
+		len += BALLOC(b);
+		dlen += BLEN(b);
+	}
+	q->blast = b;
+	q->len += len;
+	q->dlen += dlen;
+	return dlen;
+}
+
+/*
+ *  called with q ilocked
+ */
+Block*
+qremove(Queue *q)
+{
+	Block *b;
+
+	b = q->bfirst;
+	if(b == nil)
+		return nil;
+	QDEBUG checkb(b, "qremove");
+	q->bfirst = b->next;
+	b->next = nil;
+	q->dlen -= BLEN(b);
+	q->len -= BALLOC(b);
+	return b;
+}
+
+/*
+ *  copy the contents of a string of blocks into
+ *  memory from an offset. blocklist kept unchanged.
+ *  return number of copied bytes.
+ */
+long
+readblist(Block *b, uchar *p, long n, ulong o)
+{
+	ulong m, r;
+
+	r = 0;
+	while(n > 0 && b != nil){
+		m = BLEN(b);
+		if(o >= m)
+			o -= m;
+		else {
+			m -= o;
+			if(n < m)
+				m = n;
+			memmove(p, b->rp + o, m);
+			p += m;
+			r += m;
+			n -= m;
+			o = 0;
+		}
+		b = b->next;
+	}
+	return r;
+}
+
+/*
+ *  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);
+}
+
+/*
+ *  cut off n bytes from the end of *h. return a new
+ *  block with the tail and change *h to refer to the
+ *  head.
+ */
+static Block*
+splitblock(Block **h, int n)
+{
+	Block *a, *b;
+	int m;
+
+	a = *h;
+	m = BLEN(a) - n;
+	if(m < n){
+		b = allocb(m);
+		memmove(b->wp, a->rp, m);
+		b->wp += m;
+		a->rp += m;
+		*h = b;
+		return a;
+	} else {
+		b = allocb(n);
+		a->wp -= n;
+		memmove(b->wp, a->wp, n);
+		b->wp += n;
+		return 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 != nil)
+			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;
+	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 */
+	if(n > len){
+		n -= len;
+		if((q->state & Qmsg) == 0)
+			qputback(q, splitblock(&b, n));
+		else
+			b->wp -= n;
+	}
+
+	/* restart producer */
+	qwakeup_iunlock(q);
+
+	qunlock(&q->rlock);
+	poperror();
+
+	return b;
+}
+
+/*
+ *  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, **last;
+	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 */
+	last = &first;
+	if(q->state & Qcoalesce){
+		/* when coalescing, 0 length blocks just go away */
+		b = q->bfirst;
+		m = BLEN(b);
+		if(m <= 0){
+			freeb(qremove(q));
+			goto again;
+		}
+
+		/*  grab the first block plus as many
+		 *  following blocks as will partially
+		 *  fit in the read.
+		 */
+		n = 0;
+		for(;;) {
+			*last = qremove(q);
+			n += m;
+			if(n >= len || q->bfirst == nil)
+				break;
+			last = &b->next;
+			b = q->bfirst;
+			m = BLEN(b);
+		}
+	} else {
+		first = qremove(q);
+		n = BLEN(first);
+	}
+
+	/* split last block if it's too big and this is not a message queue */
+	if(n > len && (q->state & Qmsg) == 0)
+		qputback(q, splitblock(last, n - len));
+
+	/* restart producer */
+	qwakeup_iunlock(q);
+
+	qunlock(&q->rlock);
+	poperror();
+
+	if(waserror()){
+		freeblist(first);
+		nexterror();
+	}
+	n = readblist(first, vp, len, 0);
+	freeblist(first);
+	poperror();
+
+	return n;
+}
+
+static int
+qnotfull(void *a)
+{
+	Queue *q = a;
+
+	return q->len < q->limit || (q->state & Qclosed);
+}
+
+/*
+ *  flow control, wait for queue to get below the limit
+ */
+static void
+qflow(Queue *q)
+{
+	for(;;){
+		if(q->noblock || qnotfull(q))
+			break;
+
+		ilock(&q->lk);
+		q->state |= Qflow;
+		iunlock(&q->lk);
+
+		qlock(&q->wlock);
+		if(waserror()){
+			qunlock(&q->wlock);
+			nexterror();
+		}
+		sleep(&q->wr, qnotfull, q);
+		qunlock(&q->wlock);
+		poperror();
+	}
+}
+
+/*
+ *  add a block to a queue obeying flow control
+ */
+long
+qbwrite(Queue *q, Block *b)
+{
+	int len, dowakeup;
+
+	if(q->bypass != nil){
+		len = blocklen(b);
+		(*q->bypass)(q->arg, b);
+		return len;
+	}
+
+	dowakeup = 0;
+	if(waserror()){
+		freeblist(b);
+		nexterror();
+	}
+	ilock(&q->lk);
+
+	/* give up if the queue is closed */
+	if(q->state & Qclosed){
+		iunlock(&q->lk);
+		error(q->err);
+	}
+
+	/* don't queue over the limit */
+	if(q->len >= q->limit && q->noblock){
+		iunlock(&q->lk);
+		poperror();
+		len = blocklen(b);
+		freeblist(b);
+		return len;
+	}
+
+	len = qaddlist(q, b);
+
+	/* make sure other end gets awakened */
+	if(q->state & Qstarve){
+		q->state &= ~Qstarve;
+		dowakeup = 1;
+	}
+	iunlock(&q->lk);
+	poperror();
+
+	/*  get output going again */
+	if(q->kick != nil && (dowakeup || (q->state&Qkick)))
+		q->kick(q->arg);
+
+	/* wakeup anyone consuming at the other end */
+	if(dowakeup)
+		wakeup(&q->rr);
+
+	/*
+	 *  flow control, 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.
+	 */
+	qflow(q);
+
+	return len;
+}
+
+/*
+ *  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 %#p\n", getcallerpc(&q));
+
+	/* stop queue bloat before allocating blocks */
+	if(q->len/2 >= q->limit && q->noblock == 0 && q->bypass == nil){
+		while(waserror())
+			;
+		qflow(q);
+		poperror();
+	}
+
+	sofar = 0;
+	do {
+		n = len-sofar;
+		if(n > Maxatomic)
+			n = Maxatomic;
+
+		b = allocb(n);
+		if(waserror()){
+			freeb(b);
+			nexterror();
+		}
+		memmove(b->wp, p+sofar, n);
+		poperror();
+		b->wp += n;
+
+		sofar += qbwrite(q, b);
+	} 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);
+
+		/* we use an artificially high limit for kernel prints since anything
+		 * over the limit gets dropped
+		 */
+		if((q->state & Qclosed) != 0 || q->len/2 >= q->limit){
+			iunlock(&q->lk);
+			freeb(b);
+			break;
+		}
+
+		qaddlist(q, b);
+
+		if(q->state & Qstarve){
+			q->state &= ~Qstarve;
+			dowakeup = 1;
+		}
+
+		iunlock(&q->lk);
+
+		if(dowakeup){
+			if(q->kick != nil)
+				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);
+	kstrcpy(q->err, Ehungup, ERRMAX);
+	bfirst = q->bfirst;
+	q->bfirst = nil;
+	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 == nil || *msg == '\0')
+		msg = Ehungup;
+	kstrcpy(q->err, msg, ERRMAX);
+	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 != nil;
+}
+
+/*
+ *  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 = nil;
+	q->len = 0;
+	q->dlen = 0;
+	iunlock(&q->lk);
+
+	/* free queued blocks */
+	freeblist(bfirst);
+
+	/* wake up writers */
+	wakeup(&q->wr);
+}
+
+int
+qfull(Queue *q)
+{
+	return q->state & Qflow;
+}
--- /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,89 @@
+#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);
+	}
+
+	/*
+	 *  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,60 @@
+typedef struct Mouseinfo Mouseinfo;
+typedef struct Mousestate Mousestate;
+typedef struct Cursorinfo Cursorinfo;
+
+struct Mousestate {
+	Point	xy;
+	int	buttons;
+	ulong	counter;
+	ulong	msec;
+};
+
+struct Mouseinfo {
+	Lock		lk;
+	Mousestate	state;
+	ulong		lastcounter;
+	int		resize;		/* generate resize event */
+	Rendez		r;
+	int		open;
+	Mousestate	queue[16];	/* circular buffer of click events */
+	ulong		ri;		/* read index into queue */
+	ulong		wi;		/* write index into queue */
+};
+
+struct Cursorinfo {
+	Lock	lk;
+	Point	offset;
+	uchar	clr[2*16];
+	uchar	set[2*16];
+};
+
+extern	Memimage *gscreen;
+extern	Mouseinfo mouse;
+extern	Cursorinfo cursor;
+extern	Cursorinfo arrow;
+
+void	screeninit(void);
+void	screenload(Rectangle, int, uchar *, Point, int);
+
+void	getcolor(ulong, ulong*, ulong*, ulong*);
+void	setcolor(ulong, ulong, ulong, ulong);
+
+void	setcursor(void);
+void	mouseset(Point);
+void	flushmemscreen(Rectangle);
+Memdata*attachscreen(Rectangle*, ulong*, int*, int*, int*);
+void	deletescreenimage(void);
+void	resetscreenimage(void);
+
+extern	QLock drawlock;
+#define	ishwimage(i)	0
+
+void	terminit(void);
+void	screenresize(Rectangle);
+void	screensize(Rectangle, ulong);
+
+void	mouseresize(void);
+void	mousetrack(int, int, int, ulong);
+void	absmousetrack(int, int, int, ulong);
+void	cpubody(void);
+void	guimain(void);
--- /dev/null
+++ b/kern/sleep.c
@@ -1,0 +1,89 @@
+#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);
+	}
+
+	/*
+	 *  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/stub.c
@@ -1,0 +1,77 @@
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+int
+iseve(void)
+{
+	return 1;
+}
+
+void
+splx(int x)
+{
+	USED(x);
+}
+
+int
+splhi(void)
+{
+	return 0;
+}
+
+int
+spllo(void)
+{
+	return 0;
+}
+
+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
+setmalloctag(void *v, uintptr tag)
+{
+	USED(v);
+	USED(tag);
+}
+
+void
+setrealloctag(void *v, uintptr tag)
+{
+	USED(v);
+	USED(tag);
+}
+
+int
+postnote(Proc *p, int x, char *msg, int flag)
+{
+	USED(p);
+	USED(x);
+	USED(msg);
+	USED(flag);
+	return 0;
+}
+
+void
+exhausted(char *s)
+{
+	panic("out of %s", s);
+}
--- /dev/null
+++ b/kern/sysfile.c
@@ -1,0 +1,1253 @@
+#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
+#undef iounit
+
+/*
+ * 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->path == nil)
+		snprint(buf, nbuf, "<null>");
+	else
+		snprint(buf, nbuf, "%s", c->path->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;
+}
+
+static 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;
+}
+
+/* name conflicts with netbsd
+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;
+	Dir *d;
+
+	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);
+		d = dirchanstat(c);
+		off = d->length + off;
+		free(d);
+		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;
+}
+
+static int
+bindmount(int ismount, int fd, int afd, char* arg0, char* arg1, int flag, char* spec)
+{
+	int ret;
+	Chan *c0, *c1, *ac, *bc;
+
+	if((flag&~MMASK) || (flag&MORDER)==(MBEFORE|MAFTER))
+		error(Ebadarg);
+
+	if(ismount){
+		validaddr((uintptr)spec, 1, 0);
+		spec = validnamedup(spec, 1);
+		if(waserror()){
+			free(spec);
+			nexterror();
+		}
+
+		if(up->pgrp->noattach)
+			error(Enoattach);
+
+		ac = nil;
+		bc = fdtochan(fd, ORDWR, 0, 1);
+		if(waserror()) {
+			if(ac != nil)
+				cclose(ac);
+			cclose(bc);
+			nexterror();
+		}
+
+		if(afd >= 0)
+			ac = fdtochan(afd, ORDWR, 0, 1);
+
+		c0 = mntattach(bc, ac, spec, flag&MCACHE);
+		poperror();	/* ac bc */
+		if(ac != nil)
+			cclose(ac);
+		cclose(bc);
+	}else{
+		spec = nil;
+		validaddr((uintptr)arg0, 1, 0);
+		c0 = namec(arg0, Abind, 0, 0);
+	}
+
+	if(waserror()){
+		cclose(c0);
+		nexterror();
+	}
+
+	validaddr((uintptr)arg1, 1, 0);
+	c1 = namec(arg1, Amount, 0, 0);
+	if(waserror()){
+		cclose(c1);
+		nexterror();
+	}
+
+	ret = cmount(c0, c1, flag, spec);
+
+	poperror();
+	cclose(c1);
+	poperror();
+	cclose(c0);
+	if(ismount){
+		_fdclose(fd, 0);
+		poperror();
+		free(spec);
+	}
+	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
+enderror(void)
+{
+	assert(up->nerrlab == 1);
+	poperror();
+}
+
+static void
+_syserror(void)
+{
+	char *p;
+
+	p = up->syserrstr;
+	up->syserrstr = up->errstr;
+	up->errstr = p;
+}
+
+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;
+}
+
+ulong
+sysiounit(int fd)
+{
+	Chan *c;
+	ulong m;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return (ulong)-1;
+	}
+	c = fdtochan(fd, -1, 0, 0);
+	m = c->iounit;
+	enderror();
+	return m;
+}
+
+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);
+}
+
+void*
+_sysrendezvous(void* arg0, void* arg1)
+{
+	void *tag, *val;
+	Proc *p, **l;
+
+	tag = arg0;
+	l = &REND(up->rgrp, (uintptr)tag);
+	up->rendval = (void*)~0;
+
+	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;
+}
+
+void*
+sysrendezvous(void *tag, void *val)
+{
+	void *n;
+
+	starterror();
+	if(waserror()){
+		_syserror();
+		return (void*)~0;
+	}
+	n = _sysrendezvous(tag, val);
+	enderror();
+	return n;
+}
+
+int
+sysgetpid(void)
+{
+	return up->pid;
+}
--- /dev/null
+++ b/kern/term.c
@@ -1,0 +1,242 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	<draw.h>
+#include	<memdraw.h>
+#include	"screen.h"
+
+extern Memimage		*gscreen;
+
+static Memsubfont	*memdefont;
+static Lock		screenlock;
+static Memimage		*conscol;
+static Memimage		*back;
+static Rectangle	flushr;
+static Rectangle	window;
+static Point		curpos;
+static int		h;
+
+static void termscreenputs(char*, int);
+
+static void
+screenflush(void)
+{
+	flushmemscreen(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;
+	char *greet;
+	Memimage *grey;
+
+	back = memwhite;
+	conscol = memblack;
+	memfillcolor(gscreen, 0x444488FF);
+	
+	h = memdefont->height;
+
+	window = insetrect(gscreen->clipr, 20);
+	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));
+	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);
+
+	termscreenputs(kmesg.buf, kmesg.n);
+}
+
+static struct {
+	Rectangle	r;
+	Rendez		z;
+	int		f;
+} resize;
+
+static int
+isresized(void *arg)
+{
+	return resize.f != 0;
+}
+
+static void
+resizeproc(void *arg)
+{
+	USED(arg);
+	for(;;){
+		sleep(&resize.z, isresized, nil);
+		qlock(&drawlock);
+		resize.f = 0;
+		if(gscreen == nil
+		|| badrect(resize.r)
+		|| eqrect(resize.r, gscreen->clipr)){
+			qunlock(&drawlock);
+			continue;
+		}
+		screensize(resize.r, gscreen->chan);
+		if(gscreen == nil
+		|| rectclip(&resize.r, gscreen->r) == 0
+		|| eqrect(resize.r, gscreen->clipr)){
+			qunlock(&drawlock);
+			continue;
+		}
+		gscreen->clipr = resize.r;
+
+		screenwin();
+		deletescreenimage();
+		resetscreenimage();
+		qunlock(&drawlock);
+		osmsleep(1000);
+	}
+}
+
+void
+screenresize(Rectangle r)
+{
+	qlock(&drawlock);
+	resize.r = r;
+	resize.f = 1;
+	wakeup(&resize.z);
+	qunlock(&drawlock);
+}
+
+void
+terminit(void)
+{
+	memdefont = getmemdefont();
+	screenwin();
+	screenputs = termscreenputs;
+	kproc("resize", resizeproc, nil);
+}
+
+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);
+	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[nelem(xbuf)])
+		xp = xbuf;
+
+	switch(buf[0]) {
+	case '\n':
+		if(curpos.y+h >= window.max.y){
+			scroll();
+			flushr = window;
+		}
+		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)
+{
+	static char rb[UTFmax+1];
+	static int nrb;
+	int locked;
+	char *e;
+
+	lock(&screenlock);
+	locked = canqlock(&drawlock);
+	e = s + n;
+	while(s < e){
+		rb[nrb++] = *s++;
+		if(nrb >= UTFmax || fullrune(rb, nrb)){
+			rb[nrb] = 0;
+			screenputc(rb);
+			nrb = 0;
+		}
+	}
+	if(locked){
+		screenflush();
+		qunlock(&drawlock);
+	}
+	unlock(&screenlock);
+}
--- /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/win32.c
@@ -1,0 +1,505 @@
+#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;
+};
+
+static int tlsx = TLS_OUT_OF_INDEXES;
+
+Proc*
+_getproc(void)
+{
+	if(tlsx == TLS_OUT_OF_INDEXES)
+		return nil;
+	return TlsGetValue(tlsx);
+}
+
+void
+_setproc(Proc *p)
+{
+	if(tlsx == TLS_OUT_OF_INDEXES){
+		tlsx = TlsAlloc();
+		if(tlsx == TLS_OUT_OF_INDEXES)
+			panic("out of indexes");
+	}
+	TlsSetValue(tlsx, p);
+}
+
+void
+oserror(void)
+{
+	oserrstr();
+	nexterror();
+}
+
+void
+osinit(void)
+{
+	Oproc *t;
+	static Proc firstprocCTstore;
+
+	_setproc(&firstprocCTstore);
+	t = (Oproc*)firstprocCTstore.oproc;
+	assert(t != 0);
+
+	t->tid = GetCurrentThreadId();
+	t->sema = CreateSemaphore(0, 0, 1000, 0);
+	if(t->sema == 0) {
+		oserror();
+		panic("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();
+		panic("could not create semaphore: %r");
+	}
+}
+
+void
+osmsleep(int ms)
+{
+	Sleep((DWORD) ms);
+}
+
+void
+osyield(void)
+{
+	Sleep(0);
+}
+
+static DWORD WINAPI
+tramp(LPVOID vp)
+{
+	Proc *p = (Proc *) vp;
+	Oproc *op = (Oproc*) p->oproc;
+
+	_setproc(p);
+	op->tid = GetCurrentThreadId();
+ 	(*p->fn)(p->arg);
+	pexit("", 0);
+	return 0;
+}
+
+void
+osproc(Proc *p)
+{
+	DWORD tid;
+
+	if(CreateThread(0, 0, tramp, p, 0, &tid) == 0) {
+		oserror();
+		panic("osproc: %r");
+	}
+}
+
+void
+osexit(void)
+{
+	ExitThread(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);
+}
+
+BOOLEAN WINAPI (*RtlGenRandom)(PVOID, ULONG);
+
+void
+randominit(void)
+{
+	HMODULE mod;
+	
+	mod = LoadLibraryW(L"ADVAPI32.DLL");
+	if(mod != NULL)
+		RtlGenRandom = (void *) GetProcAddress(mod, "SystemFunction036");
+}
+
+ulong
+randomread(void *v, ulong n)
+{
+	RtlGenRandom(v, n);
+	return n;
+}
+
+#undef time
+long
+seconds(void)
+{
+	return time(0);
+}
+
+ulong
+ticks(void)
+{
+	return GetTickCount();
+}
+
+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 s-s0+wstrutflen(t)+1;
+		}
+		i = runetochar(s, t);
+		s += i;
+		n -= i;
+		t++;
+	}
+	*s = 0;
+	return s-s0;
+}
+
+/*
+ * Break the command line into arguments
+ * The rules for this are not documented but appear to be the following
+ * according to the source for the microsoft C library.
+ * Words are seperated by space or tab
+ * Words containing a space or tab can be quoted using "
+ * 2N backslashes + " ==> N backslashes and end quote
+ * 2N+1 backslashes + " ==> N backslashes + literal "
+ * N backslashes not followed by " ==> N backslashes
+ */
+static int
+args(char *argv[], int n, char *p)
+{
+	char *p2;
+	int i, j, quote, nbs;
+
+	for(i=0; *p && i<n-1; i++) {
+		while(*p == ' ' || *p == '\t')
+			p++;
+		quote = 0;
+		argv[i] = p2 = p;
+		for(;*p; p++) {
+			if(!quote && (*p == ' ' || *p == '\t'))
+				break;
+			for(nbs=0; *p == '\\'; p++,nbs++)
+				;
+			if(*p == '"') {
+				for(j=0; j<(nbs>>1); j++)
+					*p2++ = '\\';
+				if(nbs&1)
+					*p2++ = *p;
+				else
+					quote = !quote;
+			} else {
+				for(j=0; j<nbs; j++)
+					*p2++ = '\\';
+				*p2++ = *p;
+			}
+		}
+		/* move p up one to avoid pointing to null at end of p2 */
+		if(*p)
+			p++;
+		*p2 = 0;	
+	}
+	argv[i] = 0;
+
+	return i;
+}
+
+/*
+ * Quote a single command line argument using the rules above.
+ */
+static char*
+qarg(char *s)
+{
+	char *d, *p;
+	int n, c;
+
+	n = strlen(s);
+	d = smalloc(3+2*n);
+	for(p = s; (c = *p) != 0; p++)
+		if(strchr(" \t\n\r\"", c) != nil)
+			break;
+	if(c == 0 && p != s){
+		memmove(d, s, n+1);
+		return d;
+	}
+	p = d;
+	*p++ = '"';
+	for(;;){
+		for(n = 0; (c = *s++) == '\\'; n++)
+			*p++ = c;
+		if(c == 0){
+			while(n-- > 0)
+				*p++ = '\\';
+			break;
+		}
+		if(c == '"'){
+			while(n-- >= 0)
+				*p++ = '\\';
+		}
+		*p++ = c;
+	}
+	*p++ = '"';
+	*p = 0;
+	return d;
+}
+
+extern int	main(int, char*[]);
+
+int APIENTRY
+WinMain(HINSTANCE x, HINSTANCE y, LPSTR z, int w)
+{
+	int argc, n;
+	char *arg, *p, **argv;
+	wchar_t *warg;
+
+	warg = GetCommandLineW();
+	n = wcslen(warg)*UTFmax+1;
+	arg = smalloc(n);
+	WideCharToMultiByte(CP_UTF8, 0, warg, -1, arg, n, 0, 0);
+
+	/* conservative guess at the number of args */
+	for(argc=4,p=arg; *p; p++)
+		if(*p == ' ' || *p == '\t')
+			argc++;
+	argv = smalloc(argc*sizeof(char*));
+	argc = args(argv, argc, arg);
+
+	main(argc, argv);
+	ExitThread(0);
+	return 0;
+}
+
+
+static wchar_t*
+wcmdline(char **argv)
+{
+	wchar_t *s, *w, *e;
+	int n, i;
+	char *q;
+
+	n = 0;
+	for(i = 0; argv[i] != nil; i++){
+		q = qarg(argv[i]);
+		n += strlen(q)+1;
+		free(q);
+	}
+	s = smalloc((n+1)*sizeof(wchar_t));
+	w = s;
+	e = s + n;
+	for(i = 0; argv[i] != nil; i++){
+		if(i != 0)
+			*w++ = L' ';
+		q = qarg(argv[i]);
+		w += MultiByteToWideChar(CP_UTF8, 0, q, strlen(q), w, e - w);
+		free(q);
+	}
+	*w = 0;
+	return s;
+}
+
+void*
+oscmd(char **argv, int nice, char *dir, Chan **fd)
+{
+	SECURITY_ATTRIBUTES sa;
+	PROCESS_INFORMATION pi;
+	STARTUPINFOW si;
+	HANDLE p[3][2], tmp;
+	wchar_t *wcmd, *wdir;
+	int i;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.nLength = sizeof(sa);
+	sa.lpSecurityDescriptor = NULL;
+	sa.bInheritHandle = TRUE;
+
+	for(i = 0; i < 3; i++){
+		if(!CreatePipe(&p[i][i==0], &p[i][i!=0], &sa, 0)
+		|| !DuplicateHandle(GetCurrentProcess(), p[i][0], GetCurrentProcess(), &tmp, 0, FALSE, DUPLICATE_SAME_ACCESS)){
+			while(--i >= 0){
+				CloseHandle(p[i][0]);
+				CloseHandle(p[i][1]);
+			}
+			oserror();
+		}
+		CloseHandle(p[i][0]);
+		p[i][0] = tmp;
+	}
+
+	if(waserror()){
+		for(i = 0; i < 3; i++){
+			CloseHandle(p[i][0]);
+			CloseHandle(p[i][1]);
+		}
+		nexterror();
+	}
+
+	memset(&pi, 0, sizeof(pi));
+	memset(&si, 0, sizeof(si));
+	si.cb = sizeof(si);
+	si.dwFlags = STARTF_USESTDHANDLES;
+	si.hStdInput = p[0][1];
+	si.hStdOutput = p[1][1];
+	si.hStdError = p[2][1];
+	si.lpDesktop = L"";
+
+	i = strlen(dir)+1;
+	wdir = smalloc(i*sizeof(wchar_t));
+	MultiByteToWideChar(CP_UTF8, 0, dir, i, wdir, i);
+
+	wcmd = wcmdline(argv);
+	if(waserror()){
+		free(wcmd);
+		nexterror();
+	}
+
+	if(!CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW, NULL, wdir, &si, &pi))
+		oserror();
+
+	poperror();
+	free(wcmd);
+	free(wdir);
+
+	poperror();
+	for(i = 0; i < 3; i++){
+		fd[i] = lfdchan((void*)p[i][0]);
+		CloseHandle(p[i][1]);
+	}
+	CloseHandle(pi.hThread);
+	return (void*)pi.hProcess;
+}
+
+int
+oscmdwait(void *c, char *status, int nstatus)
+{
+	DWORD code = -1;
+	for(;;){
+		if(!GetExitCodeProcess((HANDLE)c, &code))
+			return -1;
+		if(code != STILL_ACTIVE)
+			break;
+		WaitForSingleObject((HANDLE)c, INFINITE);
+	}
+	if(code == 0)
+		return snprint(status, nstatus, "0 0 0 0 ''");
+	return snprint(status, nstatus, "0 0 0 0 %d", (int)code);
+}
+
+int
+oscmdkill(void *c)
+{
+	TerminateProcess((HANDLE)c, 0);
+	return 0;
+}
+
+void
+oscmdfree(void *c)
+{
+	CloseHandle((HANDLE)c);
+}
+
+void
+oserrstr(void)
+{
+	char *p, *q;
+	int e, r;
+
+	e = GetLastError();
+	p = up->errstr;	/* up kills last error */
+	r = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
+		0, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+		p, ERRMAX, 0);
+	if(r == 0)
+		snprint(p, ERRMAX, "windows error %d", e);
+	for(q=p; *p; p++) {
+		if(*p == '\r')
+			continue;
+		if(*p == '\n')
+			*q++ = ' ';
+		else
+			*q++ = *p;
+	}
+	*q = '\0';
+}
+
+long
+showfilewrite(char *a, int n)
+{
+	wchar_t *action, *arg, *cmd, *p;
+	int m;
+
+	cmd = smalloc((n+1)*sizeof(wchar_t));
+	m = MultiByteToWideChar(CP_UTF8,0,a,n,cmd,n);
+	while(m > 0 && cmd[m-1] == '\n')
+		m--;
+	cmd[m] = 0;
+	p = wcschr(cmd, ' ');
+	if(p){
+		action = cmd;
+		*p++ = 0;
+		arg = p;
+	}else{
+		action = L"open";
+		arg = cmd;
+	}
+	ShellExecuteW(0, action, arg, 0, 0, SW_SHOWNORMAL);
+	free(cmd);
+	return n;
+}
+
+void
+setterm(int raw)
+{
+	DWORD mode;
+	HANDLE h;
+
+	h = GetStdHandle(STD_INPUT_HANDLE);
+	if(!GetConsoleMode(h, &mode))
+		return;
+	if(raw)
+		mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
+	else
+		mode |= (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
+	SetConsoleMode(h, mode);
+	FlushConsoleInputBuffer(h);
+	_setmode(0, raw? _O_BINARY: _O_TEXT);
+}
--- /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/libauth/Makefile
@@ -1,0 +1,18 @@
+ROOT=..
+include ../Make.config
+
+LIB=libauth.a
+OFILES=\
+	attr.$O\
+	auth_attr.$O\
+	auth_proxy.$O\
+	auth_rpc.$O\
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libauth/attr.c
@@ -1,0 +1,164 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+int
+_attrfmt(Fmt *fmt)
+{
+	Attr *a;
+	int first = 1;
+
+	for(a=va_arg(fmt->args, Attr*); a != nil; a=a->next){
+		if(a->name == nil)
+			continue;
+		switch(a->type){
+		default:
+			continue;
+		case AttrQuery:
+			fmtprint(fmt, first+" %q?", a->name);
+			break;
+		case AttrNameval:
+		case AttrDefault:
+			fmtprint(fmt, first+" %q=%q", a->name, a->val);
+			break;
+		}
+		first = 0;
+	}
+	return 0;
+}
+
+Attr*
+_copyattr(Attr *a)
+{
+	Attr **la, *na;
+
+	na = nil;
+	la = &na;
+	for(; a; a=a->next){
+		*la = _mkattr(a->type, a->name, a->val, nil);
+		setmalloctag(*la, getcallerpc(&a));
+		la = &(*la)->next;
+	}
+	*la = nil;
+	return na;
+}
+
+Attr*
+_delattr(Attr *a, char *name)
+{
+	Attr *fa;
+	Attr **la;
+
+	for(la=&a; *la; ){
+		if(strcmp((*la)->name, name) == 0){
+			fa = *la;
+			*la = (*la)->next;
+			fa->next = nil;
+			_freeattr(fa);
+		}else
+			la=&(*la)->next;
+	}
+	return a;
+}
+
+Attr*
+_findattr(Attr *a, char *n)
+{
+	for(; a; a=a->next)
+		if(strcmp(a->name, n) == 0 && a->type != AttrQuery)
+			return a;
+	return nil;
+}
+
+void
+_freeattr(Attr *a)
+{
+	Attr *anext;
+
+	for(; a; a=anext){
+		anext = a->next;
+		free(a->name);
+		free(a->val);
+		a->name = (void*)~0;
+		a->val = (void*)~0;
+		a->next = (void*)~0;
+		free(a);
+	}
+}
+
+Attr*
+_mkattr(int type, char *name, char *val, Attr *next)
+{
+	Attr *a;
+
+	a = malloc(sizeof(*a));
+	if(a==nil)
+		sysfatal("_mkattr malloc: %r");
+	a->type = type;
+	a->name = strdup(name);
+	a->val = strdup(val);
+	if(a->name==nil || a->val==nil)
+		sysfatal("_mkattr malloc: %r");
+	a->next = next;
+	setmalloctag(a, getcallerpc(&type));
+	return a;
+}
+
+static Attr*
+cleanattr(Attr *a)
+{
+	Attr *fa;
+	Attr **la;
+
+	for(la=&a; *la; ){
+		if((*la)->type==AttrQuery && _findattr(a, (*la)->name)){
+			fa = *la;
+			*la = (*la)->next;
+			fa->next = nil;
+			_freeattr(fa);
+		}else
+			la=&(*la)->next;
+	}
+	return a;
+}
+
+Attr*
+_parseattr(char *s)
+{
+	char *p, *t, *tok[256];
+	int i, ntok;
+	Attr *a;
+
+	s = strdup(s);
+	if(s == nil)
+		sysfatal("_parseattr strdup: %r");
+
+	ntok = tokenize(s, tok, nelem(tok));
+	a = nil;
+	for(i=ntok-1; i>=0; i--){
+		t = tok[i];
+		if((p = strchr(t, '=')) != nil){
+			*p++ = '\0';
+			a = _mkattr(AttrNameval, t, p, a);
+		}else if((p = strchr(t, '\0')-1) >= t && *p == '?'){
+			*p = '\0';
+			a = _mkattr(AttrQuery, t, "", a);
+		}else{
+			/* really a syntax error, but better to provide some indication */
+			a = _mkattr(AttrNameval, t, "", a);
+		}
+		setmalloctag(a, getcallerpc(&s));
+	}
+	free(s);
+	return cleanattr(a);
+}
+
+char*
+_strfindattr(Attr *a, char *n)
+{
+	a = _findattr(a, n);
+	if(a == nil)
+		return nil;
+	return a->val;
+}
+
--- /dev/null
+++ b/libauth/auth_attr.c
@@ -1,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include "authlocal.h"
+
+Attr*
+auth_attr(AuthRpc *rpc)
+{
+	if(auth_rpc(rpc, "attr", nil, 0) != ARok)
+		return nil;
+	return _parseattr(rpc->arg);
+}
--- /dev/null
+++ b/libauth/auth_proxy.c
@@ -1,0 +1,218 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <auth.h>
+#include "authlocal.h"
+
+enum {
+	ARgiveup = 100,
+};
+
+static uchar*
+gstring(uchar *p, uchar *ep, char **s)
+{
+	uint n;
+
+	if(p == nil)
+		return nil;
+	if(p+BIT16SZ > ep)
+		return nil;
+	n = GBIT16(p);
+	p += BIT16SZ;
+	if(p+n > ep)
+		return nil;
+	*s = malloc(n+1);
+	memmove((*s), p, n);
+	(*s)[n] = '\0';
+	p += n;
+	return p;
+}
+
+static uchar*
+gcarray(uchar *p, uchar *ep, uchar **s, int *np)
+{
+	uint n;
+
+	if(p == nil)
+		return nil;
+	if(p+BIT16SZ > ep)
+		return nil;
+	n = GBIT16(p);
+	p += BIT16SZ;
+	if(p+n > ep)
+		return nil;
+	*s = malloc(n);
+	if(*s == nil)
+		return nil;
+	memmove((*s), p, n);
+	*np = n;
+	p += n;
+	return p;
+}
+
+void
+auth_freeAI(AuthInfo *ai)
+{
+	if(ai == nil)
+		return;
+	free(ai->cuid);
+	free(ai->suid);
+	free(ai->cap);
+	free(ai->secret);
+	free(ai);
+}
+
+static uchar*
+convM2AI(uchar *p, int n, AuthInfo **aip)
+{
+	uchar *e = p+n;
+	AuthInfo *ai;
+
+	ai = mallocz(sizeof(*ai), 1);
+	if(ai == nil)
+		return nil;
+
+	p = gstring(p, e, &ai->cuid);
+	p = gstring(p, e, &ai->suid);
+	p = gstring(p, e, &ai->cap);
+	p = gcarray(p, e, &ai->secret, &ai->nsecret);
+	if(p == nil)
+		auth_freeAI(ai);
+	else
+		*aip = ai;
+	return p;
+}
+
+AuthInfo*
+auth_getinfo(AuthRpc *rpc)
+{
+	AuthInfo *a;
+
+	if(auth_rpc(rpc, "authinfo", nil, 0) != ARok)
+		return nil;
+	if(convM2AI((uchar*)rpc->arg, rpc->narg, &a) == nil){
+		werrstr("bad auth info from factotum");
+		return nil;
+	}
+	return a;
+}
+
+static int
+dorpc(AuthRpc *rpc, char *verb, char *val, int len, AuthGetkey *getkey)
+{
+	int ret;
+
+	for(;;){
+		if((ret = auth_rpc(rpc, verb, val, len)) != ARneedkey && ret != ARbadkey)
+			return ret;
+		if(getkey == nil)
+			return ARgiveup;	/* don't know how */
+		if((*getkey)(rpc->arg) < 0)
+			return ARgiveup;	/* user punted */
+	}
+}
+
+/*
+ *  this just proxies what the factotum tells it to.
+ */
+AuthInfo*
+fauth_proxy(int fd, AuthRpc *rpc, AuthGetkey *getkey, char *params)
+{
+	char *buf;
+	int m, n, ret;
+	AuthInfo *a;
+	char oerr[ERRMAX];
+
+	if(rpc == nil){
+		werrstr("fauth_proxy - no factotum");
+		return nil;
+	}
+
+	strcpy(oerr, "UNKNOWN AUTH ERROR");
+	errstr(oerr, sizeof oerr);
+
+	if(dorpc(rpc, "start", params, strlen(params), getkey) != ARok){
+		werrstr("fauth_proxy start: %r");
+		return nil;
+	}
+
+	buf = malloc(AuthRpcMax);
+	if(buf == nil)
+		return nil;
+	for(;;){
+		switch(dorpc(rpc, "read", nil, 0, getkey)){
+		case ARdone:
+			free(buf);
+			a = auth_getinfo(rpc);
+			/* no error, restore whatever was there */
+			errstr(oerr, sizeof oerr);
+			return a;
+		case ARok:
+			if(write(fd, rpc->arg, rpc->narg) != rpc->narg){
+				werrstr("auth_proxy write fd: %r");
+				goto Error;
+			}
+			break;
+		case ARphase:
+			n = 0;
+			memset(buf, 0, AuthRpcMax);
+			while((ret = dorpc(rpc, "write", buf, n, getkey)) == ARtoosmall){
+				m = atoi(rpc->arg);
+				if(m <= n || m > AuthRpcMax)
+					break;
+				m = read(fd, buf + n, m - n);
+				if(m <= 0){
+					if(m == 0)
+						werrstr("auth_proxy short read");
+					else
+						werrstr("auth_proxy read fd: %r");
+					goto Error;
+				}
+				n += m;
+			}
+			if(ret != ARok){
+				werrstr("auth_proxy rpc write: %r");
+				goto Error;
+			}
+			break;
+		default:
+			werrstr("auth_proxy rpc: %r");
+			goto Error;
+		}
+	}
+Error:
+	free(buf);
+	return nil;
+}
+
+AuthInfo*
+auth_proxy(int fd, AuthGetkey *getkey, char *fmt, ...)
+{
+	int afd;
+	char *p;
+	va_list arg;
+	AuthInfo *ai;
+	AuthRpc *rpc;
+
+	quotefmtinstall();	/* just in case */
+	va_start(arg, fmt);
+	p = vsmprint(fmt, arg);
+	va_end(arg);
+
+	ai = nil;
+	afd = open("/mnt/factotum/rpc", ORDWR);
+	if(afd < 0){
+		werrstr("opening /mnt/factotum/rpc: %r");
+		free(p);
+		return nil;
+	}
+
+	rpc = auth_allocrpc(afd);
+	if(rpc){
+		ai = fauth_proxy(fd, rpc, getkey, p);
+		auth_freerpc(rpc);
+	}
+	close(afd);
+	free(p);
+	return ai;
+}
--- /dev/null
+++ b/libauth/auth_rpc.c
@@ -1,0 +1,116 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include "authlocal.h"
+
+static struct {
+	char *verb;
+	int val;
+} tab[] = {
+	"ok",			ARok,
+	"done",		ARdone,
+	"error",		ARerror,
+	"needkey",	ARneedkey,
+	"badkey",		ARbadkey,
+	"phase",		ARphase,
+	"toosmall",	ARtoosmall,
+	"error",		ARerror,
+};
+
+static int
+classify(char *buf, uint n, AuthRpc *rpc)
+{
+	int i, len;
+
+	for(i=0; i<nelem(tab); i++){
+		len = strlen(tab[i].verb);
+		if(n >= len && memcmp(buf, tab[i].verb, len) == 0 && (n==len || buf[len]==' ')){
+			if(n==len){
+				rpc->narg = 0;
+				rpc->arg = "";
+			}else{
+				rpc->narg = n - (len+1);
+				rpc->arg = (char*)buf+len+1;
+			}
+			return tab[i].val;
+		}
+	}
+	werrstr("malformed rpc response: %s", buf);
+	return ARrpcfailure;
+}
+
+AuthRpc*
+auth_allocrpc(int afd)
+{
+	AuthRpc *rpc;
+
+	rpc = mallocz(sizeof(*rpc), 1);
+	if(rpc == nil)
+		return nil;
+	rpc->afd = afd;
+	return rpc;
+}
+
+void
+auth_freerpc(AuthRpc *rpc)
+{
+	free(rpc);
+}
+
+uint
+auth_rpc(AuthRpc *rpc, char *verb, void *a, int na)
+{
+	int l, n, type;
+	char *f[4];
+
+	l = strlen(verb);
+	if(na+l+1 > AuthRpcMax){
+		werrstr("rpc too big");
+		return ARtoobig;
+	}
+
+	memmove(rpc->obuf, verb, l);
+	rpc->obuf[l] = ' ';
+	memmove(rpc->obuf+l+1, a, na);
+	if((n=write(rpc->afd, rpc->obuf, l+1+na)) != l+1+na){
+		if(n >= 0)
+			werrstr("auth_rpc short write");
+		return ARrpcfailure;
+	}
+
+	if((n=read(rpc->afd, rpc->ibuf, AuthRpcMax)) < 0)
+		return ARrpcfailure;
+	rpc->ibuf[n] = '\0';
+
+	/*
+	 * Set error string for good default behavior.
+	 */
+	switch(type = classify(rpc->ibuf, n, rpc)){
+	default:
+		werrstr("unknown rpc type %d (bug in auth_rpc.c)", type);
+		break;
+	case ARok:
+		break;
+	case ARrpcfailure:
+		break;
+	case ARerror:
+		if(rpc->narg == 0)
+			werrstr("unspecified rpc error");
+		else
+			werrstr("%s", rpc->arg);
+		break;
+	case ARneedkey:
+		werrstr("needkey %s", rpc->arg);
+		break;
+	case ARbadkey:
+		if(getfields(rpc->arg, f, nelem(f), 0, "\n") < 2)
+			werrstr("badkey %s", rpc->arg);
+		else
+			werrstr("badkey %s", f[1]);
+		break;
+	case ARphase:
+		werrstr("phase error %s", rpc->arg);
+		break;
+	}
+	return type;
+}
--- /dev/null
+++ b/libauth/authlocal.h
@@ -1,0 +1,1 @@
+extern AuthInfo*	_fauth_proxy(int fd, AuthRpc *rpc, AuthGetkey *getkey, char *params);
--- /dev/null
+++ b/libauthsrv/Makefile
@@ -1,0 +1,35 @@
+ROOT=..
+include ../Make.config
+LIB=libauthsrv.a
+
+OFILES=\
+	_asgetticket.$O\
+	_asrdresp.$O\
+	convA2M.$O\
+	convM2A.$O\
+	convM2PR.$O\
+	convM2T.$O\
+	convM2TR.$O\
+	convPR2M.$O\
+	convT2M.$O\
+	convTR2M.$O\
+	nvcsum.$O\
+	passtokey.$O\
+	readcons.$O\
+	_asgetpakkey.$O\
+	_asgetresp.$O\
+	_asrequest.$O\
+	authpak.$O\
+	form1.$O\
+	
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+authpak.$O:	msqrt.mpc edwards.mpc ed448.mpc decaf.mpc elligator2.mpc spake2ee.mpc
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libauthsrv/_asgetpakkey.c
@@ -1,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+_asgetpakkey(int fd, Ticketreq *tr, Authkey *a)
+{
+	uchar y[PAKYLEN];
+	PAKpriv p;
+	int type;
+
+	type = tr->type;
+	tr->type = AuthPAK;
+	if(_asrequest(fd, tr) != 0){
+		tr->type = type;
+		return -1;
+	}
+	tr->type = type;
+	authpak_new(&p, a, y, 1);
+	if(write(fd, y, PAKYLEN) != PAKYLEN
+	|| _asrdresp(fd, (char*)y, PAKYLEN) != PAKYLEN){
+		memset(&p, 0, sizeof(p));
+		return -1;
+	}
+	return authpak_finish(&p, a, y);
+}
--- /dev/null
+++ b/libauthsrv/_asgetresp.c
@@ -1,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+_asgetresp(int fd, Ticket *t, Authenticator *a, Authkey *k)
+{
+	char buf[MAXTICKETLEN+MAXAUTHENTLEN], err[ERRMAX];
+	int n, m;
+
+	memset(t, 0, sizeof(Ticket));
+	if(a != nil)
+		memset(a, 0, sizeof(Authenticator));
+
+	strcpy(err, "AS protocol botch");
+	errstr(err, ERRMAX);
+
+	if(_asrdresp(fd, buf, 0) < 0)
+		return -1;
+
+	for(n = 0; (m = convM2T(buf, n, t, k)) <= 0; n += m){
+		m = -m;
+		if(m <= n || m > sizeof(buf))
+			return -1;
+		m -= n;
+		if(readn(fd, buf+n, m) != m)
+			return -1;
+	}
+
+	if(a != nil){
+		for(n = 0; (m = convM2A(buf, n, a, t)) <= 0; n += m){
+			m = -m;
+			if(m <= n || m > sizeof(buf))
+				return -1;
+			m -= n;
+			if(readn(fd, buf+n, m) != m)
+				return -1;
+		}
+	}
+
+	errstr(err, ERRMAX);
+
+	return 0;
+}
--- /dev/null
+++ b/libauthsrv/_asgetticket.c
@@ -1,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+_asgetticket(int fd, Ticketreq *tr, char *tbuf, int tbuflen)
+{
+	char err[ERRMAX];
+	int i, n, m, r;
+
+	strcpy(err, "AS protocol botch");
+	errstr(err, ERRMAX);
+
+	if(_asrequest(fd, tr) < 0)
+		return -1;
+	if(_asrdresp(fd, tbuf, 0) < 0)
+		return -1;
+
+	r = 0;
+	for(i = 0; i<2; i++){
+		for(n=0; (m = convM2T(tbuf, n, nil, nil)) <= 0; n += m){
+			m = -m;
+			if(m <= n || m > tbuflen)
+				return -1;
+			m -= n;
+			if(readn(fd, tbuf+n, m) != m)
+				return -1;
+		}
+		r += n;
+		tbuf += n;
+		tbuflen -= n;
+	}
+
+	errstr(err, ERRMAX);
+
+	return r;
+}
--- /dev/null
+++ b/libauthsrv/_asrdresp.c
@@ -1,0 +1,56 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+static char *pbmsg = "AS protocol botch";
+
+int
+_asrdresp(int fd, char *buf, int len)
+{
+	int n;
+	char error[64];
+
+	if(read(fd, buf, 1) != 1){
+		werrstr(pbmsg);
+		return -1;
+	}
+
+	n = len;
+	switch(buf[0]){
+	case AuthOK:
+		if(readn(fd, buf, len) != len){
+			werrstr(pbmsg);
+			return -1;
+		}
+		break;
+	case AuthErr:
+		if(readn(fd, error, sizeof error) != sizeof error){
+			werrstr(pbmsg);
+			return -1;
+		}
+		error[sizeof error-1] = '\0';
+		werrstr("remote: %s", error);
+		return -1;
+	case AuthOKvar:
+		if(readn(fd, error, 5) != 5){
+			werrstr(pbmsg);
+			return -1;
+		}
+		error[5] = 0;
+		n = atoi(error);
+		if(n <= 0 || n > len){
+			werrstr(pbmsg);
+			return -1;
+		}
+		memset(buf, 0, len);
+		if(readn(fd, buf, n) != n){
+			werrstr(pbmsg);
+			return -1;
+		}
+		break;
+	default:
+		werrstr(pbmsg);
+		return -1;
+	}
+	return n;
+}
--- /dev/null
+++ b/libauthsrv/_asrequest.c
@@ -1,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+_asrequest(int fd, Ticketreq *tr)
+{
+	char trbuf[TICKREQLEN];
+	int n;
+
+	n = convTR2M(tr, trbuf, sizeof(trbuf));
+	if(write(fd, trbuf, n) != n)
+		return -1;
+
+	return 0;
+}
--- /dev/null
+++ b/libauthsrv/authdial.c
@@ -1,0 +1,50 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include <ndb.h>
+
+int
+authdial(char *netroot, char *dom)
+{
+	Ndbtuple *t, *nt;
+	char *p;
+	int rv;
+
+	if(dom == nil)
+		/* look for one relative to my machine */
+		return dial(netmkaddr("$auth", nil, "ticket"), nil, nil, nil);
+
+	/* look up an auth server in an authentication domain */
+	p = csgetvalue(netroot, "authdom", dom, "auth", &t);
+
+	/* if that didn't work, just try the IP domain */
+	if(p == nil)
+		p = csgetvalue(netroot, "dom", dom, "auth", &t);
+
+	/*
+	 * if that didn't work, try p9auth.$dom.  this is very helpful if
+	 * you can't edit /lib/ndb.
+	 */
+	if(p == nil) {
+		p = smprint("p9auth.%s", dom);
+		t = ndbnew("auth", p);
+	}
+	free(p);
+
+	/*
+	 * allow multiple auth= attributes for backup auth servers,
+	 * try each one in order.
+	 */
+	rv = -1;
+	for(nt = t; nt != nil; nt = nt->entry) {
+		if(strcmp(nt->attr, "auth") == 0) {
+			rv = dial(netmkaddr(nt->val, nil, "ticket"), nil, nil, nil);
+			if(rv >= 0)
+				break;
+		}
+	}
+	ndbfree(t);
+
+	return rv;
+}
--- /dev/null
+++ b/libauthsrv/authpak.c
@@ -1,0 +1,214 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include <authsrv.h>
+
+#include "msqrt.mpc"
+#include "decaf.mpc"
+#include "edwards.mpc"
+#include "elligator2.mpc"
+#include "spake2ee.mpc"
+#include "ed448.mpc"
+
+typedef struct PAKcurve PAKcurve;
+struct PAKcurve
+{
+	Lock	lk;
+	mpint	*P;
+	mpint	*A;
+	mpint	*D;
+	mpint	*X;
+	mpint	*Y;
+};
+
+static PAKcurve*
+authpak_curve(void)
+{
+	static PAKcurve a;
+
+	lock(&a.lk);
+	if(a.P == nil){
+		a.P = mpnew(0);
+		a.A = mpnew(0);
+		a.D = mpnew(0);
+		a.X = mpnew(0);
+		a.Y = mpnew(0);
+		ed448_curve(a.P, a.A, a.D, a.X, a.Y);
+		a.P = mpfield(a.P);
+	}
+	unlock(&a.lk);
+	return &a;
+}
+
+void
+authpak_hash(Authkey *k, char *u)
+{
+	static char info[] = "Plan 9 AuthPAK hash";
+	uchar *bp, salt[SHA2_256dlen], h[2*PAKSLEN];
+	mpint *H, *PX,*PY,*PZ,*PT;
+	PAKcurve *c;
+
+	H = mpnew(0);
+	PX = mpnew(0);
+	PY = mpnew(0);
+	PZ = mpnew(0);
+	PT = mpnew(0);
+
+	sha2_256((uchar*)u, strlen(u), salt, nil);
+
+	hkdf_x(	salt, SHA2_256dlen,
+		(uchar*)info, sizeof(info)-1,
+		k->aes, AESKEYLEN,
+		h, sizeof(h),
+		hmac_sha2_256, SHA2_256dlen);
+
+	c = authpak_curve();
+
+	betomp(h + 0*PAKSLEN, PAKSLEN, H);		/* HM */
+	spake2ee_h2P(c->P,c->A,c->D, H, PX,PY,PZ,PT);	/* PM */
+
+	bp = k->pakhash;
+	mptober(PX, bp, PAKSLEN), bp += PAKSLEN;
+	mptober(PY, bp, PAKSLEN), bp += PAKSLEN;
+	mptober(PZ, bp, PAKSLEN), bp += PAKSLEN;
+	mptober(PT, bp, PAKSLEN), bp += PAKSLEN;
+
+	betomp(h + 1*PAKSLEN, PAKSLEN, H);		/* HN */
+	spake2ee_h2P(c->P,c->A,c->D, H, PX,PY,PZ,PT);	/* PN */
+
+	mptober(PX, bp, PAKSLEN), bp += PAKSLEN;
+	mptober(PY, bp, PAKSLEN), bp += PAKSLEN;
+	mptober(PZ, bp, PAKSLEN), bp += PAKSLEN;
+	mptober(PT, bp, PAKSLEN);
+
+	mpfree(PX);
+	mpfree(PY);
+	mpfree(PZ);
+	mpfree(PT);
+	mpfree(H);
+}
+
+void
+authpak_new(PAKpriv *p, Authkey *k, uchar y[PAKYLEN], int isclient)
+{
+	mpint *PX,*PY,*PZ,*PT, *X, *Y;
+	PAKcurve *c;
+	uchar *bp;
+
+	memset(p, 0, sizeof(PAKpriv));
+	p->isclient = isclient != 0;
+
+	X = mpnew(0);
+	Y = mpnew(0);
+
+	PX = mpnew(0);
+	PY = mpnew(0);
+	PZ = mpnew(0);
+	PT = mpnew(0);
+
+	PX->flags |= MPtimesafe;
+	PY->flags |= MPtimesafe;
+	PZ->flags |= MPtimesafe;
+	PT->flags |= MPtimesafe;
+
+	bp = k->pakhash + PAKPLEN*(p->isclient == 0);
+	betomp(bp, PAKSLEN, PX), bp += PAKSLEN;
+	betomp(bp, PAKSLEN, PY), bp += PAKSLEN;
+	betomp(bp, PAKSLEN, PZ), bp += PAKSLEN;
+	betomp(bp, PAKSLEN, PT);
+
+	c = authpak_curve();
+
+	X->flags |= MPtimesafe;
+	mpnrand(c->P, genrandom, X);
+
+	spake2ee_1(c->P,c->A,c->D, X, c->X,c->Y, PX,PY,PZ,PT, Y);
+
+	mptober(X, p->x, PAKXLEN);
+	mptober(Y, p->y, PAKYLEN);
+
+	memmove(y, p->y, PAKYLEN);
+
+	mpfree(PX);
+	mpfree(PY);
+	mpfree(PZ);
+	mpfree(PT);
+
+	mpfree(X);
+	mpfree(Y);
+}
+
+int
+authpak_finish(PAKpriv *p, Authkey *k, uchar y[PAKYLEN])
+{
+	static char info[] = "Plan 9 AuthPAK key";
+	uchar *bp, z[PAKSLEN], salt[SHA2_256dlen];
+	mpint *PX,*PY,*PZ,*PT, *X, *Y, *Z, *ok;
+	DigestState *s;
+	PAKcurve *c;
+	int ret;
+
+	X = mpnew(0);
+	Y = mpnew(0);
+	Z = mpnew(0);
+	ok = mpnew(0);
+
+	PX = mpnew(0);
+	PY = mpnew(0);
+	PZ = mpnew(0);
+	PT = mpnew(0);
+
+	PX->flags |= MPtimesafe;
+	PY->flags |= MPtimesafe;
+	PZ->flags |= MPtimesafe;
+	PT->flags |= MPtimesafe;
+
+	bp = k->pakhash + PAKPLEN*(p->isclient != 0);
+	betomp(bp, PAKSLEN, PX), bp += PAKSLEN;
+	betomp(bp, PAKSLEN, PY), bp += PAKSLEN;
+	betomp(bp, PAKSLEN, PZ), bp += PAKSLEN;
+	betomp(bp, PAKSLEN, PT);
+
+	Z->flags |= MPtimesafe;
+	X->flags |= MPtimesafe;
+	betomp(p->x, PAKXLEN, X);
+
+	betomp(y, PAKYLEN, Y);
+
+	c = authpak_curve();
+	spake2ee_2(c->P,c->A,c->D, PX,PY,PZ,PT, X, Y, ok, Z);
+
+	if(mpcmp(ok, mpzero) == 0){
+		ret = -1;
+		goto out;
+	}
+
+	mptober(Z, z, sizeof(z));
+
+	s = sha2_256(p->isclient ? p->y : y, PAKYLEN, nil, nil);
+	sha2_256(p->isclient ? y : p->y, PAKYLEN, salt, s);
+
+	hkdf_x(	salt, SHA2_256dlen,
+		(uchar*)info, sizeof(info)-1,
+		z, sizeof(z),
+		k->pakkey, PAKKEYLEN,
+		hmac_sha2_256, SHA2_256dlen);
+
+	ret = 0;
+out:
+	memset(z, 0, sizeof(z));
+	memset(p, 0, sizeof(PAKpriv));
+
+	mpfree(PX);
+	mpfree(PY);
+	mpfree(PZ);
+	mpfree(PT);
+
+	mpfree(X);
+	mpfree(Y);
+	mpfree(Z);
+	mpfree(ok);
+
+	return ret;
+}
--- /dev/null
+++ b/libauthsrv/convA2M.c
@@ -1,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+extern int form1B2M(char *ap, int n, uchar key[32]);
+
+int
+convA2M(Authenticator *f, char *ap, int n, Ticket *t)
+{
+	uchar *p;
+
+	if(n < 1+CHALLEN)
+		return 0;
+
+	p = (uchar*)ap;
+	*p++ = f->num;
+	memmove(p, f->chal, CHALLEN), p += CHALLEN;
+	switch(t->form){
+	case 0:
+		if(n < 1+CHALLEN+4)
+			return 0;
+
+		memset(p, 0, 4), p += 4;	/* unused id field */
+		n = p - (uchar*)ap;
+		encrypt(t->key, ap, n);
+		return n;
+	case 1:
+		if(n < 12+CHALLEN+NONCELEN+16)
+			return 0;
+
+		memmove(p, f->rand, NONCELEN), p += NONCELEN;
+		return form1B2M(ap, (char*)p - ap, t->key);
+	}
+
+	return 0;
+}
--- /dev/null
+++ b/libauthsrv/convM2A.c
@@ -1,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+extern int form1M2B(char *ap, int n, uchar key[32]);
+
+int
+convM2A(char *ap, int n, Authenticator *f, Ticket *t)
+{
+	uchar buf[MAXAUTHENTLEN], *p;
+	int m;
+
+	memset(f, 0, sizeof(Authenticator));
+	if(t->form == 0){
+		m = 1+CHALLEN+4;
+		if(n < m)
+			return -m;
+		memmove(buf, ap, m);
+		decrypt(t->key, buf, m);
+	} else {
+		m = 12+CHALLEN+NONCELEN+16;
+		if(n < m)
+			return -m;
+		memmove(buf, ap, m);
+		if(form1M2B((char*)buf, m, t->key) < 0)
+			return m;
+	}
+	p = buf;
+	f->num = *p++;
+	memmove(f->chal, p, CHALLEN);
+	p += CHALLEN;
+	if(t->form == 1)
+		memmove(f->rand, p, NONCELEN);
+
+	return m;
+}
--- /dev/null
+++ b/libauthsrv/convM2PR.c
@@ -1,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+extern int form1M2B(char *ap, int n, uchar key[32]);
+
+int
+convM2PR(char *ap, int n, Passwordreq *f, Ticket *t)
+{
+	uchar *p, buf[MAXPASSREQLEN];
+	int m;
+
+	memset(f, 0, sizeof(Passwordreq));
+	if(t->form == 0){
+		m = 1+2*PASSWDLEN+1+SECRETLEN;
+		if(n < m)
+			return -m;
+		memmove(buf, ap, m);
+		decrypt(t->key, buf, m);
+	} else {
+		m = 12+2*PASSWDLEN+1+SECRETLEN+16;
+		if(n < m)
+			return -m;
+		memmove(buf, ap, m);
+		if(form1M2B((char*)buf, m, t->key) < 0)
+			return m;
+	}
+	p = buf;
+	f->num = *p++;
+	memmove(f->old, p, PASSWDLEN), p += PASSWDLEN;
+	memmove(f->new, p, PASSWDLEN), p += PASSWDLEN;
+	f->changesecret = *p++;
+	memmove(f->secret, p, SECRETLEN);
+	f->old[PASSWDLEN-1] = 0;
+	f->new[PASSWDLEN-1] = 0;
+	f->secret[SECRETLEN-1] = 0;
+
+	return m;
+}
--- /dev/null
+++ b/libauthsrv/convM2T.c
@@ -1,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+extern int form1check(char *ap, int n);
+extern int form1M2B(char *ap, int n, uchar key[32]);
+
+int
+convM2T(char *ap, int n, Ticket *f, Authkey *k)
+{
+	uchar buf[MAXTICKETLEN], *p;
+	int m;
+
+	if(f != nil)
+		memset(f, 0, sizeof(Ticket));
+
+	if(n < 8)
+		return -8;
+
+	if(form1check(ap, n) < 0){
+		m = 1+CHALLEN+2*ANAMELEN+DESKEYLEN;
+		if(n < m)
+			return -m;
+		if(f == nil || k == nil)
+			return m;
+		f->form = 0;
+		memmove(buf, ap, m);
+		decrypt(k->des, buf, m);
+	} else {
+		m = 12+CHALLEN+2*ANAMELEN+NONCELEN+16;
+		if(n < m)
+			return -m;
+		if(f == nil || k == nil)
+			return m;
+		f->form = 1;
+		memmove(buf, ap, m);
+		if(form1M2B((char*)buf, m, k->pakkey) < 0)
+			return m;
+	}
+	p = buf;
+	f->num = *p++;
+	memmove(f->chal, p, CHALLEN), p += CHALLEN;
+	memmove(f->cuid, p, ANAMELEN), p += ANAMELEN;
+	memmove(f->suid, p, ANAMELEN), p += ANAMELEN;
+	memmove(f->key, p, f->form == 0 ? DESKEYLEN : NONCELEN);
+
+	f->cuid[ANAMELEN-1] = 0;
+	f->suid[ANAMELEN-1] = 0;
+
+	return m;
+}
--- /dev/null
+++ b/libauthsrv/convM2TR.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+convM2TR(char *ap, int n, Ticketreq *f)
+{
+	uchar *p;
+
+	memset(f, 0, sizeof(Ticketreq));
+	if(n < TICKREQLEN)
+		return -TICKREQLEN;
+
+	p = (uchar*)ap;
+	f->type = *p++;
+	memmove(f->authid, p, ANAMELEN), p += ANAMELEN;
+	memmove(f->authdom, p, DOMLEN), p += DOMLEN;
+	memmove(f->chal, p, CHALLEN), p += CHALLEN;
+	memmove(f->hostid, p, ANAMELEN), p += ANAMELEN;
+	memmove(f->uid, p, ANAMELEN), p += ANAMELEN;
+
+	f->authid[ANAMELEN-1] = 0;
+	f->authdom[DOMLEN-1] = 0;
+	f->hostid[ANAMELEN-1] = 0;
+	f->uid[ANAMELEN-1] = 0;
+	n = p - (uchar*)ap;
+
+	return n;
+}
--- /dev/null
+++ b/libauthsrv/convPR2M.c
@@ -1,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+extern int form1B2M(char *ap, int n, uchar key[32]);
+
+int
+convPR2M(Passwordreq *f, char *ap, int n, Ticket *t)
+{
+	uchar *p;
+
+	if(n < 1+2*PASSWDLEN+1+SECRETLEN)
+		return 0;
+
+	p = (uchar*)ap;
+	*p++ = f->num;
+	memmove(p, f->old, PASSWDLEN), p += PASSWDLEN;
+	memmove(p, f->new, PASSWDLEN), p += PASSWDLEN;
+	*p++ = f->changesecret;
+	memmove(p, f->secret, SECRETLEN), p += SECRETLEN;
+	switch(t->form){
+	case 0:
+		n = p - (uchar*)ap;
+		encrypt(t->key, ap, n);
+		return n;
+	case 1:
+		if(n < 12+2*PASSWDLEN+1+SECRETLEN+16)
+			return 0;
+		return form1B2M(ap, p - (uchar*)ap, t->key);
+	}
+
+	return 0;
+}
+
--- /dev/null
+++ b/libauthsrv/convT2M.c
@@ -1,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <libsec.h>
+
+extern int form1B2M(char *ap, int n, uchar key[32]);
+
+int
+convT2M(Ticket *f, char *ap, int n, Authkey *key)
+{
+	uchar *p;
+
+	if(n < 1+CHALLEN+2*ANAMELEN)
+		return 0;
+
+	p = (uchar*)ap;
+	*p++ = f->num;
+	memmove(p, f->chal, CHALLEN), p += CHALLEN;
+	memmove(p, f->cuid, ANAMELEN), p += ANAMELEN;
+	memmove(p, f->suid, ANAMELEN), p += ANAMELEN;
+	switch(f->form){
+	case 0:
+		if(n < 1+CHALLEN+2*ANAMELEN+DESKEYLEN)
+			return 0;
+
+		memmove(p, f->key, DESKEYLEN), p += DESKEYLEN;
+		n = p - (uchar*)ap;
+		encrypt(key->des, ap, n);
+		return n;
+	case 1:
+		if(n < 12+CHALLEN+2*ANAMELEN+NONCELEN+16)
+			return 0;
+
+		memmove(p, f->key, NONCELEN), p += NONCELEN;
+		return form1B2M(ap, p - (uchar*)ap, key->pakkey);
+	}
+
+	return 0;
+}
--- /dev/null
+++ b/libauthsrv/convTR2M.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+int
+convTR2M(Ticketreq *f, char *ap, int n)
+{
+	uchar *p;
+
+	if(n < TICKREQLEN)
+		return 0;
+
+	p = (uchar*)ap;
+	*p++ = f->type;
+	memmove(p, f->authid, ANAMELEN), p += ANAMELEN;
+	memmove(p, f->authdom, DOMLEN), p += DOMLEN;
+	memmove(p, f->chal, CHALLEN), p += CHALLEN;
+	memmove(p, f->hostid, ANAMELEN), p += ANAMELEN;
+	memmove(p, f->uid, ANAMELEN), p += ANAMELEN;
+	n = p - (uchar*)ap;
+
+	return n;
+}
--- /dev/null
+++ b/libauthsrv/decaf.mpc
@@ -1,0 +1,130 @@
+void decaf_neg(mpint *p, mpint *n, mpint *r){
+	mpint *m = mpnew(0);
+	mpmodsub(mpzero, r, p, m);
+	mpint *tmp1 = mpnew(0);
+	mpsub(p, mpone, tmp1);
+	mpright(tmp1, 1, tmp1);
+	mpsel(-mpcmp(n, tmp1) >> (sizeof(int)*8-1), m, r, r);
+	mpfree(tmp1);
+	mpfree(m);
+	}
+void decaf_encode(mpint *p, mpint *a, mpint *d, mpint *X, mpint *Y, mpint *Z, mpint *T, mpint *s){
+	mpint *u = mpnew(0);
+	mpint *r = mpnew(0);
+	mpint *tmp1 = mpnew(0);
+	mpint *tmp2 = mpnew(0);
+	mpint *tmp3 = mpnew(0);
+	mpmodsub(a, d, p, tmp3);
+	mpint *tmp4 = mpnew(0);
+	mpmodadd(Z, Y, p, tmp4);
+	mpmodmul(tmp3, tmp4, p, tmp2);
+	mpfree(tmp3);
+	mpfree(tmp4);
+	tmp4 = mpnew(0);
+	mpmodsub(Z, Y, p, tmp4);
+	mpmodmul(tmp2, tmp4, p, tmp1);
+	mpfree(tmp2);
+	mpfree(tmp4);
+	misqrt(tmp1, p, r);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	mpmodsub(a, d, p, tmp1);
+	mpmodmul(tmp1, r, p, u);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	tmp4 = mpnew(0);
+	mpmodadd(u, u, p, tmp4); // 2*u
+	mpmodmul(tmp4, Z, p, tmp1);
+	mpfree(tmp4);
+	mpmodsub(mpzero, tmp1, p, tmp1);
+	decaf_neg(p, tmp1, r);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	tmp4 = mpnew(0);
+	tmp2 = mpnew(0);
+	tmp3 = mpnew(0);
+	mpmodmul(a, Z, p, tmp3);
+	mpmodmul(tmp3, X, p, tmp2);
+	mpfree(tmp3);
+	tmp3 = mpnew(0);
+	mpint *tmp5 = mpnew(0);
+	mpmodmul(d, Y, p, tmp5);
+	mpmodmul(tmp5, T, p, tmp3);
+	mpfree(tmp5);
+	mpmodsub(tmp2, tmp3, p, tmp2);
+	mpfree(tmp3);
+	mpmodmul(r, tmp2, p, tmp4);
+	mpfree(tmp2);
+	mpmodadd(tmp4, Y, p, tmp4);
+	mpmodmul(u, tmp4, p, tmp1);
+	mpfree(tmp4);
+	tmp4 = mpnew(0);
+	mpinvert(a, p, tmp4);
+	mpmodmul(tmp1, tmp4, p, s);
+	mpfree(tmp4);
+	mpfree(tmp1);
+	decaf_neg(p, s, s);
+	mpfree(u);
+	mpfree(r);
+	}
+void decaf_decode(mpint *p, mpint *a, mpint *d, mpint *s, mpint *ok, mpint *X, mpint *Y, mpint *Z, mpint *T){
+	mpint *w = mpnew(0);
+	mpint *v = mpnew(0);
+	mpint *u = mpnew(0);
+	mpint *ss = mpnew(0);
+	mpint *tmp1 = mpnew(0);
+	mpsub(p, mpone, tmp1);
+	mpright(tmp1, 1, tmp1);
+	if(mpcmp(s, tmp1) > 0){
+		mpassign(mpzero, ok);
+		}else{
+		mpmodmul(s, s, p, ss);
+		mpmodmul(a, ss, p, Z);
+		mpmodadd(mpone, Z, p, Z);
+		mpmodmul(Z, Z, p, u);
+		mpint *tmp2 = mpnew(0);
+		mpint *tmp3 = mpnew(0);
+		mpint *tmp4 = mpnew(0);
+		uitomp(4UL, tmp4);
+		mpmodmul(tmp4, d, p, tmp3);
+		mpfree(tmp4);
+		mpmodmul(tmp3, ss, p, tmp2);
+		mpfree(tmp3);
+		mpmodsub(u, tmp2, p, u);
+		mpfree(tmp2);
+		mpmodmul(u, ss, p, v);
+		if(mpcmp(v, mpzero) == 0){
+			mpassign(mpone, ok);
+			}else{
+			msqrt(v, p, ok);
+			if(mpcmp(ok, mpzero) != 0){
+				mpinvert(ok, p, v);
+				mpassign(mpone, ok);
+				}
+			}
+		if(mpcmp(ok, mpzero) != 0){
+			mpint *tmp5 = mpnew(0);
+			mpmodmul(u, v, p, tmp5);
+			decaf_neg(p, tmp5, v);
+			mpfree(tmp5);
+			tmp5 = mpnew(0);
+			mpmodmul(v, s, p, tmp5);
+			mpint *tmp6 = mpnew(0);
+			mpmodsub(mptwo, Z, p, tmp6);
+			mpmodmul(tmp5, tmp6, p, w);
+			mpfree(tmp5);
+			mpfree(tmp6);
+			if(mpcmp(s, mpzero) == 0){
+				mpmodadd(w, mpone, p, w);
+				}
+			mpmodadd(s, s, p, X); // 2*s
+			mpmodmul(w, Z, p, Y);
+			mpmodmul(w, X, p, T);
+			}
+		}
+	mpfree(tmp1);
+	mpfree(w);
+	mpfree(v);
+	mpfree(u);
+	mpfree(ss);
+	}
--- /dev/null
+++ b/libauthsrv/ed448.mpc
@@ -1,0 +1,8 @@
+void ed448_curve(mpint *p, mpint *a, mpint *d, mpint *x, mpint *y){
+	strtomp("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", nil, 16, p);
+	mpassign(mpone, a);
+	uitomp(39081UL, d);
+	d->sign = -1;
+	strtomp("297EA0EA2692FF1B4FAFF46098453A6A26ADF733245F065C3C59D0709CECFA96147EAAF3932D94C63D96C170033F4BA0C7F0DE840AED939F", nil, 16, x);
+	uitomp(19UL, y);
+	}
--- /dev/null
+++ b/libauthsrv/edwards.mpc
@@ -1,0 +1,98 @@
+void edwards_add(mpint *p, mpint *a, mpint *d, mpint *X1, mpint *Y1, mpint *Z1, mpint *T1, mpint *X2, mpint *Y2, mpint *Z2, mpint *T2, mpint *X3, mpint *Y3, mpint *Z3, mpint *T3){
+	mpint *H = mpnew(0);
+	mpint *G = mpnew(0);
+	mpint *F = mpnew(0);
+	mpint *E = mpnew(0);
+	mpint *D = mpnew(0);
+	mpint *C = mpnew(0);
+	mpint *B = mpnew(0);
+	mpint *A = mpnew(0);
+	mpmodmul(X1, X2, p, A);
+	mpmodmul(Y1, Y2, p, B);
+	mpint *tmp1 = mpnew(0);
+	mpmodmul(d, T1, p, tmp1);
+	mpmodmul(tmp1, T2, p, C);
+	mpfree(tmp1);
+	mpmodmul(Z1, Z2, p, D);
+	tmp1 = mpnew(0);
+	mpmodadd(X1, Y1, p, tmp1);
+	mpint *tmp2 = mpnew(0);
+	mpmodadd(X2, Y2, p, tmp2);
+	mpmodmul(tmp1, tmp2, p, E);
+	mpfree(tmp1);
+	mpfree(tmp2);
+	mpmodsub(E, A, p, E);
+	mpmodsub(E, B, p, E);
+	mpmodsub(D, C, p, F);
+	mpmodadd(D, C, p, G);
+	mpmodmul(a, A, p, H);
+	mpmodsub(B, H, p, H);
+	mpmodmul(E, F, p, X3);
+	mpmodmul(G, H, p, Y3);
+	mpmodmul(F, G, p, Z3);
+	mpmodmul(E, H, p, T3);
+	mpfree(H);
+	mpfree(G);
+	mpfree(F);
+	mpfree(E);
+	mpfree(D);
+	mpfree(C);
+	mpfree(B);
+	mpfree(A);
+	}
+void edwards_sel(mpint *s, mpint *X1, mpint *Y1, mpint *Z1, mpint *T1, mpint *X2, mpint *Y2, mpint *Z2, mpint *T2, mpint *X3, mpint *Y3, mpint *Z3, mpint *T3){
+	mpsel(mpcmp(s, mpzero), X1, X2, X3);
+	mpsel(mpcmp(s, mpzero), Y1, Y2, Y3);
+	mpsel(mpcmp(s, mpzero), Z1, Z2, Z3);
+	mpsel(mpcmp(s, mpzero), T1, T2, T3);
+	}
+void edwards_new(mpint *x, mpint *y, mpint *z, mpint *t, mpint *X, mpint *Y, mpint *Z, mpint *T){
+	mpassign(x, X);
+	mpassign(y, Y);
+	mpassign(z, Z);
+	mpassign(t, T);
+	}
+void edwards_scale(mpint *p, mpint *a, mpint *d, mpint *s, mpint *X1, mpint *Y1, mpint *Z1, mpint *T1, mpint *X3, mpint *Y3, mpint *Z3, mpint *T3){
+	mpint *j = mpnew(0);
+	mpint *k = mpnew(0);
+	mpint *T4 = mpnew(0);
+	mpint *Z4 = mpnew(0);
+	mpint *Y4 = mpnew(0);
+	mpint *X4 = mpnew(0);
+	mpint *T2 = mpnew(0);
+	mpint *Z2 = mpnew(0);
+	mpint *Y2 = mpnew(0);
+	mpint *X2 = mpnew(0);
+	edwards_new(X1, Y1, Z1, T1, X2, Y2, Z2, T2);
+	edwards_new(mpzero, mpone, mpone, mpzero, X4, Y4, Z4, T4);
+	mpint *tmp1 = mpnew(0);
+	mpmod(s, mptwo, tmp1);
+	edwards_sel(tmp1, X2, Y2, Z2, T2, X4, Y4, Z4, T4, X3, Y3, Z3, T3);
+	mpfree(tmp1);
+	mpright(s, 1, k);
+	mpright(p, 1, j);
+	for(;;){
+		if(mpcmp(j, mpzero) != 0){
+			edwards_add(p, a, d, X2, Y2, Z2, T2, X2, Y2, Z2, T2, X2, Y2, Z2, T2);
+			edwards_add(p, a, d, X2, Y2, Z2, T2, X3, Y3, Z3, T3, X4, Y4, Z4, T4);
+			mpint *tmp2 = mpnew(0);
+			mpmod(k, mptwo, tmp2);
+			edwards_sel(tmp2, X4, Y4, Z4, T4, X3, Y3, Z3, T3, X3, Y3, Z3, T3);
+			mpfree(tmp2);
+			mpright(k, 1, k);
+			mpright(j, 1, j);
+			}else{
+			break;
+			}
+		}
+	mpfree(j);
+	mpfree(k);
+	mpfree(T4);
+	mpfree(Z4);
+	mpfree(Y4);
+	mpfree(X4);
+	mpfree(T2);
+	mpfree(Z2);
+	mpfree(Y2);
+	mpfree(X2);
+	}
--- /dev/null
+++ b/libauthsrv/elligator2.mpc
@@ -1,0 +1,129 @@
+void elligator2(mpint *p, mpint *a, mpint *d, mpint *n, mpint *r0, mpint *X, mpint *Y, mpint *Z, mpint *T){
+	mpint *t = mpnew(0);
+	mpint *s = mpnew(0);
+	mpint *e = mpnew(0);
+	mpint *c = mpnew(0);
+	mpint *ND = mpnew(0);
+	mpint *N = mpnew(0);
+	mpint *D = mpnew(0);
+	mpint *r = mpnew(0);
+	mpint *tmp1 = mpnew(0);
+	mpmodmul(n, r0, p, tmp1);
+	mpmodmul(tmp1, r0, p, r);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	mpmodmul(d, r, p, tmp1);
+	mpmodadd(tmp1, a, p, tmp1);
+	mpmodsub(tmp1, d, p, tmp1);
+	mpint *tmp2 = mpnew(0);
+	mpmodmul(d, r, p, tmp2);
+	mpint *tmp3 = mpnew(0);
+	mpmodmul(a, r, p, tmp3);
+	mpmodsub(tmp2, tmp3, p, tmp2);
+	mpfree(tmp3);
+	mpmodsub(tmp2, d, p, tmp2);
+	mpmodmul(tmp1, tmp2, p, D);
+	mpfree(tmp1);
+	mpfree(tmp2);
+	tmp2 = mpnew(0);
+	mpmodadd(r, mpone, p, tmp2);
+	tmp1 = mpnew(0);
+	mpmodadd(d, d, p, tmp1); // 2*d
+	mpmodsub(a, tmp1, p, tmp1);
+	mpmodmul(tmp2, tmp1, p, N);
+	mpfree(tmp2);
+	mpfree(tmp1);
+	mpmodmul(N, D, p, ND);
+	if(mpcmp(ND, mpzero) == 0){
+		mpassign(mpone, c);
+		mpassign(mpzero, e);
+		}else{
+		msqrt(ND, p, e);
+		if(mpcmp(e, mpzero) != 0){
+			mpassign(mpone, c);
+			mpinvert(e, p, e);
+			}else{
+			mpmodsub(mpzero, mpone, p, c);
+			mpint *tmp4 = mpnew(0);
+			mpmodmul(n, r0, p, tmp4);
+			mpint *tmp5 = mpnew(0);
+			mpint *tmp6 = mpnew(0);
+			mpmodmul(n, ND, p, tmp6);
+			misqrt(tmp6, p, tmp5);
+			mpfree(tmp6);
+			mpmodmul(tmp4, tmp5, p, e);
+			mpfree(tmp4);
+			mpfree(tmp5);
+			}
+		}
+	tmp1 = mpnew(0);
+	mpmodmul(c, N, p, tmp1);
+	mpmodmul(tmp1, e, p, s);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	tmp2 = mpnew(0);
+	mpmodmul(c, N, p, tmp2);
+	tmp3 = mpnew(0);
+	mpmodsub(r, mpone, p, tmp3);
+	mpmodmul(tmp2, tmp3, p, tmp1);
+	mpfree(tmp2);
+	mpfree(tmp3);
+	tmp3 = mpnew(0);
+	tmp2 = mpnew(0);
+	mpmodadd(d, d, p, tmp2); // 2*d
+	mpmodsub(a, tmp2, p, tmp2);
+	mpmodmul(tmp2, e, p, tmp3);
+	mpfree(tmp2);
+	mpmodmul(tmp3, tmp3, p, tmp3);
+	mpmodmul(tmp1, tmp3, p, t);
+	mpfree(tmp1);
+	mpfree(tmp3);
+	mpmodsub(mpzero, t, p, t);
+	mpmodsub(t, mpone, p, t);
+	tmp3 = mpnew(0);
+	mpmodadd(s, s, p, tmp3); // 2*s
+	mpmodmul(tmp3, t, p, X);
+	mpfree(tmp3);
+	tmp3 = mpnew(0);
+	tmp1 = mpnew(0);
+	mpmodmul(a, s, p, tmp1);
+	mpmodmul(tmp1, s, p, tmp3);
+	mpfree(tmp1);
+	mpmodsub(mpone, tmp3, p, tmp3);
+	tmp1 = mpnew(0);
+	tmp2 = mpnew(0);
+	mpmodmul(a, s, p, tmp2);
+	mpmodmul(tmp2, s, p, tmp1);
+	mpfree(tmp2);
+	mpmodadd(mpone, tmp1, p, tmp1);
+	mpmodmul(tmp3, tmp1, p, Y);
+	mpfree(tmp3);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	tmp3 = mpnew(0);
+	mpmodmul(a, s, p, tmp3);
+	mpmodmul(tmp3, s, p, tmp1);
+	mpfree(tmp3);
+	mpmodadd(mpone, tmp1, p, tmp1);
+	mpmodmul(tmp1, t, p, Z);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	mpmodadd(s, s, p, tmp1); // 2*s
+	tmp3 = mpnew(0);
+	tmp2 = mpnew(0);
+	mpmodmul(a, s, p, tmp2);
+	mpmodmul(tmp2, s, p, tmp3);
+	mpfree(tmp2);
+	mpmodsub(mpone, tmp3, p, tmp3);
+	mpmodmul(tmp1, tmp3, p, T);
+	mpfree(tmp1);
+	mpfree(tmp3);
+	mpfree(t);
+	mpfree(s);
+	mpfree(e);
+	mpfree(c);
+	mpfree(ND);
+	mpfree(N);
+	mpfree(D);
+	mpfree(r);
+	}
--- /dev/null
+++ b/libauthsrv/form1.c
@@ -1,0 +1,90 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <libsec.h>
+
+/*
+ * new ticket format: the reply protector/type is replaced by a
+ * 8 byte signature and a 4 byte counter forming the 12 byte
+ * nonce for chacha20/poly1305 encryption. a 16 byte poly1305 
+ * authentication tag is appended for message authentication.
+ * the counter is needed for the AuthPass message which uses
+ * the same key for several messages.
+ */
+
+static struct {
+	char	num;
+	char	sig[8];
+} form1sig[] = {
+	AuthPass,	"form1 PR",	/* password change request encrypted with ticket key */
+	AuthTs,		"form1 Ts",	/* ticket encrypted with server's key */
+	AuthTc, 	"form1 Tc",	/* ticket encrypted with client's key */
+	AuthAs,		"form1 As",	/* server generated authenticator */
+	AuthAc,		"form1 Ac",	/* client generated authenticator */
+	AuthTp,		"form1 Tp",	/* ticket encrypted with client's key for password change */
+	AuthHr,		"form1 Hr",	/* http reply */
+};
+
+int
+form1check(char *ap, int n)
+{
+	if(n < 8)
+		return -1;
+
+	for(n=0; n<nelem(form1sig); n++)
+		if(memcmp(form1sig[n].sig, ap, 8) == 0)
+			return form1sig[n].num;
+
+	return -1;
+}
+
+int
+form1B2M(char *ap, int n, uchar key[32])
+{
+	static u32int counter;
+	Chachastate s;
+	uchar *p;
+	int i;
+
+	for(i=nelem(form1sig)-1; i>=0; i--)
+		if(form1sig[i].num == *ap)
+			break;
+	if(i < 0)
+		abort();
+
+	p = (uchar*)ap + 12;
+	memmove(p, ap+1, --n);
+
+	/* nonce[12] = sig[8] | counter[4] */
+	memmove(ap, form1sig[i].sig, 8);
+	i = counter++;
+	ap[8] = i, ap[9] = i>>8, ap[10] = i>>16, ap[11] = i>>24;
+
+	setupChachastate(&s, key, 32, (uchar*)ap, 12, 20);
+	ccpoly_encrypt(p, n, nil, 0, p+n, &s);
+	return 12+16 + n;
+}
+
+int
+form1M2B(char *ap, int n, uchar key[32])
+{
+	Chachastate s;
+	uchar *p;
+	int num;
+
+	num = form1check(ap, n);
+	if(num < 0)
+		return -1;
+	n -= 12+16;
+	if(n <= 0)
+		return -1;
+
+	p = (uchar*)ap + 12;
+	setupChachastate(&s, key, 32, (uchar*)ap, 12, 20);
+	if(ccpoly_decrypt(p, n, nil, 0, p+n, &s))
+		return -1;
+
+	memmove(ap+1, p, n);
+	ap[0] = num;
+	return n+1;
+}
--- /dev/null
+++ b/libauthsrv/msqrt.mpc
@@ -1,0 +1,149 @@
+void legendresymbol(mpint *a, mpint *p, mpint *r){
+	mpint *pm1 = mpnew(0);
+	mpsub(p, mpone, pm1);
+	mpright(pm1, 1, r);
+	mpexp(a, r, p, r);
+	if(mpcmp(r, pm1) == 0){
+		mpassign(mpone, r);
+		r->sign = -1;
+		}
+	mpfree(pm1);
+	}
+void msqrt(mpint *a, mpint *p, mpint *r){
+	mpint *gs = mpnew(0);
+	mpint *m = mpnew(0);
+	mpint *t = mpnew(0);
+	mpint *g = mpnew(0);
+	mpint *b = mpnew(0);
+	mpint *x = mpnew(0);
+	mpint *n = mpnew(0);
+	mpint *s = mpnew(0);
+	mpint *e = mpnew(0);
+	mpint *tmp1 = mpnew(0);
+	legendresymbol(a, p, tmp1);
+	if(mpcmp(tmp1, mpone) != 0){
+		mpassign(mpzero, r);
+		}else{
+		if(mpcmp(a, mpzero) == 0){
+			mpassign(mpzero, r);
+			}else{
+			if(mpcmp(p, mptwo) == 0){
+				mpassign(a, r);
+				}else{
+				mpint *tmp2 = mpnew(0);
+				uitomp(4UL, tmp2);
+				mpmod(p, tmp2, tmp2);
+				mpint *tmp3 = mpnew(0);
+				uitomp(3UL, tmp3);
+				if(mpcmp(tmp2, tmp3) == 0){
+					mpadd(p, mpone, e);
+					mpright(e, 2, e);
+					mpexp(a, e, p, r);
+					}else{
+					mpsub(p, mpone, s);
+					mpassign(mpzero, e);
+					for(;;){
+						mpint *tmp4 = mpnew(0);
+						mpmod(s, mptwo, tmp4);
+						if(mpcmp(tmp4, mpzero) == 0){
+							mpright(s, 1, s);
+							mpadd(e, mpone, e);
+							}else{
+							mpfree(tmp4);
+							break;
+							}
+						mpfree(tmp4);
+						}
+					mpassign(mptwo, n);
+					for(;;){
+						mpint *tmp5 = mpnew(0);
+						legendresymbol(n, p, tmp5);
+						mpint *tmp6 = mpnew(0);
+						mpassign(mpone, tmp6);
+						tmp6->sign = -1;
+						if(mpcmp(tmp5, tmp6) != 0){
+							mpadd(n, mpone, n);
+							}else{
+							mpfree(tmp6);
+							mpfree(tmp5);
+							break;
+							}
+						mpfree(tmp5);
+						mpfree(tmp6);
+						}
+					mpmodadd(s, mpone, p, x);
+					mpright(x, 1, x);
+					mpexp(a, x, p, x);
+					mpexp(a, s, p, b);
+					mpexp(n, s, p, g);
+					for(;;){
+						if(0 == 0){
+							mpassign(b, t);
+							mpassign(mpzero, m);
+							for(;;){
+								if(mpcmp(m, e) < 0){
+									if(mpcmp(t, mpone) == 0){
+										break;
+										}
+									mpmul(t, t, t);
+									mpmod(t, p, t);
+									mpadd(m, mpone, m);
+									}else{
+									break;
+									}
+								}
+							if(mpcmp(m, mpzero) == 0){
+								mpassign(x, r);
+								break;
+								}
+							mpsub(e, m, t);
+							mpsub(t, mpone, t);
+							mpexp(mptwo, t, nil, t);
+							mpexp(g, t, p, gs);
+							mpmodmul(gs, gs, p, g);
+							mpmodmul(x, gs, p, x);
+							mpmodmul(b, g, p, b);
+							mpassign(m, e);
+							}else{
+							break;
+							}
+						}
+					}
+				mpfree(tmp2);
+				mpfree(tmp3);
+				}
+			}
+		}
+	mpfree(tmp1);
+	mpfree(gs);
+	mpfree(m);
+	mpfree(t);
+	mpfree(g);
+	mpfree(b);
+	mpfree(x);
+	mpfree(n);
+	mpfree(s);
+	mpfree(e);
+	}
+void misqrt(mpint *a, mpint *p, mpint *r){
+	mpint *e = mpnew(0);
+	mpint *tmp1 = mpnew(0);
+	uitomp(4UL, tmp1);
+	mpmod(p, tmp1, tmp1);
+	mpint *tmp2 = mpnew(0);
+	uitomp(3UL, tmp2);
+	if(mpcmp(tmp1, tmp2) == 0){
+		uitomp(3UL, e);
+		mpsub(p, e, e);
+		mpright(e, 2, e);
+		mpexp(a, e, p, r);
+		}else{
+		msqrt(a, p, r);
+		if(mpcmp(r, mpzero) != 0){
+			mpinvert(r, p, r);
+			}
+		}
+	mpfree(tmp1);
+	mpfree(tmp2);
+	mpfree(e);
+	}
--- /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/passtokey.c
@@ -1,0 +1,48 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <libsec.h>
+
+void
+passtodeskey(char key[DESKEYLEN], char *p)
+{
+	uchar buf[PASSWDLEN], *t;
+	int i, n;
+
+	memset(buf, ' ', 8);
+	n = strlen(p);
+	if(n >= sizeof(buf))
+		n = sizeof(buf)-1;
+	memmove(buf, p, n);
+	buf[n] = 0;
+	memset(key, 0, DESKEYLEN);
+	t = buf;
+	for(;;){
+		for(i = 0; i < DESKEYLEN; i++)
+			key[i] = (t[i] >> i) + (t[i+1] << (8 - (i+1)));
+		if(n <= 8)
+			return;
+		n -= 8;
+		t += 8;
+		if(n < 8){
+			t -= 8 - n;
+			n = 8;
+		}
+		encrypt(key, t, 8);
+	}
+}
+
+void
+passtoaeskey(uchar key[AESKEYLEN], char *p)
+{
+	static char salt[] = "Plan 9 key derivation";
+	pbkdf2_x((uchar*)p, strlen(p), (uchar*)salt, sizeof(salt)-1, 9001, key, AESKEYLEN, hmac_sha1, SHA1dlen);
+}
+
+void
+passtokey(Authkey *key, char *pw)
+{
+	memset(key, 0, sizeof(Authkey));
+	passtodeskey(key->des, pw);
+	passtoaeskey(key->aes, pw);
+}
--- /dev/null
+++ b/libauthsrv/readcons.c
@@ -1,0 +1,82 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ *  prompt for a string with a possible default response
+ */
+char*
+readcons(char *prompt, char *def, int raw)
+{
+	int fdin, fdout, ctl, n;
+	char *s, *p;
+
+	s = p = nil;
+	fdout = ctl = -1;
+
+	if((fdin = open("/dev/cons", OREAD)) < 0)
+		goto Out;
+	if((fdout = open("/dev/cons", OWRITE)) < 0)
+		goto Out;
+
+	if(raw){
+		if((ctl = open("/dev/consctl", OWRITE)) < 0)
+			goto Out;
+		write(ctl, "rawon", 5);
+	}
+
+	if(def != nil)
+		fprint(fdout, "%s[%s]: ", prompt, def);
+	else
+		fprint(fdout, "%s: ", prompt);
+
+	for(;;){
+		n = p - s;
+		if((n % 32) == 0){
+			if((p = realloc(s, n+32)) == nil)
+				break;
+			s = p, p += n;
+		}
+
+		if(read(fdin, p, 1) <= 0 || *p == 0x7f)
+			break;
+
+		if(*p == '\n' || *p == '\r'){
+			if(p == s && def != nil){
+				free(s);
+				s = strdup(def);
+			} else
+				*p = 0;
+			if(raw)
+				write(fdout, "\n", 1);
+			goto Out;
+		} else if(*p == '\b') {
+			while(p > s && (p[-1] & 0xc0) == 0x80)
+				*p-- = 0;
+			if(p > s)
+				*p-- = 0;
+		} else if(*p == 0x15) {	/* ^U: line kill */
+			if(def != nil)
+				fprint(fdout, "\n%s[%s]: ", prompt, def);
+			else
+				fprint(fdout, "\n%s: ", prompt);
+			while(p > s)
+				*p-- = 0;
+		} else if((*p & 0xff) >= ' ')
+			p++;
+	}
+	free(s);
+	s = nil;
+	if(raw)
+		write(fdout, "\n", 1);
+Out:
+	if(ctl >= 0){
+		write(ctl, "rawoff", 6);
+		close(ctl);
+	}
+	if(fdin >= 0)
+		close(fdin);
+	if(fdout >= 0)
+		close(fdout);
+
+	return s;
+}
--- /dev/null
+++ b/libauthsrv/readnvram.c
@@ -1,0 +1,419 @@
+#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/sdC1/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sdC1/9fat", -1, sizeof(Nvrsafe),
+	"pc", "#S/sdD0/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sdD0/9fat", -1, sizeof(Nvrsafe),
+	"pc", "#S/sdE0/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sdE0/9fat", -1, sizeof(Nvrsafe),
+	"pc", "#S/sdF0/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sdF0/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", "#S/sd10/nvram", 0, sizeof(Nvrsafe),
+	"pc", "#S/sd10/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", 0x440000, sizeof(Nvrsafe),
+	"power", "#F/flash/flash", 0x440000, sizeof(Nvrsafe),
+	"power", "#r/nvram", 4352, sizeof(Nvrsafe),	/* OK for MTX-604e */
+	"power", "/nvram", 0, sizeof(Nvrsafe),	/* OK for Ucu */
+	"arm", "#F/flash/flash0", 0x100000, sizeof(Nvrsafe),
+	"arm", "#F/flash/flash", 0x100000, sizeof(Nvrsafe),
+	"debug", "/tmp/nvram", 0, sizeof(Nvrsafe),
+};
+
+typedef struct {
+	int	fd;
+	int	safelen;
+	vlong	safeoff;
+} Nvrwhere;
+
+static char *nvrfile = nil, *cputype = nil;
+
+/* returns with *locp filled in and locp->fd open, if possible */
+static void
+findnvram(Nvrwhere *locp)
+{
+	char *nvrlen, *nvroff, *v[2];
+	int fd, i, safelen;
+	vlong safeoff;
+
+	if (nvrfile == nil)
+		nvrfile = getenv("nvram");
+	if (cputype == nil)
+		cputype = getenv("cputype");
+	if(cputype == nil)
+		cputype = strdup("mips");
+	if(strcmp(cputype, "386")==0 || strcmp(cputype, "amd64")==0 || strcmp(cputype, "alpha")==0) {
+		free(cputype);
+		cputype = strdup("pc");
+	}
+
+	fd = -1;
+	safeoff = -1;
+	safelen = -1;
+	if(nvrfile != nil && *nvrfile != '\0'){
+		/* accept device and device!file */
+		i = gettokens(nvrfile, v, nelem(v), "!");
+		if (i < 1) {
+			i = 1;
+			v[0] = "";
+			v[1] = nil;
+		}
+		fd = open(v[0], ORDWR);
+		if (fd < 0)
+			fd = open(v[0], OREAD);
+		safelen = sizeof(Nvrsafe);
+		if(strstr(v[0], "/9fat") == nil)
+			safeoff = 0;
+		nvrlen = getenv("nvrlen");
+		if(nvrlen != nil)
+			safelen = strtol(nvrlen, 0, 0);
+		nvroff = getenv("nvroff");
+		if(nvroff != nil)
+			if(strcmp(nvroff, "dos") == 0)
+				safeoff = -1;
+			else
+				safeoff = strtoll(nvroff, 0, 0);
+		if(safeoff < 0 && fd >= 0){
+			safelen = 512;
+			safeoff = finddosfile(fd, i == 2? v[1]: "plan9.nvr");
+			if(safeoff < 0){	/* didn't find plan9.nvr? */
+				close(fd);
+				fd = -1;
+			}
+		}
+		free(nvroff);
+		free(nvrlen);
+	}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){  /* didn't find plan9.nvr? */
+					close(fd);
+					fd = -1;
+					continue;
+				}
+			}
+			break;
+		}
+	locp->fd = fd;
+	locp->safelen = safelen;
+	locp->safeoff = safeoff;
+}
+
+static int
+ask(char *prompt, char *buf, int len, int raw)
+{
+	char *s;
+	int n;
+
+	memset(buf, 0, len);
+	for(;;){
+		if((s = readcons(prompt, nil, raw)) == nil)
+			return -1;
+		if((n = strlen(s)) >= len)
+			fprint(2, "%s longer than %d characters; try again\n", prompt, len-1);
+		else {
+			memmove(buf, s, n);
+			memset(s, 0, n);
+			free(s);
+			return 0;
+		}
+		memset(s, 0, n);
+		free(s);
+	}
+}
+
+/*
+ *  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)
+{
+	int err;
+	char buf[512];		/* 512 for floppy i/o */
+	Nvrsafe *safe;
+	Nvrwhere loc;
+
+	err = 0;
+	safe = (Nvrsafe*)buf;
+	memset(&loc, 0, sizeof loc);
+	findnvram(&loc);
+	if (loc.safelen < 0)
+		loc.safelen = sizeof *safe;
+	else if (loc.safelen > sizeof buf)
+		loc.safelen = sizeof buf;
+	if (loc.safeoff < 0) {
+		fprint(2, "readnvram: couldn't find nvram\n");
+		if(!(flag&NVwritemem))
+			memset(safep, 0, sizeof(*safep));
+		safe = safep;
+		/*
+		 * allow user to type the data for authentication,
+		 * even if there's no nvram to store it in.
+		 */
+	}
+
+	if(flag&NVwritemem)
+		safe = safep;
+	else {
+		memset(safep, 0, sizeof(*safep));
+		if(loc.fd < 0
+		|| seek(loc.fd, loc.safeoff, 0) < 0
+		|| read(loc.fd, buf, loc.safelen) != loc.safelen){
+			err = 1;
+			if(flag&(NVwrite|NVwriteonerr))
+				if(loc.fd < 0)
+					fprint(2, "can't open %s: %r\n", nvrfile);
+				else if (seek(loc.fd, loc.safeoff, 0) < 0)
+					fprint(2, "can't seek %s to %lld: %r\n",
+						nvrfile, loc.safeoff);
+				else
+					fprint(2, "can't read %d bytes from %s: %r\n",
+						loc.safelen, nvrfile);
+			/* start from scratch */
+			memset(safep, 0, sizeof(*safep));
+			safe = safep;
+		}else{
+			*safep = *safe;	/* overwrite arg with data read */
+			safe = safep;
+
+			/* verify data read */
+			err |= check(safe->machkey, DESKEYLEN, safe->machsum,
+						"bad nvram des key");
+			err |= check(safe->authid, ANAMELEN, safe->authidsum,
+						"bad authentication id");
+			err |= check(safe->authdom, DOMLEN, safe->authdomsum,
+						"bad authentication domain");
+			if(0){
+				err |= check(safe->config, CONFIGLEN, safe->configsum,
+						"bad secstore key");
+				err |= check(safe->aesmachkey, AESKEYLEN, safe->aesmachsum,
+						"bad nvram aes key");
+			} else {
+				if(nvcsum(safe->config, CONFIGLEN) != safe->configsum)
+					memset(safe->config, 0, CONFIGLEN);
+				if(nvcsum(safe->aesmachkey, AESKEYLEN) != safe->aesmachsum)
+					memset(safe->aesmachkey, 0, AESKEYLEN);
+			}
+			if(err == 0)
+				if(safe->authid[0]==0 || safe->authdom[0]==0){
+					fprint(2, "empty nvram authid or authdom\n");
+					err = 1;
+				}
+		}
+	}
+
+	if((flag&(NVwrite|NVwritemem)) || (err && (flag&NVwriteonerr))){
+		if (!(flag&NVwritemem)) {
+			char pass[PASSWDLEN];
+			Authkey k;
+
+			if(ask("authid", safe->authid, sizeof safe->authid, 0))
+				goto Out;
+			if(ask("authdom", safe->authdom, sizeof safe->authdom, 0))
+				goto Out;
+			if(ask("secstore key", safe->config, sizeof safe->config, 1))
+				goto Out;
+			if(ask("password", pass, sizeof pass, 1))
+				goto Out;
+			passtokey(&k, pass);
+			memset(pass, 0, sizeof pass);
+			memmove(safe->machkey, k.des, DESKEYLEN);
+			memmove(safe->aesmachkey, k.aes, AESKEYLEN);
+			memset(&k, 0, sizeof k);
+		}
+
+		safe->machsum = nvcsum(safe->machkey, DESKEYLEN);
+		// safe->authsum = nvcsum(safe->authkey, DESKEYLEN);
+		safe->configsum = nvcsum(safe->config, CONFIGLEN);
+		safe->authidsum = nvcsum(safe->authid, sizeof safe->authid);
+		safe->authdomsum = nvcsum(safe->authdom, sizeof safe->authdom);
+		safe->aesmachsum = nvcsum(safe->aesmachkey, AESKEYLEN);
+
+		*(Nvrsafe*)buf = *safe;
+		if(loc.fd < 0
+		|| seek(loc.fd, loc.safeoff, 0) < 0
+		|| write(loc.fd, buf, loc.safelen) != loc.safelen){
+			fprint(2, "can't write key to nvram: %r\n");
+			err = 1;
+		}else
+			err = 0;
+	}
+Out:
+	if (loc.fd >= 0)
+		close(loc.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/libauthsrv/spake2ee.mpc
@@ -1,0 +1,63 @@
+void spake2ee_h2P(mpint *p, mpint *a, mpint *d, mpint *h, mpint *PX, mpint *PY, mpint *PZ, mpint *PT){
+	mpint *n = mpnew(0);
+	mpassign(mptwo, n);
+	for(;;){
+		mpint *tmp1 = mpnew(0);
+		legendresymbol(n, p, tmp1);
+		mpint *tmp2 = mpnew(0);
+		mpassign(mpone, tmp2);
+		tmp2->sign = -1;
+		if(mpcmp(tmp1, tmp2) != 0){
+			mpadd(n, mpone, n);
+			}else{
+			mpfree(tmp2);
+			mpfree(tmp1);
+			break;
+			}
+		mpfree(tmp1);
+		mpfree(tmp2);
+		}
+	mpint *tmp3 = mpnew(0);
+	mpmod(h, p, tmp3);
+	elligator2(p, a, d, n, tmp3, PX, PY, PZ, PT);
+	mpfree(tmp3);
+	mpfree(n);
+	}
+void spake2ee_1(mpint *p, mpint *a, mpint *d, mpint *x, mpint *GX, mpint *GY, mpint *PX, mpint *PY, mpint *PZ, mpint *PT, mpint *y){
+	mpint *T = mpnew(0);
+	mpint *Z = mpnew(0);
+	mpint *Y = mpnew(0);
+	mpint *X = mpnew(0);
+	mpint *tmp1 = mpnew(0);
+	mpmodmul(GX, GY, p, tmp1);
+	edwards_scale(p, a, d, x, GX, GY, mpone, tmp1, X, Y, Z, T);
+	mpfree(tmp1);
+	edwards_add(p, a, d, X, Y, Z, T, PX, PY, PZ, PT, X, Y, Z, T);
+	decaf_encode(p, a, d, X, Y, Z, T, y);
+	mpfree(T);
+	mpfree(Z);
+	mpfree(Y);
+	mpfree(X);
+	}
+void spake2ee_2(mpint *p, mpint *a, mpint *d, mpint *PX, mpint *PY, mpint *PZ, mpint *PT, mpint *x, mpint *y, mpint *ok, mpint *z){
+	mpint *T = mpnew(0);
+	mpint *Z = mpnew(0);
+	mpint *Y = mpnew(0);
+	mpint *X = mpnew(0);
+	decaf_decode(p, a, d, y, ok, X, Y, Z, T);
+	if(mpcmp(ok, mpzero) != 0){
+		mpint *tmp1 = mpnew(0);
+		mpmodsub(mpzero, PX, p, tmp1);
+		mpint *tmp2 = mpnew(0);
+		mpmodsub(mpzero, PT, p, tmp2);
+		edwards_add(p, a, d, X, Y, Z, T, tmp1, PY, PZ, tmp2, X, Y, Z, T);
+		mpfree(tmp1);
+		mpfree(tmp2);
+		edwards_scale(p, a, d, x, X, Y, Z, T, X, Y, Z, T);
+		decaf_encode(p, a, d, X, Y, Z, T, z);
+		}
+	mpfree(T);
+	mpfree(Z);
+	mpfree(Y);
+	mpfree(X);
+	}
--- /dev/null
+++ b/libc/Makefile
@@ -1,0 +1,93 @@
+ROOT=..
+include ../Make.config
+LIB=libc.a
+
+OFILES=\
+	atexit.$O\
+	charstod.$O\
+	cleanname.$O\
+	convD2M.$O\
+	convM2D.$O\
+	convM2S.$O\
+	convS2M.$O\
+	ctime.$O\
+	crypt.$O\
+	dial.$O\
+	dirfstat.$O\
+	dirfwstat.$O\
+	dirmodefmt.$O\
+	dirstat.$O\
+	dirwstat.$O\
+	dofmt.$O\
+	dorfmt.$O\
+	encodefmt.$O\
+	fcallfmt.$O\
+	fltfmt.$O\
+	fmt.$O\
+	fmtfd.$O\
+	fmtfdflush.$O\
+	fmtlock.$O\
+	fmtprint.$O\
+	fmtquote.$O\
+	fmtrune.$O\
+	fmtstr.$O\
+	fmtvprint.$O\
+	fprint.$O\
+	getenv.$O\
+	getfields.$O\
+	lock.$O\
+	mallocz.$O\
+	nan64.$O\
+	netmkaddr.$O\
+	nsec.$O\
+	pow10.$O\
+	pushssl.$O\
+	pushtls.$O\
+	read9pmsg.$O\
+	readn.$O\
+	rune.$O\
+	runefmtstr.$O\
+	runeseprint.$O\
+	runesmprint.$O\
+	runesnprint.$O\
+	runesprint.$O\
+	runestrchr.$O\
+	runestrlen.$O\
+	runestrstr.$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
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libc/atexit.c
@@ -1,0 +1,46 @@
+#include <u.h>
+#include <libc.h>
+
+#define	NEXIT	33
+
+typedef struct Onex Onex;
+struct Onex{
+	void	(*f)(void);
+	int	pid;
+};
+
+static Lock onexlock;
+Onex onex[NEXIT];
+
+int
+atexit(void (*f)(void))
+{
+	int i;
+
+	lock(&onexlock);
+	for(i=0; i<NEXIT; i++)
+		if(onex[i].f == 0) {
+			onex[i].pid = getpid();
+			onex[i].f = f;
+			unlock(&onexlock);
+			return 1;
+		}
+	unlock(&onexlock);
+	return 0;
+}
+
+void
+exits(char *s)
+{
+	int i, pid;
+	void (*f)(void);
+
+	pid = getpid();
+	for(i = NEXIT-1; i >= 0; i--)
+		if((f = onex[i].f) && pid == onex[i].pid) {
+			onex[i].f = 0;
+			(*f)();
+		}
+	exit(s && *s);
+}
+
--- /dev/null
+++ b/libc/charstod.c
@@ -1,0 +1,70 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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.
+ */
+
+double
+fmtcharstod(int(*f)(void*), void *vp)
+{
+	double num, dem;
+	int neg, eneg, dig, exp, c;
+
+	num = 0;
+	neg = 0;
+	dig = 0;
+	exp = 0;
+	eneg = 0;
+
+	c = (*f)(vp);
+	while(c == ' ' || c == '\t')
+		c = (*f)(vp);
+	if(c == '-' || c == '+'){
+		if(c == '-')
+			neg = 1;
+		c = (*f)(vp);
+	}
+	while(c >= '0' && c <= '9'){
+		num = num*10 + c-'0';
+		c = (*f)(vp);
+	}
+	if(c == '.')
+		c = (*f)(vp);
+	while(c >= '0' && c <= '9'){
+		num = num*10 + c-'0';
+		dig++;
+		c = (*f)(vp);
+	}
+	if(c == 'e' || c == 'E'){
+		c = (*f)(vp);
+		if(c == '-' || c == '+'){
+			if(c == '-'){
+				dig = -dig;
+				eneg = 1;
+			}
+			c = (*f)(vp);
+		}
+		while(c >= '0' && c <= '9'){
+			exp = exp*10 + c-'0';
+			c = (*f)(vp);
+		}
+	}
+	exp -= dig;
+	if(exp < 0){
+		exp = -exp;
+		eneg = !eneg;
+	}
+	dem = __fmtpow10(exp);
+	if(eneg)
+		num /= dem;
+	else
+		num *= dem;
+	if(neg)
+		return -num;
+	return num;
+}
--- /dev/null
+++ b/libc/cleanname.c
@@ -1,0 +1,63 @@
+#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, erasedprefix;
+
+	rooted = name[0] == '/';
+	erasedprefix = 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])) {
+			if(p == name)
+				erasedprefix = 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;
+			}
+			if(q == name)
+				erasedprefix = 1;	/* erased entire path via dotdot */
+		} 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';
+	if(erasedprefix && name[0] == '#'){
+		/* this was not a #x device path originally - make it not one now */
+		memmove(name+2, name, strlen(name)+1);
+		name[0] = '.';
+		name[1] = '/';
+	}
+	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,67 @@
+/*
+ *	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 <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/ctime.c
@@ -1,0 +1,118 @@
+/*
+ * This routine converts time as follows.
+ * The epoch is 0000 Jan 1 1970 GMT.
+ * The argument time is in seconds since then.
+ * The localtime(t) entry returns a pointer to an array
+ * containing
+ *
+ *	seconds (0-59)
+ *	minutes (0-59)
+ *	hours (0-23)
+ *	day of month (1-31)
+ *	month (0-11)
+ *	year-1970
+ *	weekday (0-6, Sun is 0)
+ *	day of the year
+ *	daylight savings flag
+ *
+ * The routine gets the daylight savings time from the environment.
+ *
+ * asctime(tvec))
+ * where tvec is produced by localtime
+ * returns a ptr to a character string
+ * that has the ascii time in the form
+ *
+ *	                            \\
+ *	Thu Jan 01 00:00:00 GMT 1970n0
+ *	012345678901234567890123456789
+ *	0	  1	    2
+ *
+ * ctime(t) just calls localtime, then asctime.
+ */
+
+#include <u.h>
+#include <libc.h>
+
+static	char	dmsize[12] =
+{
+	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ * The following table is used for 1974 and 1975 and
+ * gives the day number of the first day after the Sunday of the
+ * change.
+ */
+
+#define dysize _dysize	/* conflicts on unix */
+
+static int
+dysize(int y)
+{
+
+	if(y%4 == 0 && (y%100 != 0 || y%400 == 0))
+		return 366;
+	return 365;
+}
+
+Tm*
+gmtime(long tim)
+{
+	int d0, d1;
+	long hms, day;
+	static Tm xtime;
+
+	/*
+	 * break initial number into days
+	 */
+	hms = tim % 86400L;
+	day = tim / 86400L;
+	if(hms < 0) {
+		hms += 86400L;
+		day -= 1;
+	}
+
+	/*
+	 * generate hours:minutes:seconds
+	 */
+	xtime.sec = hms % 60;
+	d1 = hms / 60;
+	xtime.min = d1 % 60;
+	d1 /= 60;
+	xtime.hour = d1;
+
+	/*
+	 * day is the day number.
+	 * generate day of the week.
+	 * The addend is 4 mod 7 (1/1/1970 was Thursday)
+	 */
+
+	xtime.wday = (day + 7340036L) % 7;
+
+	/*
+	 * year number
+	 */
+	if(day >= 0)
+		for(d1 = 1970; day >= dysize(d1); d1++)
+			day -= dysize(d1);
+	else
+		for (d1 = 1970; day < 0; d1--)
+			day += dysize(d1-1);
+	xtime.year = d1-1900;
+	xtime.yday = d0 = day;
+
+	/*
+	 * generate month
+	 */
+
+	if(dysize(d1) == 366)
+		dmsize[1] = 29;
+	for(d1 = 0; d0 >= dmsize[d1]; d1++)
+		d0 -= dmsize[d1];
+	dmsize[1] = 28;
+	xtime.mday = d0 + 1;
+	xtime.mon = d1;
+	strcpy(xtime.zone, "GMT");
+	return &xtime;
+}
+
--- /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,539 @@
+#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 = (Rune*)f->to;
+			rs = (Rune*)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 = (char*)f->to;
+			s = (char*)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 = (char*)__fmtflush(f, t, n);
+						if(t != nil)
+							s = (char*)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 = (char*)__fmtdispatch(f, fmt, 0);
+		if(fmt == nil)
+			return -1;
+	}
+}
+
+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 = (char*)f->to;
+	s = (char*)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 = (Rune*)f->to;
+	s = (Rune*)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, const void *vm, int n, int sz)
+{
+	Rune *rt, *rs, r;
+	char *t, *s, *m, *me;
+	ulong fl;
+	int nc, w;
+
+	m = (char*)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 = (Rune*)f->to;
+		rs = (Rune*)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(fl & FmtLeft && __rfmtpad(f, w - n) < 0)
+			return -1;
+	}else{
+		if(!(fl & FmtLeft) && __fmtpad(f, w - n) < 0)
+			return -1;
+		t = (char*)f->to;
+		s = (char*)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, const void *vm, int n)
+{
+	Rune r, *m, *me, *rt, *rs;
+	char *t, *s;
+	ulong fl;
+	int w;
+
+	m = (Rune*)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 = (Rune*)f->to;
+		rs = (Rune*)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 = (char*)f->to;
+		s = (char*)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, (const char*)x, 1, 1);
+}
+
+/* fmt out one rune */
+int
+__runefmt(Fmt *f)
+{
+	Rune x[1];
+
+	x[0] = va_arg(f->args, int);
+	return __fmtrcpy(f, (const void*)x, 1);
+}
+
+/* public helper routine: fmt out a null terminated string already in hand */
+int
+fmtstrcpy(Fmt *f, char *s)
+{
+	int i, j;
+	Rune r;
+
+	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){
+		i = 0;
+		for(j=0; j<f->prec && s[i]; j++)
+			i += chartorune(&r, s+i);
+		return __fmtcpy(f, s, j, i);
+	}
+	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, (const void*)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;
+	/*
+	 * Unsigned verbs for ANSI C
+	 */
+	switch(f->r){
+	case 'x':
+	case 'X':
+	case 'o':
+	case 'u':
+	case 'p':
+		fl |= FmtUnsigned;
+		fl &= ~(FmtSign|FmtSpace);
+		break;
+	}
+	if(f->r == 'p'){
+		if(sizeof(void*) == sizeof(uvlong)){
+			isv = 1;
+			vu = (uvlong)va_arg(f->args, uvlong);
+		}else
+			u = (ulong)va_arg(f->args, ulong);
+		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':
+	case 'i':
+	case 'u':
+		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|FmtPrec))){
+		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':
+		f->flags |= FmtLDouble;
+		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, (const void*)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, const 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, (Rune*)fmt, 1);
+		if(fmt == nil)
+			return -1;
+	}
+	return 0;		/* not reached */
+}
--- /dev/null
+++ b/libc/encodefmt.c
@@ -1,0 +1,78 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+int
+encodefmt(Fmt *f)
+{
+	char *out;
+	char *buf;
+	int len;
+	int ilen;
+	int rv;
+	uchar *b;
+	char *p;
+	char obuf[64];	// rsc optimization
+
+	if(!(f->flags&FmtPrec) || f->prec < 1)
+		goto error;
+
+	b = va_arg(f->args, uchar*);
+	if(b == 0)
+		return fmtstrcpy(f, "<nil>");
+
+	ilen = f->prec;
+	f->prec = 0;
+	f->flags &= ~FmtPrec;
+	switch(f->r){
+	case '<':
+		len = (8*ilen+4)/5 + 3;
+		break;
+	case '[':
+		len = (8*ilen+5)/6 + 4;
+		break;
+	case 'H':
+		len = 2*ilen + 1;
+		break;
+	default:
+		goto error;
+	}
+
+	if(len > sizeof(obuf)){
+		buf = malloc(len);
+		if(buf == nil)
+			goto error;
+	} else
+		buf = obuf;
+
+	// convert
+	out = buf;
+	switch(f->r){
+	case '[':
+		rv = enc64(out, len, b, ilen);
+		break;
+	case '<':
+		rv = enc32(out, len, b, ilen);
+		break;
+	case 'H':
+		rv = enc16(out, len, b, ilen);
+		break;
+	default:
+		rv = -1;
+		break;
+	}
+	if(rv < 0)
+		goto error;
+
+	if((f->flags & FmtLong) != 0 && f->r != '[')
+		for(p = buf; *p; p++)
+			*p = tolower(*p);
+
+	fmtstrcpy(f, buf);
+	if(buf != obuf)
+		free(buf);
+	return 0;
+
+error:
+	return fmtstrcpy(f, "<encodefmt>");
+}
--- /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') || (uchar)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,375 @@
+#include <u.h>
+#include <libc.h>
+#include <float.h>
+#include <ctype.h>
+#include "fmtdef.h"
+
+enum
+{
+	FDIGIT	= 30,
+	FDEFLT	= 6,
+	NSIGNIF	= 17
+};
+
+/*
+ * first few powers of 10, enough for about 1/2 of the
+ * total space for doubles.
+ */
+static double pows10[] =
+{
+	  1e0,   1e1,   1e2,   1e3,   1e4,   1e5,   1e6,   1e7,   1e8,   1e9,  
+	 1e10,  1e11,  1e12,  1e13,  1e14,  1e15,  1e16,  1e17,  1e18,  1e19,  
+	 1e20,  1e21,  1e22,  1e23,  1e24,  1e25,  1e26,  1e27,  1e28,  1e29,  
+	 1e30,  1e31,  1e32,  1e33,  1e34,  1e35,  1e36,  1e37,  1e38,  1e39,  
+	 1e40,  1e41,  1e42,  1e43,  1e44,  1e45,  1e46,  1e47,  1e48,  1e49,  
+	 1e50,  1e51,  1e52,  1e53,  1e54,  1e55,  1e56,  1e57,  1e58,  1e59,  
+	 1e60,  1e61,  1e62,  1e63,  1e64,  1e65,  1e66,  1e67,  1e68,  1e69,  
+	 1e70,  1e71,  1e72,  1e73,  1e74,  1e75,  1e76,  1e77,  1e78,  1e79,  
+	 1e80,  1e81,  1e82,  1e83,  1e84,  1e85,  1e86,  1e87,  1e88,  1e89,  
+	 1e90,  1e91,  1e92,  1e93,  1e94,  1e95,  1e96,  1e97,  1e98,  1e99,  
+	1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109, 
+	1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119, 
+	1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129, 
+	1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139, 
+	1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149, 
+	1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159, 
+};
+
+#undef pow10
+#define  pow10(x)  fmtpow10(x)
+
+static double
+pow10(int n)
+{
+	double d;
+	int neg;
+
+	neg = 0;
+	if(n < 0){
+		if(n < DBL_MIN_10_EXP){
+			return 0.;
+		}
+		neg = 1;
+		n = -n;
+	}else if(n > DBL_MAX_10_EXP){
+		return HUGE_VAL;
+	}
+	if(n < (int)(sizeof(pows10)/sizeof(pows10[0])))
+		d = pows10[n];
+	else{
+		d = pows10[sizeof(pows10)/sizeof(pows10[0]) - 1];
+		for(;;){
+			n -= sizeof(pows10)/sizeof(pows10[0]) - 1;
+			if(n < (int)(sizeof(pows10)/sizeof(pows10[0]))){
+				d *= pows10[n];
+				break;
+			}
+			d *= pows10[sizeof(pows10)/sizeof(pows10[0]) - 1];
+		}
+	}
+	if(neg){
+		return 1./d;
+	}
+	return d;
+}
+
+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;
+}
+
+static int
+floatfmt(Fmt *fmt, double f)
+{
+	char s[341];		/* precision+exponent+sign+'.'+null */
+
+	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,216 @@
+#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,	/* Plan 9 addition */
+	'E',	__efgfmt,
+#ifndef PLAN9PORT
+	'F',	__efgfmt,	/* ANSI only */
+#endif
+	'G',	__efgfmt,
+#ifndef PLAN9PORT
+	'L',	__flagfmt,	/* ANSI only */
+#endif
+	'S',	__runesfmt,	/* Plan 9 addition */
+	'X',	__ifmt,
+	'b',	__ifmt,		/* Plan 9 addition */
+	'c',	__charfmt,
+	'd',	__ifmt,
+	'e',	__efgfmt,
+	'f',	__efgfmt,
+	'g',	__efgfmt,
+	'h',	__flagfmt,
+#ifndef PLAN9PORT
+	'i',	__ifmt,		/* ANSI only */
+#endif
+	'l',	__flagfmt,
+	'n',	__countfmt,
+	'o',	__ifmt,
+	'p',	__ifmt,
+	'r',	__errfmt,
+	's',	__strfmt,
+#ifdef PLAN9PORT
+	'u',	__flagfmt,
+#else
+	'u',	__ifmt,
+#endif
+	'x',	__ifmt,
+	0,	0,
+};
+
+
+int	(*fmtdoquote)(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, int (*f)(Fmt*))
+{
+	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 == 0)	/* 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, (char*)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){
+				/*
+				 * negative precision =>
+				 * ignore the precision.
+				 */
+				if(f->flags & FmtPrec){
+					f->flags &= ~FmtPrec;
+					f->prec = 0;
+					continue;
+				}
+				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/fmtdef.h
@@ -1,0 +1,103 @@
+/*
+ * 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 */
+};
+
+/* Edit .+1,/^$/ |cfn |grep -v static | grep __ */
+double       __Inf(int sign);
+double       __NaN(void);
+int          __badfmt(Fmt *f);
+int          __charfmt(Fmt *f);
+int          __countfmt(Fmt *f);
+int          __efgfmt(Fmt *fmt);
+int          __errfmt(Fmt *f);
+int          __flagfmt(Fmt *f);
+int          __fmtFdFlush(Fmt *f);
+int          __fmtcpy(Fmt *f, const void *vm, int n, int sz);
+void*        __fmtdispatch(Fmt *f, void *fmt, int isrunes);
+void *       __fmtflush(Fmt *f, void *t, int len);
+void         __fmtlock(void);
+int          __fmtpad(Fmt *f, int n);
+double       __fmtpow10(int n);
+int          __fmtrcpy(Fmt *f, const void *vm, int n);
+void         __fmtunlock(void);
+int          __ifmt(Fmt *f);
+int          __isInf(double d, int sign);
+int          __isNaN(double d);
+int          __needsquotes(char *s, int *quotelenp);
+int          __percentfmt(Fmt *f);
+void         __quotesetup(char *s, Rune *r, int nin, int nout, Quoteinfo *q, int sharp, int runesout);
+int          __quotestrfmt(int runesin, Fmt *f);
+int          __rfmtpad(Fmt *f, int n);
+int          __runefmt(Fmt *f);
+int          __runeneedsquotes(Rune *r, int *quotelenp);
+int          __runesfmt(Fmt *f);
+int          __strfmt(Fmt *f);
+
+#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)
+
+#ifdef va_copy
+#	define VA_COPY(a,b) va_copy(a,b)
+#	define VA_END(a) va_end(a)
+#else
+#	define VA_COPY(a,b) (a) = (b)
+#	define VA_END(a)
+#endif
+
+#define PLAN9PORT
--- /dev/null
+++ b/libc/fmtfd.c
@@ -1,0 +1,32 @@
+#include <inttypes.h>
+#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*)(uintptr_t)fd;
+	f->nfmt = 0;
+	return 0;
+}
--- /dev/null
+++ b/libc/fmtfdflush.c
@@ -1,0 +1,20 @@
+#include <inttypes.h>
+#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((uintptr_t)f->farg, f->start, n) != n)
+		return 0;
+	f->to = f->start;
+	return 1;
+}
--- /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,33 @@
+#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_COPY(va, f->args);
+	VA_END(f->args);
+	va_start(f->args, fmt);
+	n = dofmt(f, fmt);
+	va_end(f->args);
+	f->flags = 0;
+	f->width = 0;
+	f->prec = 0;
+	VA_COPY(f->args,va);
+	VA_END(va);
+	if(n >= 0)
+		return 0;
+	return n;
+}
+
--- /dev/null
+++ b/libc/fmtquote.c
@@ -1,0 +1,249 @@
+#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--){
+		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'\'') || (fmtdoquote!=0 && fmtdoquote(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 = (char*)f->to;
+	s = (char*)f->stop;
+	rt = (Rune*)f->to;
+	rs = (Rune*)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 nin, outlen;
+	Rune *r;
+	char *s;
+	Quoteinfo q;
+
+	nin = -1;
+	if(f->flags&FmtPrec)
+		nin = f->prec;
+	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, (void*)"<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, nin, 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 = (Rune*)f->to;
+		FMTRCHAR(f, rt, f->stop, r);
+		f->to = rt;
+		n = 1;
+	}else{
+		t = (char*)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,12 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+char*
+fmtstrflush(Fmt *f)
+{
+	if(f->start == nil)
+		return nil;
+	*(char*)f->to = '\0';
+	return (char*)f->start;
+}
--- /dev/null
+++ b/libc/fmtvprint.c
@@ -1,0 +1,34 @@
+#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_COPY(va,f->args);
+	VA_END(f->args);
+	VA_COPY(f->args,args);
+	n = dofmt(f, fmt);
+	f->flags = 0;
+	f->width = 0;
+	f->prec = 0;
+	VA_END(f->args);
+	VA_COPY(f->args,va);
+	VA_END(va);
+	if(n >= 0)
+		return 0;
+	return n;
+}
+
--- /dev/null
+++ b/libc/fprint.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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/getenv.c
@@ -1,0 +1,45 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+getenv(char *name)
+{
+	enum { HUNK = 100, };
+	char *s, *p;
+	int f, r, n;
+
+	if(name[0]=='\0' || strcmp(name, ".")==0 || strcmp(name, "..")==0 || strchr(name, '/')!=nil
+	|| strlen(name) >= HUNK-5){
+		werrstr("bad env name: %s", name);
+		return nil;
+	}
+	if((s = malloc(HUNK)) == nil)
+		return nil;
+	snprint(s, HUNK, "/env/%s", name);
+	n = 0;
+	r = -1;
+	if((f = open(s, OREAD)) >= 0){
+		while((r = read(f, s+n, HUNK)) > 0){
+			n += r;
+			r = -1;
+			if((p = realloc(s, n+HUNK)) == nil)
+				break;
+			s = p;
+		}
+		close(f);
+	}
+	if(r < 0 || (p = realloc(s, n+1)) == nil){
+		free(s);
+		return nil;
+	}
+	s = p;
+	setmalloctag(s, getcallerpc(&name));
+	while(n > 0 && s[n-1] == '\0')
+		n--;
+	s[n] = '\0';
+	while(--n >= 0){
+		if(s[n] == '\0')
+			s[n] = ' ';
+	}
+	return s;
+}
--- /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/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,120 @@
+#include <u.h>
+#include <libc.h>
+
+#ifdef PTHREAD
+
+static pthread_mutex_t initmutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void
+lockinit(Lock *lk)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutex_lock(&initmutex);
+	if(lk->init == 0){
+		pthread_mutexattr_init(&attr);
+		pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+		pthread_mutex_init(&lk->mutex, &attr);
+		pthread_mutexattr_destroy(&attr);
+		lk->init = 1;
+	}
+	pthread_mutex_unlock(&initmutex);
+}
+
+void
+lock(Lock *lk)
+{
+	if(!lk->init)
+		lockinit(lk);
+	if(pthread_mutex_lock(&lk->mutex) != 0)
+		abort();
+}
+
+int
+canlock(Lock *lk)
+{
+	int r;
+
+	if(!lk->init)
+		lockinit(lk);
+	r = pthread_mutex_trylock(&lk->mutex);
+	if(r == 0)
+		return 1;
+	if(r == EBUSY)
+		return 0;
+	abort();
+}
+
+void
+unlock(Lock *lk)
+{
+	if(pthread_mutex_unlock(&lk->mutex) != 0)
+		abort();
+}
+
+#else 
+
+/* old, non-pthread systems */
+
+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();
+		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=%p\n", getpid(), lk->key, lk, getcallerpc(&lk));
+		osmsleep(1000);
+	}
+}
+
+void
+unlock(Lock *lk)
+{
+	assert(lk->key);
+	lk->key = 0;
+}
+
+#endif
+
+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/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,67 @@
+/*
+ * 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 "fmtdef.h"
+
+#if defined (__APPLE__) || (__powerpc__)
+#define _NEEDLL
+#endif
+
+static uvlong uvnan    = ((uvlong)0x7FF00000<<32)|0x00000001;
+static uvlong uvinf    = ((uvlong)0x7FF00000<<32)|0x00000000;
+static uvlong uvneginf = ((uvlong)0xFFF00000<<32)|0x00000000;
+
+double
+__NaN(void)
+{
+	uvlong *p;
+
+	/* gcc complains about "return *(double*)&uvnan;" */
+	p = &uvnan;
+	return *(double*)p;
+}
+
+int
+__isNaN(double d)
+{
+	uvlong x;
+	double *p;
+
+	p = &d;
+	x = *(uvlong*)p;
+	return (ulong)(x>>32)==0x7FF00000 && !__isInf(d, 0);
+}
+
+double
+__Inf(int sign)
+{
+	uvlong *p;
+
+	if(sign < 0)
+		p = &uvinf;
+	else
+		p = &uvneginf;
+	return *(double*)p;
+}
+
+int
+__isInf(double d, int sign)
+{
+	uvlong x;
+	double *p;
+
+	p = &d;
+	x = *(uvlong*)p;
+	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,21 @@
+#include	<u.h>
+#include	<libc.h>
+
+#define	MASK	0x7fffffffL
+
+int
+nrand(int n)
+{
+	long slop, v;
+
+	if(n < 0)
+		return n;
+	if(n == 1)
+		return 0;
+	/* and if n == 0, you deserve what you get */
+	slop = MASK % n;
+	do
+		v = lrand();
+	while(v <= slop);
+	return v % n;
+}
--- /dev/null
+++ b/libc/nsec.c
@@ -1,0 +1,65 @@
+#include <u.h>
+#include <libc.h>
+
+static uvlong order = (uvlong) 0x0001020304050607ULL;
+
+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,42 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,
+};
+
+double
+__fmtpow10(int n)
+{
+	int m;
+
+	if(n < 0) {
+		n = -n;
+		if(n < (int)(sizeof(tab)/sizeof(tab[0])))
+			return 1/tab[n];
+		m = n/2;
+		return __fmtpow10(-m) * __fmtpow10(m-n);
+	}
+	if(n < (int)(sizeof(tab)/sizeof(tab[0])))
+		return tab[n];
+	m = n/2;
+	return __fmtpow10(m) * __fmtpow10(n-m);
+}
--- /dev/null
+++ b/libc/print.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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/pushtls.c
@@ -1,0 +1,99 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+
+enum {
+	TLSFinishedLen = 12,
+	HFinished = 20,
+};
+
+static int
+finished(int hand, int isclient)
+{
+	int i, n;
+	uchar buf[500], buf2[500];
+
+	buf[0] = HFinished;
+	buf[1] = TLSFinishedLen>>16;
+	buf[2] = TLSFinishedLen>>8;
+	buf[3] = TLSFinishedLen;
+	n = TLSFinishedLen+4;
+
+	for(i=0; i<2; i++){
+		if(i==0)
+			memmove(buf+4, "client finished", TLSFinishedLen);
+		else
+			memmove(buf+4, "server finished", TLSFinishedLen);
+		if(isclient == 1-i){
+			if(write(hand, buf, n) != n)
+				return -1;
+		}else{
+			if(readn(hand, buf2, n) != n || memcmp(buf,buf2,n) != 0)
+				return -1;
+		}
+	}
+	return 1;
+}
+
+
+// given a plain fd and secrets established beforehand, return encrypted connection
+int
+pushtls(int fd, char *hashalg, char *encalg, int isclient, char *secret, char *dir)
+{
+	char buf[8];
+	char dname[64];
+	int n, data, ctl, hand;
+
+	// open a new filter; get ctl fd
+	data = hand = -1;
+	// /net/tls uses decimal file descriptors to name channels, hence a
+	// user-level file server can't stand in for #a; may as well hard-code it.
+	ctl = open("#a/tls/clone", ORDWR);
+	if(ctl < 0)
+		goto error;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0)
+		goto error;
+	buf[n] = 0;
+	if(dir)
+		sprint(dir, "#a/tls/%s", buf);
+
+	// get application fd
+	sprint(dname, "#a/tls/%s/data", buf);
+	data = open(dname, ORDWR);
+	if(data < 0)
+		goto error;
+
+	// get handshake fd
+	sprint(dname, "#a/tls/%s/hand", buf);
+	hand = open(dname, ORDWR);
+	if(hand < 0)
+		goto error;
+
+	// speak a minimal handshake
+	if(fprint(ctl, "fd %d 0x301", fd) < 0 ||
+	   fprint(ctl, "version 0x301") < 0 ||
+	   fprint(ctl, "secret %s %s %d %s", hashalg, encalg, isclient, secret) < 0 ||
+	   fprint(ctl, "changecipher") < 0 ||
+	   finished(hand, isclient) < 0 ||
+	   fprint(ctl, "opened") < 0){
+		close(hand);
+		hand = -1;
+		goto error;
+	}
+	close(ctl);
+	close(hand);
+	close(fd);
+	return data;
+
+error:
+	if(data>=0)
+		close(data);
+	if(ctl>=0)
+		close(ctl);
+	if(hand>=0)
+		close(hand);
+	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,204 @@
+#include	<u.h>
+#include	<libc.h>
+
+enum
+{
+	Bit1	= 7,
+	Bitx	= 6,
+	Bit2	= 5,
+	Bit3	= 4,
+	Bit4	= 3,
+	Bit5	= 2,
+
+	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 */
+	T5	= ((1<<(Bit5+1))-1) ^ 0xFF,	/* 1111 1000 */
+
+	Rune1	= (1<<(Bit1+0*Bitx))-1,		/* 0000 0000 0000 0000 0111 1111 */
+	Rune2	= (1<<(Bit2+1*Bitx))-1,		/* 0000 0000 0000 0111 1111 1111 */
+	Rune3	= (1<<(Bit3+2*Bitx))-1,		/* 0000 0000 1111 1111 1111 1111 */
+	Rune4	= (1<<(Bit4+3*Bitx))-1,		/* 0011 1111 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, c3;
+	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;
+	}
+
+ 	/*
+	 * four character sequence
+	 *	10000-10FFFF => T4 Tx Tx Tx
+	 */
+	if(UTFmax >= 4) {
+		c3 = *(uchar*)(str+3) ^ Tx;
+		if(c3 & Testx)
+			goto bad;
+		if(c < T5) {
+			l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) & Rune4;
+			if(l <= Rune3)
+				goto bad;
+			if(l > Runemax)
+				goto bad;
+			*rune = l;
+			return 4;
+		}
+	}
+
+	/*
+	 * bad decoding
+	 */
+bad:
+	*rune = Bad;
+	return 1;
+}
+
+int
+runetochar(char *str, Rune *rune)
+{
+	long c;
+
+	c = *rune;
+	if(c > Runemax)
+		c = Runeerror;
+
+	/*
+	 * one character sequence
+	 *	00000-0007F => 00-7F
+	 */
+	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
+	 */
+	if(c <= Rune3) {
+		str[0] = T3 |  (c >> 2*Bitx);
+		str[1] = Tx | ((c >> 1*Bitx) & Maskx);
+		str[2] = Tx |  (c & Maskx);
+		return 3;
+	}
+
+	/*
+	 * four character sequence
+	 *	10000-1FFFFF => T4 Tx Tx Tx
+	 */
+	str[0] = T4 |  (c >> 3*Bitx);
+	str[1] = Tx | ((c >> 2*Bitx) & Maskx);
+	str[2] = Tx | ((c >> 1*Bitx) & Maskx);
+	str[3] = Tx |  (c & Maskx);
+	return 4;
+}
+
+int
+runelen(long c)
+{
+	Rune rune;
+	char str[UTFmax];
+
+	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
+		if(c <= Rune3 || c > Runemax)
+			nb += 3;
+		else
+			nb += 4;
+	}
+	return nb;
+}
+
+int
+fullrune(char *str, int n)
+{
+	int c;
+
+	if(n <= 0)
+		return 0;
+	c = *(uchar*)str;
+	if(c < Tx)
+		return 1;
+	if(c < T3)
+		return n >= 2;
+	if(UTFmax == 3 || c < T4)
+		return n >= 3;
+	return n >= 4;
+}
+
--- /dev/null
+++ b/libc/runefmtstr.c
@@ -1,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,15 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,15 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,16 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,15 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,17 @@
+#include <u.h>
+#include <libc.h>
+
+Rune*
+runestrchr(Rune *s, Rune c)
+{
+	Rune r;
+
+	if(c == 0)
+		while(*s++)
+			;
+	else
+		while((r = *s++) != c)
+			if(r == 0)
+				return 0;
+	return s-1;
+}
--- /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;
+	setmalloctag(ns, getcallerpc(&s));
+	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[-1] = '\0';
+			break;
+		}
+	}
+	return s1-1;
+}
--- /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,1182 @@
+#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 */
+	0x0085, 0x0085,
+	0x00a0,	0x00a0,	/*   */
+	0x1680, 0x1680,
+	0x180e, 0x180e,
+	0x2000,	0x200b,	/*   - ​ */
+	0x2028,	0x2029,	/* 
 - 
 */
+	0x202f, 0x202f,
+	0x205f, 0x205f,
+	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,	/* ῳ ῼ */
+};
+
+static Rune __isdigitr[] = {
+	0x0030, 0x0039,
+	0x0660, 0x0669,
+	0x06f0, 0x06f9,
+	0x07c0, 0x07c9,
+	0x0966, 0x096f,
+	0x09e6, 0x09ef,
+	0x0a66, 0x0a6f,
+	0x0ae6, 0x0aef,
+	0x0b66, 0x0b6f,
+	0x0be6, 0x0bef,
+	0x0c66, 0x0c6f,
+	0x0ce6, 0x0cef,
+	0x0d66, 0x0d6f,
+	0x0e50, 0x0e59,
+	0x0ed0, 0x0ed9,
+	0x0f20, 0x0f29,
+	0x1040, 0x1049,
+	0x17e0, 0x17e9,
+	0x1810, 0x1819,
+	0x1946, 0x194f,
+	0x19d0, 0x19d9,
+	0x1b50, 0x1b59,
+	0xff10, 0xff19,
+	0x104a0, 0x104a9,
+	0x1d7ce, 0x1d7ff,
+};
+
+/*
+ * 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;
+}
+
+int
+isdigitrune(Rune c)
+{
+	Rune *p;
+
+	p = bsearch(c, __isdigitr, nelem(__isdigitr)/2, 2);
+	if(p && c >= p[0] && c <= p[1])
+		return 1;
+	return 0;
+}
--- /dev/null
+++ b/libc/runevseprint.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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 = 0;
+	f.farg = nil;
+	f.nfmt = 0;
+	VA_COPY(f.args,args);
+	dofmt(&f, fmt);
+	VA_END(f.args);
+	*(Rune*)f.to = '\0';
+	return (Rune*)f.to;
+}
+
--- /dev/null
+++ b/libc/runevsmprint.c
@@ -1,0 +1,71 @@
+#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 = (uintptr)f->farg;
+	n *= 2;
+	s = (Rune*)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*)(uintptr)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*)(uintptr)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;
+	VA_COPY(f.args,args);
+	n = dofmt(&f, fmt);
+	VA_END(f.args);
+	if(f.start == nil)
+		return nil;
+	if(n < 0){
+		free(f.start);
+		return nil;
+	}
+	*(Rune*)f.to = '\0';
+	return (Rune*)f.start;
+}
--- /dev/null
+++ b/libc/runevsnprint.c
@@ -1,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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 = 0;
+	f.farg = nil;
+	f.nfmt = 0;
+	VA_COPY(f.args,args);
+	dofmt(&f, fmt);
+	VA_END(f.args);
+	*(Rune*)f.to = '\0';
+	return (Rune*)f.to - buf;
+}
--- /dev/null
+++ b/libc/seprint.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,15 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,16 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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,24 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+int
+sprint(char *buf, char *fmt, ...)
+{
+	int n;
+	uint len;
+	va_list args;
+
+	len = 1<<30;  /* big number, but sprint is deprecated anyway */
+	/*
+	 * on PowerPC, the stack is near the top of memory, so
+	 * we must be sure not to overflow a 32-bit pointer.
+	 */
+	if((uintptr)buf+len < (uintptr)buf)
+		len = -(uintptr)buf-1;
+
+	va_start(args, fmt);
+	n = vsnprint(buf, len, fmt, args);
+	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,543 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include "fmtdef.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,
+
+	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 */
+
+	Fsign	= 1<<0,		/* found - */
+	Fesign	= 1<<1,		/* found e- */
+	Fdpoint	= 1<<2,		/* found . */
+};
+
+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);
+static ulong	umuldiv(ulong, ulong, ulong);
+
+typedef	struct	Tab	Tab;
+struct	Tab
+{
+	int	bp;
+	int	siz;
+	char*	cmp;
+};
+
+#ifndef ERANGE
+#define ERANGE 12345
+#endif
+
+double
+fmtstrtod(const 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=(char*)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 = (char*)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 */
+		errno = ERANGE;
+		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);
+	}
+	goto out;
+
+ret0:
+	return 0;
+
+retnan:
+	return __NaN();
+
+retinf:
+	/*
+	 * Unix strtod requires these.  Plan 9 would return Inf(0) or Inf(-1). */
+	errno = ERANGE;
+	if(flag & Fsign)
+		return -HUGE_VAL;
+	return HUGE_VAL;
+
+out:
+	d = 0;
+	for(i=0; i<Prec; i++)
+		d = d*One + mid[i];
+	if(flag & Fsign)
+		d = -d;
+	d = ldexp(d, bp - Prec*Nbits);
+	if(d == 0){	/* underflow */
+		errno = ERANGE;
+	}
+	return d;
+}
+
+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:;
+	}
+}
+
+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 >= (int)(nelem(tab1)))
+		d = (int)(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 >= (int)(nelem(tab2)))
+		d = (int)(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++) != 0) {
+		c2 = *a++;
+		if(isupper(c2))
+			c2 = tolower(c2);
+		if(c1 != c2)
+			return 1;
+	}
+	return 0;
+}
+
+static ulong
+umuldiv(ulong a, ulong b, ulong c)
+{
+	return ((uvlong)a * (uvlong)b) / c;
+}
--- /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)~(((uvlong)1)<<63))
+#define VLONG_MIN	((vlong)(((uvlong)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,27 @@
+#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);
+	exits("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,13 @@
+#include <u.h>
+#include <libc.h>
+
+long
+time(long *tp)
+{
+	vlong t;
+
+	t = nsec()/1000000000LL;
+	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,69 @@
+#include <u.h>
+#include <libc.h>
+
+#define between(x,min,max)	(((min-1-x) & (x-max-1))>>8)
+
+int
+enc16chr(int o)
+{
+	int c;
+
+	c  = between(o,  0,  9) & ('0'+o);
+	c |= between(o, 10, 15) & ('A'+(o-10));
+	return c;
+}
+
+int
+dec16chr(int c)
+{
+	int o;
+
+	o  = between(c, '0', '9') & (1+(c-'0'));
+	o |= between(c, 'A', 'F') & (1+10+(c-'A'));
+	o |= between(c, 'a', 'f') & (1+10+(c-'a'));
+	return o-1;
+}
+
+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 = dec16chr(*in++);
+		if(c < 0)
+			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++ = enc16chr(c>>4);
+		*out++ = enc16chr(c&15);
+	}
+exhausted:
+	*out = 0;
+	return out - start;
+}
--- /dev/null
+++ b/libc/u32.c
@@ -1,0 +1,132 @@
+#include <u.h>
+#include <libc.h>
+
+#define between(x,min,max)	(((min-1-x) & (x-max-1))>>8)
+
+int
+enc32chr(int o)
+{
+	int c;
+
+	c  = between(o,  0, 25) & ('A'+o);
+	c |= between(o, 26, 31) & ('2'+(o-26));
+	return c;
+}
+
+int
+dec32chr(int c)
+{
+	int o;
+
+	o  = between(c, 'A', 'Z') & (1+(c-'A'));
+	o |= between(c, 'a', 'z') & (1+(c-'a'));
+	o |= between(c, '2', '7') & (1+26+(c-'2'));
+	return o-1;
+}
+
+int
+dec32(uchar *dest, int ndest, char *src, int nsrc)
+{
+	uchar *start;
+	int i, j, u[8];
+
+	if(ndest+1 < (5*nsrc+7)/8)
+		return -1;
+	start = dest;
+	while(nsrc>=8){
+		for(i=0; i<8; i++){
+			j = dec32chr(src[i]);
+			if(j < 0)
+				j = 0;
+			u[i] = j;
+		}
+		*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++){
+			j = dec32chr(src[i]);
+			if(j < 0)
+				j = 0;
+			u[i] = j;
+		}
+		*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 *start;
+	int j;
+
+	if(ndest <= (8*nsrc+4)/5)
+		return -1;
+	start = dest;
+	while(nsrc>=5){
+		j = (0x1f & (src[0]>>3));
+		*dest++ = enc32chr(j);
+		j = (0x1c & (src[0]<<2)) | (0x03 & (src[1]>>6));
+		*dest++ = enc32chr(j);
+		j = (0x1f & (src[1]>>1));
+		*dest++ = enc32chr(j);
+		j = (0x10 & (src[1]<<4)) | (0x0f & (src[2]>>4));
+		*dest++ = enc32chr(j);
+		j = (0x1e & (src[2]<<1)) | (0x01 & (src[3]>>7));
+		*dest++ = enc32chr(j);
+		j = (0x1f & (src[3]>>2));
+		*dest++ = enc32chr(j);
+		j = (0x18 & (src[3]<<3)) | (0x07 & (src[4]>>5));
+		*dest++ = enc32chr(j);
+		j = (0x1f & (src[4]));
+		*dest++ = enc32chr(j);
+		src  += 5;
+		nsrc -= 5;
+	}
+	if(nsrc){
+		j = (0x1f & (src[0]>>3));
+		*dest++ = enc32chr(j);
+		j = (0x1c & (src[0]<<2));
+		if(nsrc == 1)
+			goto out;
+		j |= (0x03 & (src[1]>>6));
+		*dest++ = enc32chr(j);
+		j = (0x1f & (src[1]>>1));
+		*dest++ = enc32chr(j);
+		j = (0x10 & (src[1]<<4));
+		if(nsrc == 2)
+			goto out;
+		j |= (0x0f & (src[2]>>4));
+		*dest++ = enc32chr(j);
+		j = (0x1e & (src[2]<<1));
+		if(nsrc == 3)
+			goto out;
+		j |= (0x01 & (src[3]>>7));
+		*dest++ = enc32chr(j);
+		j = (0x1f & (src[3]>>2));
+		*dest++ = enc32chr(j);
+		j = (0x18 & (src[3]<<3));
+out:
+		*dest++ = enc32chr(j);
+	}
+	*dest = 0;
+	return dest-start;
+}
--- /dev/null
+++ b/libc/u64.c
@@ -1,0 +1,130 @@
+#include <u.h>
+#include <libc.h>
+
+#define between(x,min,max)	(((min-1-x) & (x-max-1))>>8)
+
+int
+enc64chr(int o)
+{
+	int c;
+
+	c  = between(o,  0, 25) & ('A'+o);
+	c |= between(o, 26, 51) & ('a'+(o-26));
+	c |= between(o, 52, 61) & ('0'+(o-52));
+	c |= between(o, 62, 62) & ('+');
+	c |= between(o, 63, 63) & ('/');
+	return c;
+}
+
+int
+dec64chr(int c)
+{
+	int o;
+
+	o  = between(c, 'A', 'Z') & (1+(c-'A'));
+	o |= between(c, 'a', 'z') & (1+26+(c-'a'));
+	o |= between(c, '0', '9') & (1+52+(c-'0'));
+	o |= between(c, '+', '+') & (1+62);
+	o |= between(c, '/', '/') & (1+63);
+	return o-1;
+}
+
+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 = dec64chr(*in++);
+		if(c < 0)
+			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 = 0;
+			continue;
+		}
+		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++ = enc64chr(b24>>18);
+		*out++ = enc64chr((b24>>12)&0x3f);
+		*out++ = enc64chr((b24>>6)&0x3f);
+		*out++ = enc64chr(b24&0x3f);
+	}
+
+	switch(n%3){
+	case 2:
+		b24 = *in++<<16;
+		b24 |= *in<<8;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = enc64chr(b24>>18);
+		*out++ = enc64chr((b24>>12)&0x3f);
+		*out++ = enc64chr((b24>>6)&0x3f);
+		*out++ = '=';
+		break;
+	case 1:
+		b24 = *in<<16;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = enc64chr(b24>>18);
+		*out++ = enc64chr((b24>>12)&0x3f);
+		*out++ = '=';
+		*out++ = '=';
+		break;
+	}
+exhausted:
+	*out = 0;
+	return out - start;
+}
--- /dev/null
+++ b/libc/utf.h
@@ -1,0 +1,53 @@
+#ifndef _UTFH_
+#define _UTFH_ 1
+
+typedef unsigned int Rune;	/* 32 bits */
+
+enum
+{
+	UTFmax		= 4,		/* maximum bytes per rune */
+	Runesync	= 0x80,		/* cannot represent part of a UTF sequence (<) */
+	Runeself	= 0x80,		/* rune and UTF sequences are the same (<) */
+	Runeerror	= 0xFFFD,	/* decoding error in UTF */
+	Runemax		= 0x10FFFF,	/* 21-bit rune */
+	Runemask	= 0x1FFFFF,	/* bits used by runes (see grep) */
+};
+
+/*
+ * 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;
+		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,19 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.h"
+
+int
+vfprint(int fd, char *fmt, va_list args)
+{
+	Fmt f;
+	char buf[256];
+	int n;
+
+	fmtfdinit(&f, fd, buf, sizeof(buf));
+	VA_COPY(f.args,args);
+	n = dofmt(&f, fmt);
+	VA_END(f.args);
+	if(n > 0 && __fmtFdFlush(&f) == 0)
+		return -1;
+	return n;
+}
--- /dev/null
+++ b/libc/vseprint.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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 = 0;
+	f.farg = nil;
+	f.nfmt = 0;
+	VA_COPY(f.args,args);
+	dofmt(&f, fmt);
+	VA_END(f.args);
+	*(char*)f.to = '\0';
+	return (char*)f.to;
+}
+
--- /dev/null
+++ b/libc/vsmprint.c
@@ -1,0 +1,68 @@
+#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 = (uintptr)f->farg;
+	n *= 2;
+	s = (char*)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*)(uintptr)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*)(uintptr)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;
+	VA_COPY(f.args,args);
+	n = dofmt(&f, fmt);
+	VA_END(f.args);
+	if(n < 0){
+		free(f.start);
+		return nil;
+	}
+	return fmtstrflush(&f);
+}
--- /dev/null
+++ b/libc/vsnprint.c
@@ -1,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include "fmtdef.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 = 0;
+	f.farg = nil;
+	f.nfmt = 0;
+	VA_COPY(f.args,args);
+	dofmt(&f, fmt);
+	VA_END(f.args);
+	*(char*)f.to = '\0';
+	return (char*)f.to - buf;
+}
--- /dev/null
+++ b/libdraw/Makefile
@@ -1,0 +1,26 @@
+ROOT=..
+include ../Make.config
+LIB=libdraw.a
+
+OFILES=\
+	alloc.$O\
+	arith.$O\
+	badrect.$O\
+	bytesperline.$O\
+	chan.$O\
+	defont.$O\
+	drawrepl.$O\
+	fmt.$O\
+	icossin.$O\
+	icossin2.$O\
+	rectclip.$O\
+	rgb.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libdraw/alloc.c
@@ -1,0 +1,243 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Image*
+allocimage(Display *d, Rectangle r, ulong chan, int repl, ulong col)
+{
+	Image *i;
+
+	i = _allocimage(nil, d, r, chan, repl, col, 0, 0);
+	if(i != nil)
+		setmalloctag(i, getcallerpc(&d));
+	return i;
+}
+
+Image*
+_allocimage(Image *ai, Display *d, Rectangle r, ulong chan, int repl, ulong col, int screenid, int refresh)
+{
+	uchar *a;
+	char *err;
+	Image *i;
+	Rectangle clipr;
+	int id;
+	int depth;
+
+	err = nil;
+	i = nil;
+
+	if(badrect(r)){
+		werrstr("bad rectangle");
+		return nil;
+	}
+	if(chan == 0){
+		werrstr("bad channel descriptor");
+		return nil;
+	}
+
+	depth = chantodepth(chan);
+	if(depth == 0){
+		err = "bad channel descriptor";
+    Error:
+		if(err != nil)
+			werrstr("allocimage: %s", err);
+		else
+			werrstr("allocimage: %r");
+		free(i);
+		return nil;
+	}
+
+	/* 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 == nil)
+		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, col);
+	if(flushimage(d, 0) < 0)
+		goto Error;
+
+	if(ai != nil)
+		i = ai;
+	else{
+		i = malloc(sizeof(Image));
+		if(i == nil){
+			a = bufimage(d, 1+4);
+			if(a != nil){
+				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 = nil;
+	i->next = nil;
+	return i;
+}
+
+Image*
+namedimage(Display *d, char *name)
+{
+	uchar *a;
+	char *err, buf[12*12+1];
+	Image *i;
+	int id, n;
+	ulong chan;
+
+	err = nil;
+	i = nil;
+
+	n = strlen(name);
+	if(n >= 256){
+		err = "name too long";
+    Error:
+		if(err != nil)
+			werrstr("namedimage: %s", err);
+		else
+			werrstr("namedimage: %r");
+		free(i);
+		return nil;
+	}
+	/* flush pending data so we don't get error allocating the image */
+	flushimage(d, 0);
+	a = bufimage(d, 1+4+1+n);
+	if(a == nil)
+		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 != nil){
+			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 = nil;
+	i->next = nil;
+	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 == nil)
+		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 == nil || i->display == nil)
+		return 0;
+	d = i->display;
+	flushimage(d, 0);
+	if(i->screen != nil){
+		w = d->windows;
+		if(w == i)
+			d->windows = i->next;
+		else
+			while(w != nil){
+				if(w->next == i){
+					w->next = i->next;
+					break;
+				}
+				w = w->next;
+			}
+	}
+	a = bufimage(d, 1+4);
+	if(a == nil)
+		return -1;
+	a[0] = 'f';
+	BPLONG(a+1, i->id);
+	if(flushimage(d, i->screen!=nil) < 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,185 @@
+#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,
+};
+
+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;
--- /dev/null
+++ b/libdraw/badrect.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * check for zero, negative size or insanely huge rectangle.
+ */
+int
+badrect(Rectangle r)
+{
+	int x, y;
+	uint z;
+
+	x = Dx(r);
+	y = Dy(r);
+	if(x > 0 && y > 0){
+		z = x*y;
+		if(z/x == y && z < 0x10000000)
+			return 0;
+	}
+	return 1;
+}
--- /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,81 @@
+#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 drawcpu etc. */
+static int
+xisspace(char c)
+{
+	return c==' ' || c== '\t' || c=='\r' || c=='\n';
+}
+
+ulong
+strtochan(char *s)
+{
+	char *p, *q;
+	ulong c;
+	int t, n, d;
+
+	c = 0;
+	d = 0;
+	p=s;
+	while(*p && xisspace(*p))
+		p++;
+
+	while(*p && !xisspace(*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';
+		d += n;
+		c = (c<<8) | __DC(t, n);
+		p += 2;
+	}
+	if(d==0 || (d>8 && d%8) || (d<8 && 8%d))
+		return 0;
+	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,388 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * vga/vga00, 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,0x30,0x34,0x38,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x31,0x36,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,
+0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,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,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x0c,0x10,0x76,
+0x6c,0x38,0x00,0x00,0x30,0x0c,0x10,0x6c,0x30,0x0c,0x18,0x66,0x00,0x76,0x60,0x0c,
+0x10,0x76,0x6c,0x00,0x00,0x60,0x0c,0x10,0x6c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0xe0,
+0xe0,0xe0,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xe0,0xe0,0xe0,
+0xe0,0x90,0x70,0xe0,0x70,0x00,0x70,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x00,
+0x18,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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,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,
+0x00,0x00,0x00,0x00,0x00,0x00,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,0x38,
+0x00,0x00,0x00,0x7c,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x70,0x70,
+0x00,0x00,0x00,0x00,0x00,0x30,0x38,0x00,0xc0,0xc0,0xe0,0x00,0x30,0x18,0x38,0xdc,
+0x6c,0x6c,0x00,0x00,0x18,0x18,0x38,0x6c,0x18,0x18,0x3c,0x66,0x00,0xdc,0x30,0x18,
+0x38,0xdc,0x6c,0x00,0x00,0x30,0x18,0x38,0x6c,0x18,0x00,0x00,0x00,0x00,0x10,0x00,
+0x00,0x38,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,
+0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0xda,0x00,0x80,0x80,
+0x80,0x80,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x90,0x90,0x90,
+0x90,0xd0,0x80,0x80,0x90,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x18,0x66,0x00,
+0x7c,0x00,0x38,0x30,0x0c,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x18,0x7c,0x7c,
+0x0c,0xfe,0x38,0xfe,0x7c,0x7c,0x00,0x00,0x00,0x00,0x00,0x7c,0x00,0x10,0xfc,0x3c,
+0xf8,0xfe,0xfe,0x3c,0xc6,0x3c,0x1e,0xe6,0xf0,0xc6,0xc6,0x7c,0xfc,0x7c,0xfc,0x7c,
+0x7e,0xc6,0xc6,0xc6,0xc6,0x66,0xfe,0x3c,0x00,0x3c,0x6c,0x00,0x18,0x00,0xe0,0x00,
+0x1c,0x00,0x38,0x00,0xe0,0x18,0x06,0xe0,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x18,0x70,0x76,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,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,0x18,0x6c,
+0x00,0x66,0x18,0xc6,0x6c,0x3c,0x6c,0x00,0x00,0x00,0x38,0x00,0x6c,0x00,0xd8,0xd8,
+0x0c,0x00,0x7f,0x00,0x00,0x70,0x6c,0x00,0xc0,0xc0,0x30,0x30,0x00,0x00,0x6c,0x00,
+0x00,0x38,0x3e,0x3c,0x00,0x00,0x44,0x00,0x00,0x00,0x42,0x00,0xf8,0x00,0x00,0x00,
+0x44,0x00,0x00,0x00,0x7a,0x00,0x00,0x44,0x00,0x00,0xf0,0x3c,0x60,0x18,0x38,0x76,
+0x6c,0x6c,0x00,0x00,0x60,0x0c,0x38,0x6c,0x30,0x0c,0x38,0x66,0x76,0x76,0x60,0x0c,
+0x38,0x76,0x6c,0x00,0x00,0x60,0x18,0x38,0xcc,0x0c,0xe0,0x6c,0x02,0x00,0xe0,0xe0,
+0xe0,0xe0,0xf0,0x18,0x70,0x48,0x20,0x48,0x70,0x38,0x38,0x38,0x90,0x90,0x90,0x90,
+0x90,0xb0,0x60,0xe0,0x80,0xe0,0x60,0xe0,0x70,0x38,0x70,0x48,0x00,0x3c,0x66,0x6c,
+0xc6,0x00,0x6c,0x30,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x6c,0x38,0xc6,0xc6,
+0x1c,0xc0,0x60,0xc6,0xc6,0xc6,0x00,0x00,0x06,0x00,0x60,0xc6,0x7c,0x38,0x66,0x66,
+0x6c,0x66,0x66,0x66,0xc6,0x18,0x0c,0x66,0x60,0xee,0xe6,0xc6,0x66,0xc6,0x66,0xc6,
+0x7e,0xc6,0xc6,0xc6,0xc6,0x66,0xc6,0x30,0x80,0x0c,0xc6,0x00,0x00,0x00,0x60,0x00,
+0x0c,0x00,0x6c,0x00,0x60,0x18,0x06,0x60,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0xdc,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,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,0x3c,0x64,
+0x00,0x66,0x18,0x60,0x6c,0x42,0x6c,0x00,0x00,0x00,0x44,0x7c,0x6c,0x00,0x30,0x30,
+0x18,0x00,0xdb,0x00,0x00,0x30,0x6c,0x00,0xc2,0xc2,0x62,0x30,0x10,0x10,0x10,0x10,
+0x10,0x10,0x6c,0x66,0xfe,0xfe,0xfe,0xfe,0x3c,0x3c,0x3c,0x3c,0x6c,0xc6,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x00,0xc4,0xc6,0xc6,0xc6,0xc6,0x66,0x60,0x66,0x30,0x30,0x6c,0xdc,
+0x6c,0x38,0x00,0x00,0x30,0x18,0x6c,0x6c,0x18,0x18,0x6c,0x66,0x1c,0xdc,0x30,0x18,
+0x6c,0xdc,0x6c,0x00,0x00,0x30,0x30,0x6c,0xcc,0x18,0x60,0x6c,0x80,0x00,0x10,0x80,
+0x80,0x80,0x90,0x3c,0x48,0x48,0x20,0x48,0x40,0x48,0x40,0x40,0x90,0x90,0x90,0x90,
+0x90,0x90,0x10,0x80,0x80,0x80,0x10,0x80,0x40,0x40,0x48,0x48,0x00,0x3c,0x24,0x6c,
+0xc2,0xc2,0x6c,0x20,0x30,0x0c,0x00,0x00,0x00,0x00,0x00,0x02,0xc6,0x78,0x06,0x06,
+0x3c,0xc0,0xc0,0x06,0xc6,0xc6,0x18,0x18,0x0c,0x00,0x30,0xc6,0xc6,0x6c,0x66,0xc2,
+0x66,0x62,0x62,0xc2,0xc6,0x18,0x0c,0x66,0x60,0xfe,0xf6,0xc6,0x66,0xc6,0x66,0xc6,
+0x5a,0xc6,0xc6,0xc6,0x6c,0x66,0x86,0x30,0xc0,0x0c,0x00,0x00,0x00,0x00,0x60,0x00,
+0x0c,0x00,0x64,0x00,0x60,0x00,0x00,0x60,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x10,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x60,
+0x66,0x3c,0x18,0x38,0x00,0x99,0x3e,0x00,0x00,0x00,0xba,0x00,0x38,0x18,0x60,0x18,
+0x00,0x00,0xdb,0x00,0x00,0x30,0x38,0x00,0xc6,0xc6,0x36,0x00,0x38,0x38,0x38,0x38,
+0x38,0x38,0xcc,0xc2,0x66,0x66,0x66,0x66,0x18,0x18,0x18,0x18,0x66,0xe6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0xce,0xc6,0xc6,0xc6,0xc6,0x66,0x7c,0x66,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,
+0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x82,0x00,0xe0,0xe0,
+0xe0,0xe0,0x90,0x3c,0x70,0x78,0x20,0x48,0x70,0x40,0x30,0x30,0xe0,0xe0,0xe0,0xe0,
+0xe0,0x90,0xe0,0xe0,0x70,0xe0,0xe0,0xe0,0x70,0x58,0x70,0x48,0x00,0x3c,0x00,0xfe,
+0xc0,0xc6,0x38,0x00,0x30,0x0c,0x66,0x18,0x00,0x00,0x00,0x06,0xc6,0x18,0x0c,0x06,
+0x6c,0xc0,0xc0,0x06,0xc6,0xc6,0x18,0x18,0x18,0x7e,0x18,0x0c,0xc6,0xc6,0x66,0xc0,
+0x66,0x68,0x68,0xc0,0xc6,0x18,0x0c,0x6c,0x60,0xfe,0xfe,0xc6,0x66,0xc6,0x66,0x60,
+0x18,0xc6,0xc6,0xc6,0x7c,0x66,0x0c,0x30,0xe0,0x0c,0x00,0x00,0x00,0x78,0x78,0x7c,
+0x3c,0x7c,0x60,0x76,0x6c,0x38,0x0e,0x66,0x18,0xec,0xdc,0x7c,0xdc,0x76,0xdc,0x7c,
+0xfc,0xcc,0x66,0xc6,0xc6,0xc6,0xfe,0x18,0x18,0x18,0x00,0x38,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,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,0x60,0xf0,
+0x3c,0x18,0x18,0x6c,0x00,0xa5,0x00,0x36,0x00,0x00,0xb2,0x00,0x00,0x18,0xc8,0xd8,
+0x00,0xcc,0xdb,0x00,0x00,0x30,0x00,0xd8,0xcc,0xcc,0xec,0x30,0x6c,0x6c,0x6c,0x6c,
+0x6c,0x6c,0xcc,0xc0,0x62,0x62,0x62,0x62,0x18,0x18,0x18,0x18,0x66,0xf6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x66,0xce,0xc6,0xc6,0xc6,0xc6,0x66,0x66,0x66,0x78,0x78,0x78,0x78,
+0x78,0x78,0xcc,0x7c,0x7c,0x7c,0x7c,0x7c,0x38,0x38,0x38,0x38,0x06,0xdc,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x18,0x7a,0xcc,0xcc,0xcc,0xcc,0xc6,0x7c,0xc6,0x02,0x00,0x1c,0x1c,
+0x18,0x24,0x1c,0x3c,0x48,0x48,0x20,0x30,0x40,0x40,0x08,0x08,0x10,0x1c,0x1c,0x1c,
+0x1c,0x0c,0x44,0x1c,0x0c,0x80,0x24,0x1c,0x40,0x48,0x50,0x48,0x00,0x18,0x00,0x6c,
+0x7c,0x0c,0x76,0x00,0x30,0x0c,0x3c,0x18,0x00,0x00,0x00,0x0c,0xd6,0x18,0x18,0x3c,
+0xcc,0xfc,0xfc,0x0c,0x7c,0x7e,0x00,0x00,0x30,0x00,0x0c,0x18,0xde,0xc6,0x7c,0xc0,
+0x66,0x78,0x78,0xc0,0xfe,0x18,0x0c,0x78,0x60,0xd6,0xde,0xc6,0x7c,0xc6,0x7c,0x38,
+0x18,0xc6,0xc6,0xd6,0x38,0x3c,0x18,0x30,0x70,0x0c,0x00,0x00,0x00,0x0c,0x6c,0xc6,
+0x6c,0xc6,0xf0,0xcc,0x76,0x18,0x06,0x6c,0x18,0xfe,0x66,0xc6,0x66,0xcc,0x76,0xc6,
+0x30,0xcc,0x66,0xc6,0x6c,0xc6,0xcc,0x70,0x00,0x0e,0x00,0x6c,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,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,0x60,0x60,
+0x66,0x7e,0x00,0xc6,0x00,0xa1,0x7e,0x6c,0xfe,0x00,0xaa,0x00,0x00,0x7e,0xf8,0x70,
+0x00,0xcc,0x7b,0x00,0x00,0x78,0x7c,0x6c,0x18,0x18,0x18,0x30,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xfe,0xc0,0x68,0x68,0x68,0x68,0x18,0x18,0x18,0x18,0xf6,0xfe,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x3c,0xd6,0xc6,0xc6,0xc6,0xc6,0x3c,0x66,0x6c,0x0c,0x0c,0x0c,0x0c,
+0x0c,0x0c,0x76,0xc6,0xc6,0xc6,0xc6,0xc6,0x18,0x18,0x18,0x18,0x7e,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0xc4,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x80,0x00,0x08,0x08,
+0x24,0x34,0x24,0x3c,0x70,0x48,0x38,0x30,0x40,0x38,0x70,0x70,0x10,0x24,0x24,0x24,
+0x24,0x12,0x28,0x08,0x12,0xe0,0x24,0x20,0x40,0x38,0x48,0x30,0x00,0x18,0x00,0x6c,
+0x06,0x18,0xdc,0x00,0x30,0x0c,0xff,0x7e,0x00,0xfe,0x00,0x18,0xd6,0x18,0x30,0x06,
+0xfe,0x06,0xc6,0x18,0xc6,0x06,0x00,0x00,0x60,0x00,0x06,0x18,0xde,0xfe,0x66,0xc0,
+0x66,0x68,0x68,0xde,0xc6,0x18,0x0c,0x78,0x60,0xc6,0xce,0xc6,0x60,0xc6,0x6c,0x0c,
+0x18,0xc6,0xc6,0xd6,0x38,0x18,0x30,0x30,0x38,0x0c,0x00,0x00,0x00,0x7c,0x66,0xc0,
+0xcc,0xfe,0x60,0xcc,0x66,0x18,0x06,0x78,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x66,0x60,
+0x30,0xcc,0x66,0xd6,0x38,0xc6,0x18,0x18,0x18,0x18,0x00,0xc6,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,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,0x60,0x60,
+0x66,0x18,0x18,0xc6,0x00,0xa1,0x00,0xd8,0x06,0x3c,0x44,0x00,0x00,0x18,0x00,0x00,
+0x00,0xcc,0x1b,0x00,0x00,0x00,0x00,0x36,0x30,0x30,0x30,0x60,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xcc,0xc0,0x78,0x78,0x78,0x78,0x18,0x18,0x18,0x18,0x66,0xde,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x18,0xd6,0xc6,0xc6,0xc6,0xc6,0x18,0x66,0x66,0x7c,0x7c,0x7c,0x7c,
+0x7c,0x7c,0x36,0xc0,0xfe,0xfe,0xfe,0xfe,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x7e,0xce,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x82,0x00,0x08,0x08,
+0x24,0x2c,0x20,0x66,0x07,0x07,0x07,0x07,0x0e,0x00,0x06,0x07,0x10,0x20,0x20,0x20,
+0x20,0x1e,0x10,0x08,0x1e,0x11,0x24,0x18,0x0e,0x07,0x07,0x07,0x00,0x18,0x00,0x6c,
+0x06,0x30,0xcc,0x00,0x30,0x0c,0x3c,0x18,0x00,0x00,0x00,0x30,0xc6,0x18,0x60,0x06,
+0x0c,0x06,0xc6,0x30,0xc6,0x06,0x00,0x00,0x30,0x7e,0x0c,0x18,0xde,0xc6,0x66,0xc0,
+0x66,0x60,0x60,0xc6,0xc6,0x18,0xcc,0x6c,0x60,0xc6,0xc6,0xc6,0x60,0xc6,0x66,0x06,
+0x18,0xc6,0xc6,0xd6,0x7c,0x18,0x60,0x30,0x1c,0x0c,0x00,0x00,0x00,0xcc,0x66,0xc0,
+0xcc,0xc0,0x60,0xcc,0x66,0x18,0x06,0x78,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x60,0x38,
+0x30,0xcc,0x66,0xd6,0x38,0xc6,0x30,0x18,0x18,0x18,0x00,0xc6,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x66,0x60,
+0x66,0x7e,0x18,0x6c,0x00,0xa5,0x00,0x6c,0x06,0x00,0x38,0x00,0x00,0x18,0x00,0x00,
+0x00,0xcc,0x1b,0x18,0x00,0x00,0x00,0x6c,0x66,0x60,0x66,0xc0,0xfe,0xfe,0xfe,0xfe,
+0xfe,0xfe,0xcc,0xc0,0x68,0x68,0x68,0x68,0x18,0x18,0x18,0x18,0x66,0xce,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x3c,0xe6,0xc6,0xc6,0xc6,0xc6,0x18,0x66,0x66,0xcc,0xcc,0xcc,0xcc,
+0xcc,0xcc,0x7e,0xc0,0xc0,0xc0,0xc0,0xc0,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0xd6,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x02,0x00,0x08,0x08,
+0x24,0x24,0x20,0xc3,0x08,0x02,0x04,0x02,0x08,0x0e,0x09,0x02,0x10,0x20,0x20,0x20,
+0x20,0x12,0x10,0x08,0x12,0x1b,0x24,0x04,0x10,0x08,0x08,0x08,0x00,0x00,0x00,0xfe,
+0x86,0x60,0xcc,0x00,0x30,0x0c,0x66,0x18,0x18,0x00,0x00,0x60,0xc6,0x18,0xc0,0x06,
+0x0c,0x06,0xc6,0x30,0xc6,0x06,0x18,0x18,0x18,0x00,0x18,0x00,0xdc,0xc6,0x66,0xc2,
+0x66,0x62,0x60,0xc6,0xc6,0x18,0xcc,0x66,0x62,0xc6,0xc6,0xc6,0x60,0xd6,0x66,0xc6,
+0x18,0xc6,0x6c,0xfe,0x6c,0x18,0xc2,0x30,0x0e,0x0c,0x00,0x00,0x00,0xcc,0x66,0xc0,
+0xcc,0xc0,0x60,0xcc,0x66,0x18,0x06,0x6c,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x60,0x0c,
+0x30,0xcc,0x66,0xd6,0x38,0xc6,0x60,0x18,0x18,0x18,0x00,0xc6,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x3c,0x60,
+0x3c,0x18,0x18,0x38,0x00,0x99,0x00,0x36,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xcc,0x1b,0x00,0x00,0x00,0x00,0xd8,0xce,0xdc,0xce,0xc6,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xcc,0xc2,0x62,0x62,0x62,0x62,0x18,0x18,0x18,0x18,0x66,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x66,0xe6,0xc6,0xc6,0xc6,0xc6,0x18,0x7c,0x66,0xcc,0xcc,0xcc,0xcc,
+0xcc,0xcc,0xd8,0xc0,0xc0,0xc0,0xc0,0xc0,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x18,0xe6,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x80,0x00,0x08,0x08,
+0x18,0x24,0x1c,0xff,0x06,0x02,0x07,0x02,0x0e,0x09,0x09,0x02,0x1c,0x1c,0x1c,0x1c,
+0x1c,0x12,0x10,0x08,0x12,0x15,0x18,0x38,0x0c,0x06,0x06,0x06,0x00,0x18,0x00,0x6c,
+0xc6,0xc6,0xcc,0x00,0x18,0x18,0x00,0x00,0x18,0x00,0x18,0xc0,0x6c,0x18,0xc6,0xc6,
+0x0c,0xc6,0xc6,0x30,0xc6,0x0c,0x18,0x18,0x0c,0x00,0x30,0x18,0xc0,0xc6,0x66,0x66,
+0x6c,0x66,0x60,0x66,0xc6,0x18,0xcc,0x66,0x66,0xc6,0xc6,0xc6,0x60,0xde,0x66,0xc6,
+0x18,0xc6,0x38,0xee,0xc6,0x18,0xc6,0x30,0x06,0x0c,0x00,0x00,0x00,0xcc,0x66,0xc6,
+0xcc,0xc6,0x60,0xcc,0x66,0x18,0x06,0x66,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x60,0xc6,
+0x36,0xcc,0x3c,0xfe,0x6c,0xc6,0xc6,0x18,0x18,0x18,0x00,0xfe,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x18,0xe6,
+0x66,0x18,0x18,0x0c,0x00,0x42,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x7e,0x00,0x00,
+0x00,0xcc,0x1b,0x00,0x00,0x00,0x00,0x00,0x9e,0x86,0x9e,0xc6,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xcc,0x66,0x66,0x66,0x66,0x66,0x18,0x18,0x18,0x18,0x6c,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0x46,0xc6,0xc6,0xc6,0xc6,0x18,0x60,0x66,0xcc,0xcc,0xcc,0xcc,
+0xcc,0xcc,0xd8,0xc6,0xc6,0xc6,0xc6,0xc6,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x18,0x46,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0xb6,0x00,0x05,0x05,
+0x07,0x0c,0x09,0x18,0x01,0x02,0x04,0x02,0x08,0x0e,0x09,0x02,0x07,0x02,0x06,0x06,
+0x02,0x09,0x09,0x0e,0x09,0x15,0x0e,0x07,0x02,0x01,0x01,0x01,0x00,0x18,0x00,0x6c,
+0x7c,0x86,0x76,0x00,0x0c,0x30,0x00,0x00,0x18,0x00,0x18,0x80,0x38,0x7e,0xfe,0x7c,
+0x1e,0x7c,0x7c,0x30,0x7c,0x78,0x00,0x30,0x06,0x00,0x60,0x18,0x7c,0xc6,0xfc,0x3c,
+0xf8,0xfe,0xf0,0x3a,0xc6,0x3c,0x78,0xe6,0xfe,0xc6,0xc6,0x7c,0xf0,0x7c,0xe6,0x7c,
+0x3c,0x7c,0x10,0x6c,0xc6,0x3c,0xfe,0x3c,0x02,0x3c,0x00,0x00,0x00,0x76,0x7c,0x7c,
+0x76,0x7c,0xf0,0x7c,0xe6,0x3c,0x06,0xe6,0x3c,0xc6,0x66,0x7c,0x7c,0x7c,0xf0,0x7c,
+0x1c,0x76,0x18,0x6c,0xc6,0x7e,0xfe,0x0e,0x18,0x70,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,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,0x18,0xfc,
+0x00,0x18,0x18,0xc6,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xf6,0x1b,0x00,0x00,0x00,0x00,0x00,0x3e,0x0c,0x3e,0x7c,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xce,0x3c,0xfe,0xfe,0xfe,0xfe,0x3c,0x3c,0x3c,0x3c,0xf8,0xc6,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x00,0xbc,0x7c,0x7c,0x7c,0x7c,0x3c,0xf0,0xec,0x76,0x76,0x76,0x76,
+0x76,0x76,0x6e,0x7c,0x7c,0x7c,0x7c,0x7c,0x3c,0x3c,0x3c,0x3c,0x7c,0x66,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x00,0xbc,0x76,0x76,0x76,0x76,0x7e,0x7c,0x7e,0x00,0x00,0x05,0x05,
+0x02,0x12,0x0a,0x00,0x0e,0x02,0x04,0x02,0x08,0x0a,0x06,0x07,0x04,0x06,0x09,0x01,
+0x06,0x0a,0x0d,0x09,0x0d,0x11,0x09,0x09,0x1c,0x0e,0x0e,0x0e,0x00,0x00,0x00,0x00,
+0x18,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,0x00,0x0c,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x0c,0x00,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x60,0x0c,0x00,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,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xc0,0x00,0x00,0x18,0x00,0x00,0x00,0x06,0x18,0x06,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x60,0x06,0x00,0x00,0x02,0x02,
+0x02,0x12,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x07,0x02,0x02,0x06,
+0x0a,0x0c,0x0b,0x0e,0x0b,0x00,0x0e,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0xcc,0x00,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x60,0x0c,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xc0,0x00,0x00,0x0c,0x00,0x00,0x00,0x06,0x3e,0x06,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x60,0x0c,0x00,0x00,0x05,0x05,
+0x02,0x16,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x02,0x04,0x01,
+0x1f,0x0a,0x09,0x09,0x09,0x00,0x09,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,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,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,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x1e,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xc0,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xf0,0xf8,0x00,0x00,0x05,0x05,
+0x02,0x0d,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x0f,0x06,
+0x02,0x09,0x09,0x0e,0x09,0x00,0x0e,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x35,0x36,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x31,0x36,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x00,0x00,0x00,0x0f,0x00,0x08,0x08,0x00,0x00,0x0f,0x00,0x08,0x10,0x00,0x00,0x0f,
+0x00,0x08,0x18,0x00,0x00,0x0f,0x00,0x08,0x20,0x00,0x00,0x0f,0x00,0x08,0x28,0x00,
+0x00,0x0f,0x00,0x08,0x30,0x00,0x00,0x0f,0x00,0x08,0x38,0x00,0x00,0x0f,0x00,0x08,
+0x40,0x00,0x00,0x0f,0x00,0x08,0x48,0x00,0x00,0x0f,0x00,0x08,0x50,0x00,0x00,0x0f,
+0x00,0x08,0x58,0x00,0x00,0x0f,0x00,0x08,0x60,0x00,0x00,0x0f,0x00,0x08,0x68,0x00,
+0x00,0x0f,0x00,0x08,0x70,0x00,0x00,0x0f,0x00,0x08,0x78,0x00,0x00,0x0f,0x00,0x08,
+0x80,0x00,0x00,0x0f,0x00,0x08,0x88,0x00,0x00,0x0f,0x00,0x08,0x90,0x00,0x00,0x0f,
+0x00,0x08,0x98,0x00,0x00,0x0f,0x00,0x08,0xa0,0x00,0x00,0x0f,0x00,0x08,0xa8,0x00,
+0x00,0x0f,0x00,0x08,0xb0,0x00,0x00,0x0f,0x00,0x08,0xb8,0x00,0x00,0x0f,0x00,0x08,
+0xc0,0x00,0x00,0x0f,0x00,0x08,0xc8,0x00,0x00,0x0f,0x00,0x08,0xd0,0x00,0x00,0x0f,
+0x00,0x08,0xd8,0x00,0x00,0x0f,0x00,0x08,0xe0,0x00,0x00,0x0f,0x00,0x08,0xe8,0x00,
+0x00,0x0f,0x00,0x08,0xf0,0x00,0x00,0x0f,0x00,0x08,0xf8,0x00,0x00,0x0f,0x00,0x08,
+0x00,0x01,0x00,0x0f,0x00,0x08,0x08,0x01,0x00,0x0f,0x00,0x08,0x10,0x01,0x00,0x0f,
+0x00,0x08,0x18,0x01,0x00,0x0f,0x00,0x08,0x20,0x01,0x00,0x0f,0x00,0x08,0x28,0x01,
+0x00,0x0f,0x00,0x08,0x30,0x01,0x00,0x0f,0x00,0x08,0x38,0x01,0x00,0x0f,0x00,0x08,
+0x40,0x01,0x00,0x0f,0x00,0x08,0x48,0x01,0x00,0x0f,0x00,0x08,0x50,0x01,0x00,0x0f,
+0x00,0x08,0x58,0x01,0x00,0x0f,0x00,0x08,0x60,0x01,0x00,0x0f,0x00,0x08,0x68,0x01,
+0x00,0x0f,0x00,0x08,0x70,0x01,0x00,0x0f,0x00,0x08,0x78,0x01,0x00,0x0f,0x00,0x08,
+0x80,0x01,0x00,0x0f,0x00,0x08,0x88,0x01,0x00,0x0f,0x00,0x08,0x90,0x01,0x00,0x0f,
+0x00,0x08,0x98,0x01,0x00,0x0f,0x00,0x08,0xa0,0x01,0x00,0x0f,0x00,0x08,0xa8,0x01,
+0x00,0x0f,0x00,0x08,0xb0,0x01,0x00,0x0f,0x00,0x08,0xb8,0x01,0x00,0x0f,0x00,0x08,
+0xc0,0x01,0x00,0x0f,0x00,0x08,0xc8,0x01,0x00,0x0f,0x00,0x08,0xd0,0x01,0x00,0x0f,
+0x00,0x08,0xd8,0x01,0x00,0x0f,0x00,0x08,0xe0,0x01,0x00,0x0f,0x00,0x08,0xe8,0x01,
+0x00,0x0f,0x00,0x08,0xf0,0x01,0x00,0x0f,0x00,0x08,0xf8,0x01,0x00,0x0f,0x00,0x08,
+0x00,0x02,0x00,0x0f,0x00,0x08,0x08,0x02,0x00,0x0f,0x00,0x08,0x10,0x02,0x00,0x0f,
+0x00,0x08,0x18,0x02,0x00,0x0f,0x00,0x08,0x20,0x02,0x00,0x0f,0x00,0x08,0x28,0x02,
+0x00,0x0f,0x00,0x08,0x30,0x02,0x00,0x0f,0x00,0x08,0x38,0x02,0x00,0x0f,0x00,0x08,
+0x40,0x02,0x00,0x0f,0x00,0x08,0x48,0x02,0x00,0x0f,0x00,0x08,0x50,0x02,0x00,0x0f,
+0x00,0x08,0x58,0x02,0x00,0x0f,0x00,0x08,0x60,0x02,0x00,0x0f,0x00,0x08,0x68,0x02,
+0x00,0x0f,0x00,0x08,0x70,0x02,0x00,0x0f,0x00,0x08,0x78,0x02,0x00,0x0f,0x00,0x08,
+0x80,0x02,0x00,0x0f,0x00,0x08,0x88,0x02,0x00,0x0f,0x00,0x08,0x90,0x02,0x00,0x0f,
+0x00,0x08,0x98,0x02,0x00,0x0f,0x00,0x08,0xa0,0x02,0x00,0x0f,0x00,0x08,0xa8,0x02,
+0x00,0x0f,0x00,0x08,0xb0,0x02,0x00,0x0f,0x00,0x08,0xb8,0x02,0x00,0x0f,0x00,0x08,
+0xc0,0x02,0x00,0x0f,0x00,0x08,0xc8,0x02,0x00,0x0f,0x00,0x08,0xd0,0x02,0x00,0x0f,
+0x00,0x08,0xd8,0x02,0x00,0x0f,0x00,0x08,0xe0,0x02,0x00,0x0f,0x00,0x08,0xe8,0x02,
+0x00,0x0f,0x00,0x08,0xf0,0x02,0x00,0x0f,0x00,0x08,0xf8,0x02,0x00,0x0f,0x00,0x08,
+0x00,0x03,0x00,0x0f,0x00,0x08,0x08,0x03,0x00,0x0f,0x00,0x08,0x10,0x03,0x00,0x0f,
+0x00,0x08,0x18,0x03,0x00,0x0f,0x00,0x08,0x20,0x03,0x00,0x0f,0x00,0x08,0x28,0x03,
+0x00,0x0f,0x00,0x08,0x30,0x03,0x00,0x0f,0x00,0x08,0x38,0x03,0x00,0x0f,0x00,0x08,
+0x40,0x03,0x00,0x0f,0x00,0x08,0x48,0x03,0x00,0x0f,0x00,0x08,0x50,0x03,0x00,0x0f,
+0x00,0x08,0x58,0x03,0x00,0x0f,0x00,0x08,0x60,0x03,0x00,0x0f,0x00,0x08,0x68,0x03,
+0x00,0x0f,0x00,0x08,0x70,0x03,0x00,0x0f,0x00,0x08,0x78,0x03,0x00,0x0f,0x00,0x08,
+0x80,0x03,0x00,0x0f,0x00,0x08,0x88,0x03,0x00,0x0f,0x00,0x08,0x90,0x03,0x00,0x0f,
+0x00,0x08,0x98,0x03,0x00,0x0f,0x00,0x08,0xa0,0x03,0x00,0x0f,0x00,0x08,0xa8,0x03,
+0x00,0x0f,0x00,0x08,0xb0,0x03,0x00,0x0f,0x00,0x08,0xb8,0x03,0x00,0x0f,0x00,0x08,
+0xc0,0x03,0x00,0x0f,0x00,0x08,0xc8,0x03,0x00,0x0f,0x00,0x08,0xd0,0x03,0x00,0x0f,
+0x00,0x08,0xd8,0x03,0x00,0x0f,0x00,0x08,0xe0,0x03,0x00,0x0f,0x00,0x08,0xe8,0x03,
+0x00,0x0f,0x00,0x08,0xf0,0x03,0x00,0x0f,0x00,0x08,0xf8,0x03,0x00,0x0f,0x00,0x08,
+0x00,0x04,0x00,0x0f,0x00,0x08,0x08,0x04,0x00,0x0f,0x00,0x08,0x10,0x04,0x00,0x0f,
+0x00,0x08,0x18,0x04,0x00,0x0f,0x00,0x08,0x20,0x04,0x00,0x0f,0x00,0x08,0x28,0x04,
+0x00,0x0f,0x00,0x08,0x30,0x04,0x00,0x0f,0x00,0x08,0x38,0x04,0x00,0x0f,0x00,0x08,
+0x40,0x04,0x00,0x0f,0x00,0x08,0x48,0x04,0x00,0x0f,0x00,0x08,0x50,0x04,0x00,0x0f,
+0x00,0x08,0x58,0x04,0x00,0x0f,0x00,0x08,0x60,0x04,0x00,0x0f,0x00,0x08,0x68,0x04,
+0x00,0x0f,0x00,0x08,0x70,0x04,0x00,0x0f,0x00,0x08,0x78,0x04,0x00,0x0f,0x00,0x08,
+0x80,0x04,0x00,0x0f,0x00,0x08,0x88,0x04,0x00,0x0f,0x00,0x08,0x90,0x04,0x00,0x0f,
+0x00,0x08,0x98,0x04,0x00,0x0f,0x00,0x08,0xa0,0x04,0x00,0x0f,0x00,0x08,0xa8,0x04,
+0x00,0x0f,0x00,0x08,0xb0,0x04,0x00,0x0f,0x00,0x08,0xb8,0x04,0x00,0x0f,0x00,0x08,
+0xc0,0x04,0x00,0x0f,0x00,0x08,0xc8,0x04,0x00,0x0f,0x00,0x08,0xd0,0x04,0x00,0x0f,
+0x00,0x08,0xd8,0x04,0x00,0x0f,0x00,0x08,0xe0,0x04,0x00,0x0f,0x00,0x08,0xe8,0x04,
+0x00,0x0f,0x00,0x08,0xf0,0x04,0x00,0x0f,0x00,0x08,0xf8,0x04,0x00,0x0f,0x00,0x08,
+0x00,0x05,0x00,0x0f,0x00,0x08,0x08,0x05,0x00,0x0f,0x00,0x08,0x10,0x05,0x00,0x0f,
+0x00,0x08,0x18,0x05,0x00,0x0f,0x00,0x08,0x20,0x05,0x00,0x0f,0x00,0x08,0x28,0x05,
+0x00,0x0f,0x00,0x08,0x30,0x05,0x00,0x0f,0x00,0x08,0x38,0x05,0x00,0x0f,0x00,0x08,
+0x40,0x05,0x00,0x0f,0x00,0x08,0x48,0x05,0x00,0x0f,0x00,0x08,0x50,0x05,0x00,0x0f,
+0x00,0x08,0x58,0x05,0x00,0x0f,0x00,0x08,0x60,0x05,0x00,0x0f,0x00,0x08,0x68,0x05,
+0x00,0x0f,0x00,0x08,0x70,0x05,0x00,0x0f,0x00,0x08,0x78,0x05,0x00,0x0f,0x00,0x08,
+0x80,0x05,0x00,0x0f,0x00,0x08,0x88,0x05,0x00,0x0f,0x00,0x08,0x90,0x05,0x00,0x0f,
+0x00,0x08,0x98,0x05,0x00,0x0f,0x00,0x08,0xa0,0x05,0x00,0x0f,0x00,0x08,0xa8,0x05,
+0x00,0x0f,0x00,0x08,0xb0,0x05,0x00,0x0f,0x00,0x08,0xb8,0x05,0x00,0x0f,0x00,0x08,
+0xc0,0x05,0x00,0x0f,0x00,0x08,0xc8,0x05,0x00,0x0f,0x00,0x08,0xd0,0x05,0x00,0x0f,
+0x00,0x08,0xd8,0x05,0x00,0x0f,0x00,0x08,0xe0,0x05,0x00,0x0f,0x00,0x08,0xe8,0x05,
+0x00,0x0f,0x00,0x08,0xf0,0x05,0x00,0x0f,0x00,0x08,0xf8,0x05,0x00,0x0f,0x00,0x08,
+0x00,0x06,0x00,0x0f,0x00,0x08,0x08,0x06,0x00,0x0f,0x00,0x08,0x10,0x06,0x00,0x0f,
+0x00,0x08,0x18,0x06,0x00,0x0f,0x00,0x08,0x20,0x06,0x00,0x0f,0x00,0x08,0x28,0x06,
+0x00,0x0f,0x00,0x08,0x30,0x06,0x00,0x0f,0x00,0x08,0x38,0x06,0x00,0x0f,0x00,0x08,
+0x40,0x06,0x00,0x0f,0x00,0x08,0x48,0x06,0x00,0x0f,0x00,0x08,0x50,0x06,0x00,0x0f,
+0x00,0x08,0x58,0x06,0x00,0x0f,0x00,0x08,0x60,0x06,0x00,0x0f,0x00,0x08,0x68,0x06,
+0x00,0x0f,0x00,0x08,0x70,0x06,0x00,0x0f,0x00,0x08,0x78,0x06,0x00,0x0f,0x00,0x08,
+0x80,0x06,0x00,0x0f,0x00,0x08,0x88,0x06,0x00,0x0f,0x00,0x08,0x90,0x06,0x00,0x0f,
+0x00,0x08,0x98,0x06,0x00,0x0f,0x00,0x08,0xa0,0x06,0x00,0x0f,0x00,0x08,0xa8,0x06,
+0x00,0x0f,0x00,0x08,0xb0,0x06,0x00,0x0f,0x00,0x08,0xb8,0x06,0x00,0x0f,0x00,0x08,
+0xc0,0x06,0x00,0x0f,0x00,0x08,0xc8,0x06,0x00,0x0f,0x00,0x08,0xd0,0x06,0x00,0x0f,
+0x00,0x08,0xd8,0x06,0x00,0x0f,0x00,0x08,0xe0,0x06,0x00,0x0f,0x00,0x08,0xe8,0x06,
+0x00,0x0f,0x00,0x08,0xf0,0x06,0x00,0x0f,0x00,0x08,0xf8,0x06,0x00,0x0f,0x00,0x08,
+0x00,0x07,0x00,0x0f,0x00,0x08,0x08,0x07,0x00,0x0f,0x00,0x08,0x10,0x07,0x00,0x0f,
+0x00,0x08,0x18,0x07,0x00,0x0f,0x00,0x08,0x20,0x07,0x00,0x0f,0x00,0x08,0x28,0x07,
+0x00,0x0f,0x00,0x08,0x30,0x07,0x00,0x0f,0x00,0x08,0x38,0x07,0x00,0x0f,0x00,0x08,
+0x40,0x07,0x00,0x0f,0x00,0x08,0x48,0x07,0x00,0x0f,0x00,0x08,0x50,0x07,0x00,0x0f,
+0x00,0x08,0x58,0x07,0x00,0x0f,0x00,0x08,0x60,0x07,0x00,0x0f,0x00,0x08,0x68,0x07,
+0x00,0x0f,0x00,0x08,0x70,0x07,0x00,0x0f,0x00,0x08,0x78,0x07,0x00,0x0f,0x00,0x08,
+0x80,0x07,0x00,0x0f,0x00,0x08,0x88,0x07,0x00,0x0f,0x00,0x08,0x90,0x07,0x00,0x0f,
+0x00,0x08,0x98,0x07,0x00,0x0f,0x00,0x08,0xa0,0x07,0x00,0x0f,0x00,0x08,0xa8,0x07,
+0x00,0x0f,0x00,0x08,0xb0,0x07,0x00,0x0f,0x00,0x08,0xb8,0x07,0x00,0x0f,0x00,0x08,
+0xc0,0x07,0x00,0x0f,0x00,0x08,0xc8,0x07,0x00,0x0f,0x00,0x08,0xd0,0x07,0x00,0x0f,
+0x00,0x08,0xd8,0x07,0x00,0x0f,0x00,0x08,0xe0,0x07,0x00,0x0f,0x00,0x08,0xe8,0x07,
+0x00,0x0f,0x00,0x08,0xf0,0x07,0x00,0x0f,0x00,0x08,0xf8,0x07,0x00,0x0f,0x00,0x08,
+0x00,0x08,0x00,0x0f,0x00,0x08,
+};
+
+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/fmt.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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/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/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/libip/Makefile
@@ -1,0 +1,19 @@
+ROOT=..
+include ../Make.config
+LIB=libip.a
+
+OFILES=\
+	eipfmt.$O\
+	parseip.$O\
+	classmask.$O\
+	bo.$O\
+	ipaux.$O\
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libip/bo.c
@@ -1,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+void
+hnputv(void *p, uvlong v)
+{
+	uchar *a;
+
+	a = p;
+	a[0] = v>>56;
+	a[1] = v>>48;
+	a[2] = v>>40;
+	a[3] = v>>32;
+	a[4] = v>>24;
+	a[5] = v>>16;
+	a[6] = v>>8;
+	a[7] = v;
+}
+
+void
+hnputl(void *p, uint v)
+{
+	uchar *a;
+
+	a = p;
+	a[0] = v>>24;
+	a[1] = v>>16;
+	a[2] = v>>8;
+	a[3] = v;
+}
+
+void
+hnputs(void *p, ushort v)
+{
+	uchar *a;
+
+	a = p;
+	a[0] = v>>8;
+	a[1] = v;
+}
+
+uvlong
+nhgetv(void *p)
+{
+	uchar *a;
+	uvlong v;
+
+	a = p;
+	v = (uvlong)a[0]<<56;
+	v |= (uvlong)a[1]<<48;
+	v |= (uvlong)a[2]<<40;
+	v |= (uvlong)a[3]<<32;
+	v |= a[4]<<24;
+	v |= a[5]<<16;
+	v |= a[6]<<8;
+	v |= a[7]<<0;
+	return v;
+}
+
+uint
+nhgetl(void *p)
+{
+	uchar *a;
+
+	a = p;
+	return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
+}
+
+ushort
+nhgets(void *p)
+{
+	uchar *a;
+
+	a = p;
+	return (a[0]<<8)|(a[1]<<0);
+}
--- /dev/null
+++ b/libip/classmask.c
@@ -1,0 +1,86 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+static uchar classmask[4][16] = {
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0x00,0x00,0x00,
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0x00,0x00,0x00,
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0x00,0x00,
+	0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0xff,  0xff,0xff,0xff,0x00,
+};
+
+static uchar v6loopback[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0x01
+};
+
+static uchar v6linklocal[IPaddrlen] = {
+	0xfe, 0x80, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static uchar v6linklocalmask[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static int v6llpreflen = 8;	/* link-local prefix length in bytes */
+
+static uchar v6multicast[IPaddrlen] = {
+	0xff, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static uchar v6multicastmask[IPaddrlen] = {
+	0xff, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0
+};
+static int v6mcpreflen = 1;	/* multicast prefix length */
+
+static uchar v6solicitednode[IPaddrlen] = {
+	0xff, 0x02, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0x01,
+	0xff, 0, 0, 0
+};
+static uchar v6solicitednodemask[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0x0, 0x0, 0x0
+};
+static int v6snpreflen = 13;
+
+uchar*
+defmask(uchar *ip)
+{
+	if(isv4(ip))
+		return classmask[ip[IPv4off]>>6];
+	else {
+		if(ipcmp(ip, v6loopback) == 0)
+			return IPallbits;
+		else if(memcmp(ip, v6linklocal, v6llpreflen) == 0)
+			return v6linklocalmask;
+		else if(memcmp(ip, v6solicitednode, v6snpreflen) == 0)
+			return v6solicitednodemask;
+		else if(memcmp(ip, v6multicast, v6mcpreflen) == 0)
+			return v6multicastmask;
+		return IPallbits;
+	}
+}
+
+void
+maskip(uchar *from, uchar *mask, uchar *to)
+{
+	int i;
+
+	for(i = 0; i < IPaddrlen; i++)
+		to[i] = from[i] & mask[i];
+}
--- /dev/null
+++ b/libip/eipfmt.c
@@ -1,0 +1,109 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+enum
+{
+	Isprefix= 16,
+};
+
+uchar prefixvals[256] =
+{
+[0x00] 0 | Isprefix,
+[0x80] 1 | Isprefix,
+[0xC0] 2 | Isprefix,
+[0xE0] 3 | Isprefix,
+[0xF0] 4 | Isprefix,
+[0xF8] 5 | Isprefix,
+[0xFC] 6 | Isprefix,
+[0xFE] 7 | Isprefix,
+[0xFF] 8 | Isprefix,
+};
+
+int
+eipfmt(Fmt *f)
+{
+	char buf[5*8];
+	static char *efmt = "%.2ux%.2ux%.2ux%.2ux%.2ux%.2ux";
+	static char *ifmt = "%d.%d.%d.%d";
+	uchar *p, ip[16];
+	ulong *lp;
+	ushort s;
+	int i, j, n, eln, eli;
+
+	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':		/* Ip address */
+		p = va_arg(f->args, uchar*);
+common:
+		if(memcmp(p, v4prefix, 12) == 0){
+			snprint(buf, sizeof buf, ifmt, p[12], p[13], p[14], p[15]);
+			return fmtstrcpy(f, buf);
+		}
+
+		/* find longest elision */
+		eln = eli = -1;
+		for(i = 0; i < 16; i += 2){
+			for(j = i; j < 16; j += 2)
+				if(p[j] != 0 || p[j+1] != 0)
+					break;
+			if(j > i && j - i > eln){
+				eli = i;
+				eln = j - i;
+			}
+		}
+
+		/* print with possible elision */
+		n = 0;
+		for(i = 0; i < 16; i += 2){
+			if(i == eli){
+				n += sprint(buf+n, "::");
+				i += eln;
+				if(i >= 16)
+					break;
+			} else if(i != 0)
+				n += sprint(buf+n, ":");
+			s = (p[i]<<8) + p[i+1];
+			n += sprint(buf+n, "%ux", s);
+		}
+		return fmtstrcpy(f, buf);
+
+	case 'i':		/* v6 address as 4 longs */
+		lp = va_arg(f->args, ulong*);
+		for(i = 0; i < 4; i++)
+			hnputl(ip+4*i, *lp++);
+		p = ip;
+		goto common;
+
+	case 'V':		/* v4 ip address */
+		p = va_arg(f->args, uchar*);
+		snprint(buf, sizeof buf, ifmt, p[0], p[1], p[2], p[3]);
+		return fmtstrcpy(f, buf);
+
+	case 'M':		/* ip mask */
+		p = va_arg(f->args, uchar*);
+
+		/* look for a prefix mask */
+		for(i = 0; i < 16; i++)
+			if(p[i] != 0xff)
+				break;
+		if(i < 16){
+			if((prefixvals[p[i]] & Isprefix) == 0)
+				goto common;
+			for(j = i+1; j < 16; j++)
+				if(p[j] != 0)
+					goto common;
+			n = 8*i + (prefixvals[p[i]] & ~Isprefix);
+		} else
+			n = 8*16;
+
+		/* got one, use /xx format */
+		snprint(buf, sizeof buf, "/%d", n);
+		return fmtstrcpy(f, buf);
+	}
+	return fmtstrcpy(f, "(eipfmt)");
+}
--- /dev/null
+++ b/libip/ipaux.c
@@ -1,0 +1,102 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+
+/*
+ *  well known IP addresses
+ */
+uchar IPv4bcast[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff
+};
+uchar IPv4allsys[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0xe0, 0, 0, 0x01
+};
+uchar IPv4allrouter[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0xe0, 0, 0, 0x02
+};
+uchar IPallbits[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff
+};
+uchar IPnoaddr[IPaddrlen];
+
+/*
+ *  prefix of all v4 addresses
+ */
+uchar v4prefix[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0, 0, 0, 0
+};
+
+int
+isv4(uchar *ip)
+{
+	return memcmp(ip, v4prefix, IPv4off) == 0;
+}
+
+/*
+ *  the following routines are unrolled with no memset's to speed
+ *  up the usual case
+ */
+void
+v4tov6(uchar *v6, uchar *v4)
+{
+	v6[0] = 0;
+	v6[1] = 0;
+	v6[2] = 0;
+	v6[3] = 0;
+	v6[4] = 0;
+	v6[5] = 0;
+	v6[6] = 0;
+	v6[7] = 0;
+	v6[8] = 0;
+	v6[9] = 0;
+	v6[10] = 0xff;
+	v6[11] = 0xff;
+	v6[12] = v4[0];
+	v6[13] = v4[1];
+	v6[14] = v4[2];
+	v6[15] = v4[3];
+}
+
+int
+v6tov4(uchar *v4, uchar *v6)
+{
+	if(v6[0] == 0
+	&& v6[1] == 0
+	&& v6[2] == 0
+	&& v6[3] == 0
+	&& v6[4] == 0
+	&& v6[5] == 0
+	&& v6[6] == 0
+	&& v6[7] == 0
+	&& v6[8] == 0
+	&& v6[9] == 0
+	&& v6[10] == 0xff
+	&& v6[11] == 0xff)
+	{
+		v4[0] = v6[12];
+		v4[1] = v6[13];
+		v4[2] = v6[14];
+		v4[3] = v6[15];
+		return 0;
+	} else {
+		memset(v4, 0, 4);
+		if(memcmp(v6, IPnoaddr, IPaddrlen) == 0)
+			return 0;
+		return -1;
+	}
+}
--- /dev/null
+++ b/libip/parseip.c
@@ -1,0 +1,176 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <ip.h>
+
+char*
+v4parseip(uchar *to, char *from)
+{
+	int i;
+	char *p;
+
+	p = from;
+	for(i = 0; i < 4 && *p; i++){
+		to[i] = strtoul(p, &p, 0);
+		if(*p == '.')
+			p++;
+	}
+	switch(CLASS(to)){
+	case 0:	/* class A - 1 uchar 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 uchar net */
+		if(i == 3){
+			to[3] = to[2];
+			to[2] = 0;
+		}
+		break;
+	}
+	return p;
+}
+
+static int
+ipcharok(int c)
+{
+	return c == '.' || c == ':' || (isascii(c) && isxdigit(c));
+}
+
+static int
+delimchar(int c)
+{
+	if(c == '\0')
+		return 1;
+	if(c == '.' || c == ':' || (isascii(c) && isalnum(c)))
+		return 0;
+	return 1;
+}
+
+/*
+ * `from' may contain an address followed by other characters,
+ * at least in /boot, so we permit whitespace (and more) after the address.
+ * we do ensure that "delete" cannot be parsed as "de::".
+ *
+ * some callers don't check the return value for errors, so
+ * set `to' to something distinctive in the case of a parse error.
+ */
+vlong
+parseip(uchar *to, char *from)
+{
+	int i, elipsis = 0, v4 = 1;
+	ulong x;
+	char *p, *op;
+
+	memset(to, 0, IPaddrlen);
+	p = from;
+	for(i = 0; i < IPaddrlen && ipcharok(*p); i+=2){
+		op = p;
+		x = strtoul(p, &p, 16);
+		if(*p == '.' || (*p == 0 && i == 0)){	/* ends with v4? */
+			if(i > IPaddrlen-4){
+				memset(to, 0, IPaddrlen);
+				return -1;		/* parse error */
+			}
+			p = v4parseip(to+i, op);
+			i += 4;
+			break;
+		}
+		/* v6: at most 4 hex digits, followed by colon or delim */
+		if(x != (ushort)x || (*p != ':' && !delimchar(*p))) {
+			memset(to, 0, IPaddrlen);
+			return -1;			/* parse error */
+		}
+		to[i] = x>>8;
+		to[i+1] = x;
+		if(*p == ':'){
+			v4 = 0;
+			if(*++p == ':'){	/* :: is elided zero short(s) */
+				if (elipsis) {
+					memset(to, 0, IPaddrlen);
+					return -1;	/* second :: */
+				}
+				elipsis = i+2;
+				p++;
+			}
+		} else if (p == op)		/* strtoul made no progress? */
+			break;
+	}
+	if (p == from || !delimchar(*p)) {
+		memset(to, 0, IPaddrlen);
+		return -1;				/* parse error */
+	}
+	if(i < IPaddrlen){
+		memmove(&to[elipsis+IPaddrlen-i], &to[elipsis], i-elipsis);
+		memset(&to[elipsis], 0, IPaddrlen-i);
+	}
+	if(v4){
+		to[10] = to[11] = 0xff;
+		return (ulong)nhgetl(to + IPv4off);
+	} else
+		return 6;
+}
+
+/*
+ *  hack to allow ip v4 masks to be entered in the old
+ *  style
+ */
+vlong
+parseipmask(uchar *to, char *from, int v4)
+{
+	vlong x;
+	int i, w;
+	uchar *p;
+
+	if(*from == '/'){
+		/* as a number of prefix bits */
+		i = atoi(from+1);
+		if(i < 0)
+			i = 0;
+		if(i <= 32 && v4)
+			i += 96;
+		if(i > 128)
+			i = 128;
+		w = i;
+		memset(to, 0, IPaddrlen);
+		for(p = to; i >= 8; i -= 8)
+			*p++ = 0xff;
+		if(i > 0)
+			*p = ~((1<<(8-i))-1);
+		/*
+		 * identify as ipv6 if the mask is inexpressible as a v4 mask
+		 * (because it has too few mask bits).  Arguably, we could
+		 * always return 6 here.
+		 */
+		if (w < 96)
+			return v4 ? -1 : 6;
+		x = (ulong)nhgetl(to+IPv4off);
+	} else {
+		/* as a straight v4 bit mask */
+		x = parseip(to, from);
+		if(memcmp(to, v4prefix, IPv4off) == 0)
+			memset(to, 0xff, IPv4off);
+		else if(v4 && memcmp(to, IPallbits, IPv4off) != 0)
+			x = -1;
+	}
+	return x;
+}
+
+vlong
+parseipandmask(uchar *ip, uchar *mask, char *ipstr, char *maskstr)
+{
+	vlong x;
+
+	x = parseip(ip, ipstr);
+	if(maskstr == nil)
+		memset(mask, 0xff, IPaddrlen);
+	else if(parseipmask(mask, maskstr, memcmp(ip, v4prefix, IPv4off) == 0) == -1)
+		x = -1;
+	return x;
+}
--- /dev/null
+++ b/libmemdraw/Makefile
@@ -1,0 +1,33 @@
+ROOT=..
+include ../Make.config
+LIB=libmemdraw.a
+
+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
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libmemdraw/alloc.c
@@ -1,0 +1,207 @@
+#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+sizeof(Memdata*)+sizeof(ulong);
+}
+
+Memimage*
+allocmemimaged(Rectangle r, ulong chan, Memdata *md)
+{
+	int d;
+	ulong l;
+	Memimage *i;
+
+	if((d = chantodepth(chan)) == 0) {
+		werrstr("bad channel descriptor %.8lux", chan);
+		return nil;
+	}
+	if(badrect(r)){
+		werrstr("bad rectangle %R", r);
+		return nil;
+	}
+
+	l = wordsperline(r, d);
+
+	i = mallocz(sizeof(Memimage), 1);
+	if(i == nil)
+		return nil;
+
+	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;
+	uchar *p;
+	ulong l, nw;
+	Memdata *md;
+	Memimage *i;
+
+	if((d = chantodepth(chan)) == 0) {
+		werrstr("bad channel descriptor %.8lux", chan);
+		return nil;
+	}
+	if(badrect(r)){
+		werrstr("bad rectangle %R", r);
+		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, sizeof(Memdata*)+(1+nw)*sizeof(ulong));
+	if(md->base == nil){
+		free(md);
+		return nil;
+	}
+
+	p = (uchar*)md->base;
+	*(Memdata**)p = md;
+	p += sizeof(Memdata*);
+
+	*(ulong*)p = getcallerpc(&r);
+	p += sizeof(ulong);
+
+	/* if this changes, memimagemove must change too */
+	md->bdata = p;
+	md->allocd = 1;
+
+	i = allocmemimaged(r, chan, md);
+	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 == 0 && 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*) ((uintptr)byteaddr(i, p) & ~(sizeof(ulong)-1));
+}
+
+uchar*
+byteaddr(Memimage *i, Point p)
+{
+	uchar *a;
+
+	a = i->data->bdata+i->zero+(int)(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,119 @@
+#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}
+};
+
+/*
+ * 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(phi == 0)
+		return;
+	if(phi <= -360 || phi >= 360){
+		memellipse(dst, c, a, b, t, src, sp, op);
+		return;
+	}
+	alpha %= 360;
+	alpha = -alpha;		/* compensate for upside-down coords */
+	phi = -phi;
+	beta = alpha + phi;
+	if(phi < 0){
+		tmp = alpha;
+		alpha = beta;
+		beta = tmp;
+		phi = -phi;
+	}
+	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
+		*/
+
+	if(a < 0)
+		a = -a;
+	if(b < 0)
+		b = -b;
+	w = t;
+	if(w < 0)
+		w = 0;
+
+	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, ZP, S);
+	figure = allocmemimage(rect, GREY1);
+	if(figure == nil)
+		goto Return;
+	memfillcolor(figure, DTransparent);
+	memellipse(figure, ZP, a, b, t, memopaque, ZP, 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(ZP, c), op);
+
+    Return:
+	freememimage(wedge);
+	freememimage(figure);
+	freememimage(mask);
+}
--- /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(badrect(r) || !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 = (uintptr)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);
+	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,2407 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.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)
+
+/*
+ * For 16-bit values, x / 255 == (t = x+1, (t+(t>>8)) >> 8).
+ * We add another 127 to round to the nearest value rather
+ * than truncate.
+ *
+ * CALCxy does x bytewise calculations on y input images (x=1,4; y=1,2).
+ * CALC2x does two parallel 16-bit calculations on y input images (y=1,2).
+ */
+#define CALC11(a, v, tmp) \
+	(tmp=(a)*(v)+128, (tmp+(tmp>>8))>>8)
+
+#define CALC12(a1, v1, a2, v2, tmp) \
+	(tmp=(a1)*(v1)+(a2)*(v2)+128, (tmp+(tmp>>8))>>8)
+
+#define MASK 0xFF00FF
+
+#define CALC21(a, vvuu, tmp) \
+	(tmp=(a)*(vvuu)+0x00800080, ((tmp+((tmp>>8)&MASK))>>8)&MASK)
+
+#define CALC41(a, rgba, tmp1, tmp2) \
+	(CALC21(a, rgba & MASK, tmp1) | \
+	 (CALC21(a, (rgba>>8)&MASK, tmp2)<<8))
+
+#define CALC22(a1, vvuu1, a2, vvuu2, tmp) \
+	(tmp=(a1)*(vvuu1)+(a2)*(vvuu2)+0x00800080, ((tmp+((tmp>>8)&MASK))>>8)&MASK)
+
+#define CALC42(a1, rgba1, a2, rgba2, tmp1, tmp2) \
+	(CALC22(a1, rgba1 & MASK, a2, rgba2 & MASK, tmp1) | \
+	 (CALC22(a1, (rgba1>>8) & MASK, a2, (rgba2>>8) & MASK, tmp2)<<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*);
+
+int
+memimageinit(void)
+{
+	static int didinit = 0;
+
+	if(didinit)
+		return 0;
+
+	mktables();
+	_memmkcmap();
+
+	fmtinstall('R', Rfmt); 
+	fmtinstall('P', Pfmt);
+
+	memones = allocmemimage(Rect(0,0,1,1), GREY1);
+	memzeros = allocmemimage(Rect(0,0,1,1), GREY1);
+	if(memones == nil || memzeros == nil)
+		return -1;
+
+	memones->flags |= Frepl;
+	memones->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+	*byteaddr(memones, ZP) = ~0;
+
+	memzeros->flags |= Frepl;
+	memzeros->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+	*byteaddr(memzeros, ZP) = 0;
+
+	memwhite = memones;
+	memblack = memzeros;
+	memopaque = memones;
+	memtransparent = memzeros;
+
+	didinit = 1;
+	return 0;
+}
+
+static ulong imgtorgba(Memimage*, ulong);
+static ulong rgbatoimg(Memimage*, ulong);
+static ulong pixelbits(Memimage*, Point);
+
+void
+memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point p0, Memimage *mask, Point p1, int op)
+{
+	Memdrawparam par;
+
+	if(mask == nil)
+		mask = memopaque;
+
+	if(drawclip(dst, &r, src, &p0, mask, &p1, &par.sr, &par.mr) == 0)
+		return;
+
+	if(op < Clear || op > SoverD)
+		return;
+
+	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))
+				return;	/* 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))
+				return;	/* no-op successfully handled */
+			par.state |= Simplemask;
+			if(par.mval == ~0)
+				par.state |= Fullmask;
+			par.mrgba = imgtorgba(mask, par.mval);
+		}
+	}
+
+	/*
+	 * 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.
+	 */
+	if(hwdraw(&par))
+		return;
+
+	/*
+	 * Optimizations using memmove and memset.
+	 */
+	if(memoptdraw(&par))
+		return;
+
+	/*
+	 * Character drawing.
+	 * Solid source color being painted through a boolean mask onto a high res image.
+	 */
+	if(chardraw(&par))
+		return;
+
+	/*
+	 * General calculation-laden case that does alpha for each pixel.
+	 */
+	alphadraw(&par);
+}
+
+
+/*
+ * 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.
+ *
+ * Return zero if the final rectangle is null.
+ */
+int
+drawclipnorepl(Memimage *dst, Rectangle *r, Memimage *src, Point *p0, Memimage *mask, Point *p1, Rectangle *sr, Rectangle *mr)
+{
+	Point rmin, delta;
+	int splitcoords;
+	Rectangle omr;
+
+	if(badrect(*r))
+		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;
+	}else{
+		if(!(mask->flags&Frepl) && !rectclip(sr, mask->r))
+			return 0;
+		if(!rectclip(sr, mask->clipr))
+			return 0;
+		*mr = *sr;
+	}
+	/* 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;
+	*p0 = sr->min;
+	*p1 = mr->min;
+
+	assert(Dx(*sr) == Dx(*mr) && Dx(*mr) == Dx(*r));
+	assert(Dy(*sr) == Dy(*mr) && Dy(*mr) == Dy(*r));
+	assert(ptinrect(r->min, dst->r));
+
+	return 1;
+}
+
+/*
+ * like drawclipnorepl() above, but 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 delta;
+
+	if(!drawclipnorepl(dst, r, src, p0, mask, p1, sr, mr))
+		return 0;
+
+	/* 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(ptinrect(*p0, src->r));
+	assert(ptinrect(*p1, mask->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;
+		
+	/* 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;	/* is &ones when unused, never nil */
+	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 void	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 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*, int*);
+
+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 */
+};
+
+/*
+ * Avoid standard Lock, QLock so that can be used in kernel.
+ */
+typedef struct Dbuf Dbuf;
+struct Dbuf
+{
+	uchar *p;
+	int n;
+	Param spar, mpar, dpar;
+	int inuse;
+};
+static Dbuf dbuf[10];
+
+static Dbuf*
+allocdbuf(void)
+{
+	int i;
+
+	for(i=0; i<nelem(dbuf); i++){
+		if(dbuf[i].inuse)
+			continue;
+		if(!tas(&dbuf[i].inuse))
+			return &dbuf[i];
+	}
+	return nil;
+}
+
+static void
+getparam(Param *p, Memimage *img, Rectangle r, int convgrey, int needbuf, int *ndrawbuf)
+{
+	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;
+}
+
+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);
+}
+
+/*
+ * 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.
+ */
+static int
+alphadraw(Memdrawparam *par)
+{
+	int isgrey, starty, endy, op;
+	int needbuf, dsty, srcy, masky;
+	int y, dir, dx, dy, ndrawbuf;
+	uchar *drawbuf;
+	Buffer bsrc, bdst, bmask;
+	Readfn *rdsrc, *rdmask, *rddst;
+	Calcfn *calc;
+	Writefn *wrdst;
+	Memimage *src, *mask, *dst;
+	Rectangle r, sr, mr;
+	Dbuf *z;
+
+	r = par->r;
+	dx = Dx(r);
+	dy = Dy(r);
+
+	z = allocdbuf();
+	if(z == nil)
+		return 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);
+
+	ndrawbuf = 0;
+	getparam(&z->spar, src, sr, isgrey, needbuf, &ndrawbuf);
+	getparam(&z->dpar, dst, r, isgrey, needbuf, &ndrawbuf);
+	getparam(&z->mpar, mask, mr, 0, needbuf, &ndrawbuf);
+
+	dir = (needbuf && byteaddr(dst, r.min) > byteaddr(src, sr.min)) ? -1 : 1;
+	z->spar.dir = z->mpar.dir = z->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(!(src->flags&Falpha) && mask->chan == GREY1 && dst->depth >= 8 && op == SoverD){
+		rdsrc = convfn(dst, &z->dpar, src, &z->spar, &ndrawbuf);
+		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);
+			z->mpar.alphaonly = 1;
+		}else{
+			z->mpar.greymaskcall = readfn(mask);
+			z->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(z->spar.replcache){
+		z->spar.replcall = rdsrc;
+		rdsrc = replread;
+	}
+	if(z->mpar.replcache){
+		z->mpar.replcall = rdmask;
+		rdmask = replread;
+	}
+
+	if(z->n < ndrawbuf){
+		free(z->p);
+		if((z->p = mallocz(ndrawbuf, 0)) == nil){
+			z->inuse = 0;
+			return 0;
+		}
+		z->n = ndrawbuf;
+	}
+	drawbuf = z->p;
+
+	/*
+	 * 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.
+	 */
+	z->spar.bufbase = drawbuf+z->spar.bufoff;
+	z->mpar.bufbase = drawbuf+z->mpar.bufoff;
+	z->dpar.bufbase = drawbuf+z->dpar.bufoff;
+	z->spar.convbuf = drawbuf+z->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(&z->spar, z->spar.bufbase, srcy);
+		bmask = rdmask(&z->mpar, z->mpar.bufbase, masky);
+		bdst = rddst(&z->dpar, z->dpar.bufbase, dsty);
+		calc(bdst, bsrc, bmask, dx, isgrey, op);
+		wrdst(&z->dpar, z->dpar.bytermin+dsty*z->dpar.bwidth, bdst);
+	}
+
+	z->inuse = 0;
+	return 1;
+}
+
+static void
+alphacalc0(Buffer bdst, Buffer b1, Buffer b2, int dx, int grey, int op)
+{
+	USED(b1);
+	USED(b2);
+	USED(grey);
+	USED(op);
+	memset(bdst.rgba, 0, dx*bdst.delta);
+}
+
+/*
+ * Do the channels in the buffers match enough
+ * that we can do word-at-a-time operations
+ * on the pixels?
+ */
+static int
+chanmatch(Buffer *bdst, Buffer *bsrc)
+{
+	uchar *drgb, *srgb;
+	
+	/*
+	 * first, r, g, b must be in the same place
+	 * in the rgba word.
+	 */
+	drgb = (uchar*)bdst->rgba;
+	srgb = (uchar*)bsrc->rgba;
+	if(bdst->red - drgb != bsrc->red - srgb
+	|| bdst->blu - drgb != bsrc->blu - srgb
+	|| bdst->grn - drgb != bsrc->grn - srgb)
+		return 0;
+	
+	/*
+	 * that implies alpha is in the same place,
+	 * if it is there at all (it might be == &ones).
+	 * if the destination is &ones, we can scribble
+	 * over the rgba slot just fine.
+	 */
+	if(bdst->alpha == &ones)
+		return 1;
+	
+	/*
+	 * if the destination is not ones but the src is,
+	 * then the simultaneous calculation will use
+	 * bogus bytes from the src's rgba.  no good.
+	 */
+	if(bsrc->alpha == &ones)
+		return 0;
+	
+	/*
+	 * otherwise, alphas are in the same place.
+	 */
+	return 1;
+}
+
+static void
+alphacalc14(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int fd, sadelta;
+	int i, sa, ma, q;
+	ulong t, t1;
+
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4 && chanmatch(&bdst, &bsrc);
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		fd = CALC11(sa, ma, t);
+		if(op == DoutS)
+			fd = 255-fd;
+
+		if(grey){
+			*bdst.grey = CALC11(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = CALC41(fd, *bdst.rgba, t, t1);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				continue;
+			}
+			*bdst.red = CALC11(fd, *bdst.red, t);
+			*bdst.grn = CALC11(fd, *bdst.grn, t);
+			*bdst.blu = CALC11(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 = CALC11(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+}
+
+static void
+alphacalc2810(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int fs, sadelta;
+	int i, ma, da, q;
+	ulong t, t1;
+
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4 && chanmatch(&bdst, &bsrc);
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		if(op == SoutD)
+			da = 255-da;
+		fs = ma;
+		if(op != S)
+			fs = CALC11(fs, da, t);
+
+		if(grey){
+			*bdst.grey = CALC11(fs, *bsrc.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = CALC41(fs, *bsrc.rgba, t, t1);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bmask.alpha += bmask.delta;
+				bdst.alpha += bdst.delta;
+				continue;
+			}
+			*bdst.red = CALC11(fs, *bsrc.red, t);
+			*bdst.grn = CALC11(fs, *bsrc.grn, t);
+			*bdst.blu = CALC11(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 = CALC11(fs, *bsrc.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+}
+
+static void
+alphacalc3679(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int fs, fd, sadelta;
+	int i, sa, ma, da, q;
+	ulong t, t1;
+
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4 && chanmatch(&bdst, &bsrc);
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		if(op == SatopD)
+			fs = CALC11(ma, da, t);
+		else
+			fs = CALC11(ma, 255-da, t);
+		if(op == DoverS)
+			fd = 255;
+		else{
+			fd = CALC11(sa, ma, t);
+			if(op != DatopS)
+				fd = 255-fd;
+		}
+
+		if(grey){
+			*bdst.grey = CALC12(fs, *bsrc.grey, fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = CALC42(fs, *bsrc.rgba, fd, *bdst.rgba, t, t1);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				bdst.alpha += bdst.delta;
+				continue;
+			}
+			*bdst.red = CALC12(fs, *bsrc.red, fd, *bdst.red, t);
+			*bdst.grn = CALC12(fs, *bsrc.grn, fd, *bdst.grn, t);
+			*bdst.blu = CALC12(fs, *bsrc.blu, 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 = CALC12(fs, sa, fd, da, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+}
+
+static void
+alphacalc5(Buffer bdst, Buffer b1, Buffer b2, int dx, int grey, int op)
+{
+	USED(bdst);
+	USED(b1);
+	USED(b2);
+	USED(dx);
+	USED(grey);
+	USED(op);
+}
+
+static void
+alphacalc11(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int fd, sadelta;
+	int i, sa, ma, q;
+	ulong t, t1;
+
+	USED(op);
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4 && chanmatch(&bdst, &bsrc);
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		fd = 255-CALC11(sa, ma, t);
+
+		if(grey){
+			*bdst.grey = CALC12(ma, *bsrc.grey, fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = CALC42(ma, *bsrc.rgba, fd, *bdst.rgba, t, t1);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				continue;
+			}
+			*bdst.red = CALC12(ma, *bsrc.red, fd, *bdst.red, t);
+			*bdst.grn = CALC12(ma, *bsrc.grn, fd, *bdst.grn, t);
+			*bdst.blu = CALC12(ma, *bsrc.blu, 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 = CALC12(ma, sa, fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+}
+
+/*
+not used yet
+source and mask alpha 1
+static void
+alphacalcS0(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int i;
+
+	USED(op);
+	if(bsrc.delta == bdst.delta){
+		memmove(bdst.rgba, bsrc.rgba, dx*bdst.delta);
+		return;
+	}
+	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;
+		}
+	}
+}
+*/
+
+/* source alpha 1 */
+static void
+alphacalcS(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int fd;
+	int i, ma;
+	ulong t;
+
+	USED(op);
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		fd = 255-ma;
+
+		if(grey){
+			*bdst.grey = CALC12(ma, *bsrc.grey, fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			*bdst.red = CALC12(ma, *bsrc.red, fd, *bdst.red, t);
+			*bdst.grn = CALC12(ma, *bsrc.grn, fd, *bdst.grn, t);
+			*bdst.blu = CALC12(ma, *bsrc.blu, 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+CALC11(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+	}
+}
+
+static void
+boolcalc14(Buffer bdst, Buffer b1, Buffer bmask, int dx, int grey, int op)
+{
+	int i, ma, zero;
+
+	USED(b1);
+
+	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;
+		}
+	}
+}
+
+static void
+boolcalc236789(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int fs, fd;
+	int i, ma, da, zero;
+	ulong t;
+
+	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 = CALC12(fs, *bsrc.grey, fd, *bdst.grey, t);
+			else if(zero)
+				*bdst.grey = 0;
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(ma){
+				*bdst.red = CALC12(fs, *bsrc.red, fd, *bdst.red, t);
+				*bdst.grn = CALC12(fs, *bsrc.grn, fd, *bdst.grn, t);
+				*bdst.blu = CALC12(fs, *bsrc.blu, 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+CALC11(fd, da, t);
+			else if(zero)
+				*bdst.alpha = 0;
+			bdst.alpha += bdst.delta;
+		}
+	}
+}
+
+static void
+boolcalc1011(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	int i, ma, zero;
+
+	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;
+		}
+	}
+}
+/*
+ * 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;
+}
+
+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;
+	bits = *r++;
+	nbits = 8;
+	if((i=x&(npack-1)) != 0){
+		bits <<= depth*i;
+		nbits -= depth*i;
+	}
+	for(i=0; i<n; i++){
+		if(nbits == 0){
+			bits = *r++;
+			nbits = 8;
+		}
+		*w++ = repl[bits>>sh];
+		bits <<= depth;
+		nbits -= depth;
+	}
+	dx -= n;
+	if(dx == 0)
+		goto done;
+
+	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;
+	bits = *r++;
+	nbits = 8;
+	if((i=x&(npack-1)) != 0){
+		bits <<= depth*i;
+		nbits -= depth*i;
+	}
+	for(i=0; i<n; i++){
+		if(nbits == 0){
+			bits = *r++;
+			nbits = 8;
+		}
+		*w++ = repl[bits>>sh];
+		bits <<= depth;
+		nbits -= depth;
+	}
+	dx -= n;
+	if(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++;
+	}
+done:
+	return b;
+}
+
+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;
+		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;
+	}
+	return;
+}
+
+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) != 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, *alpha;
+	int i, dx, delta, a, m;
+
+	cmap = p->img->cmap->rgb2cmap;
+	
+	delta = src.delta;
+	red= src.red;
+	grn = src.grn;
+	blu = src.blu;
+
+	dx = p->dx;
+	if(p->img->flags&Falpha){
+		alpha = src.alpha;
+		m = p->img->shift[CMap]/8;
+		a = p->img->shift[CAlpha]/8;
+		for(i=0; i<dx; i++, red+=delta, grn+=delta, blu+=delta, w+=2){
+			w[a] = *alpha;
+			if(alpha != &ones)
+				alpha+=delta;
+			w[m] = cmap[(*red>>4)*256+(*grn>>4)*16+(*blu>>4)];
+		}
+	} else {
+		for(i=0; i<dx; i++, red+=delta, grn+=delta, blu+=delta)
+			*w++ = cmap[(*red>>4)*256+(*grn>>4)*16+(*blu>>4)];
+	}
+}
+
+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) != 0;
+
+	/* 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;
+	}
+
+	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]];
+		}
+
+		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){
+				*w++ = RGB2K(ured, ugrn, ublu);
+			}else{
+				w[0] = ublu;
+				w[1] = ugrn;
+				w[2] = ured;
+				w += 3;
+			}
+		}
+		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;
+	}else{
+		b.blu = buf+copyalpha;
+		b.grn = buf+copyalpha+1;
+		b.grey = nil;
+		b.red = buf+copyalpha+2;
+		b.delta = copyalpha+3;
+	}
+	return b;
+}
+
+static void
+writebyte(Param *p, uchar *w, Buffer src)
+{
+	Memimage *img;
+	int i, isalpha, isgrey, nb, delta, dx, adelta;
+	uchar *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;
+
+	isalpha = img->flags&Falpha;
+	isgrey = img->flags&Fgrey;
+	adelta = src.delta;
+
+	if(isalpha && alpha == &ones)
+		adelta = 0;
+
+	if((img->flags&Fbytes) != 0){
+		int ogry, ored, ogrn, oblu, oalp;
+
+		ogry = img->shift[CGrey]/8;
+		ored = img->shift[CRed]/8;
+		ogrn = img->shift[CGreen]/8;
+		oblu = img->shift[CBlue]/8;
+		oalp = img->shift[CAlpha]/8;
+
+		for(i=0; i<dx; i++){
+			if(isgrey){
+				w[ogry] = *grey;
+				grey += delta;
+			} else {
+				w[ored] = *red;
+				w[ogrn] = *grn;
+				w[oblu] = *blu;
+				red += delta;
+				grn += delta;
+				blu += delta;
+			}
+			if(isalpha){
+				w[oalp] = *alpha;
+				alpha += adelta;
+			}
+			w += nb;
+		}
+		return;
+	}
+
+	mask = (nb==4) ? 0 : ~((1<<img->depth)-1);
+	for(i=0; i<dx; i++){
+		u = w[0] | (w[1]<<8) | (w[2]<<16) | (w[3]<<24);
+		u &= mask;
+		if(isgrey){
+			u |= ((*grey >> (8-img->nbits[CGrey])) & img->mask[CGrey]) << img->shift[CGrey];
+			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;
+		}
+
+		if(isalpha){
+			u |= ((*alpha >> (8-img->nbits[CAlpha])) & img->mask[CAlpha]) << img->shift[CAlpha];
+			alpha += adelta;
+		}
+
+		w[0] = u;
+		w[1] = u>>8;
+		w[2] = u>>16;
+		w[3] = u>>24;
+		w += nb;
+	}
+}
+
+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->nbits[CMap] == 8)
+		return writecmap;
+	return writebyte;
+}
+
+static void
+nullwrite(Param *p, uchar *s, Buffer b)
+{
+	USED(p);
+	USED(s);
+	USED(b);
+}
+
+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 = nil;
+	b.alpha = &ones;
+	b.rgba = (ulong*)q;
+	b.delta = p->img->depth/8;
+	return b;
+}
+
+static void
+boolmemmove(Buffer bdst, Buffer bsrc, Buffer b1, int dx, int i, int o)
+{
+	USED(i);
+	USED(o);
+	USED(b1);
+	memmove(bdst.red, bsrc.red, dx*bdst.delta);
+}
+
+static void
+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;
+}
+
+static void
+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;
+}
+
+static void
+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;
+		}
+	}
+}
+
+static void
+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;
+}
+
+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 = nil;
+	b.alpha = &ones;
+	b.rgba = (ulong*)buf;
+	b.delta = 0;
+	
+	return b;
+}
+
+static Readfn*
+convfn(Memimage *dst, Param *dpar, Memimage *src, Param *spar, int *ndrawbuf)
+{
+	if(dst->chan == src->chan && !(src->flags&Frepl))
+		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. */
+		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);
+	}
+
+	return genconv;
+}
+
+static 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
+memsets(void *vp, ushort val, int n)
+{
+	ushort *p, *ep;
+	uchar b[2];
+
+	/* make little endian */
+	b[0] = val;
+	b[1] = val>>8;
+	val = *(ushort*)b;
+
+	p = vp;
+	ep = p+n;
+	while(p<ep)
+		*p++ = val;
+}
+
+static void
+memsetl(void *vp, ulong val, int n)
+{
+	ulong *p, *ep;
+	uchar b[4];
+
+	/* make little endian */
+	b[0] = val;
+	b[1] = val>>8;
+	b[2] = val>>16;
+	b[3] = val>>24;
+	val = *(ulong*)b;
+
+	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;
+
+	a = val;
+	b = val>>8;
+	c = val>>16;
+
+	p = vp;
+	ep = p+3*n;
+	while(p<ep){
+		p[0] = a;
+		p[1] = b;
+		p[2] = c;
+		p += 3;
+	}
+}
+
+static 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[0];
+			g = p[1];
+			b = p[2];
+			break;
+		}
+	}
+	return (r<<24)|(g<<16)|(b<<8)|a;	
+}
+
+static 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;
+	}
+	return v;
+}
+
+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;
+
+	/*
+	 * 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)){
+		int d, dwid, ppb, np, nb;
+		uchar *dp, lm, rm;
+
+		dwid = dst->width*sizeof(ulong);
+		dp = byteaddr(dst, par->r.min);
+		v = par->sdval;
+		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;
+
+			/* 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);
+
+			/* 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){
+						*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:
+			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:
+			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;	
+}
+
+/*
+ * Boolean character drawing.
+ * Solid opaque color through a 1-bit greyscale mask.
+ */
+static int
+chardraw(Memdrawparam *par)
+{
+	int i, ddepth, dy, dx, x, bx, ex, y, npack, bsh, depth, op;
+	ulong bits, v, maskwid, dstwid;
+	uchar *wp, *rp, *q, *wc;
+	ushort *ws;
+	ulong *wl;
+	uchar sp[4];
+	Rectangle r, mr;
+	Memimage *mask, *src, *dst;
+
+	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;
+
+	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);
+	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;
+
+	for(y=0; y<dy; y++, rp+=maskwid, wp+=dstwid){
+		q = rp;
+		if(bsh)
+			bits = *q++;
+		switch(ddepth){
+		case 8:
+			wc = wp;
+			for(x=bx; x>ex; x--, wc++){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+				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++;
+				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++;
+				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++;
+				if((bits>>i)&1)
+					*wl = v;
+			}
+			break;
+		}
+	}
+	return 1;	
+}
+
+
+void
+memfillcolor(Memimage *i, ulong val)
+{
+	ulong bits;
+	int d, y;
+
+	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;
+		memsetl(wordaddr(i, i->r.min), bits, i->width*Dy(i->r));
+		break;
+	}
+}
+
--- /dev/null
+++ b/libmemdraw/ellipse.c
@@ -1,0 +1,246 @@
+#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;
+	}
+}
+
+/*
+ * 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, ZP, t, t, -1, memopaque, ZP, 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, ZP, 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,494 @@
+#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(const void*, const void*);
+static	int	xcompare(const void*, const void*);
+static	int	zcompare(const void*, const 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
+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;
+
+	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;
+			}
+			fillline(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(const void *a, const void *b)
+{
+	Seg **s0, **s1;
+	long y0, y1;
+
+	s0 = (Seg**)a;
+	s1 = (Seg**)b;
+	y0 = (*s0)->p0.y;
+	y1 = (*s1)->p0.y;
+
+	if(y0 < y1)
+		return -1;
+	if(y0 == y1)
+		return 0;
+	return 1;
+}
+
+static int
+xcompare(const void *a, const void *b)
+{
+	Seg **s0, **s1;
+	long x0, x1;
+
+	s0 = (Seg**)a;
+	s1 = (Seg**)b;
+	x0 = (*s0)->p0.x;
+	x1 = (*s1)->p0.x;
+
+	if(x0 < x1)
+		return -1;
+	if(x0 == x1)
+		return 0;
+	return 1;
+}
+
+static int
+zcompare(const void *a, const void *b)
+{
+	Seg **s0, **s1;
+	long z0, z1;
+
+	s0 = (Seg**)a;
+	s1 = (Seg**)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/line.c
@@ -1,0 +1,487 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+enum
+{
+	Arrow1 = 8,
+	Arrow2 = 10,
+	Arrow3 = 3,
+};
+
+static
+int
+lmax(int a, int b)
+{
+	if(a > b)
+		return a;
+	return b;
+}
+
+#ifdef NOTUSED
+
+static
+int
+lmin(int a, int b)
+{
+	if(a < b)
+		return a;
+	return b;
+}
+
+/*
+ * 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;
+		sp = addpt(r.min, d);
+		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(pp[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,79 @@
+#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;
+	Memdrawparam par;
+	uchar *q;
+
+	if(badrect(r) || !rectinrect(r, i->r))
+		return -1;
+
+	memset(&par, 0, sizeof par);
+	par.dst = i;
+	par.r = r;
+	hwdraw(&par);
+
+	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/openmemsubfont.c
@@ -1,0 +1,56 @@
+#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(readn(fd, hdr, 3*12) != 3*12){
+		werrstr("openmemsubfont: header read error: %r");
+		goto Err;
+	}
+	n = atoi(hdr);
+	if(n <= 0 || n > 0x7fff){
+		werrstr("openmemsubfont: bad fontchar count %d", n);
+		goto Err;
+	}
+	p = malloc(6*(n+1));
+	if(p == nil)
+		goto Err;
+	if(readn(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;
+	}
+	close(fd);
+	free(p);
+	return sf;
+Err:
+	close(fd);
+	free(p);
+	freememimage(i);
+	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,107 @@
+#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;
+		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,67 @@
+#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) != 0; 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;
+		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) != 0; 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,35 @@
+#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->name);
+	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(badrect(r) || !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,189 @@
+#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 */
+	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 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);
+	ncblock = _compblocksize(r, i->depth);
+	if(ncblock > CHUNK){
+		sprint(hdr, "%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, 5*12) != 5*12)
+			return -1;
+		for(; r.min.y < r.max.y; r.min.y++)
+			if(write(fd, byteaddr(i, r.min), bpl) != bpl)
+				return -1;
+		return 0;
+	}
+
+	n = Dy(r)*bpl;
+	data = malloc(n);
+	if(data == 0){
+	ErrOut0:
+		free(data);
+		return -1;
+	}
+	if(unloadmemimage(i, r, data, n) != n)
+		goto ErrOut0;
+	outbuf = malloc(ncblock);
+	hash = malloc(NHASH*sizeof(Hlist));
+	chain = malloc(NMEM*sizeof(Hlist));
+	if(outbuf == 0 || hash == 0 || chain == 0){
+	ErrOut:
+		free(outbuf);
+		free(hash);
+		free(chain);
+		goto ErrOut0;
+	}
+	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 @@
+ROOT=..
+include ../Make.config
+LIB=libmemlayer.a
+
+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
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libmemlayer/draw.c
@@ -1,0 +1,185 @@
+#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 = drawclipnorepl(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(mask == nil)
+		mask = memopaque;
+
+	if(mask->layer)
+		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(drawclipnorepl(dst, &r, src, &p0, mask, &p1, &srcr, &mr) == 0)
+		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);
+	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,117 @@
+#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;
+	}
+
+	/* 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/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,56 @@
+ROOT=..
+include ../Make.config
+# N.B.  This is used only for secstore.  It needn't be fast.
+
+LIB=libmp.a
+
+OFILES=\
+	betomp.$O\
+	cnfield.$O\
+	crt.$O\
+	gmfield.$O\
+	letomp.$O\
+	mpadd.$O\
+	mpaux.$O\
+	mpcmp.$O\
+	mpdigdiv.$O\
+	mpdiv.$O\
+	mpexp.$O\
+	mpextendedgcd.$O\
+	mpfactorial.$O\
+	mpfield.$O\
+	mpfmt.$O\
+	mpinvert.$O\
+	mpleft.$O\
+	mplogic.$O\
+	mpmod.$O\
+	mpmodop.$O\
+	mpmul.$O\
+	mpnrand.$O\
+	mprand.$O\
+	mpright.$O\
+	mpsel.$O\
+	mpsub.$O\
+	mptobe.$O\
+	mptober.$O\
+	mptoi.$O\
+	mptole.$O\
+	mptolel.$O\
+	mptoui.$O\
+	mptouv.$O\
+	mptov.$O\
+	mpvecadd.$O\
+	mpveccmp.$O\
+	mpvecdigmuladd.$O\
+	mpvecsub.$O\
+	mpvectscmp.$O\
+	strtomp.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libmp/betomp.c
@@ -1,0 +1,34 @@
+#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);
+		setmalloctag(b, getcallerpc(&p));
+	}
+	mpbits(b, n*8);
+
+	m = DIGITS(n*8);
+	b->top = m--;
+	b->sign = 1;
+
+	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 mpnorm(b);
+}
--- /dev/null
+++ b/libmp/cnfield.c
@@ -1,0 +1,114 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+/*
+ * fast reduction for crandall numbers of the form: 2^n - c
+ */
+
+enum {
+	MAXDIG = 1024 / Dbits,
+};
+
+typedef struct CNfield CNfield;
+struct CNfield
+{
+	Mfield f;	
+
+	mpint	m[1];
+
+	int	s;
+	mpdigit	c;
+};
+
+static int
+cnreduce(Mfield *m, mpint *a, mpint *r)
+{
+	mpdigit q[MAXDIG-1], t[MAXDIG], d;
+	CNfield *f = (CNfield*)m;
+	int qn, tn, k;
+
+	k = f->f.m.top;
+	if((a->top - k) >= MAXDIG)
+		return -1;
+
+	mpleft(a, f->s, r);
+	if(r->top <= k)
+		mpbits(r, (k+1)*Dbits);
+
+	/* q = hi(r) */
+	qn = r->top - k;
+	memmove(q, r->p+k, qn*Dbytes);
+
+	/* r = lo(r) */
+	r->top = k;
+	r->sign = 1;
+
+	do {
+		/* t = q*c */
+		tn = qn+1;
+		memset(t, 0, tn*Dbytes);
+		mpvecdigmuladd(q, qn, f->c, t);
+
+		/* q = hi(t) */
+		qn = tn - k;
+		if(qn <= 0) qn = 0;
+		else memmove(q, t+k, qn*Dbytes);
+
+		/* r += lo(t) */
+		if(tn > k)
+			tn = k;
+		mpvecadd(r->p, k, t, tn, r->p);
+
+		/* if(r >= m) r -= m */
+		mpvecsub(r->p, k+1, f->m->p, k, t);
+		d = t[k];
+		for(tn = 0; tn < k; tn++)
+			r->p[tn] = (r->p[tn] & d) | (t[tn] & ~d);
+	} while(qn > 0);
+
+	if(f->s != 0)
+		mpright(r, f->s, r);
+	mpnorm(r);
+
+	return 0;
+}
+
+Mfield*
+cnfield(mpint *N)
+{
+	mpint *M, *C;
+	CNfield *f;
+	mpdigit d;
+	int s;
+
+	if(N->top <= 2 || N->top >= MAXDIG)
+		return nil;
+	f = nil;
+	d = N->p[N->top-1];
+	for(s = 0; (d & (mpdigit)1<<(Dbits-1)) == 0; s++)
+		d <<= 1;
+	C = mpnew(0);
+	M = mpcopy(N);
+	mpleft(N, s, M);
+	mpleft(mpone, M->top*Dbits, C);
+	mpsub(C, M, C);
+	if(C->top != 1)
+		goto out;
+	f = mallocz(sizeof(CNfield) + M->top*sizeof(mpdigit), 1);
+	if(f == nil)
+		goto out;
+	f->s = s;
+	f->c = C->p[0];
+	f->m->size = M->top;
+	f->m->p = (mpdigit*)&f[1];
+	mpassign(M, f->m);
+	mpassign(N, (mpint*)f);
+	f->f.reduce = cnreduce;
+	f->f.m.flags |= MPfield;
+out:
+	mpfree(M);
+	mpfree(C);
+
+	return (Mfield*)f;
+}
--- /dev/null
+++ b/libmp/crt.c
@@ -1,0 +1,121 @@
+#include "os.h"
+#include <mp.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,52 @@
+#include "os.h"
+#include <mp.h>
+
+void
+testcrt(mpint **p)
+{
+	CRTpre *crt;
+	CRTres *res;
+	mpint *m, *x, *y;
+
+	fmtinstall('B', mpfmt);
+
+	// 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("%ld secs with more\n", time(0)-start);
+	exits(0);
+}
--- /dev/null
+++ b/libmp/dat.h
@@ -1,0 +1,12 @@
+#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)
--- /dev/null
+++ b/libmp/gmfield.c
@@ -1,0 +1,173 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+/*
+ * fast reduction for generalized mersenne numbers (GM)
+ * using a series of additions and subtractions.
+ */
+
+enum {
+	MAXDIG = 1024/Dbits,
+};
+
+typedef struct GMfield GMfield;
+struct GMfield
+{
+	Mfield f;	
+
+	mpint	m2[1];
+
+	int	nadd;
+	int	nsub;
+	int	indx[256];
+};
+
+static int
+gmreduce(Mfield *m, mpint *a, mpint *r)
+{
+	GMfield *g = (GMfield*)m;
+	mpdigit d0, t[MAXDIG];
+	int i, j, d, *x;
+
+	if(mpmagcmp(a, g->m2) >= 0)
+		return -1;
+
+	if(a != r)
+		mpassign(a, r);
+
+	d = g->f.m.top;
+	mpbits(r, (d+1)*Dbits*2);
+	memmove(t+d, r->p+d, d*Dbytes);
+
+	r->sign = 1;
+	r->top = d;
+	r->p[d] = 0;
+
+	if(g->nsub > 0)
+		mpvecdigmuladd(g->f.m.p, d, g->nsub, r->p);
+
+	x = g->indx;
+	for(i=0; i<g->nadd; i++){
+		t[0] = 0;
+		d0 = t[*x++];
+		for(j=1; j<d; j++)
+			t[j] = t[*x++];
+		t[0] = d0;
+
+		mpvecadd(r->p, d+1, t, d, r->p);
+	}
+
+	for(i=0; i<g->nsub; i++){
+		t[0] = 0;
+		d0 = t[*x++];
+		for(j=1; j<d; j++)
+			t[j] = t[*x++];
+		t[0] = d0;
+
+		mpvecsub(r->p, d+1, t, d, r->p);
+	}
+
+	mpvecdigmulsub(g->f.m.p, d, r->p[d], r->p);
+	r->p[d] = 0;
+
+	mpvecsub(r->p, d+1, g->f.m.p, d, r->p+d+1);
+	d0 = r->p[2*d+1];
+	for(j=0; j<d; j++)
+		r->p[j] = (r->p[j] & d0) | (r->p[j+d+1] & ~d0);
+
+	mpnorm(r);
+
+	return 0;
+}
+
+Mfield*
+gmfield(mpint *N)
+{
+	int i,j,d, s, *C, *X, *x, *e;
+	mpint *M, *T;
+	GMfield *g;
+
+	d = N->top;
+	if(d <= 2 || d > MAXDIG/2 || (mpsignif(N) % Dbits) != 0)
+		return nil;
+	g = nil;
+	T = mpnew(0);
+	M = mpcopy(N);
+	C = malloc(sizeof(int)*(d+1));
+	X = malloc(sizeof(int)*(d*d));
+	if(C == nil || X == nil)
+		goto out;
+
+	for(i=0; i<=d; i++){
+		if((M->p[i]>>8) != 0 && (~M->p[i]>>8) != 0)
+			goto out;
+		j = M->p[i];
+		C[d - i] = -j;
+		itomp(j, T);
+		mpleft(T, i*Dbits, T);
+		mpsub(M, T, M);
+	}
+	for(j=0; j<d; j++)
+		X[j] = C[d-j];
+	for(i=1; i<d; i++){
+		X[d*i] = X[d*(i-1) + d-1]*C[d];
+		for(j=1; j<d; j++)
+			X[d*i + j] = X[d*(i-1) + j-1] + X[d*(i-1) + d-1]*C[d-j];
+	}
+	g = mallocz(sizeof(GMfield) + (d+1)*sizeof(mpdigit)*2, 1);
+	if(g == nil)
+		goto out;
+
+	g->m2->p = (mpdigit*)&g[1];
+	g->m2->size = d*2+1;
+	mpmul(N, N, g->m2);
+	mpassign(N, (mpint*)g);
+	g->f.reduce = gmreduce;
+	g->f.m.flags |= MPfield;
+
+	s = 0;
+	x = g->indx;
+	e = x + nelem(g->indx) - d;
+	for(g->nadd=0; x <= e; x += d, g->nadd++){
+		s = 0;
+		for(i=0; i<d; i++){
+			for(j=0; j<d; j++){
+				if(X[d*i+j] > 0 && x[j] == 0){
+					X[d*i+j]--;
+					x[j] = d+i;
+					s = 1;
+					break;
+				}
+			}
+		}
+		if(s == 0)
+			break;
+	}
+	for(g->nsub=0; x <= e; x += d, g->nsub++){
+		s = 0;
+		for(i=0; i<d; i++){
+			for(j=0; j<d; j++){
+				if(X[d*i+j] < 0 && x[j] == 0){
+					X[d*i+j]++;
+					x[j] = d+i;
+					s = 1;
+					break;
+				}
+			}
+		}
+		if(s == 0)
+			break;
+	}
+	if(s != 0){
+		mpfree((mpint*)g);
+		g = nil;
+	}
+out:
+	free(C);
+	free(X);
+	mpfree(M);
+	mpfree(T);
+	return (Mfield*)g;
+}
+
--- /dev/null
+++ b/libmp/letomp.c
@@ -1,0 +1,31 @@
+#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);
+		setmalloctag(b, getcallerpc(&s));
+	}
+	mpbits(b, 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;
+	b->sign = 1;
+	return mpnorm(b);
+}
--- /dev/null
+++ b/libmp/mpadd.c
@@ -1,0 +1,58 @@
+#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;
+
+	sum->flags |= (b1->flags | b2->flags) & MPtimesafe;
+
+	// 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);
+		sum->sign = 1;
+		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){
+		assert(((b1->flags | b2->flags | sum->flags) & MPtimesafe) == 0);
+		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,205 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+static mpdigit _mptwodata[1] = { 2 };
+static mpint _mptwo =
+{
+	1, 1, 1,
+	_mptwodata,
+	MPstatic|MPnorm
+};
+mpint *mptwo = &_mptwo;
+
+static mpdigit _mponedata[1] = { 1 };
+static mpint _mpone =
+{
+	1, 1, 1,
+	_mponedata,
+	MPstatic|MPnorm
+};
+mpint *mpone = &_mpone;
+
+static mpdigit _mpzerodata[1] = { 0 };
+static mpint _mpzero =
+{
+	1, 1, 0,
+	_mpzerodata,
+	MPstatic|MPnorm
+};
+mpint *mpzero = &_mpzero;
+
+static int mpmindigits = 33;
+
+// set minimum digit allocation
+void
+mpsetminbits(int n)
+{
+	if(n < 0)
+		sysfatal("mpsetminbits: n < 0");
+	if(n == 0)
+		n = 1;
+	mpmindigits = DIGITS(n);
+}
+
+// allocate an n bit 0'd number 
+mpint*
+mpnew(int n)
+{
+	mpint *b;
+
+	if(n < 0)
+		sysfatal("mpsetminbits: n < 0");
+
+	n = DIGITS(n);
+	if(n < mpmindigits)
+		n = mpmindigits;
+	b = mallocz(sizeof(mpint) + n*Dbytes, 1);
+	if(b == nil)
+		sysfatal("mpnew: %r");
+	setmalloctag(b, getcallerpc(&n));
+	b->p = (mpdigit*)&b[1];
+	b->size = n;
+	b->sign = 1;
+	b->flags = MPnorm;
+
+	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;
+	} else {
+		if(b->p == (mpdigit*)&b[1]){
+			b->p = (mpdigit*)mallocz(n*Dbytes, 0);
+			if(b->p == nil)
+				sysfatal("mpbits: %r");
+			memmove(b->p, &b[1], Dbytes*b->top);
+			memset(&b[1], 0, Dbytes*b->size);
+		} else {
+			b->p = (mpdigit*)realloc(b->p, n*Dbytes);
+			if(b->p == nil)
+				sysfatal("mpbits: %r");
+		}
+		b->size = n;
+	}
+	memset(&b->p[b->top], 0, Dbytes*(n - b->top));
+	b->top = n;
+	b->flags &= ~MPnorm;
+}
+
+void
+mpfree(mpint *b)
+{
+	if(b == nil)
+		return;
+	if(b->flags & MPstatic)
+		sysfatal("freeing mp constant");
+	memset(b->p, 0, b->size*Dbytes);
+	if(b->p != (mpdigit*)&b[1])
+		free(b->p);
+	free(b);
+}
+
+mpint*
+mpnorm(mpint *b)
+{
+	int i;
+
+	if(b->flags & MPtimesafe){
+		assert(b->sign == 1);
+		b->flags &= ~MPnorm;
+		return b;
+	}
+	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;
+	b->flags |= MPnorm;
+	return b;
+}
+
+mpint*
+mpcopy(mpint *old)
+{
+	mpint *new;
+
+	new = mpnew(Dbits*old->size);
+	setmalloctag(new, getcallerpc(&old));
+	new->sign = old->sign;
+	new->top = old->top;
+	new->flags = old->flags & ~(MPstatic|MPfield);
+	memmove(new->p, old->p, Dbytes*old->top);
+	return new;
+}
+
+void
+mpassign(mpint *old, mpint *new)
+{
+	if(new == nil || old == new)
+		return;
+	new->top = 0;
+	mpbits(new, Dbits*old->top);
+	new->sign = old->sign;
+	new->top = old->top;
+	new->flags &= ~MPnorm;
+	new->flags |= old->flags & ~(MPstatic|MPfield);
+	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;
+
+	assert(n->flags & MPnorm);
+	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,30 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// return neg, 0, pos as abs(b1)-abs(b2) is neg, 0, pos
+int
+mpmagcmp(mpint *b1, mpint *b2)
+{
+	int i;
+
+	i = b1->flags | b2->flags;
+	if(i & MPtimesafe)
+		return mpvectscmp(b1->p, b1->top, b2->p, b2->top);
+	if(i & MPnorm){
+		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)
+{
+	int sign;
+
+	sign = (b1->sign - b2->sign) >> 1;	// -1, 0, 1
+	return sign | (((sign&1)-1) & mpmagcmp(b1, b2)*b1->sign);
+}
--- /dev/null
+++ b/libmp/mpdigdiv.c
@@ -1,0 +1,56 @@
+#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;
+	}
+
+	// very common case
+	if(~divisor == 0){
+		lo += hi;
+		if(lo < hi){
+			hi++;
+			lo++;
+		}
+		if(lo+1 == 0)
+			hi++;
+		*quotient = hi;
+		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,142 @@
+#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, qsign, rsign;
+	mpdigit qd, *up, *vp, *qp;
+	mpint *u, *v, *t;
+
+	assert(quotient != remainder);
+	assert(divisor->flags & MPnorm);
+
+	// divide bv zero
+	if(divisor->top == 0)
+		abort();
+
+	// division by one or small powers of two
+	if(divisor->top == 1 && (divisor->p[0] & (divisor->p[0]-1)) == 0){
+		vlong r = 0;
+		if(dividend->top > 0)
+			r = (vlong)dividend->sign * (dividend->p[0] & (divisor->p[0]-1));
+		if(quotient != nil){
+			sign = divisor->sign;
+			for(s = 0; ((divisor->p[0] >> s) & 1) == 0; s++)
+				;
+			mpright(dividend, s, quotient);
+			if(sign < 0)
+				quotient->sign ^= (-mpmagcmp(quotient, mpzero) >> 31) << 1;
+		}
+		if(remainder != nil){
+			remainder->flags |= dividend->flags & MPtimesafe;
+			vtomp(r, remainder);
+		}
+		return;
+	}
+	assert((dividend->flags & MPtimesafe) == 0);
+
+	// quick check
+	if(mpmagcmp(dividend, divisor) < 0){
+		if(remainder != nil)
+			mpassign(dividend, remainder);
+		if(quotient != nil)
+			mpassign(mpzero, quotient);
+		return;
+	}
+	
+	qsign = divisor->sign * dividend->sign;
+	rsign = dividend->sign;
+
+	// 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){
+		assert((quotient->flags & MPtimesafe) == 0);
+		mpnorm(quotient);
+		if(quotient->top != 0)
+			quotient->sign = qsign;
+	}
+
+	if(remainder != nil){
+		assert((remainder->flags & MPtimesafe) == 0);
+		mpright(u, s, remainder);	// u is the remainder shifted
+		if(remainder->top != 0)
+			remainder->sign = rsign;
+	}
+
+	mpfree(t);
+	mpfree(u);
+	if(v != divisor)
+		mpfree(v);
+}
--- /dev/null
+++ b/libmp/mpexp.c
@@ -1,0 +1,96 @@
+#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;
+
+	assert(m == nil || m->flags & MPnorm);
+	assert((e->flags & MPtimesafe) == 0);
+	res->flags |= b->flags & MPtimesafe;
+
+	i = mpcmp(e,mpzero);
+	if(i==0){
+		mpassign(mpone, res);
+		return;
+	}
+	if(i<0)
+		sysfatal("mpexp: negative exponent");
+
+	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){
+			if(m != nil)
+				mpmodmul(t[j], t[j], m, t[j^1]);
+			else
+				mpmul(t[j], t[j], t[j^1]);
+			if(bit & d) {
+				if(m != nil)
+					mpmodmul(t[j^1], b, m, t[j]);
+				else
+					mpmul(t[j^1], b, t[j]);
+			} else
+				j ^= 1;
+		}
+		if(--i < 0)
+			break;
+		bit = mpdighi;
+		d = e->p[i];
+	}
+	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,115 @@
+#include "os.h"
+#include <mp.h>
+
+#define iseven(a)	(((a)->p[0] & 1) == 0)
+
+// extended binary gcd
+//
+// For a and 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(v == nil){
+		v = mpnew(0);
+		mpextendedgcd(a, b, v, x, y);
+		mpfree(v);
+		return;
+	}
+	assert(x == nil || (x->flags & MPtimesafe) == 0);
+	assert(y == nil || (y->flags & MPtimesafe) == 0);
+	assert((a->flags&b->flags) & MPnorm);
+	assert(((a->flags|b->flags|v->flags) & MPtimesafe) == 0);
+
+	if(a->sign < 0 || b->sign < 0){
+		mpassign(mpzero, v);
+		mpassign(mpzero, y);
+		mpassign(mpzero, x);
+		return;
+	}
+
+	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/mpfactorial.c
@@ -1,0 +1,74 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+mpint*
+mpfactorial(ulong n)
+{
+	int i;
+	ulong k;
+	unsigned cnt;
+	int max, mmax;
+	mpdigit p, pp[2];
+	mpint *r, *s, *stk[31];
+
+	cnt = 0;
+	max = mmax = -1;
+	p = 1;
+	r = mpnew(0);
+	for(k=2; k<=n; k++){
+		pp[0] = 0;
+		pp[1] = 0;
+		mpvecdigmuladd(&p, 1, (mpdigit)k, pp);
+		if(pp[1] == 0)	/* !overflow */
+			p = pp[0];
+		else{
+			cnt++;
+			if((cnt & 1) == 0){
+				s = stk[max];
+				mpbits(r, Dbits*(s->top+1+1));
+				memset(r->p, 0, Dbytes*(s->top+1+1));
+				mpvecmul(s->p, s->top, &p, 1, r->p);
+				r->sign = 1;
+				r->top = s->top+1+1;		/* XXX: norm */
+				mpassign(r, s);
+				for(i=4; (cnt & (i-1)) == 0; i=i<<1){
+					mpmul(stk[max], stk[max-1], r);
+					mpassign(r, stk[max-1]);
+					max--;
+				}
+			}else{
+				max++;
+				if(max > mmax){
+					mmax++;
+					if(max > nelem(stk))
+						abort();
+					stk[max] = mpnew(Dbits);
+				}
+				stk[max]->top = 1;
+				stk[max]->p[0] = p;
+			}
+			p = (mpdigit)k;
+		}
+	}
+	if(max < 0){
+		mpbits(r, Dbits);
+		r->top = 1;
+		r->sign = 1;
+		r->p[0] = p;
+	}else{
+		s = stk[max--];
+		mpbits(r, Dbits*(s->top+1+1));
+		memset(r->p, 0, Dbytes*(s->top+1+1));
+		mpvecmul(s->p, s->top, &p, 1, r->p);
+		r->sign = 1;
+		r->top = s->top+1+1;		/* XXX: norm */
+	}
+
+	while(max >= 0)
+		mpmul(r, stk[max--], r);
+	for(max=mmax; max>=0; max--)
+		mpfree(stk[max]);
+	mpnorm(r);
+	return r;
+}
--- /dev/null
+++ b/libmp/mpfield.c
@@ -1,0 +1,21 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+mpint*
+mpfield(mpint *N)
+{
+	Mfield *f;
+
+	if(N == nil || N->flags & (MPfield|MPstatic))
+		return N;
+	if((f = cnfield(N)) != nil)
+		goto Exchange;
+	if((f = gmfield(N)) != nil)
+		goto Exchange;
+	return N;
+Exchange:
+	setmalloctag(f, getcallerpc(&N));
+	mpfree(N);
+	return (mpint*)f;
+}
--- /dev/null
+++ b/libmp/mpfmt.c
@@ -1,0 +1,254 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+static int
+toencx(mpint *b, char *buf, int len, int (*enc)(char*, int, uchar*, int))
+{
+	uchar *p;
+	int n, rv;
+
+	p = nil;
+	n = mptobe(b, nil, 0, &p);
+	if(n < 0)
+		return -1;
+	rv = (*enc)(buf, len, p, n);
+	free(p);
+	return rv;
+}
+
+static int
+topow2(mpint *b, char *buf, int len, int s)
+{
+	mpdigit *p, x;
+	int i, j, sn;
+	char *out, *eout;
+
+	if(len < 1)
+		return -1;
+
+	sn = 1<<s;
+	out = buf;
+	eout = buf+len;
+	for(p = &b->p[b->top-1]; p >= b->p; p--){
+		x = *p;
+		for(i = Dbits-s; i >= 0; i -= s){
+			j = (x >> i) & (sn - 1);
+			if(j != 0 || out != buf){
+				if(out >= eout)
+					return -1;
+				*out++ = enc16chr(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);
+	d->flags &= ~MPtimesafe;
+	mpnorm(d);
+	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;
+}
+
+static int
+to8(mpint *b, char *buf, int len)
+{
+	mpdigit x, y;
+	char *out;
+	int i, j;
+
+	if(len < 2)
+		return -1;
+
+	out = buf+len;
+	*--out = 0;
+
+	i = j = 0;
+	x = y = 0;
+	while(j < b->top){
+		y = b->p[j++];
+		if(i > 0)
+			x |= y << i;
+		else
+			x = y;
+		i += Dbits;
+		while(i >= 3){
+Digout:			i -= 3;
+			if(out > buf)
+				out--;
+			else if(x != 0)
+				return -1;
+			*out = '0' + (x & 7);
+			x = y >> (Dbits-i);
+		}
+	}
+	if(i > 0)
+		goto Digout;
+
+	while(*out == '0') out++;
+	if(*out == '\0')
+		*--out = '0';
+
+	len -= out-buf;
+	if(out != buf)
+		memmove(buf, out, len);
+	return 0;
+}
+
+int
+mpfmt(Fmt *fmt)
+{
+	mpint *b;
+	char *x, *p;
+	int base;
+
+	b = va_arg(fmt->args, mpint*);
+	if(b == nil)
+		return fmtstrcpy(fmt, "*");
+
+	base = fmt->prec;
+	if(base == 0)
+		base = 16;	/* default */
+	fmt->flags &= ~FmtPrec;
+	p = mptoa(b, base, nil, 0);
+	if(p == nil)
+		return fmtstrcpy(fmt, "*");
+	else{
+		if((fmt->flags & FmtSharp) != 0){
+			switch(base){
+			case 16:
+				x = "0x";
+				break;
+			case 8:
+				x = "0";
+				break;
+			case 2:
+				x = "0b";
+				break;
+			default:
+				x = "";
+			}
+			if(*p == '-')
+				fmtprint(fmt, "-%s%s", x, p + 1);
+			else
+				fmtprint(fmt, "%s%s", x, p);
+		}
+		else
+			fmtstrcpy(fmt, p);
+		free(p);
+		return 0;
+	}
+}
+
+char*
+mptoa(mpint *b, int base, char *buf, int len)
+{
+	char *out;
+	int rv, alloced;
+
+	if(base == 0)
+		base = 16;	/* default */
+	alloced = 0;
+	if(buf == nil){
+		/* rv <= log₂(base) */
+		for(rv=1; (base >> rv) > 1; rv++)
+			;
+		len = 10 + (b->top*Dbits / rv);
+		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 = toencx(b, out, len, enc64);
+		break;
+	case 32:
+		rv = toencx(b, out, len, enc32);
+		break;
+	case 16:
+		rv = topow2(b, out, len, 4);
+		break;
+	case 10:
+		rv = to10(b, out, len);
+		break;
+	case 8:
+		rv = to8(b, out, len);
+		break;
+	case 4:
+		rv = topow2(b, out, len, 2);
+		break;
+	case 2:
+		rv = topow2(b, out, len, 1);
+		break;
+	default:
+		abort();
+		return nil;
+	}
+	if(rv < 0){
+		if(alloced)
+			free(buf);
+		return nil;
+	}
+	return buf;
+}
--- /dev/null
+++ b/libmp/mpinvert.c
@@ -1,0 +1,17 @@
+#include "os.h"
+#include <mp.h>
+
+// use extended gcd to find the multiplicative inverse
+// res = b**-1 mod m
+void
+mpinvert(mpint *b, mpint *m, mpint *res)
+{
+	mpint *v;
+
+	v = mpnew(0);
+	mpextendedgcd(b, m, v, res, nil);
+	if(mpcmp(v, mpone) != 0)
+		abort();
+	mpfree(v);
+	mpmod(res, m, res);
+}
--- /dev/null
+++ b/libmp/mpleft.c
@@ -1,0 +1,51 @@
+#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;
+
+	res->sign = b->sign;
+	if(b->top==0){
+		res->top = 0;
+		return;
+	}
+
+	// a zero or 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;
+
+	res->flags |= b->flags & MPtimesafe;
+	mpnorm(res);
+}
--- /dev/null
+++ b/libmp/mplogic.c
@@ -1,0 +1,212 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+/*
+	mplogic calculates b1|b2 subject to the
+	following flag bits (fl)
+
+	bit 0: subtract 1 from b1
+	bit 1: invert b1
+	bit 2: subtract 1 from b2
+	bit 3: invert b2
+	bit 4: add 1 to output
+	bit 5: invert output
+	
+	it inverts appropriate bits automatically
+	depending on the signs of the inputs
+*/
+
+static void
+mplogic(mpint *b1, mpint *b2, mpint *sum, int fl)
+{
+	mpint *t;
+	mpdigit *dp1, *dp2, *dpo, d1, d2, d;
+	int c1, c2, co;
+	int i;
+
+	assert(((b1->flags | b2->flags | sum->flags) & MPtimesafe) == 0);
+	if(b1->sign < 0) fl ^= 0x03;
+	if(b2->sign < 0) fl ^= 0x0c;
+	sum->sign = (int)(((fl|fl>>2)^fl>>4)<<30)>>31|1;
+	if(sum->sign < 0) fl ^= 0x30;
+	if(b2->top > b1->top){
+		t = b1;
+		b1 = b2;
+		b2 = t;
+		fl = ((fl >> 2) & 0x03) | ((fl << 2) & 0x0c) | (fl & 0x30);
+	}
+	mpbits(sum, b1->top*Dbits+1);
+	dp1 = b1->p;
+	dp2 = b2->p;
+	dpo = sum->p;
+	c1 = fl & 1;
+	c2 = fl >> 2 & 1;
+	co = fl >> 4 & 1;
+	for(i = 0; i < b1->top; i++){
+		d1 = dp1[i] - c1;
+		if(i < b2->top)
+			d2 = dp2[i] - c2;
+		else
+			d2 = 0;
+		if(d1 != (mpdigit)-1) c1 = 0;
+		if(d2 != (mpdigit)-1) c2 = 0;
+		if((fl & 2) != 0) d1 ^= -1;
+		if((fl & 8) != 0) d2 ^= -1;
+		d = d1 | d2;
+		if((fl & 32) != 0) d ^= -1;
+		d += co;
+		if(d != 0) co = 0;
+		dpo[i] = d;
+	}
+	sum->top = i;
+	if(co)
+		dpo[sum->top++] = co;
+	mpnorm(sum);
+}
+
+void
+mpor(mpint *b1, mpint *b2, mpint *sum)
+{
+	mplogic(b1, b2, sum, 0);
+}
+
+void
+mpand(mpint *b1, mpint *b2, mpint *sum)
+{
+	mplogic(b1, b2, sum, 0x2a);
+}
+
+void
+mpbic(mpint *b1, mpint *b2, mpint *sum)
+{
+	mplogic(b1, b2, sum, 0x22);
+}
+
+void
+mpnot(mpint *b, mpint *r)
+{
+	mpadd(b, mpone, r);
+	if(r->top != 0)
+		r->sign ^= -2;
+}
+
+void
+mpxor(mpint *b1, mpint *b2, mpint *sum)
+{
+	mpint *t;
+	mpdigit *dp1, *dp2, *dpo, d1, d2, d;
+	int c1, c2, co;
+	int i, fl;
+
+	assert(((b1->flags | b2->flags | sum->flags) & MPtimesafe) == 0);
+	if(b2->top > b1->top){
+		t = b1;
+		b1 = b2;
+		b2 = t;
+	}
+	fl = (b1->sign & 10) ^ (b2->sign & 12);
+	sum->sign = (int)(fl << 28) >> 31 | 1;
+	mpbits(sum, b1->top*Dbits+1);
+	dp1 = b1->p;
+	dp2 = b2->p;
+	dpo = sum->p;
+	c1 = fl >> 1 & 1;
+	c2 = fl >> 2 & 1;
+	co = fl >> 3 & 1;
+	for(i = 0; i < b1->top; i++){
+		d1 = dp1[i] - c1;
+		if(i < b2->top)
+			d2 = dp2[i] - c2;
+		else
+			d2 = 0;
+		if(d1 != (mpdigit)-1) c1 = 0;
+		if(d2 != (mpdigit)-1) c2 = 0;
+		d = d1 ^ d2;
+		d += co;
+		if(d != 0) co = 0;
+		dpo[i] = d;
+	}
+	sum->top = i;
+	if(co)
+		dpo[sum->top++] = co;
+	mpnorm(sum);
+}
+
+void
+mptrunc(mpint *b, int n, mpint *r)
+{
+	int d, m, i, c;
+
+	assert(((b->flags | r->flags) & MPtimesafe) == 0);
+	mpbits(r, n);
+	r->top = DIGITS(n);
+	d = n / Dbits;
+	m = n % Dbits;
+	if(b->sign == -1){
+		c = 1;
+		for(i = 0; i < r->top; i++){
+			if(i < b->top)
+				r->p[i] = ~(b->p[i] - c);
+			else
+				r->p[i] = -1;
+			if(r->p[i] != 0)
+				c = 0;
+		}
+		if(m != 0)
+			r->p[d] &= (1<<m) - 1;
+	}else if(b->sign == 1){
+		if(d >= b->top){
+			mpassign(b, r);
+			mpnorm(r);
+			return;
+		}
+		if(b != r)
+			for(i = 0; i < d; i++)
+				r->p[i] = b->p[i];
+		if(m != 0)
+			r->p[d] = b->p[d] & ((1<<m)-1);
+	}
+	r->sign = 1;
+	mpnorm(r);
+}
+
+void
+mpxtend(mpint *b, int n, mpint *r)
+{
+	int d, m, c, i;
+
+	d = (n - 1) / Dbits;
+	m = (n - 1) % Dbits;
+	if(d >= b->top){
+		mpassign(b, r);
+		return;
+	}
+	mptrunc(b, n, r);
+	mpbits(r, n);
+	if((r->p[d] & 1<<m) == 0){
+		mpnorm(r);
+		return;
+	}
+	r->p[d] |= -(1<<m);
+	r->sign = -1;
+	c = 1;
+	for(i = 0; i < r->top; i++){
+		r->p[i] = ~(r->p[i] - c);
+		if(r->p[i] != 0)
+			c = 0;
+	}
+	mpnorm(r);
+}
+
+void
+mpasr(mpint *b, int n, mpint *r)
+{
+	if(b->sign > 0 || n <= 0){
+		mpright(b, n, r);
+		return;
+	}
+	mpadd(b, mpone, r);
+	mpright(r, n, r);
+	mpsub(r, mpone, r);
+}
--- /dev/null
+++ b/libmp/mpmod.c
@@ -1,0 +1,20 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+void
+mpmod(mpint *x, mpint *n, mpint *r)
+{
+	int sign;
+	mpint *ns;
+
+	sign = x->sign;
+	ns = sign < 0 && n == r ? mpcopy(n) : n;
+	if((n->flags & MPfield) == 0
+	|| ((Mfield*)n)->reduce((Mfield*)n, x, r) != 0)
+		mpdiv(x, n, nil, r);
+	if(sign < 0){
+		mpmagsub(ns, r, r);
+		if(ns != n) mpfree(ns);
+	}
+}
--- /dev/null
+++ b/libmp/mpmodop.c
@@ -1,0 +1,95 @@
+#include "os.h"
+#include <mp.h>
+
+/* operands need to have m->top+1 digits of space and satisfy 0 ≤ a ≤ m-1 */
+static mpint*
+modarg(mpint *a, mpint *m)
+{
+	if(a->size <= m->top || a->sign < 0 || mpmagcmp(a, m) >= 0){
+		a = mpcopy(a);
+		mpmod(a, m, a);
+		mpbits(a, Dbits*(m->top+1));
+		a->top = m->top;
+	} else if(a->top < m->top){
+		memset(&a->p[a->top], 0, (m->top - a->top)*Dbytes);
+	}
+	return a;
+}
+
+void
+mpmodadd(mpint *b1, mpint *b2, mpint *m, mpint *sum)
+{
+	mpint *a, *b;
+	mpdigit d;
+	int i, j;
+
+	a = modarg(b1, m);
+	b = modarg(b2, m);
+
+	sum->flags |= (a->flags | b->flags) & MPtimesafe;
+	mpbits(sum, Dbits*2*(m->top+1));
+
+	mpvecadd(a->p, m->top, b->p, m->top, sum->p);
+	mpvecsub(sum->p, m->top+1, m->p, m->top, sum->p+m->top+1);
+
+	d = sum->p[2*m->top+1];
+	for(i = 0, j = m->top+1; i < m->top; i++, j++)
+		sum->p[i] = (sum->p[i] & d) | (sum->p[j] & ~d);
+
+	sum->top = m->top;
+	sum->sign = 1;
+	mpnorm(sum);
+
+	if(a != b1)
+		mpfree(a);
+	if(b != b2)
+		mpfree(b);
+}
+
+void
+mpmodsub(mpint *b1, mpint *b2, mpint *m, mpint *diff)
+{
+	mpint *a, *b;
+	mpdigit d;
+	int i, j;
+
+	a = modarg(b1, m);
+	b = modarg(b2, m);
+
+	diff->flags |= (a->flags | b->flags) & MPtimesafe;
+	mpbits(diff, Dbits*2*(m->top+1));
+
+	a->p[m->top] = 0;
+	mpvecsub(a->p, m->top+1, b->p, m->top, diff->p);
+	mpvecadd(diff->p, m->top, m->p, m->top, diff->p+m->top+1);
+
+	d = ~diff->p[m->top];
+	for(i = 0, j = m->top+1; i < m->top; i++, j++)
+		diff->p[i] = (diff->p[i] & d) | (diff->p[j] & ~d);
+
+	diff->top = m->top;
+	diff->sign = 1;
+	mpnorm(diff);
+
+	if(a != b1)
+		mpfree(a);
+	if(b != b2)
+		mpfree(b);
+}
+
+void
+mpmodmul(mpint *b1, mpint *b2, mpint *m, mpint *prod)
+{
+	mpint *a, *b;
+
+	a = modarg(b1, m);
+	b = modarg(b2, m);
+
+	mpmul(a, b, prod);
+	mpmod(prod, m, prod);
+
+	if(a != b1)
+		mpfree(a);
+	if(b != b2)
+		mpfree(b);
+}
--- /dev/null
+++ b/libmp/mpmul.c
@@ -1,0 +1,176 @@
+#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(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
+mpvectsmul(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *p)
+{
+	int i;
+	mpdigit *t;
+
+	if(alen < blen){
+		i = alen;
+		alen = blen;
+		blen = i;
+		t = a;
+		a = b;
+		b = t;
+	}
+	if(blen == 0)
+		return;
+	for(i = 0; i < blen; i++)
+		mpvecdigmuladd(a, alen, b[i], &p[i]);
+}
+
+void
+mpmul(mpint *b1, mpint *b2, mpint *prod)
+{
+	mpint *oprod;
+
+	oprod = prod;
+	if(prod == b1 || prod == b2){
+		prod = mpnew(0);
+		prod->flags = oprod->flags;
+	}
+	prod->flags |= (b1->flags | b2->flags) & MPtimesafe;
+
+	prod->top = 0;
+	mpbits(prod, (b1->top+b2->top+1)*Dbits);
+	if(prod->flags & MPtimesafe)
+		mpvectsmul(b1->p, b1->top, b2->p, b2->top, prod->p);
+	else
+		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 != prod){
+		mpassign(prod, oprod);
+		mpfree(prod);
+	}
+}
--- /dev/null
+++ b/libmp/mpnrand.c
@@ -1,0 +1,23 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+/* return uniform random [0..n-1] */
+mpint*
+mpnrand(mpint *n, void (*gen)(uchar*, int), mpint *b)
+{
+	int bits;
+
+	bits = mpsignif(n);
+	if(bits == 0)
+		abort();
+	if(b == nil){
+		b = mpnew(bits);
+		setmalloctag(b, getcallerpc(&n));
+	}
+	do {
+		mprand(bits, gen, b);
+	} while(mpmagcmp(b, n) >= 0);
+
+	return b;
+}
--- /dev/null
+++ b/libmp/mprand.c
@@ -1,0 +1,25 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+mpint*
+mprand(int bits, void (*gen)(uchar*, int), mpint *b)
+{
+	mpdigit mask;
+
+	if(b == nil){
+		b = mpnew(bits);
+		setmalloctag(b, getcallerpc(&bits));
+	}else
+		mpbits(b, bits);
+
+	b->sign = 1;
+	b->top = DIGITS(bits);
+	(*gen)((uchar*)b->p, b->top*Dbytes);
+
+	mask = ((mpdigit)1 << (bits%Dbits))-1;
+	if(mask != 0)
+		b->p[b->top-1] &= mask;
+
+	return mpnorm(b);
+}
--- /dev/null
+++ b/libmp/mpright.c
@@ -1,0 +1,57 @@
+#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;
+
+	res->sign = b->sign;
+	if(b->top==0){
+		res->top = 0;
+		return;
+	}
+
+	// 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);
+	else if(shift == 0)
+		return;
+
+	d = shift/Dbits;
+	r = shift - d*Dbits;
+	l = Dbits - r;
+
+	//  shift all the bits out == zero
+	if(d>=b->top){
+		res->sign = 1;
+		res->top = 0;
+		return;
+	}
+
+	// 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;
+	}
+
+	res->top = i;
+	res->flags |= b->flags & MPtimesafe;
+	mpnorm(res);
+}
--- /dev/null
+++ b/libmp/mpsel.c
@@ -1,0 +1,42 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// res = s != 0 ? b1 : b2
+void
+mpsel(int s, mpint *b1, mpint *b2, mpint *res)
+{
+	mpdigit d;
+	int n, m, i;
+
+	res->flags |= (b1->flags | b2->flags) & MPtimesafe;
+	if((res->flags & MPtimesafe) == 0){
+		mpassign(s ? b1 : b2, res);
+		return;
+	}
+	res->flags &= ~MPnorm;
+
+	n = b1->top;
+	m = b2->top;
+	mpbits(res, Dbits*(n >= m ? n : m));
+	res->top = n >= m ? n : m;
+
+	s = ((-s^s)|s)>>(sizeof(s)*8-1);
+	res->sign = (b1->sign & s) | (b2->sign & ~s);
+
+	d = -((mpdigit)s & 1);
+
+	i = 0;
+	while(i < n && i < m){
+		res->p[i] = (b1->p[i] & d) | (b2->p[i] & ~d);
+		i++;
+	}
+	while(i < n){
+		res->p[i] = b1->p[i] & d;
+		i++;
+	}
+	while(i < m){
+		res->p[i] = b2->p[i] & ~d;
+		i++;
+	}
+}
--- /dev/null
+++ b/libmp/mpsub.c
@@ -1,0 +1,56 @@
+#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){
+		assert(((b1->flags | b2->flags | diff->flags) & MPtimesafe) == 0);
+		sign = -1;
+		t = b1;
+		b1 = b2;
+		b2 = t;
+	} else {
+		diff->flags |= (b1->flags | b2->flags) & MPtimesafe;
+		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){
+		assert(((b1->flags | b2->flags | diff->flags) & MPtimesafe) == 0);
+		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,32 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+// convert an mpint into a big endian byte array (most significant byte first; left adjusted)
+//   return number of bytes converted
+//   if p == nil, allocate and result array
+int
+mptobe(mpint *b, uchar *p, uint n, uchar **pp)
+{
+	int m;
+
+	m = (mpsignif(b)+7)/8;
+	if(m == 0)
+		m++;
+	if(p == nil){
+		n = m;
+		p = malloc(n);
+		if(p == nil)
+			sysfatal("mptobe: %r");
+		setmalloctag(p, getcallerpc(&b));
+	} else {
+		if(n < m)
+			return -1;
+		if(n > m)
+			memset(p+m, 0, n-m);
+	}
+	if(pp != nil)
+		*pp = p;
+	mptober(b, p, m);
+	return m;
+}
--- /dev/null
+++ b/libmp/mptober.c
@@ -1,0 +1,34 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+void
+mptober(mpint *b, uchar *p, int n)
+{
+	int i, j, m;
+	mpdigit x;
+
+	memset(p, 0, n);
+
+	p += n;
+	m = b->top*Dbytes;
+	if(m < n)
+		n = m;
+
+	i = 0;
+	while(n >= Dbytes){
+		n -= Dbytes;
+		x = b->p[i++];
+		for(j = 0; j < Dbytes; j++){
+			*--p = x;
+			x >>= 8;
+		}
+	}
+	if(n > 0){
+		x = b->p[i];
+		for(j = 0; j < n; j++){
+			*--p = x;
+			x >>= 8;
+		}
+	}
+}
--- /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);
+		setmalloctag(b, getcallerpc(&i));
+	}
+	b->sign = (i >> (sizeof(i)*8 - 1)) | 1;
+	i *= b->sign;
+	*b->p = i;
+	b->top = 1;
+	return mpnorm(b);
+}
+
+int
+mptoi(mpint *b)
+{
+	uint x;
+
+	if(b->top==0)
+		return 0;
+	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,28 @@
+#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 m;
+
+	m = (mpsignif(b)+7)/8;
+	if(m == 0)
+		m++;
+	if(p == nil){
+		n = m;
+		p = malloc(n);
+		if(p == nil)
+			sysfatal("mptole: %r");
+		setmalloctag(p, getcallerpc(&b));
+	} else if(n < m)
+		return -1;
+	if(pp != nil)
+		*pp = p;
+	mptolel(b, p, n);
+	return m;
+}
--- /dev/null
+++ b/libmp/mptolel.c
@@ -1,0 +1,33 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+void
+mptolel(mpint *b, uchar *p, int n)
+{
+	int i, j, m;
+	mpdigit x;
+
+	memset(p, 0, n);
+
+	m = b->top*Dbytes;
+	if(m < n)
+		n = m;
+
+	i = 0;
+	while(n >= Dbytes){
+		n -= Dbytes;
+		x = b->p[i++];
+		for(j = 0; j < Dbytes; j++){
+			*p++ = x;
+			x >>= 8;
+		}
+	}
+	if(n > 0){
+		x = b->p[i];
+		for(j = 0; j < n; j++){
+			*p++ = x;
+			x >>= 8;
+		}
+	}
+}
--- /dev/null
+++ b/libmp/mptoui.c
@@ -1,0 +1,34 @@
+#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);
+		setmalloctag(b, getcallerpc(&i));
+	}
+	*b->p = i;
+	b->top = 1;
+	b->sign = 1;
+	return mpnorm(b);
+}
+
+uint
+mptoui(mpint *b)
+{
+	uint x;
+
+	x = *b->p;
+	if(b->sign < 0)
+		x = 0;
+	else if(b->top > 1 || (sizeof(mpdigit) > sizeof(uint) && x > MAXUINT))
+		x =  MAXUINT;
+	return x;
+}
--- /dev/null
+++ b/libmp/mptouv.c
@@ -1,0 +1,47 @@
+#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*Dbits);
+		setmalloctag(b, getcallerpc(&v));
+	}else
+		mpbits(b, VLDIGITS*Dbits);
+	b->sign = 1;
+	for(s = 0; s < VLDIGITS; s++){
+		b->p[s] = v;
+		v >>= sizeof(mpdigit)*8;
+	}
+	b->top = s;
+	return mpnorm(b);
+}
+
+uvlong
+mptouv(mpint *b)
+{
+	uvlong v;
+	int s;
+
+	if(b->top == 0 || b->sign < 0)
+		return 0LL;
+
+	if(b->top > VLDIGITS)
+		return -1LL;
+
+	v = 0ULL;
+	for(s = 0; s < b->top; s++)
+		v |= (uvlong)b->p[s]<<(s*sizeof(mpdigit)*8);
+
+	return v;
+}
--- /dev/null
+++ b/libmp/mptov.c
@@ -1,0 +1,63 @@
+#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*Dbits);
+		setmalloctag(b, getcallerpc(&v));
+	}else
+		mpbits(b, VLDIGITS*Dbits);
+	b->sign = (v >> (sizeof(v)*8 - 1)) | 1;
+	uv = v * b->sign;
+	for(s = 0; s < VLDIGITS; s++){
+		b->p[s] = uv;
+		uv >>= sizeof(mpdigit)*8;
+	}
+	b->top = s;
+	return mpnorm(b);
+}
+
+vlong
+mptov(mpint *b)
+{
+	uvlong v;
+	int s;
+
+	if(b->top == 0)
+		return 0LL;
+
+	if(b->top > VLDIGITS){
+		if(b->sign > 0)
+			return (vlong)MAXVLONG;
+		else
+			return (vlong)MINVLONG;
+	}
+
+	v = 0ULL;
+	for(s = 0; s < b->top; s++)
+		v |= (uvlong)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,27 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+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/mpvectscmp.c
@@ -1,0 +1,34 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+int
+mpvectscmp(mpdigit *a, int alen, mpdigit *b, int blen)
+{
+	mpdigit x, y, z, v;
+	int m, p;
+
+	if(alen > blen){
+		v = 0;
+		while(alen > blen)
+			v |= a[--alen];
+		m = p = ((-v^v)|v)>>(Dbits-1);
+	} else if(blen > alen){
+		v = 0;
+		while(blen > alen)
+			v |= b[--blen];
+		m = ((-v^v)|v)>>(Dbits-1);
+		p = m^1;
+	} else
+		m = p = 0;
+	while(alen-- > 0){
+		x = a[alen];
+		y = b[alen];
+		z = x - y;
+		x = ~x;
+		v = (((-z^z)|z)>>(Dbits-1)) & ~m;
+		p = ((~((x&y)|(x&z)|(y&z))>>(Dbits-1)) & v) | (p & ~v);
+		m |= v;
+	}
+	return (p-m) | m;
+}
--- /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,206 @@
+#include "os.h"
+#include <mp.h>
+#include "dat.h"
+
+static char*
+frompow2(char *a, mpint *b, int s)
+{
+	char *p, *next;
+	mpdigit x;
+	int i;
+
+	i = 1<<s;
+	for(p = a; (dec16chr(*p) & 255) < i; p++)
+		;
+
+	mpbits(b, (p-a)*s);
+	b->top = 0;
+	next = p;
+
+	while(p > a){
+		x = 0;
+		for(i = 0; i < Dbits; i += s){
+			if(p <= a)
+				break;
+			x |= dec16chr(*--p)<<i;
+		}
+		b->p[b->top++] = x;
+	}
+	return next;
+}
+
+static char*
+from8(char *a, mpint *b)
+{
+	char *p, *next;
+	mpdigit x, y;
+	int i;
+
+	for(p = a; ((*p - '0') & 255) < 8; p++)
+		;
+
+	mpbits(b, (p-a)*3);
+	b->top = 0;
+	next = p;
+
+	i = 0;
+	x = y = 0;
+	while(p > a){
+		y = *--p - '0';
+		x |= y << i;
+		i += 3;
+		if(i >= Dbits){
+Digout:
+			i -= Dbits;
+			b->p[b->top++] = x;
+			x = y >> (3-i);
+		}
+	}
+	if(i > 0)
+		goto Digout;
+
+	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 = *a - '0';
+			if(y > 9)
+				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*
+fromdecx(char *a, mpint *b, int (*chr)(int), int (*dec)(uchar*, int, char*, int))
+{
+	char *buf = a;
+	uchar *p;
+	int n, m;
+
+	b->top = 0;
+	for(; (*chr)(*a) >= 0; a++)
+		;
+	n = a-buf;
+	if(n > 0){
+		p = malloc(n);
+		if(p == nil)
+			sysfatal("malloc: %r");
+		m = (*dec)(p, n, buf, n);
+		if(m > 0)
+			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);
+		setmalloctag(b, getcallerpc(&a));
+	}
+
+	while(*a==' ' || *a=='\t')
+		a++;
+
+	sign = 1;
+	for(;; a++){
+		switch(*a){
+		case '-':
+			sign *= -1;
+			continue;
+		}
+		break;
+	}
+
+	if(base == 0){
+		base = 10;
+		if(a[0] == '0'){
+			if(a[1] == 'x' || a[1] == 'X') {
+				a += 2;
+				base = 16;
+			} else if(a[1] == 'b' || a[1] == 'B') {
+				a += 2;
+				base = 2;
+			} else if(a[1] >= '0' && a[1] <= '7') {
+				a++;
+				base = 8;
+			}
+		}
+	}
+
+	switch(base){
+	case 2:
+		e = frompow2(a, b, 1);
+		break;
+	case 4:
+		e = frompow2(a, b, 2);
+		break;
+	case 8:
+		e = from8(a, b);
+		break;
+	case 10:
+		e = from10(a, b);
+		break;
+	case 16:
+		e = frompow2(a, b, 4);
+		break;
+	case 32:
+		e = fromdecx(a, b, dec32chr, dec32);
+		break;
+	case 64:
+		e = fromdecx(a, b, dec64chr, dec64);
+		break;
+	default:
+		abort();
+		return nil;
+	}
+
+	if(pp != nil)
+		*pp = e;
+
+	// if no characters parsed, there wasn't a number to convert
+	if(e == a)
+		return nil;
+
+	b->sign = sign;
+	return mpnorm(b);
+}
--- /dev/null
+++ b/librc/Makefile
@@ -1,0 +1,30 @@
+ROOT=..
+include ../Make.config
+
+LIB=librc.a
+OFILES=\
+	code.$O\
+	exec.$O\
+	getflags.$O\
+	glob.$O\
+	here.$O\
+	io.$O\
+	lex.$O\
+	pcmd.$O\
+	pfnc.$O\
+	simple.$O\
+	subr.$O\
+	trap.$O\
+	tree.$O\
+	var.$O\
+	havefork.$O\
+	unix.$O\
+	y.tab.c
+
+default: $(LIB)
+$(LIB): $(OFILES) $(YFILES)
+	$(AR) r $(LIB) $(OFILES) $(YFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
--- /dev/null
+++ b/librc/code.c
@@ -1,0 +1,547 @@
+#include "rc.h"
+#include "io.h"
+#include "exec.h"
+#include "fns.h"
+#include "getflags.h"
+#define	c0	t->child[0]
+#define	c1	t->child[1]
+#define	c2	t->child[2]
+code *codebuf;
+static int codep, ncode, codeline;
+#define	emitf(x) ((codep!=ncode || morecode()), codebuf[codep].f = (x), codep++)
+#define	emiti(x) ((codep!=ncode || morecode()), codebuf[codep].i = (x), codep++)
+#define	emits(x) ((codep!=ncode || morecode()), codebuf[codep].s = (x), codep++)
+
+void stuffdot(int);
+void outcode(tree*, int);
+void codeswitch(tree*, int);
+int iscase(tree*);
+code *codecopy(code*);
+void codefree(code*);
+
+int
+morecode(void)
+{
+	ncode+=ncode;
+	codebuf = (code *)erealloc((char *)codebuf, ncode*sizeof codebuf[0]);
+	return 0;
+}
+
+void
+stuffdot(int a)
+{
+	if(a<0 || codep<=a)
+		panic("Bad address %d in stuffdot", a);
+	codebuf[a].i = codep;
+}
+
+int
+compile(tree *t)
+{
+	ncode = 100;
+	codebuf = emalloc(ncode*sizeof codebuf[0]);
+	codep = 0;
+	codeline = 0;			/* force source */
+	emiti(0);			/* reference count */
+	emits(estrdup(lex->file));	/* source file name */
+	outcode(t, !lex->qflag && flag['e']!=0);
+	if(nerror){
+		free(codebuf);
+		return 0;
+	}
+	emitf(Xreturn);
+	emitf(0);
+	return 1;
+}
+
+/*
+ * called on a tree where we expect eigther
+ * a pattern or a string instead of a glob to
+ * remove the GLOB chars from the strings
+ * or set glob to 2 for pattern so Xglob
+ * is not inserted when compiling the tree.
+ */
+void
+noglobs(tree *t, int pattern)
+{
+Again:
+	if(t==0)
+		return;
+	if(t->type==WORD && t->glob){
+		if(pattern)
+			t->glob=2;
+		else{
+			deglob(t->str);
+			t->glob=0;
+		}
+	}
+	if(t->type==PAREN || t->type==WORDS || t->type=='^'){
+		t->glob=0;
+		noglobs(c1, pattern);
+		t = c0;
+		goto Again;
+	}
+}
+
+void
+outcode(tree *t, int eflag)
+{
+	void (*f)(void);
+	int p, q;
+	tree *tt;
+	if(t==0)
+		return;
+	if(t->type!=NOT && t->type!=';')
+		lex->iflast = 0;
+	if(t->line != codeline){
+		codeline = t->line;
+		if(codebuf && codep >= 2 && codebuf[codep-2].f == Xsrcline)
+			codebuf[codep-1].i = codeline;
+		else {
+			emitf(Xsrcline);
+			emiti(codeline);
+		}
+	}
+	switch(t->type){
+	default:
+		pfmt(err, "bad type %d in outcode\n", t->type);
+		break;
+	case '$':
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xdol);
+		break;
+	case '"':
+		emitf(Xmark);
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xdol);
+		emitf(Xqw);
+		emitf(Xpush);
+		break;
+	case SUB:
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xmark);
+		noglobs(c1, 0);
+		outcode(c1, eflag);
+		emitf(Xsub);
+		break;
+	case '&':
+		emitf(Xasync);
+		p = emiti(0);
+
+		/* undocumented? */
+		emitf(Xmark);
+		emitf(Xword);
+		emits(estrdup("/dev/null"));
+		emitf(Xread);
+		emiti(0);
+
+		/* insert rfork s for plan9 */
+		f = builtinfunc("rfork");
+		if(f){
+			emitf(Xmark);
+			emitf(Xword);
+			emits(estrdup("s"));
+			emitf(Xword);
+			emits(estrdup("rfork"));
+			emitf(f);
+		}
+
+		codeline = 0;	/* force source */
+		outcode(c0, eflag);
+		emitf(Xexit);
+		stuffdot(p);
+		break;
+	case ';':
+		outcode(c0, eflag);
+		outcode(c1, eflag);
+		break;
+	case '^':
+		emitf(Xmark);
+		outcode(c1, eflag);
+		emitf(Xmark);
+		outcode(c0, eflag);
+		emitf(Xconc);
+		break;
+	case '`':
+		emitf(Xmark);
+		if(c0){
+			noglobs(c0, 0);
+			outcode(c0, 0);
+		} else {
+			emitf(Xmark);
+			emitf(Xword);
+			emits(estrdup("ifs"));
+			emitf(Xdol);
+		}
+		emitf(Xqw);
+		emitf(Xbackq);
+		p = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c1, 0);
+		emitf(Xexit);
+		stuffdot(p);
+		break;
+	case ANDAND:
+		outcode(c0, 0);
+		emitf(Xtrue);
+		p = emiti(0);
+		outcode(c1, eflag);
+		stuffdot(p);
+		break;
+	case ARGLIST:
+		outcode(c1, eflag);
+		outcode(c0, eflag);
+		break;
+	case BANG:
+		outcode(c0, eflag);
+		emitf(Xbang);
+		break;
+	case PCMD:
+	case BRACE:
+		outcode(c0, eflag);
+		break;
+	case COUNT:
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xcount);
+		break;
+	case FN:
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		if(c1){
+			emitf(Xfn);
+			p = emiti(0);
+			emits(fnstr(c1));
+			codeline = 0;	/* force source */
+			outcode(c1, eflag);
+			emitf(Xreturn);
+			stuffdot(p);
+		}
+		else
+			emitf(Xdelfn);
+		break;
+	case IF:
+		outcode(c0, 0);
+		emitf(Xif);
+		p = emiti(0);
+		outcode(c1, eflag);
+		emitf(Xwastrue);
+		stuffdot(p);
+		break;
+	case NOT:
+		if(!lex->iflast)
+			yyerror("`if not' does not follow `if(...)'");
+		emitf(Xifnot);
+		p = emiti(0);
+		outcode(c0, eflag);
+		stuffdot(p);
+		break;
+	case OROR:
+		outcode(c0, 0);
+		emitf(Xfalse);
+		p = emiti(0);
+		outcode(c1, eflag);
+		stuffdot(p);
+		break;
+	case PAREN:
+		outcode(c0, eflag);
+		break;
+	case SIMPLE:
+		emitf(Xmark);
+		outcode(c0, eflag);
+		emitf(Xsimple);
+		if(eflag)
+			emitf(Xeflag);
+		break;
+	case SUBSHELL:
+		emitf(Xsubshell);
+		p = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c0, eflag);
+		emitf(Xexit);
+		stuffdot(p);
+		if(eflag)
+			emitf(Xeflag);
+		break;
+	case SWITCH:
+		codeswitch(t, eflag);
+		break;
+	case TWIDDLE:
+		emitf(Xmark);
+		noglobs(c1, 1);
+		outcode(c1, eflag);
+		emitf(Xmark);
+		outcode(c0, eflag);
+		emitf(Xqw);
+		emitf(Xmatch);
+		if(eflag)
+			emitf(Xeflag);
+		break;
+	case WHILE:
+		q = codep;
+		outcode(c0, 0);
+		if(q==codep)
+			emitf(Xsettrue);	/* empty condition == while(true) */
+		emitf(Xtrue);
+		p = emiti(0);
+		outcode(c1, eflag);
+		emitf(Xjump);
+		emiti(q);
+		stuffdot(p);
+		break;
+	case WORDS:
+		outcode(c1, eflag);
+		outcode(c0, eflag);
+		break;
+	case FOR:
+		emitf(Xmark);
+		if(c1){
+			outcode(c1, eflag);
+		}
+		else{
+			emitf(Xmark);
+			emitf(Xword);
+			emits(estrdup("*"));
+			emitf(Xdol);
+		}
+		emitf(Xmark);		/* dummy value for Xlocal */
+		emitf(Xmark);
+		noglobs(c0, 0);
+		outcode(c0, eflag);
+		emitf(Xlocal);
+		p = emitf(Xfor);
+		q = emiti(0);
+		outcode(c2, eflag);
+		emitf(Xjump);
+		emiti(p);
+		stuffdot(q);
+		emitf(Xunlocal);
+		break;
+	case WORD:
+		emitf(Xword);
+		emits(t->str);
+		t->str=0;	/* passed ownership */
+		break;
+	case DUP:
+		if(t->rtype==DUPFD){
+			emitf(Xdup);
+			emiti(t->fd0);
+			emiti(t->fd1);
+		}
+		else{
+			emitf(Xclose);
+			emiti(t->fd0);
+		}
+		outcode(c1, eflag);
+		emitf(Xpopredir);
+		break;
+	case PIPEFD:
+		emitf(Xpipefd);
+		emiti(t->rtype);
+		p = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c0, eflag);
+		emitf(Xexit);
+		stuffdot(p);
+		break;
+	case REDIR:
+		if(t->rtype!=HERE){
+			emitf(Xmark);
+			outcode(c0, eflag);
+		}
+		switch(t->rtype){
+		case APPEND:
+			emitf(Xappend);
+			break;
+		case WRITE:
+			emitf(Xwrite);
+			break;
+		case READ:
+			emitf(Xread);
+			break;
+		case RDWR:
+			emitf(Xrdwr);
+			break;
+		case HERE:
+			emitf(c0->quoted?Xhereq:Xhere);
+			emits(t->str);
+			t->str=0;	/* passed ownership */
+			break;
+		}
+		emiti(t->fd0);
+		outcode(c1, eflag);
+		emitf(Xpopredir);
+		break;
+	case '=':
+		tt = t;
+		for(;t && t->type=='=';t = c2);
+		if(t){					/* var=value cmd */
+			for(t = tt;t->type=='=';t = c2){
+				emitf(Xmark);
+				outcode(c1, eflag);
+				emitf(Xmark);
+				noglobs(c0, 0);
+				outcode(c0, eflag);
+				emitf(Xlocal);		/* push var for cmd */
+			}
+			outcode(t, eflag);		/* gen. code for cmd */
+			for(t = tt; t->type == '='; t = c2)
+				emitf(Xunlocal);	/* pop var */
+		}
+		else{					/* var=value */
+			for(t = tt;t;t = c2){
+				emitf(Xmark);
+				outcode(c1, eflag);
+				emitf(Xmark);
+				noglobs(c0, 0);
+				outcode(c0, eflag);
+				emitf(Xassign);	/* set var permanently */
+			}
+		}
+		t = tt;	/* so tests below will work */
+		break;
+	case PIPE:
+		emitf(Xpipe);
+		emiti(t->fd0);
+		emiti(t->fd1);
+		p = emiti(0);
+		q = emiti(0);
+		codeline = 0;	/* force source */
+		outcode(c0, eflag);
+		emitf(Xexit);
+		stuffdot(p);
+		codeline = 0;	/* force source */
+		outcode(c1, eflag);
+		emitf(Xreturn);
+		stuffdot(q);
+		emitf(Xpipewait);
+		break;
+	}
+	if(t->glob==1)
+		emitf(Xglob);
+	if(t->type!=NOT && t->type!=';')
+		lex->iflast = t->type==IF;
+	else if(c0)
+		lex->iflast = c0->type==IF;
+}
+/*
+ * switch code looks like this:
+ *	Xmark
+ *	(get switch value)
+ *	Xjump	1f
+ * out:	Xjump	leave
+ * 1:	Xmark
+ *	(get case values)
+ *	Xcase	1f
+ *	(commands)
+ *	Xjump	out
+ * 1:	Xmark
+ *	(get case values)
+ *	Xcase	1f
+ *	(commands)
+ *	Xjump	out
+ * 1:
+ * leave:
+ *	Xpopm
+ */
+
+void
+codeswitch(tree *t, int eflag)
+{
+	int leave;		/* patch jump address to leave switch */
+	int out;		/* jump here to leave switch */
+	int nextcase;	/* patch jump address to next case */
+	tree *tt;
+	if(c1->child[0]==0
+	|| c1->child[0]->type!=';'
+	|| !iscase(c1->child[0]->child[0])){
+		yyerror("case missing in switch");
+		return;
+	}
+	emitf(Xmark);
+	outcode(c0, eflag);
+	emitf(Xqw);
+	emitf(Xjump);
+	nextcase = emiti(0);
+	out = emitf(Xjump);
+	leave = emiti(0);
+	stuffdot(nextcase);
+	t = c1->child[0];
+	while(t->type==';'){
+		tt = c1;
+		emitf(Xmark);
+		for(t = c0->child[0];t->type==ARGLIST;t = c0) {
+			noglobs(c1, 1);
+			outcode(c1, eflag);
+		}
+		emitf(Xcase);
+		nextcase = emiti(0);
+		t = tt;
+		for(;;){
+			if(t->type==';'){
+				if(iscase(c0)) break;
+				outcode(c0, eflag);
+				t = c1;
+			}
+			else{
+				if(!iscase(t)) outcode(t, eflag);
+				break;
+			}
+		}
+		emitf(Xjump);
+		emiti(out);
+		stuffdot(nextcase);
+	}
+	stuffdot(leave);
+	emitf(Xpopm);
+}
+
+int
+iscase(tree *t)
+{
+	if(t->type!=SIMPLE)
+		return 0;
+	do t = c0; while(t->type==ARGLIST);
+	return t->type==WORD && !t->quoted && strcmp(t->str, "case")==0;
+}
+
+code*
+codecopy(code *cp)
+{
+	cp[0].i++;
+	return cp;
+}
+
+void
+codefree(code *cp)
+{
+	code *p;
+	if(--cp[0].i!=0)
+		return;
+	for(p = cp+2;p->f;p++){
+		if(p->f==Xappend || p->f==Xclose || p->f==Xread || p->f==Xwrite
+		|| p->f==Xrdwr
+		|| p->f==Xasync || p->f==Xbackq || p->f==Xcase || p->f==Xfalse
+		|| p->f==Xfor || p->f==Xjump
+		|| p->f==Xsrcline
+		|| p->f==Xsubshell || p->f==Xtrue) p++;
+		else if(p->f==Xdup || p->f==Xpipefd) p+=2;
+		else if(p->f==Xpipe) p+=4;
+		else if(p->f==Xhere || p->f==Xhereq) free(p[1].s), p+=2;
+		else if(p->f==Xword) free((++p)->s);
+		else if(p->f==Xfn){
+			free(p[2].s);
+			p+=2;
+		}
+	}
+	free(cp[1].s);
+	free(cp);
+}
--- /dev/null
+++ b/librc/exec.c
@@ -1,0 +1,1167 @@
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+char *argv0="rc";
+io *err;
+int mypid;
+thread *runq;
+
+/*
+ * Start executing the given code at the given pc with the given redirection
+ */
+void
+start(code *c, int pc, var *local, redir *redir)
+{
+	thread *p = new(thread);
+	p->code = codecopy(c);
+	p->line = 0;
+	p->pc = pc;
+	p->argv = 0;
+	p->redir = p->startredir = redir;
+	p->lex = 0;
+	p->local = local;
+	p->iflag = 0;
+	p->pid = 0;
+	p->status = 0;
+	p->ret = runq;
+	runq = p;
+}
+
+void
+startfunc(var *func, word *starval, var *local, redir *redir)
+{
+	start(func->fn, func->pc, local, redir);
+	runq->local = newvar("*", runq->local);
+	runq->local->val = starval;
+	runq->local->changed = 1;
+}
+
+static void
+popthread(void)
+{
+	thread *p = runq;
+	while(p->argv) poplist();
+	while(p->local && (p->ret==0 || p->local!=p->ret->local))
+		Xunlocal();
+	runq = p->ret;
+	if(p->lex) freelexer(p->lex);
+	codefree(p->code);
+	free(p->status);
+	free(p);
+}
+
+word*
+Newword(char *s, word *next)
+{
+	word *p=new(word);
+	p->word = s;
+	p->next = next;
+	return p;
+}
+word*
+newword(char *s, word *next)
+{
+	return Newword(estrdup(s), next);
+}
+word*
+Pushword(char *s)
+{
+	word *p;
+	if(s==0)
+		panic("null pushword", 0);
+	if(runq->argv==0)
+		panic("pushword but no argv!", 0);
+	p = Newword(s, runq->argv->words);
+	runq->argv->words = p;
+	return p;
+}
+word*
+pushword(char *s)
+{
+	return Pushword(estrdup(s));
+}
+char*
+Freeword(word *p)
+{
+	char *s = p->word;
+	free(p);
+	return s;
+}
+void
+freewords(word *w)
+{
+	word *p;
+	while((p = w)!=0){
+		w = w->next;
+		free(Freeword(p));
+	}
+}
+char*
+Popword(void)
+{
+	word *p;
+	if(runq->argv==0)
+		panic("popword but no argv!", 0);
+	p = runq->argv->words;
+	if(p==0)
+		panic("popword but no word!", 0);
+	runq->argv->words = p->next;
+	return Freeword(p);
+}
+void
+popword(void)
+{
+	free(Popword());
+}
+
+void
+pushlist(void)
+{
+	list *p = new(list);
+	p->words = 0;
+	p->next = runq->argv;
+	runq->argv = p;
+}
+word*
+Poplist(void)
+{
+	word *w;
+	list *p = runq->argv;
+	if(p==0)
+		panic("poplist but no argv", 0);
+	w = p->words;
+	runq->argv = p->next;
+	free(p);
+	return w;
+}
+void
+poplist(void)
+{
+	freewords(Poplist());
+}
+
+int
+count(word *w)
+{
+	int n;
+	for(n = 0;w;n++) w = w->next;
+	return n;
+}
+
+void
+pushredir(int type, int from, int to)
+{
+	redir *rp = new(redir);
+	rp->type = type;
+	rp->from = from;
+	rp->to = to;
+	rp->next = runq->redir;
+	runq->redir = rp;
+}
+
+static void
+dontclose(int fd)
+{
+	redir *rp;
+
+	if(fd<0)
+		return;
+	for(rp = runq->redir; rp != runq->startredir; rp = rp->next){
+		if(rp->type == RCLOSE && rp->from == fd){
+			rp->type = 0;
+			break;
+		}
+	}
+}
+
+/*
+ * we are about to start a new thread that should exit on
+ * return, so the current stack is not needed anymore.
+ * free all the threads and lexers, but preserve the
+ * redirections and anything referenced by local.
+ */
+void
+turfstack(var *local)
+{
+	while(local){
+		thread *p;
+
+		for(p = runq; p && p->local == local; p = p->ret)
+			p->local = local->next;
+		local = local->next;
+	}
+	while(runq) {
+		if(runq->lex) dontclose(runq->lex->input->fd);
+		popthread();
+	}
+}
+
+void
+shuffleredir(void)
+{
+	redir **rr, *rp;
+
+	rp = runq->redir;
+	if(rp==0)
+		return;
+	runq->redir = rp->next;
+	rp->next = runq->startredir;
+	for(rr = &runq->redir; *rr != rp->next; rr = &((*rr)->next))
+		;
+	*rr = rp;
+}
+
+/*
+ * get command line flags, initialize keywords & traps.
+ * get values from environment.
+ * set $pid, $cflag, $*
+ * fabricate bootstrap code and start it (*=(argv);. -bq /usr/lib/rcmain $*)
+ * start interpreting code
+ */
+void
+rcmain(int argc, char *argv[])
+{
+	code bootstrap[20];
+	char num[12];
+	char *rcmain=Rcmain;
+
+	int i;
+	argv0 = argv[0];
+	argc = getflags(argc, argv, "srdiIlxebpvVc:1m:1[command]", 1);
+	if(argc==-1)
+		usage("[file [arg ...]]");
+	if(argv[0][0]=='-')
+		flag['l'] = flagset;
+	if(flag['I'])
+		flag['i'] = 0;
+	else if(flag['i']==0 && argc==1 && Isatty(0)) flag['i'] = flagset;
+	if(flag['m']) rcmain = flag['m'][0];
+	err = openiofd(2);
+	kinit();
+	Trapinit();
+	Vinit();
+	inttoascii(num, mypid = getpid());
+	setvar("pid", newword(num, (word *)0));
+	setvar("cflag", flag['c']?newword(flag['c'][0], (word *)0)
+				:(word *)0);
+	setvar("rcname", newword(argv[0], (word *)0));
+	bootstrap[0].i = 1;
+	bootstrap[1].s="*bootstrap*";
+	bootstrap[2].f = Xmark;
+	bootstrap[3].f = Xword;
+	bootstrap[4].s="*";
+	bootstrap[5].f = Xassign;
+	bootstrap[6].f = Xmark;
+	bootstrap[7].f = Xmark;
+	bootstrap[8].f = Xword;
+	bootstrap[9].s="*";
+	bootstrap[10].f = Xdol;
+	bootstrap[11].f = Xword;
+	bootstrap[12].s = rcmain;
+	bootstrap[13].f = Xword;
+	bootstrap[14].s="-bq";
+	bootstrap[15].f = Xword;
+	bootstrap[16].s=".";
+	bootstrap[17].f = Xsimple;
+	bootstrap[18].f = Xexit;
+	bootstrap[19].f = 0;
+	start(bootstrap, 2, (var*)0, (redir*)0);
+
+	/* prime bootstrap argv */
+	pushlist();
+	for(i = argc-1;i!=0;--i) pushword(argv[i]);
+
+	for(;;){
+		if(flag['r'])
+			pfnc(err, runq);
+		(*runq->code[runq->pc++].f)();
+		if(ntrap)
+			dotrap();
+	}
+}
+/*
+ * Opcode routines
+ * Arguments on stack (...)
+ * Arguments in line [...]
+ * Code in line with jump around {...}
+ *
+ * Xappend(file)[fd]			open file to append
+ * Xassign(name, val)			assign val to name
+ * Xasync{... Xexit}			make thread for {}, no wait
+ * Xbackq(split){... Xreturn}		make thread for {}, push stdout
+ * Xbang				complement condition
+ * Xcase(pat, value){...}		exec code on match, leave (value) on
+ * 					stack
+ * Xclose[i]				close file descriptor
+ * Xconc(left, right)			concatenate, push results
+ * Xcount(name)				push var count
+ * Xdelfn(name)				delete function definition
+ * Xdol(name)				get variable value
+ * Xdup[i j]				dup file descriptor
+ * Xexit				rc exits with status
+ * Xfalse{...}				execute {} if false
+ * Xfn(name){... Xreturn}		define function
+ * Xfor(var, list){... Xreturn}		for loop
+ * Xglob(list)				glob a list of words inplace
+ * Xjump[addr]				goto
+ * Xlocal(name, val)			create local variable, assign value
+ * Xmark				mark stack
+ * Xmatch(pat, str)			match pattern, set status
+ * Xpipe[i j]{... Xreturn}{... Xreturn}	construct a pipe between 2 new threads,
+ * 					wait for both
+ * Xpipefd[type]{... Xreturn}		connect {} to pipe (input or output,
+ * 					depending on type), push /dev/fd/??
+ * Xpopm(value)				pop value from stack
+ * Xpush(words)				push words down a list
+ * Xqw(words)				quote words inplace
+ * Xrdwr(file)[fd]			open file for reading and writing
+ * Xread(file)[fd]			open file to read
+ * Xreturn				kill thread
+ * Xsimple(args)			run command and wait
+ * Xsrcline[line]			set current source line number
+ * Xsubshell{... Xexit}			execute {} in a subshell and wait
+ * Xtrue{...}				execute {} if true
+ * Xunlocal				delete local variable
+ * Xword[string]			push string
+ * Xwrite(file)[fd]			open file to write
+ */
+
+void
+Xappend(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1(">> requires singleton");
+		return;
+	case 0:
+		Xerror1(">> requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Open(file, 1))<0 && (fd = Creat(file))<0){
+		Xerror3(">> can't open", file, Errstr());
+		return;
+	}
+	Seek(fd, 0L, 2);
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xsettrue(void)
+{
+	setstatus("");
+}
+
+void
+Xbang(void)
+{
+	setstatus(truestatus()?"false":"");
+}
+
+void
+Xclose(void)
+{
+	pushredir(RCLOSE, runq->code[runq->pc++].i, 0);
+}
+
+void
+Xdup(void)
+{
+	pushredir(RDUP, runq->code[runq->pc].i, runq->code[runq->pc+1].i);
+	runq->pc+=2;
+}
+
+void
+Xeflag(void)
+{
+	if(!truestatus()) Xexit();
+}
+
+void
+Xexit(void)
+{
+	static int beenhere = 0;
+
+	if(getpid()==mypid && !beenhere){
+		var *trapreq = vlook("sigexit");
+		word *starval = vlook("*")->val;
+		if(trapreq->fn){
+			beenhere = 1;
+			--runq->pc;
+			startfunc(trapreq, copywords(starval, (word*)0), (var*)0, (redir*)0);
+			return;
+		}
+	}
+	Exit();
+}
+
+void
+Xfalse(void)
+{
+	if(truestatus()) runq->pc = runq->code[runq->pc].i;
+	else runq->pc++;
+}
+int ifnot;		/* dynamic if not flag */
+
+void
+Xifnot(void)
+{
+	if(ifnot)
+		runq->pc++;
+	else
+		runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xjump(void)
+{
+	runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xmark(void)
+{
+	pushlist();
+}
+
+void
+Xpopm(void)
+{
+	poplist();
+}
+
+void
+Xpush(void)
+{
+	word *t, *h = Poplist();
+	for(t = h; t->next; t = t->next)
+		;
+	t->next = runq->argv->words;
+	runq->argv->words = h;
+}
+
+static int
+herefile(char *tmp)
+{
+	char *s = tmp+strlen(tmp)-1;
+	static int ser;
+	int fd, i;
+
+	i = ser++;
+	while(*s == 'Y'){
+		*s-- = (i%26) + 'A';
+		i = i/26;
+	}
+	i = getpid();
+	while(*s == 'X'){
+		*s-- = (i%10) + '0';
+		i = i/10;
+	}
+	s++;
+	for(i='a'; i<'z'; i++){
+		if(access(tmp, 0)!=0 && (fd = Creat(tmp))>=0)
+			return fd;
+		*s = i;
+	}
+	return -1;
+}
+
+void
+Xhere(void)
+{
+	char file[]="/tmp/hereXXXXXXXXXXYY";
+	int fd;
+	io *io;
+
+	if((fd = herefile(file))<0){
+		Xerror3("<< can't get temp file", file, Errstr());
+		return;
+	}
+	io = openiofd(fd);
+	psubst(io, (unsigned char*)runq->code[runq->pc++].s);
+	flushio(io);
+	closeio(io);
+
+	/* open for reading and unlink */
+	if((fd = Open(file, 3))<0){
+		Xerror3("<< can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+}
+
+void
+Xhereq(void)
+{
+	char file[]="/tmp/hereXXXXXXXXXXYY", *body;
+	int fd;
+
+	if((fd = herefile(file))<0){
+		Xerror3("<< can't get temp file", file, Errstr());
+		return;
+	}
+	body = runq->code[runq->pc++].s;
+	Write(fd, body, strlen(body));
+	Close(fd);
+
+	/* open for reading and unlink */
+	if((fd = Open(file, 3))<0){
+		Xerror3("<< can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+}
+
+void
+Xread(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("< requires singleton");
+		return;
+	case 0:
+		Xerror1("< requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Open(file, 0))<0){
+		Xerror3("< can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xrdwr(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("<> requires singleton");
+		return;
+	case 0:
+		Xerror1("<> requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Open(file, 2))<0){
+		Xerror3("<> can't open", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xpopredir(void)
+{
+	redir *rp = runq->redir;
+
+	if(rp==0)
+		panic("Xpopredir null!", 0);
+	runq->redir = rp->next;
+	if(rp->type==ROPEN)
+		Close(rp->from);
+	free(rp);
+}
+
+void
+Xreturn(void)
+{
+	while(runq->redir!=runq->startredir)
+		Xpopredir();
+	popthread();
+	if(runq==0)
+		Exit();
+}
+
+void
+Xtrue(void)
+{
+	if(truestatus()) runq->pc++;
+	else runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xif(void)
+{
+	ifnot = 1;
+	if(truestatus()) runq->pc++;
+	else runq->pc = runq->code[runq->pc].i;
+}
+
+void
+Xwastrue(void)
+{
+	ifnot = 0;
+}
+
+void
+Xword(void)
+{
+	pushword(runq->code[runq->pc++].s);
+}
+
+void
+Xwrite(void)
+{
+	char *file;
+	int fd;
+
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("> requires singleton");
+		return;
+	case 0:
+		Xerror1("> requires file");
+		return;
+	case 1:
+		break;
+	}
+	file = runq->argv->words->word;
+	if((fd = Creat(file))<0){
+		Xerror3("> can't create", file, Errstr());
+		return;
+	}
+	pushredir(ROPEN, fd, runq->code[runq->pc++].i);
+	poplist();
+}
+
+void
+Xmatch(void)
+{
+	word *p;
+	char *s;
+
+	setstatus("no match");
+	s = runq->argv->words->word;
+	for(p = runq->argv->next->words;p;p = p->next)
+		if(match(s, p->word, '\0')){
+			setstatus("");
+			break;
+		}
+	poplist();
+	poplist();
+}
+
+void
+Xcase(void)
+{
+	word *p;
+	char *s;
+	int ok = 0;
+
+	s = runq->argv->next->words->word;
+	for(p = runq->argv->words;p;p = p->next){
+		if(match(s, p->word, '\0')){
+			ok = 1;
+			break;
+		}
+	}
+	if(ok)
+		runq->pc++;
+	else
+		runq->pc = runq->code[runq->pc].i;
+	poplist();
+}
+
+static word*
+conclist(word *lp, word *rp, word *tail)
+{
+	word *v, *p, **end;
+	int ln, rn;
+
+	for(end = &v;;){
+		ln = strlen(lp->word), rn = strlen(rp->word);
+		p = Newword(emalloc(ln+rn+1), (word *)0);
+		memmove(p->word, lp->word, ln);
+		memmove(p->word+ln, rp->word, rn+1);
+		*end = p, end = &p->next;
+		if(lp->next == 0 && rp->next == 0)
+			break;
+		if(lp->next) lp = lp->next;
+		if(rp->next) rp = rp->next;
+	}
+	*end = tail;
+	return v;
+}
+
+void
+Xconc(void)
+{
+	word *lp = runq->argv->words;
+	word *rp = runq->argv->next->words;
+	word *vp = runq->argv->next->next->words;
+	int lc = count(lp), rc = count(rp);
+	if(lc!=0 || rc!=0){
+		if(lc==0 || rc==0){
+			Xerror1("null list in concatenation");
+			return;
+		}
+		if(lc!=1 && rc!=1 && lc!=rc){
+			Xerror1("mismatched list lengths in concatenation");
+			return;
+		}
+		vp = conclist(lp, rp, vp);
+	}
+	poplist();
+	poplist();
+	runq->argv->words = vp;
+}
+
+void
+Xassign(void)
+{
+	var *v;
+
+	if(count(runq->argv->words)!=1){
+		Xerror1("= variable name not singleton!");
+		return;
+	}
+	v = vlook(runq->argv->words->word);
+	poplist();
+	freewords(v->val);
+	v->val = Poplist();
+	v->changed = 1;
+}
+
+/*
+ * copy arglist a, adding the copy to the front of tail
+ */
+word*
+copywords(word *a, word *tail)
+{
+	word *v = 0, **end;
+
+	for(end=&v;a;a = a->next,end=&(*end)->next)
+		*end = newword(a->word, 0);
+	*end = tail;
+	return v;
+}
+
+void
+Xdol(void)
+{
+	word *a, *star;
+	char *s, *t;
+	int n;
+
+	if(count(runq->argv->words)!=1){
+		Xerror1("$ variable name not singleton!");
+		return;
+	}
+	n = 0;
+	s = runq->argv->words->word;
+	for(t = s;'0'<=*t && *t<='9';t++) n = n*10+*t-'0';
+	a = runq->argv->next->words;
+	if(n==0 || *t)
+		a = copywords(vlook(s)->val, a);
+	else{
+		star = vlook("*")->val;
+		if(star && 1<=n && n<=count(star)){
+			while(--n) star = star->next;
+			a = newword(star->word, a);
+		}
+	}
+	poplist();
+	runq->argv->words = a;
+}
+
+void
+Xqw(void)
+{
+	char *s, *d;
+	word *a, *p;
+	int n;
+
+	a = runq->argv->words;
+	if(a==0){
+		pushword("");
+		return;
+	}
+	if(a->next==0)
+		return;
+	n=0;
+	for(p=a;p;p=p->next)
+		n+=1+strlen(p->word);
+	s = emalloc(n+1);
+	d = s;
+	d += strlen(strcpy(d, a->word));
+	for(p=a->next;p;p=p->next){
+		*d++=' ';
+		d += strlen(strcpy(d, p->word));
+	}
+	free(a->word);
+	freewords(a->next);
+	a->word = s;
+	a->next = 0;
+}
+
+static word*
+copynwords(word *a, word *tail, int n)
+{
+	word *v, **end;
+	
+	v = 0;
+	end = &v;
+	while(n-- > 0){
+		*end = newword(a->word, 0);
+		end = &(*end)->next;
+		a = a->next;
+	}
+	*end = tail;
+	return v;
+}
+
+static word*
+subwords(word *val, int len, word *sub, word *a)
+{
+	int n, m;
+	char *s;
+
+	if(sub==0)
+		return a;
+	a = subwords(val, len, sub->next, a);
+	s = sub->word;
+	m = 0;
+	n = 0;
+	while('0'<=*s && *s<='9')
+		n = n*10+ *s++ -'0';
+	if(*s == '-'){
+		if(*++s == 0)
+			m = len - n;
+		else{
+			while('0'<=*s && *s<='9')
+				m = m*10+ *s++ -'0';
+			m -= n;
+		}
+	}
+	if(n<1 || n>len || m<0)
+		return a;
+	if(n+m>len)
+		m = len-n;
+	while(--n > 0)
+		val = val->next;
+	return copynwords(val, a, m+1);
+}
+
+void
+Xsub(void)
+{
+	word *a, *v;
+	char *s;
+
+	if(count(runq->argv->next->words)!=1){
+		Xerror1("$() variable name not singleton!");
+		return;
+	}
+	s = runq->argv->next->words->word;
+	a = runq->argv->next->next->words;
+	v = vlook(s)->val;
+	a = subwords(v, count(v), runq->argv->words, a);
+	poplist();
+	poplist();
+	runq->argv->words = a;
+}
+
+void
+Xcount(void)
+{
+	word *a;
+	char *s, *t, num[12];
+	int n;
+
+	if(count(runq->argv->words)!=1){
+		Xerror1("$# variable name not singleton!");
+		return;
+	}
+	n = 0;
+	s = runq->argv->words->word;
+	for(t = s;'0'<=*t && *t<='9';t++) n = n*10+*t-'0';
+	if(n==0 || *t){
+		a = vlook(s)->val;
+		inttoascii(num, count(a));
+	}
+	else{
+		a = vlook("*")->val;
+		inttoascii(num, a && 1<=n && n<=count(a)?1:0);
+	}
+	poplist();
+	pushword(num);
+}
+
+void
+Xlocal(void)
+{
+	if(count(runq->argv->words)!=1){
+		Xerror1("local variable name must be singleton");
+		return;
+	}
+	runq->local = newvar(runq->argv->words->word, runq->local);
+	poplist();
+	runq->local->val = Poplist();
+	runq->local->changed = 1;
+}
+
+void
+Xunlocal(void)
+{
+	var *hid, *v = runq->local;
+	if(v==0)
+		panic("Xunlocal: no locals!", 0);
+	runq->local = v->next;
+	hid = vlook(v->name);
+	hid->changed = 1;
+	freevar(v);
+}
+
+void
+Xfn(void)
+{
+	var *v;
+	word *a;
+	int pc = runq->pc;
+	runq->pc = runq->code[pc].i;
+	for(a = runq->argv->words;a;a = a->next){
+		v = gvlook(a->word);
+		if(v->fn)
+			codefree(v->fn);
+		v->fn = codecopy(runq->code);
+		v->pc = pc+2;
+		v->fnchanged = 1;
+	}
+	poplist();
+}
+
+void
+Xdelfn(void)
+{
+	var *v;
+	word *a;
+	for(a = runq->argv->words;a;a = a->next){
+		v = gvlook(a->word);
+		if(v->fn)
+			codefree(v->fn);
+		v->fn = 0;
+		v->fnchanged = 1;
+	}
+	poplist();
+}
+
+static char*
+concstatus(char *s, char *t)
+{
+	int n, m;
+
+	if(t==0) return s;
+	if(s==0) return t;
+	n = strlen(s);
+	m = strlen(t);
+	s = erealloc(s, n+m+2);
+	if(n > 0) s[n++]='|';
+	memmove(s+n, t, m+1);
+	free(t);
+	return s;
+}
+
+void
+Xpipewait(void)
+{
+	char *old = Getstatus();
+	if(runq->pid==-1){
+		Setstatus(concstatus(runq->status, old));
+		runq->status=0;
+	}else{
+		while(Waitfor(runq->pid) < 0)
+			;
+		runq->pid=-1;
+		Setstatus(concstatus(Getstatus(), old));
+	}
+}
+
+static char *promptstr;
+
+void
+Xrdcmds(void)
+{
+	thread *p = runq;
+
+	if(flag['s'] && !truestatus())
+		pfmt(err, "status=%v\n", vlook("status")->val);
+	flushio(err);
+
+	lex = p->lex;
+	if(p->iflag){
+		word *prompt = vlook("prompt")->val;
+		if(prompt)
+			promptstr = prompt->word;
+		else
+			promptstr="% ";
+	}
+	Noerror();
+	nerror = 0;
+	if(yyparse()){
+		if(p->iflag && (!lex->eof || Eintr())){
+			if(Eintr()){
+				pchr(err, '\n');
+				lex->eof = 0;
+			}
+			--p->pc;	/* go back for next command */
+		}
+	}
+	else{
+		if(lex->eof){
+			dontclose(lex->input->fd);
+			freelexer(lex);
+			p->lex = 0;
+		} else
+			--p->pc;	/* re-execute Xrdcmds after codebuf runs */
+		start(codebuf, 2, p->local, p->redir);
+	}
+	lex = 0;
+	freenodes();
+}
+
+void
+pprompt(void)
+{
+	word *prompt;
+
+	if(!runq->iflag)
+		return;
+
+	Prompt(promptstr);
+	doprompt = 0;
+
+	prompt = vlook("prompt")->val;
+	if(prompt && prompt->next)
+		promptstr = prompt->next->word;
+	else
+		promptstr = "\t";
+}
+
+char*
+srcfile(thread *p)
+{
+	return p->code[1].s;
+}
+
+void
+Xerror1(char *s)
+{
+	setstatus("error");
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s\n", s);
+	flushio(err);
+	while(!runq->iflag) Xreturn();
+}
+void
+Xerror2(char *s, char *e)
+{
+	setstatus(e);
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s: %s\n", s, e);
+	flushio(err);
+	while(!runq->iflag) Xreturn();
+}
+void
+Xerror3(char *s, char *m, char *e)
+{
+	setstatus(e);
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s: %s: %s\n", s, m, e);
+	flushio(err);
+	while(!runq->iflag) Xreturn();
+}
+
+void
+Setstatus(char *s)
+{
+	setvar("status", Newword(s?s:estrdup(""), (word *)0));
+}
+void
+setstatus(char *s)
+{
+	Setstatus(estrdup(s));
+}
+char*
+Getstatus(void)
+{
+	var *status = vlook("status");
+	word *val = status->val;
+	if(val==0) return 0;
+	status->val=0;
+	status->changed=1;
+	freewords(val->next);
+	return Freeword(val);
+}
+char*
+getstatus(void)
+{
+	var *status = vlook("status");
+	return status->val?status->val->word:"";
+}
+
+int
+truestatus(void)
+{
+	char *s;
+	for(s = getstatus();*s;s++)
+		if(*s!='|' && *s!='0')
+			return 0;
+	return 1;
+}
+
+void
+Xfor(void)
+{
+	word *a = runq->argv->words;
+	if(a==0){
+		poplist();
+		runq->pc = runq->code[runq->pc].i;
+	}
+	else{
+		runq->argv->words = a->next;
+		a->next = 0;
+		freewords(runq->local->val);
+		runq->local->val = a;
+		runq->local->changed = 1;
+		runq->pc++;
+	}
+}
+
+void
+Xglob(void)
+{
+	word *a, *x;
+
+	for(a = runq->argv->words; a; a = x){
+		x = a->next;
+		globword(a);
+	}
+}
+
+void
+Xsrcline(void)
+{
+	runq->line = runq->code[runq->pc++].i;
+}
--- /dev/null
+++ b/librc/exec.h
@@ -1,0 +1,85 @@
+/*
+ * Definitions used in the interpreter
+ */
+extern void Xappend(void), Xasync(void), Xbackq(void), Xbang(void), Xclose(void);
+extern void Xconc(void), Xcount(void), Xdelfn(void), Xdol(void), Xqw(void), Xdup(void);
+extern void Xexit(void), Xfalse(void), Xfn(void), Xfor(void), Xglob(void);
+extern void Xjump(void), Xmark(void), Xmatch(void), Xpipe(void), Xread(void), Xhere(void), Xhereq(void);
+extern void Xrdwr(void), Xsrcline(void);
+extern void Xunredir(void), Xstar(void), Xreturn(void), Xsubshell(void);
+extern void Xtrue(void), Xword(void), Xwrite(void), Xpipefd(void), Xcase(void);
+extern void Xlocal(void), Xunlocal(void), Xassign(void), Xsimple(void), Xpopm(void), Xpush(void);
+extern void Xrdcmds(void), Xwastrue(void), Xif(void), Xifnot(void), Xpipewait(void);
+extern void Xpopredir(void), Xsub(void), Xeflag(void), Xsettrue(void);
+extern void Xerror1(char*);
+extern void Xerror2(char*,char*);
+extern void Xerror3(char*,char*,char*);
+
+/*
+ * word lists are in correct order,
+ * i.e. word0->word1->word2->word3->0
+ */
+struct word{
+	char *word;
+	word *next;
+};
+struct list{
+	word *words;
+	list *next;
+};
+word *newword(char *, word *), *copywords(word *, word *);
+
+struct redir{
+	int type;			/* what to do */
+	int from, to;			/* what to do it to */
+	redir *next;			/* what else to do (reverse order) */
+};
+
+/*
+ * redir types
+ */
+#define	ROPEN	1			/* dup2(from, to); close(from); */
+#define	RDUP	2			/* dup2(from, to); */
+#define	RCLOSE	3			/* close(from); */
+void	shuffleredir(void);
+
+struct thread{
+	code *code;			/* code for this thread */
+	int pc;				/* code[pc] is the next instruction */
+	int line;			/* source code line for Xsrcline */
+	list *argv;			/* argument stack */
+	redir *redir;			/* redirection stack */
+	redir *startredir;		/* redir inheritance point */
+	var *local;			/* list of local variables */
+	lexer *lex;			/* lexer for Xrdcmds */
+	int iflag;			/* interactive? */
+	int pid;			/* process for Xpipewait to wait for */
+	char *status;			/* status for Xpipewait */
+	thread *ret;			/* who continues when this finishes */
+};
+extern thread *runq;
+void turfstack(var*);
+
+extern int mypid;
+extern int ntrap;			/* number of outstanding traps */
+extern int trap[NSIG];			/* number of outstanding traps per type */
+
+code *codecopy(code*);
+extern code *codebuf;			/* compiler output */
+extern int ifnot;
+
+struct builtin{
+	char *name;
+	void (*fnc)(void);
+};
+extern void (*builtinfunc(char *name))(void);
+
+void execcd(void), execwhatis(void), execeval(void), execexec(void);
+int execforkexec(void);
+void execexit(void), execshift(void);
+void execwait(void), execumask(void), execdot(void), execflag(void);
+void execfunc(var*), execcmds(io*, char*, var*, redir*);
+void startfunc(var*, word*, var*, redir*);
+
+char *srcfile(thread*);
+char *getstatus(void);
--- /dev/null
+++ b/librc/fns.h
@@ -1,0 +1,72 @@
+void	Abort(void);
+int	Chdir(char*);
+void	Close(int);
+void	Closedir(void*);
+int	Creat(char*);
+int	Dup(int, int);
+int	Dup1(int);
+int	Eintr(void);
+int	Executable(char*);
+void	Exec(char**);
+void	Exit(void);
+char*	Errstr(void);
+char*	Freeword(word*);
+int	Fork(void);
+char*	Getstatus(void);
+int	Isatty(int);
+word*	Newword(char*,word*);
+void	Noerror(void);
+int	Open(char*, int);
+void*	Opendir(char*);
+word*	Poplist(void);
+char*	Popword(void);
+word*	Pushword(char*);
+long	Read(int, void*, long);
+char*	Readdir(void*, int);
+long	Seek(int, long, long);
+void	Setstatus(char*);
+void	Trapinit(void);
+void	Updenv(void);
+void	Vinit(void);
+int	Waitfor(int);
+long	Write(int, void*, long);
+void	addwaitpid(int);
+void	clearwaitpids(void);
+void	codefree(code*);
+int	compile(tree*);
+int	count(word*);
+char*	deglob(char*);
+void	delwaitpid(int);
+void	dotrap(void);
+void	freenodes(void);
+void	freewords(word*);
+void	globword(word*);
+int	havewaitpid(int);
+int	idchr(int);
+void	inttoascii(char*, int);
+void	kinit(void);
+int	mapfd(int);
+int	match(char*, char*, int);
+char*	makepath(char*, char*);
+void	panic(char*, int);
+void	pfln(io*, char*, int);
+void	poplist(void);
+void	popword(void);
+void	pprompt(void);
+void	Prompt(char*);
+void	psubst(io*, unsigned char*);
+void	pushlist(void);
+void	pushredir(int, int, int);
+word*	pushword(char*);
+void    rcmain(int, char*[]);
+void	readhere(io*);
+void	heredoc(tree*);
+void	setstatus(char*);
+void	skipnl(void);
+void	start(code*, int, var*, redir*);
+int	truestatus(void);
+void	usage(char*);
+int	wordchr(int);
+void	yyerror(char*);
+int	yylex(void);
+int	yyparse(void);
--- /dev/null
+++ b/librc/getflags.c
@@ -1,0 +1,234 @@
+#include "rc.h"
+#include "getflags.h"
+#include "fns.h"
+char *flagset[] = {"<flag>"};
+char **flag[NFLAG];
+char *cmdname;
+static char *flagarg="";
+static void reverse(char**, char**);
+static int scanflag(int, char*);
+static void errn(char*, int);
+static void errs(char*);
+static void errc(int);
+static int reason;
+#define	RESET	1
+#define	FEWARGS	2
+#define	FLAGSYN	3
+#define	BADFLAG	4
+static int badflag;
+
+int
+getflags(int argc, char *argv[], char *flags, int stop)
+{
+	char *s;
+	int i, j, c, count;
+	flagarg = flags;
+	if(cmdname==0)
+		cmdname = argv[0];
+
+	i = 1;
+	while(i!=argc){
+		if(argv[i][0] != '-' || argv[i][1] == '\0'){
+			if(stop)		/* always true in rc */
+				return argc;
+			i++;
+			continue;
+		}
+		s = argv[i]+1;
+		while(*s){
+			c=*s++;
+			count = scanflag(c, flags);
+			if(count==-1)
+				return -1;
+			if(flag[c]){ reason = RESET; badflag = c; return -1; }
+			if(count==0){
+				flag[c] = flagset;
+				if(*s=='\0'){
+					for(j = i+1;j<=argc;j++)
+						argv[j-1] = argv[j];
+					--argc;
+				}
+			}
+			else{
+				if(*s=='\0'){
+					for(j = i+1;j<=argc;j++)
+						argv[j-1] = argv[j];
+					--argc;
+					s = argv[i];
+				}
+				if(argc-i<count){
+					reason = FEWARGS;
+					badflag = c;
+					return -1;
+				}
+				reverse(argv+i, argv+argc);
+				reverse(argv+i, argv+argc-count);
+				reverse(argv+argc-count+1, argv+argc);
+				argc-=count;
+				flag[c] = argv+argc+1;
+				flag[c][0] = s;
+				s="";
+			}
+		}
+	}
+	return argc;
+}
+
+static void
+reverse(char **p, char **q)
+{
+	char *t;
+	for(;p<q;p++,--q){ t=*p; *p=*q; *q = t; }
+}
+
+static int
+scanflag(int c, char *f)
+{
+	int fc, count;
+	if(0<=c && c<NFLAG)
+		while(*f){
+			if(*f==' '){
+				f++;
+				continue;
+			}
+			fc=*f++;
+			if(*f==':'){
+				f++;
+				if(*f<'0' || '9'<*f){ reason = FLAGSYN; return -1; }
+				count = 0;
+				while('0'<=*f && *f<='9') count = count*10+*f++-'0';
+			}
+			else
+				count = 0;
+			if(*f=='['){
+				do{
+					f++;
+					if(*f=='\0'){ reason = FLAGSYN; return -1; }
+				}while(*f!=']');
+				f++;
+			}
+			if(c==fc)
+				return count;
+		}
+	reason = BADFLAG;
+	badflag = c;
+	return -1;
+}
+
+void
+usage(char *tail)
+{
+	char *s, *t, c;
+	int count, nflag = 0;
+	switch(reason){
+	case RESET:
+		errs("Flag -");
+		errc(badflag);
+		errs(": set twice\n");
+		break;
+	case FEWARGS:
+		errs("Flag -");
+		errc(badflag);
+		errs(": too few arguments\n");
+		break;
+	case FLAGSYN:
+		errs("Bad argument to getflags!\n");
+		break;
+	case BADFLAG:
+		errs("Illegal flag -");
+		errc(badflag);
+		errc('\n');
+		break;
+	}
+	errs("Usage: ");
+	errs(cmdname);
+	for(s = flagarg;*s;){
+		c=*s;
+		if(*s++==' ')
+			continue;
+		if(*s==':'){
+			s++;
+			count = 0;
+			while('0'<=*s && *s<='9') count = count*10+*s++-'0';
+		}
+		else count = 0;
+		if(count==0){
+			if(nflag==0)
+				errs(" [-");
+			nflag++;
+			errc(c);
+		}
+		if(*s=='['){
+			s++;
+			while(*s!=']' && *s!='\0') s++;
+			if(*s==']')
+				s++;
+		}
+	}
+	if(nflag)
+		errs("]");
+	for(s = flagarg;*s;){
+		c=*s;
+		if(*s++==' ')
+			continue;
+		if(*s==':'){
+			s++;
+			count = 0;
+			while('0'<=*s && *s<='9') count = count*10+*s++-'0';
+		}
+		else count = 0;
+		if(count!=0){
+			errs(" [-");
+			errc(c);
+			if(*s=='['){
+				s++;
+				t = s;
+				while(*s!=']' && *s!='\0') s++;
+				errs(" ");
+				errn(t, s-t);
+				if(*s==']')
+					s++;
+			}
+			else
+				while(count--) errs(" arg");
+			errs("]");
+		}
+		else if(*s=='['){
+			s++;
+			while(*s!=']' && *s!='\0') s++;
+			if(*s==']')
+				s++;
+		}
+	}
+	if(tail){
+		errs(" ");
+		errs(tail);
+	}
+	errs("\n");
+	setstatus("bad flags");
+	Exit();
+}
+
+static void
+errn(char *s, int count)
+{
+	while(count){ errc(*s++); --count; }
+}
+
+static void
+errs(char *s)
+{
+	while(*s) errc(*s++);
+}
+#define	NBUF	80
+static char buf[NBUF], *bufp = buf;
+
+static void
+errc(int c)
+{
+	*bufp++=c;
+	if(bufp==&buf[NBUF] || c=='\n'){
+		Write(2, buf, bufp-buf);
+		bufp = buf;
+	}
+}
--- /dev/null
+++ b/librc/getflags.h
@@ -1,0 +1,7 @@
+#define	NFLAG	128
+
+extern char **flag[NFLAG];
+extern char *cmdname;
+extern char *flagset[];
+
+int getflags(int, char*[], char*, int);
--- /dev/null
+++ b/librc/glob.c
@@ -1,0 +1,259 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+
+/*
+ * delete all the GLOB marks from s, in place
+ */
+char*
+deglob(char *s)
+{
+	char *r = strchr(s, GLOB);
+	if(r){
+		char *w = r++;
+		do{
+			if(*r==GLOB)
+				r++;
+			*w++=*r;
+		}while(*r++);
+	}
+	return s;
+}
+
+static int
+globcmp(const void *s, const void *t)
+{
+	return strcmp(*(char**)s, *(char**)t);
+}
+
+static void
+globsort(word *left, word *right)
+{
+	char **list;
+	word *a;
+	int n = 0;
+	for(a = left;a!=right;a = a->next) n++;
+	list = (char **)emalloc(n*sizeof(char *));
+	for(a = left,n = 0;a!=right;a = a->next,n++) list[n] = a->word;
+	qsort((void *)list, n, sizeof(void *), globcmp);
+	for(a = left,n = 0;a!=right;a = a->next,n++) a->word = list[n];
+	free(list);
+}
+
+/*
+ * Does the string s match the pattern p
+ * . and .. are only matched by patterns starting with .
+ * * matches any sequence of characters
+ * ? matches any single character
+ * [...] matches the enclosed list of characters
+ */
+
+static int
+matchfn(char *s, char *p)
+{
+	if(s[0]=='.' && (s[1]=='\0' || s[1]=='.' && s[2]=='\0') && p[0]!='.')
+		return 0;
+	return match(s, p, '/');
+}
+
+static void
+pappend(char **pdir, char *name)
+{
+	char *path = makepath(*pdir, name);
+	free(*pdir);
+	*pdir = path;
+}
+
+static word*
+globdir(word *list, char *pattern, char *name)
+{
+	char *slash, *glob, *entry;
+	void *dir;
+
+#ifdef Plan9
+	/* append slashes, Readdir() already filtered directories */
+	while(*pattern=='/'){
+		pappend(&name, "/");
+		pattern++;
+	}
+#endif
+	if(*pattern=='\0')
+		return Newword(name, list);
+
+	/* scan the pattern looking for a component with a metacharacter in it */
+	glob=strchr(pattern, GLOB);
+
+	/* If we ran out of pattern, append the name if accessible */
+	if(glob==0){
+		pappend(&name, pattern);
+		if(access(name, 0)==0)
+			return Newword(name, list);
+		goto out;
+	}
+
+	*glob='\0';
+	slash=strrchr(pattern, '/');
+	if(slash){
+		*slash='\0';
+		pappend(&name, pattern);
+		*slash='/';
+		pattern=slash+1;
+	}
+	*glob=GLOB;
+
+	/* read the directory and recur for any entry that matches */
+	dir = Opendir(name[0]?name:".");
+	if(dir==0)
+		goto out;
+	slash=strchr(glob, '/');
+	while((entry=Readdir(dir, slash!=0)) != 0){
+		if(matchfn(entry, pattern))
+			list = globdir(list, slash?slash:"", makepath(name, entry));
+	}
+	Closedir(dir);
+out:
+	free(name);
+	return list;
+}
+
+/*
+ * Subsitute a word with its glob in place.
+ */
+void
+globword(word *w)
+{
+	word *left, *right;
+
+	if(w==0 || strchr(w->word, GLOB)==0)
+		return;
+	right = w->next;
+	left = globdir(right, w->word, estrdup(""));
+	if(left == right) {
+		deglob(w->word);
+	} else {
+		free(w->word);
+		globsort(left, right);
+		w->next = left->next;
+		w->word = Freeword(left);
+	}
+}
+
+/*
+ * Return a pointer to the next utf code in the string,
+ * not jumping past nuls in broken utf codes!
+ */
+static char*
+nextutf(char *p)
+{
+	int i, n, c = *p;
+
+	if(onebyte(c))
+		return p+1;
+	if(twobyte(c))
+		n = 2;
+	else if(threebyte(c))
+		n = 3;
+	else
+		n = 4;
+	for(i = 1; i < n; i++)
+		if(!xbyte(p[i]))
+			break;
+	return p+i;
+}
+
+/*
+ * Convert the utf code at *p to a unicode value
+ */
+static int
+unicode(char *p)
+{
+	int c = *p;
+
+	if(onebyte(c))
+		return c&0xFF;
+	if(twobyte(c)){
+		if(xbyte(p[1]))
+			return ((c&0x1F)<<6) | (p[1]&0x3F);
+	} else if(threebyte(c)){
+		if(xbyte(p[1]) && xbyte(p[2]))
+			return ((c&0x0F)<<12) | ((p[1]&0x3F)<<6) | (p[2]&0x3F);
+	} else if(fourbyte(c)){
+		if(xbyte(p[1]) && xbyte(p[2]) && xbyte(p[3]))
+			return ((c&0x07)<<18) | ((p[1]&0x3F)<<12) | ((p[2]&0x3F)<<6) | (p[3]&0x3F);
+	}
+	return -1;
+}
+
+/*
+ * Do p and q point at equal utf codes
+ */
+static int
+equtf(char *p, char *q)
+{
+	if(*p!=*q)
+ 		return 0;
+	return unicode(p) == unicode(q);
+}
+
+int
+match(char *s, char *p, int stop)
+{
+	int compl, hit, lo, hi, t, c;
+
+	for(; *p!=stop && *p!='\0'; s = nextutf(s), p = nextutf(p)){
+		if(*p!=GLOB){
+			if(!equtf(p, s)) return 0;
+		}
+		else switch(*++p){
+		case GLOB:
+			if(*s!=GLOB)
+				return 0;
+			break;
+		case '*':
+			for(;;){
+				if(match(s, nextutf(p), stop)) return 1;
+				if(!*s)
+					break;
+				s = nextutf(s);
+			}
+			return 0;
+		case '?':
+			if(*s=='\0')
+				return 0;
+			break;
+		case '[':
+			if(*s=='\0')
+				return 0;
+			c = unicode(s);
+			p++;
+			compl=*p=='~';
+			if(compl)
+				p++;
+			hit = 0;
+			while(*p!=']'){
+				if(*p=='\0')
+					return 0;		/* syntax error */
+				lo = unicode(p);
+				p = nextutf(p);
+				if(*p!='-')
+					hi = lo;
+				else{
+					p++;
+					if(*p=='\0')
+						return 0;	/* syntax error */
+					hi = unicode(p);
+					p = nextutf(p);
+					if(hi<lo){ t = lo; lo = hi; hi = t; }
+				}
+				if(lo<=c && c<=hi)
+					hit = 1;
+			}
+			if(compl)
+				hit=!hit;
+			if(!hit)
+				return 0;
+			break;
+		}
+	}
+	return *s=='\0';
+}
--- /dev/null
+++ b/librc/havefork.c
@@ -1,0 +1,240 @@
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+static int *waitpids;
+static int nwaitpids;
+
+void
+addwaitpid(int pid)
+{
+	waitpids = erealloc(waitpids, (nwaitpids+1)*sizeof waitpids[0]);
+	waitpids[nwaitpids++] = pid;
+}
+
+void
+delwaitpid(int pid)
+{
+	int r, w;
+	
+	for(r=w=0; r<nwaitpids; r++)
+		if(waitpids[r] != pid)
+			waitpids[w++] = waitpids[r];
+	nwaitpids = w;
+}
+
+void
+clearwaitpids(void)
+{
+	nwaitpids = 0;
+}
+
+int
+havewaitpid(int pid)
+{
+	int i;
+
+	for(i=0; i<nwaitpids; i++)
+		if(waitpids[i] == pid)
+			return 1;
+	return 0;
+}
+
+void
+Xasync(void)
+{
+	int pid;
+	char npid[10];
+
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
+		runq->ret = 0;
+		break;
+	default:
+		addwaitpid(pid);
+		runq->pc = runq->code[runq->pc].i;
+		inttoascii(npid, pid);
+		setvar("apid", newword(npid, (word *)0));
+		break;
+	}
+}
+
+void
+Xpipe(void)
+{
+	thread *p = runq;
+	int pid, pc = p->pc;
+	int lfd = p->code[pc++].i;
+	int rfd = p->code[pc++].i;
+	int pfd[2];
+
+	if(pipe(pfd)<0){
+		Xerror2("can't get pipe", Errstr());
+		return;
+	}
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		Close(pfd[PRD]);
+		start(p->code, pc+2, runq->local, runq->redir);
+		runq->ret = 0;
+		pushredir(ROPEN, pfd[PWR], lfd);
+		break;
+	default:
+		addwaitpid(pid);
+		Close(pfd[PWR]);
+		start(p->code, p->code[pc].i, runq->local, runq->redir);
+		pushredir(ROPEN, pfd[PRD], rfd);
+		p->pc = p->code[pc+1].i;
+		p->pid = pid;
+		break;
+	}
+}
+
+/*
+ * Who should wait for the exit from the fork?
+ */
+
+void
+Xbackq(void)
+{
+	int pid, pfd[2];
+	char *s, *split;
+	word *end, **link;
+	io *f;
+
+	if(pipe(pfd)<0){
+		Xerror2("can't make pipe", Errstr());
+		return;
+	}
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		Close(pfd[PRD]);
+		Close(pfd[PWR]);
+		return;
+	case 0:
+		clearwaitpids();
+		Close(pfd[PRD]);
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
+		pushredir(ROPEN, pfd[PWR], 1);
+		return;
+	default:
+		addwaitpid(pid);
+		Close(pfd[PWR]);
+
+		split = Popword();
+		poplist();
+		f = openiofd(pfd[PRD]);
+		end = runq->argv->words;
+		link = &runq->argv->words;
+		while((s = rstr(f, split)) != 0){
+			*link = Newword(s, (word*)0);
+			link = &(*link)->next;
+		}
+		*link = end;
+		closeio(f);
+		free(split);
+
+		Waitfor(pid);
+
+		runq->pc = runq->code[runq->pc].i;
+		return;
+	}
+}
+
+void
+Xpipefd(void)
+{
+	thread *p = runq;
+	int pid, pc = p->pc;
+	char name[40];
+	int pfd[2];
+	int sidefd, mainfd;
+
+	if(pipe(pfd)<0){
+		Xerror2("can't get pipe", Errstr());
+		return;
+	}
+	if(p->code[pc].i==READ){
+		sidefd = pfd[PWR];
+		mainfd = pfd[PRD];
+	}
+	else{
+		sidefd = pfd[PRD];
+		mainfd = pfd[PWR];
+	}
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		Close(mainfd);
+		start(p->code, pc+2, runq->local, runq->redir);
+		pushredir(ROPEN, sidefd, p->code[pc].i==READ?1:0);
+		runq->ret = 0;
+		break;
+	default:
+		addwaitpid(pid);
+		Close(sidefd);
+		pushredir(ROPEN, mainfd, mainfd);
+		shuffleredir();	/* shuffle redir to bottom of stack for Xpopredir() */
+		strcpy(name, Fdprefix);
+		inttoascii(name+strlen(name), mainfd);
+		pushword(name);
+		p->pc = p->code[pc+1].i;
+		break;
+	}
+}
+
+void
+Xsubshell(void)
+{
+	int pid;
+
+	switch(pid = Fork()){
+	case -1:
+		Xerror2("try again", Errstr());
+		break;
+	case 0:
+		clearwaitpids();
+		start(runq->code, runq->pc+1, runq->local, runq->redir);
+		runq->ret = 0;
+		break;
+	default:
+		addwaitpid(pid);
+		while(Waitfor(pid) < 0)
+			;
+		runq->pc = runq->code[runq->pc].i;
+		break;
+	}
+}
+
+int
+execforkexec(void)
+{
+	int pid;
+
+	switch(pid = Fork()){
+	case -1:
+		return -1;
+	case 0:
+		clearwaitpids();
+		pushword("exec");
+		execexec();
+		/* does not return */
+	}
+	addwaitpid(pid);
+	return pid;
+}
--- /dev/null
+++ b/librc/here.c
@@ -1,0 +1,137 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+void psubst(io*, unsigned char*);
+void pstrs(io*, word*);
+
+static char*
+readhere1(tree *tag, io *in)
+{
+	io *out;
+	char c, *m;
+
+	pprompt();
+	out = openiostr();
+	m = tag->str;
+	while((c = rchr(in)) != EOF){
+		if(c=='\0'){
+			yyerror("NUL bytes in here doc");
+			closeio(out);
+			return 0;
+		}
+		if(c=='\n'){
+			lex->line++;
+			if(m && *m=='\0'){
+				out->bufp -= m - tag->str;
+				*out->bufp='\0';
+				break;
+			}
+			pprompt();
+			m = tag->str;
+		} else if(m){
+			if(*m == c){
+				m++;
+			} else {
+				m = 0;
+			}
+		}
+		pchr(out, c);
+	}
+	doprompt = 1;
+	return closeiostr(out);
+}
+
+static tree *head, *tail;
+
+void
+heredoc(tree *redir)
+{
+	if(redir->child[0]->type!=WORD){
+		yyerror("Bad here tag");
+		return;
+	}
+	redir->child[2]=0;
+	if(head)
+		tail->child[2]=redir;
+	else
+		head=redir;
+	tail=redir;
+}
+
+void
+readhere(io *in)
+{
+	while(head){
+		tail=head->child[2];
+		head->child[2]=0;
+		head->str=readhere1(head->child[0], in);
+		head=tail;
+	}
+}
+
+void
+psubst(io *f, unsigned char *s)
+{
+	unsigned char *t, *u;
+	word *star;
+	int savec, n;
+
+	while(*s){
+		if(*s!='$'){
+			if(0xa0 <= *s && *s <= 0xf5){
+				pchr(f, *s++);
+				if(*s=='\0')
+					break;
+			}
+			else if(0xf6 <= *s && *s <= 0xf7){
+				pchr(f, *s++);
+				if(*s=='\0')
+					break;
+				pchr(f, *s++);
+				if(*s=='\0')
+					break;
+			}
+			pchr(f, *s++);
+		}
+		else{
+			t=++s;
+			if(*t=='$')
+				pchr(f, *t++);
+			else{
+				while(*t && idchr(*t)) t++;
+				savec=*t;
+				*t='\0';
+				n = 0;
+				for(u = s;*u && '0'<=*u && *u<='9';u++) n = n*10+*u-'0';
+				if(n && *u=='\0'){
+					star = vlook("*")->val;
+					if(star && 1<=n && n<=count(star)){
+						while(--n) star = star->next;
+						pstr(f, star->word);
+					}
+				}
+				else
+					pstrs(f, vlook((char *)s)->val);
+				*t = savec;
+				if(savec=='^')
+					t++;
+			}
+			s = t;
+		}
+	}
+}
+
+void
+pstrs(io *f, word *a)
+{
+	if(a){
+		while(a->next && a->next->word){
+			pstr(f, a->word);
+			pchr(f, ' ');
+			a = a->next;
+		}
+		pstr(f, a->word);
+	}
+}
--- /dev/null
+++ b/librc/io.c
@@ -1,0 +1,302 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+enum {
+	NBUF = 8192,
+};
+
+void
+vpfmt(io *f, char *fmt, va_list ap)
+{
+	for(;*fmt;fmt++) {
+		if(*fmt!='%') {
+			pchr(f, *fmt);
+			continue;
+		}
+		if(*++fmt == '\0')		/* "blah%"? */
+			break;
+		switch(*fmt){
+		case 'c':
+			pchr(f, va_arg(ap, int));
+			break;
+		case 'd':
+			pdec(f, va_arg(ap, int));
+			break;
+		case 'o':
+			poct(f, va_arg(ap, unsigned));
+			break;
+		case 'p':
+			pptr(f, va_arg(ap, void*));
+			break;
+		case 'Q':
+			pquo(f, va_arg(ap, char *));
+			break;
+		case 'q':
+			pwrd(f, va_arg(ap, char *));
+			break;
+		case 's':
+			pstr(f, va_arg(ap, char *));
+			break;
+		case 't':
+			pcmd(f, va_arg(ap, tree *));
+			break;
+		case 'v':
+			pval(f, va_arg(ap, word *));
+			break;
+		default:
+			pchr(f, *fmt);
+			break;
+		}
+	}
+}
+
+void
+pfmt(io *f, char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	vpfmt(f, fmt, ap);
+	va_end(ap);
+}
+
+void
+pchr(io *b, int c)
+{
+	if(b->bufp>=b->ebuf)
+		flushio(b);
+	*b->bufp++=c;
+}
+
+int
+rchr(io *b)
+{
+	if(b->bufp>=b->ebuf)
+		return emptyiobuf(b);
+	return *b->bufp++;
+}
+
+char*
+rstr(io *b, char *stop)
+{
+	char *s, *p;
+	int l, m, n;
+
+	do {
+		l = rchr(b);
+		if(l == EOF)
+			return 0;
+	} while(l && strchr(stop, l));
+	b->bufp--;
+
+	s = 0;
+	l = 0;
+	for(;;){
+		p = (char*)b->bufp;
+		n = (char*)b->ebuf - p;
+		if(n > 0){
+			for(m = 0; m < n; m++){
+				if(strchr(stop, p[m])==0)
+					continue;
+
+				b->bufp += m+1;
+				if(m > 0 || s==0){
+					s = erealloc(s, l+m+1);
+					memmove(s+l, p, m);
+					l += m;
+				}
+				s[l]='\0';
+				return s;
+			}
+			s = erealloc(s, l+m+1);
+			memmove(s+l, p, m);
+			l += m;
+			b->bufp += m;
+		}
+		if(emptyiobuf(b) == EOF){
+			if(s) s[l]='\0';
+			return s;
+		}
+		b->bufp--;
+	}
+}
+
+void
+pquo(io *f, char *s)
+{
+	pchr(f, '\'');
+	for(;*s;s++){
+		if(*s=='\'')
+			pchr(f, *s);
+		pchr(f, *s);
+	}
+	pchr(f, '\'');
+}
+
+void
+pwrd(io *f, char *s)
+{
+	char *t;
+	for(t = s;*t;t++) if(*t >= 0 && (*t <= ' ' || strchr("`^#*[]=|\\?${}()'<>&;", *t))) break;
+	if(t==s || *t)
+		pquo(f, s);
+	else pstr(f, s);
+}
+
+void
+pptr(io *f, void *p)
+{
+	static char hex[] = "0123456789ABCDEF";
+	unsigned long long v;
+	int n;
+
+	v = (unsigned long long)p;
+	if(sizeof(v) == sizeof(p) && v>>32)
+		for(n = 60;n>=32;n-=4) pchr(f, hex[(v>>n)&0xF]);
+	for(n = 28;n>=0;n-=4) pchr(f, hex[(v>>n)&0xF]);
+}
+
+void
+pstr(io *f, char *s)
+{
+	if(s==0)
+		s="(null)";
+	while(*s) pchr(f, *s++);
+}
+
+void
+pdec(io *f, int n)
+{
+	if(n<0){
+		n=-n;
+		if(n>=0){
+			pchr(f, '-');
+			pdec(f, n);
+			return;
+		}
+		/* n is two's complement minimum integer */
+		n = 1-n;
+		pchr(f, '-');
+		pdec(f, n/10);
+		pchr(f, n%10+'1');
+		return;
+	}
+	if(n>9)
+		pdec(f, n/10);
+	pchr(f, n%10+'0');
+}
+
+void
+poct(io *f, unsigned n)
+{
+	if(n>7)
+		poct(f, n>>3);
+	pchr(f, (n&7)+'0');
+}
+
+void
+pval(io *f, word *a)
+{
+	if(a==0)
+		return;
+	while(a->next && a->next->word){
+		pwrd(f, (char *)a->word);
+		pchr(f, ' ');
+		a = a->next;
+	}
+	pwrd(f, (char *)a->word);
+}
+
+io*
+newio(unsigned char *buf, int len, int fd)
+{
+	io *f = new(io);
+	f->buf = buf;
+	f->bufp = buf;
+	f->ebuf = buf+len;
+	f->fd = fd;
+	return f;
+}
+
+/*
+ * Open a string buffer for writing.
+ */
+io*
+openiostr(void)
+{
+	unsigned char *buf = emalloc(100+1);
+	memset(buf, '\0', 100+1);
+	return newio(buf, 100, -1);
+}
+
+/*
+ * Return the buf, free the io
+ */
+char*
+closeiostr(io *f)
+{
+	void *buf = f->buf;
+	free(f);
+	return buf;
+}
+
+/*
+ * Use a open file descriptor for reading.
+ */
+io*
+openiofd(int fd)
+{
+	return newio(emalloc(NBUF), 0, fd);
+}
+
+/*
+ * Open a corebuffer to read.  EOF occurs after reading len
+ * characters from buf.
+ */
+io*
+openiocore(void *buf, int len)
+{
+	return newio(buf, len, -1);
+}
+
+void
+flushio(io *f)
+{
+	int n;
+
+	if(f->fd<0){
+		n = f->ebuf - f->buf;
+		f->buf = erealloc(f->buf, n+n+1);
+		f->bufp = f->buf + n;
+		f->ebuf = f->bufp + n;
+		memset(f->bufp, '\0', n+1);
+	}
+	else{
+		n = f->bufp - f->buf;
+		if(n && Write(f->fd, f->buf, n) != n){
+			Write(2, "Write error\n", 12);
+			if(ntrap)
+				dotrap();
+		}
+		f->bufp = f->buf;
+		f->ebuf = f->buf+NBUF;
+	}
+}
+
+void
+closeio(io *f)
+{
+	if(f->fd>=0) Close(f->fd);
+	free(closeiostr(f));
+}
+
+int
+emptyiobuf(io *f)
+{
+	int n;
+	if(f->fd<0 || (n = Read(f->fd, f->buf, NBUF))<=0) return EOF;
+	f->bufp = f->buf;
+	f->ebuf = f->buf + n;
+	return *f->bufp++;
+}
--- /dev/null
+++ b/librc/io.h
@@ -1,0 +1,28 @@
+#define	EOF	(-1)
+
+struct io{
+	int	fd;
+	unsigned char *buf, *bufp, *ebuf;
+	io	*next;
+};
+
+io *openiofd(int), *openiostr(void), *openiocore(void*, int);
+void pchr(io*, int);
+int rchr(io*);
+char *rstr(io*, char*);
+char *closeiostr(io*);
+void closeio(io*);
+int emptyiobuf(io*);
+void flushio(io*);
+void pdec(io*, int);
+void poct(io*, unsigned);
+void pptr(io*, void*);
+void pquo(io*, char*);
+void pwrd(io*, char*);
+void pstr(io*, char*);
+void pcmd(io*, tree*);
+void pval(io*, word*);
+void pfun(io*, void(*)(void));
+void pfnc(io*, thread*);
+void pfmt(io*, char*, ...);
+void vpfmt(io*, char*, va_list);
--- /dev/null
+++ b/librc/lex.c
@@ -1,0 +1,435 @@
+#include "rc.h"
+#include "io.h"
+#include "getflags.h"
+#include "fns.h"
+
+lexer *lex;
+
+int doprompt = 1;
+int nerror;
+
+int
+wordchr(int c)
+{
+	return !strchr("\n \t#;&|^$=`'{}()<>", c) && c!=EOF;
+}
+
+int
+idchr(int c)
+{
+	/*
+	 * Formerly:
+	 * return 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9'
+	 *	|| c=='_' || c=='*';
+	 */
+	return c>' ' && !strchr("!\"#$%&'()+,-./:;<=>?@[\\]^`{|}~", c);
+}
+
+lexer*
+newlexer(io *input, char *file)
+{
+	lexer *n = new(struct lexer);
+	n->input = input;
+	n->file = file;
+	n->line = 1;
+	n->eof = 0;
+	n->future = EOF;
+	n->peekc = '{';
+	n->epilog = "}\n";
+	n->lastc = 0;
+	n->inquote = 0;
+	n->incomm = 0;
+	n->lastword = 0;
+	n->lastdol = 0;
+	n->iflast = 0;
+	n->qflag = 0;
+	n->tok[0] = 0;
+	return n;
+}
+
+void
+freelexer(lexer *p)
+{
+	closeio(p->input);
+	free(p->file);
+	free(p);
+}
+
+/*
+ * read a character from the input stream
+ */	
+static int
+getnext(void)
+{
+	int c;
+
+	if(lex->peekc!=EOF){
+		c = lex->peekc;
+		lex->peekc = EOF;
+		return c;
+	}
+	if(lex->eof){
+epilog:
+		if(*lex->epilog)
+			return *lex->epilog++;
+		doprompt = 1;
+		return EOF;
+	}
+	if(doprompt)
+		pprompt();
+	c = rchr(lex->input);
+	if(c=='\\' && !lex->inquote){
+		c = rchr(lex->input);
+		if(c=='\n' && !lex->incomm){		/* don't continue a comment */
+			doprompt = 1;
+			c=' ';
+		}
+		else{
+			lex->peekc = c;
+			c='\\';
+		}
+	}
+	if(c==EOF){
+		lex->eof = 1;
+		goto epilog;
+	} else {
+		if(c=='\n')
+			doprompt = 1;
+		if((!lex->qflag && flag['v']!=0) || flag['V'])
+			pchr(err, c);
+	}
+	return c;
+}
+
+/*
+ * Look ahead in the input stream
+ */
+static int
+nextc(void)
+{
+	if(lex->future==EOF)
+		lex->future = getnext();
+	return lex->future;
+}
+
+/*
+ * Consume the lookahead character.
+ */
+static int
+advance(void)
+{
+	int c = nextc();
+	lex->lastc = lex->future;
+	lex->future = EOF;
+	if(c == '\n')
+		lex->line++;
+	return c;
+}
+
+static void
+skipwhite(void)
+{
+	int c;
+	for(;;){
+		c = nextc();
+		/* Why did this used to be  if(!inquote && c=='#') ?? */
+		if(c=='#'){
+			lex->incomm = 1;
+			for(;;){
+				c = nextc();
+				if(c=='\n' || c==EOF) {
+					lex->incomm = 0;
+					break;
+				}
+				advance();
+			}
+		}
+		if(c==' ' || c=='\t')
+			advance();
+		else return;
+	}
+}
+
+void
+skipnl(void)
+{
+	int c;
+	for(;;){
+		skipwhite();
+		c = nextc();
+		if(c!='\n')
+			return;
+		advance();
+	}
+}
+
+static int
+nextis(int c)
+{
+	if(nextc()==c){
+		advance();
+		return 1;
+	}
+	return 0;
+}
+
+static char*
+addtok(char *p, int val)
+{
+	if(p==0)
+		return 0;
+	if(p==&lex->tok[NTOK-1]){
+		*p = 0;
+		yyerror("token buffer too short");
+		return 0;
+	}
+	*p++=val;
+	return p;
+}
+
+static char*
+addutf(char *p, int c)
+{
+	int i, n;
+
+	p = addtok(p, c);	/* 1-byte UTF runes are special */
+	if(onebyte(c))
+		return p;
+	if(twobyte(c))
+		n = 2;
+	else if(threebyte(c))
+		n = 3;
+	else
+		n = 4;
+	for(i = 1; i < n; i++) {
+		c = nextc();
+		if(c == EOF || !xbyte(c))
+			break;
+		p = addtok(p, advance());
+	}
+	return p;
+}
+
+int
+yylex(void)
+{
+	int glob, c, d = nextc();
+	char *tok = lex->tok;
+	char *w = tok;
+	tree *t;
+
+	yylval.tree = 0;
+
+	/*
+	 * Embarassing sneakiness:  if the last token read was a quoted or unquoted
+	 * WORD then we alter the meaning of what follows.  If the next character
+	 * is `(', we return SUB (a subscript paren) and consume the `('.  Otherwise,
+	 * if the next character is the first character of a simple or compound word,
+	 * we insert a `^' before it.
+	 */
+	if(lex->lastword){
+		lex->lastword = 0;
+		if(d=='('){
+			advance();
+			strcpy(tok, "( [SUB]");
+			return SUB;
+		}
+		if(wordchr(d) || d=='\'' || d=='`' || d=='$' || d=='"'){
+			strcpy(tok, "^");
+			return '^';
+		}
+	}
+	lex->inquote = 0;
+	skipwhite();
+	switch(c = advance()){
+	case EOF:
+		lex->lastdol = 0;
+		strcpy(tok, "EOF");
+		return EOF;
+	case '$':
+		lex->lastdol = 1;
+		if(nextis('#')){
+			strcpy(tok, "$#");
+			return COUNT;
+		}
+		if(nextis('"')){
+			strcpy(tok, "$\"");
+			return '"';
+		}
+		strcpy(tok, "$");
+		return '$';
+	case '&':
+		lex->lastdol = 0;
+		if(nextis('&')){
+			skipnl();
+			strcpy(tok, "&&");
+			return ANDAND;
+		}
+		strcpy(tok, "&");
+		return '&';
+	case '|':
+		lex->lastdol = 0;
+		if(nextis(c)){
+			skipnl();
+			strcpy(tok, "||");
+			return OROR;
+		}
+	case '<':
+	case '>':
+		lex->lastdol = 0;
+		/*
+		 * funny redirection tokens:
+		 *	redir:	arrow | arrow '[' fd ']'
+		 *	arrow:	'<' | '<<' | '>' | '>>' | '|'
+		 *	fd:	digit | digit '=' | digit '=' digit
+		 *	digit:	'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
+		 * some possibilities are nonsensical and get a message.
+		 */
+		*w++=c;
+		t = newtree();
+		switch(c){
+		case '|':
+			t->type = PIPE;
+			t->fd0 = 1;
+			t->fd1 = 0;
+			break;
+		case '>':
+			t->type = REDIR;
+			if(nextis(c)){
+				t->rtype = APPEND;
+				*w++=c;
+			}
+			else t->rtype = WRITE;
+			t->fd0 = 1;
+			break;
+		case '<':
+			t->type = REDIR;
+			if(nextis(c)){
+				t->rtype = HERE;
+				*w++=c;
+			} else if (nextis('>')){
+				t->rtype = RDWR;
+				*w++=c;
+			} else t->rtype = READ;
+			t->fd0 = 0;
+			break;
+		}
+		if(nextis('[')){
+			*w++='[';
+			c = advance();
+			*w++=c;
+			if(c<'0' || '9'<c){
+			RedirErr:
+				*w = 0;
+				yyerror(t->type==PIPE?"pipe syntax"
+						:"redirection syntax");
+				return EOF;
+			}
+			t->fd0 = 0;
+			do{
+				t->fd0 = t->fd0*10+c-'0';
+				*w++=c;
+				c = advance();
+			}while('0'<=c && c<='9');
+			if(c=='='){
+				*w++='=';
+				if(t->type==REDIR)
+					t->type = DUP;
+				c = advance();
+				if('0'<=c && c<='9'){
+					t->rtype = DUPFD;
+					t->fd1 = t->fd0;
+					t->fd0 = 0;
+					do{
+						t->fd0 = t->fd0*10+c-'0';
+						*w++=c;
+						c = advance();
+					}while('0'<=c && c<='9');
+				}
+				else{
+					if(t->type==PIPE)
+						goto RedirErr;
+					t->rtype = CLOSE;
+				}
+			}
+			if(c!=']'
+			|| t->type==DUP && (t->rtype==HERE || t->rtype==APPEND))
+				goto RedirErr;
+			*w++=']';
+		}
+		*w='\0';
+		yylval.tree = t;
+		if(t->type==PIPE)
+			skipnl();
+		return t->type;
+	case '\'':
+		lex->lastdol = 0;
+		lex->lastword = 1;
+		lex->inquote = 1;
+		for(;;){
+			c = advance();
+			if(c==EOF)
+				break;
+			if(c=='\''){
+				if(nextc()!='\'')
+					break;
+				advance();
+			}
+			w = addutf(w, c);
+		}
+		if(w!=0)
+			*w='\0';
+		t = token(tok, WORD);
+		t->quoted = 1;
+		yylval.tree = t;
+		return t->type;
+	}
+	if(!wordchr(c)){
+		lex->lastdol = 0;
+		tok[0] = c;
+		tok[1]='\0';
+		return c;
+	}
+	glob = 0;
+	for(;;){
+		if(c=='*' || c=='[' || c=='?' || c==GLOB){
+			glob = 1;
+			w = addtok(w, GLOB);
+		}
+		w = addutf(w, c);
+		c = nextc();
+		if(lex->lastdol?!idchr(c):!wordchr(c)) break;
+		advance();
+	}
+
+	lex->lastword = 1;
+	lex->lastdol = 0;
+	if(w!=0)
+		*w='\0';
+	t = klook(tok);
+	if(t->type!=WORD)
+		lex->lastword = 0;
+	else
+		t->glob = glob;
+	t->quoted = 0;
+	yylval.tree = t;
+	return t->type;
+}
+
+void
+yyerror(char *m)
+{
+	pfln(err, lex->file, lex->line);
+	pstr(err, ": ");
+	if(lex->tok[0] && lex->tok[0]!='\n')
+		pfmt(err, "token %q: ", lex->tok);
+	pfmt(err, "%s\n", m);
+	flushio(err);
+
+	lex->lastword = 0;
+	lex->lastdol = 0;
+	while(lex->lastc!='\n' && lex->lastc!=EOF) advance();
+	nerror++;
+
+	setstatus(m);
+}
--- /dev/null
+++ b/librc/mkfile
@@ -1,0 +1,46 @@
+</$objtype/mkfile
+
+TARG=rc
+OFILES=\
+	code.$O\
+	exec.$O\
+	getflags.$O\
+	glob.$O\
+	here.$O\
+	io.$O\
+	lex.$O\
+	pcmd.$O\
+	pfnc.$O\
+	simple.$O\
+	subr.$O\
+	trap.$O\
+	tree.$O\
+	var.$O\
+	havefork.$O\
+	plan9.$O\
+	y.tab.$O\
+
+HFILES=rc.h\
+	y.tab.h\
+	io.h\
+	exec.h\
+	fns.h\
+	getflags.h\
+
+YFILES=syn.y
+
+BIN=/$objtype/bin
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	$YFILES\
+	${TARG:%=/386/bin/%}\
+
+CFLAGS=$CFLAGS -DPlan9
+
+</sys/src/cmd/mkone
+
+clean:V:
+	rm -f [$OS].out *.[$OS] y.tab.? y.debug $TARG
--- /dev/null
+++ b/librc/pcmd.c
@@ -1,0 +1,169 @@
+#include "rc.h"
+#include "io.h"
+#include "fns.h"
+
+#define	c0	t->child[0]
+#define	c1	t->child[1]
+#define	c2	t->child[2]
+
+static void
+pdeglob(io *f, char *s)
+{
+	while(*s){
+		if(*s==GLOB)
+			s++;
+		pchr(f, *s++);
+	}
+}
+
+static int ntab = 0;
+
+static char*
+tabs(void)
+{
+	return "\t\t\t\t\t\t\t\t"+8-(ntab%8);
+}
+
+void
+pcmd(io *f, tree *t)
+{
+	if(t==0)
+		return;
+	switch(t->type){
+	default:	pfmt(f, "bad %d %p %p %p", t->type, c0, c1, c2);
+	break;
+	case '$':	pfmt(f, "$%t", c0);
+	break;
+	case '"':	pfmt(f, "$\"%t", c0);
+	break;
+	case '&':	pfmt(f, "%t&", c0);
+	break;
+	case '^':	pfmt(f, "%t^%t", c0, c1);
+	break;
+	case '`':	pfmt(f, "`%t%t", c0, c1);
+	break;
+	case ANDAND:	pfmt(f, "%t && %t", c0, c1);
+	break;
+	case BANG:	pfmt(f, "! %t", c0);
+	break;
+	case BRACE:
+			ntab++;
+			pfmt(f, "{\n%s%t", tabs(), c0);
+			ntab--;
+			pfmt(f, "\n%s}", tabs());
+	break;
+	case COUNT:	pfmt(f, "$#%t", c0);
+	break;
+	case FN:	pfmt(f, "fn %t %t", c0, c1);
+	break;
+	case IF:	pfmt(f, "if%t%t", c0, c1);
+	break;
+	case NOT:	pfmt(f, "if not %t", c0);
+	break;
+	case OROR:	pfmt(f, "%t || %t", c0, c1);
+	break;
+	case PCMD:
+	case PAREN:	pfmt(f, "(%t)", c0);
+	break;
+	case SUB:	pfmt(f, "$%t(%t)", c0, c1);
+	break;
+	case SIMPLE:	pfmt(f, "%t", c0);
+	break;
+	case SUBSHELL:	pfmt(f, "@ %t", c0);
+	break;
+	case SWITCH:	pfmt(f, "switch %t %t", c0, c1);
+	break;
+	case TWIDDLE:	pfmt(f, "~ %t %t", c0, c1);
+	break;
+	case WHILE:	pfmt(f, "while %t%t", c0, c1);
+	break;
+	case ARGLIST:
+		if(c0==0)
+			pfmt(f, "%t", c1);
+		else if(c1==0)
+			pfmt(f, "%t", c0);
+		else
+			pfmt(f, "%t %t", c0, c1);
+		break;
+	case ';':
+		if(c0){
+			pfmt(f, "%t", c0);
+			if(c1){
+				if(c0->line==c1->line)
+					pstr(f, "; ");
+				else
+					pfmt(f, "\n%s", tabs());
+				pfmt(f, "%t", c1);
+			}
+		}
+		else pfmt(f, "%t", c1);
+		break;
+	case WORDS:
+		if(c0)
+			pfmt(f, "%t ", c0);
+		pfmt(f, "%t", c1);
+		break;
+	case FOR:
+		pfmt(f, "for(%t", c0);
+		if(c1)
+			pfmt(f, " in %t", c1);
+		pfmt(f, ")%t", c2);
+		break;
+	case WORD:
+		if(t->quoted)
+			pfmt(f, "%Q", t->str);
+		else pdeglob(f, t->str);
+		break;
+	case DUP:
+		if(t->rtype==DUPFD)
+			pfmt(f, ">[%d=%d]", t->fd1, t->fd0); /* yes, fd1, then fd0; read lex.c */
+		else
+			pfmt(f, ">[%d=]", t->fd0);
+		pfmt(f, "%t", c1);
+		break;
+	case PIPEFD:
+	case REDIR:
+		pchr(f, ' ');
+		switch(t->rtype){
+		case HERE:
+			if(c1)
+				pfmt(f, "%t ", c1);
+			pchr(f, '<');
+		case READ:
+		case RDWR:
+			pchr(f, '<');
+			if(t->rtype==RDWR)
+				pchr(f, '>');
+			if(t->fd0!=0)
+				pfmt(f, "[%d]", t->fd0);
+			break;
+		case APPEND:
+			pchr(f, '>');
+		case WRITE:
+			pchr(f, '>');
+			if(t->fd0!=1)
+				pfmt(f, "[%d]", t->fd0);
+			break;
+		}
+		pfmt(f, "%t", c0);
+		if(t->rtype == HERE)
+			pfmt(f, "\n%s%s\n", t->str, c0->str);
+		else if(c1)
+			pfmt(f, " %t", c1);
+		break;
+	case '=':
+		pfmt(f, "%t=%t", c0, c1);
+		if(c2)
+			pfmt(f, " %t", c2);
+		break;
+	case PIPE:
+		pfmt(f, "%t|", c0);
+		if(t->fd1==0){
+			if(t->fd0!=1)
+				pfmt(f, "[%d]", t->fd0);
+		}
+		else pfmt(f, "[%d=%d]", t->fd0, t->fd1);
+		pfmt(f, "%t", c1);
+		break;
+	}
+}
--- /dev/null
+++ b/librc/pfnc.c
@@ -1,0 +1,78 @@
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+struct{
+	void (*f)(void);
+	char *name;
+}fname[] = {
+	Xappend, "Xappend",
+	Xasync, "Xasync",
+	Xbang, "Xbang",
+	Xclose, "Xclose",
+	Xdup, "Xdup",
+	Xeflag, "Xeflag",
+	Xexit, "Xexit",
+	Xfalse, "Xfalse",
+	Xifnot, "Xifnot",
+	Xjump, "Xjump",
+	Xmark, "Xmark",
+	Xpopm, "Xpopm",
+	Xpush, "Xpush",
+	Xrdwr, "Xrdwr",
+	Xread, "Xread",
+	Xhere, "Xhere",
+	Xhereq, "Xhereq",
+	Xreturn, "Xreturn",
+	Xtrue, "Xtrue",
+	Xif, "Xif",
+	Xwastrue, "Xwastrue",
+	Xword, "Xword",
+	Xwrite, "Xwrite",
+	Xmatch, "Xmatch",
+	Xcase, "Xcase",
+	Xconc, "Xconc",
+	Xassign, "Xassign",
+	Xdol, "Xdol",
+	Xcount, "Xcount",
+	Xlocal, "Xlocal",
+	Xunlocal, "Xunlocal",
+	Xfn, "Xfn",
+	Xdelfn, "Xdelfn",
+	Xpipe, "Xpipe",
+	Xpipewait, "Xpipewait",
+	Xpopredir, "Xpopredir",
+	Xrdcmds, "Xrdcmds",
+	Xbackq, "Xbackq",
+	Xpipefd, "Xpipefd",
+	Xsubshell, "Xsubshell",
+	Xfor, "Xfor",
+	Xglob, "Xglob",
+	Xsimple, "Xsimple",
+	Xqw, "Xqw",
+	Xsrcline, "Xsrcline",
+0};
+
+void
+pfun(io *f, void (*fn)(void))
+{
+	int i;
+	for(i = 0;fname[i].f;i++) if(fname[i].f==fn){
+		pstr(f, fname[i].name);
+		return;
+	}
+	pfmt(f, "%p", fn);
+}
+
+void
+pfnc(io *f, thread *t)
+{
+	list *a;
+
+	pfln(f, srcfile(t), t->line);
+	pfmt(f, " pid %d cycle %p %d ", getpid(), t->code, t->pc);
+	pfun(f, t->code[t->pc].f);
+	for(a = t->argv;a;a = a->next) pfmt(f, " (%v)", a->words);
+	pchr(f, '\n');
+	flushio(f);
+}
--- /dev/null
+++ b/librc/plan9.c
@@ -1,0 +1,494 @@
+/*
+ * Plan 9 versions of system-specific functions
+ *	By convention, exported routines herein have names beginning with an
+ *	upper case letter.
+ */
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+#include "getflags.h"
+
+static void execrfork(void);
+static void execfinit(void);
+
+builtin Builtin[] = {
+	"cd",		execcd,
+	"whatis",	execwhatis,
+	"eval",		execeval,
+	"exec",		execexec,	/* but with popword first */
+	"exit",		execexit,
+	"shift",	execshift,
+	"wait",		execwait,
+	".",		execdot,
+	"flag",		execflag,
+	"finit",	execfinit,
+	"rfork",	execrfork,
+	0
+};
+
+char Rcmain[]="/rc/lib/rcmain";
+char Fdprefix[]="/fd/";
+
+char *Signame[] = {
+	"sigexit",	"sighup",	"sigint",	"sigquit",
+	"sigalrm",	"sigkill",	"sigfpe",	"sigterm",
+	0
+};
+static char *syssigname[] = {
+	"exit",		/* can't happen */
+	"hangup",
+	"interrupt",
+	"quit",		/* can't happen */
+	"alarm",
+	"kill",
+	"sys: fp: ",
+	"term",
+	0
+};
+
+/*
+ * finit could be removed but is kept for
+ * backwards compatibility, see: rcmain.plan9
+ */
+static void
+execfinit(void)
+{
+	char *cmds = estrdup("for(i in '/env/fn#'*){. -bq $i}\n");
+	int line = runq->line;
+	poplist();
+	execcmds(openiocore(cmds, strlen(cmds)), estrdup(srcfile(runq)), runq->local, runq->redir);
+	runq->lex->line = line;
+	runq->lex->qflag = 1;
+}
+
+static void
+execrfork(void)
+{
+	int arg;
+	char *s;
+
+	switch(count(runq->argv->words)){
+	case 1:
+		arg = RFENVG|RFNAMEG|RFNOTEG;
+		break;
+	case 2:
+		arg = 0;
+		for(s = runq->argv->words->next->word;*s;s++) switch(*s){
+		default:
+			goto Usage;
+		case 'n':
+			arg|=RFNAMEG;  break;
+		case 'N':
+			arg|=RFCNAMEG;
+			break;
+		case 'm':
+			arg|=RFNOMNT;  break;
+		case 'e':
+			arg|=RFENVG;   break;
+		case 'E':
+			arg|=RFCENVG;  break;
+		case 's':
+			arg|=RFNOTEG;  break;
+		case 'f':
+			arg|=RFFDG;    break;
+		case 'F':
+			arg|=RFCFDG;   break;
+		}
+		break;
+	default:
+	Usage:
+		pfmt(err, "Usage: %s [fnesFNEm]\n", runq->argv->words->word);
+		setstatus("rfork usage");
+		poplist();
+		return;
+	}
+	if(rfork(arg)==-1){
+		pfmt(err, "%s: %s failed\n", argv0, runq->argv->words->word);
+		setstatus("rfork failed");
+	} else {
+		if(arg & RFCFDG){
+			redir *rp;
+			for(rp = runq->redir; rp; rp = rp->next)
+				rp->type = 0;
+		}
+		setstatus("");
+	}
+	poplist();
+}
+
+char*
+Env(char *name, int fn)
+{
+	static char buf[128];
+
+	strcpy(buf, "/env/");
+	if(fn) strcat(buf, "fn#");
+	return strncat(buf, name, sizeof(buf)-1);
+}
+
+void
+Vinit(void)
+{
+	int dir, fd, i, n;
+	Dir *ent;
+
+	dir = Open(Env("", 0), 0);
+	if(dir<0){
+		pfmt(err, "%s: can't open: %s\n", argv0, Errstr());
+		return;
+	}
+	for(;;){
+		ent = 0;
+		n = dirread(dir, &ent);
+		if(n <= 0)
+			break;
+		for(i = 0; i<n; i++){
+			if(ent[i].length<=0 || strncmp(ent[i].name, "fn#", 3)==0)
+				continue;
+			if((fd = Open(Env(ent[i].name, 0), 0))>=0){
+				io *f = openiofd(fd);
+				word *w = 0, **wp = &w;
+				char *s;
+				while((s = rstr(f, "")) != 0){
+					*wp = Newword(s, (word*)0);
+					wp = &(*wp)->next;
+				}
+				closeio(f);
+				setvar(ent[i].name, w);
+				vlook(ent[i].name)->changed = 0;
+			}
+		}
+		free(ent);
+	}
+	Close(dir);
+}
+
+char*
+Errstr(void)
+{
+	static char err[ERRMAX];
+	rerrstr(err, sizeof err);
+	return err;
+}
+
+int
+Waitfor(int pid)
+{
+	thread *p;
+	Waitmsg *w;
+
+	if(pid >= 0 && !havewaitpid(pid))
+		return 0;
+
+	while((w = wait()) != nil){
+		delwaitpid(w->pid);
+		if(w->pid==pid){
+			setstatus(w->msg);
+			free(w);
+			return 0;
+		}
+		for(p = runq->ret;p;p = p->ret)
+			if(p->pid==w->pid){
+				p->pid=-1;
+				p->status = estrdup(w->msg);
+				break;
+			}
+		free(w);
+	}
+
+	if(strcmp(Errstr(), "interrupted")==0) return -1;
+	return 0;
+}
+
+static void
+addenv(var *v)
+{
+	word *w;
+	int fd;
+	io *f;
+
+	if(v->changed){
+		v->changed = 0;
+		if((fd = Creat(Env(v->name, 0)))<0)
+			pfmt(err, "%s: can't open: %s\n", argv0, Errstr());
+		else{
+			f = openiofd(fd);
+			for(w = v->val;w;w = w->next){
+				pstr(f, w->word);
+				pchr(f, '\0');
+			}
+			flushio(f);
+			closeio(f);
+		}
+	}
+	if(v->fnchanged){
+		v->fnchanged = 0;
+		if((fd = Creat(Env(v->name, 1)))<0)
+			pfmt(err, "%s: can't open: %s\n", argv0, Errstr());
+		else{
+			f = openiofd(fd);
+			if(v->fn)
+				pfmt(f, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
+			flushio(f);
+			closeio(f);
+		}
+	}
+}
+
+static void
+updenvlocal(var *v)
+{
+	if(v){
+		updenvlocal(v->next);
+		addenv(v);
+	}
+}
+
+void
+Updenv(void)
+{
+	var *v, **h;
+	for(h = gvar;h!=&gvar[NVAR];h++)
+		for(v=*h;v;v = v->next)
+			addenv(v);
+	if(runq)
+		updenvlocal(runq->local);
+	if(err)
+		flushio(err);
+}
+
+void
+Exec(char **argv)
+{
+	exec(argv[0], argv+1);
+}
+
+int
+Fork(void)
+{
+	Updenv();
+	return rfork(RFPROC|RFFDG|RFREND);
+}
+
+
+typedef struct readdir readdir;
+struct readdir {
+	Dir	*dbuf;
+	int	i, n;
+	int	fd;
+};
+
+void*
+Opendir(char *name)
+{
+	readdir *rd;
+	int fd;
+	if((fd = Open(name, 0))<0)
+		return 0;
+	rd = new(readdir);
+	rd->dbuf = 0;
+	rd->i = 0;
+	rd->n = 0;
+	rd->fd = fd;
+	return rd;
+}
+
+static int
+trimdirs(Dir *d, int nd)
+{
+	int r, w;
+
+	for(r=w=0; r<nd; r++)
+		if(d[r].mode&DMDIR)
+			d[w++] = d[r];
+	return w;
+}
+
+char*
+Readdir(void *arg, int onlydirs)
+{
+	readdir *rd = arg;
+	int n;
+Again:
+	if(rd->i>=rd->n){	/* read */
+		free(rd->dbuf);
+		rd->dbuf = 0;
+		n = dirread(rd->fd, &rd->dbuf);
+		if(n>0){
+			if(onlydirs){
+				n = trimdirs(rd->dbuf, n);
+				if(n == 0)
+					goto Again;
+			}	
+			rd->n = n;
+		}else
+			rd->n = 0;
+		rd->i = 0;
+	}
+	if(rd->i>=rd->n)
+		return 0;
+	return rd->dbuf[rd->i++].name;
+}
+
+void
+Closedir(void *arg)
+{
+	readdir *rd = arg;
+	Close(rd->fd);
+	free(rd->dbuf);
+	free(rd);
+}
+
+static int interrupted = 0;
+
+static void
+notifyf(void*, char *s)
+{
+	int i;
+
+	for(i = 0;syssigname[i];i++) if(strncmp(s, syssigname[i], strlen(syssigname[i]))==0){
+		if(strncmp(s, "sys: ", 5)!=0) interrupted = 1;
+		goto Out;
+	}
+	noted(NDFLT);
+	return;
+Out:
+	if(strcmp(s, "interrupt")!=0 || trap[i]==0){
+		trap[i]++;
+		ntrap++;
+	}
+	noted(NCONT);
+}
+
+void
+Trapinit(void)
+{
+	notify(notifyf);
+}
+
+long
+Write(int fd, void *buf, long cnt)
+{
+	return write(fd, buf, cnt);
+}
+
+long
+Read(int fd, void *buf, long cnt)
+{
+	return read(fd, buf, cnt);
+}
+
+long
+Seek(int fd, long cnt, long whence)
+{
+	return seek(fd, cnt, whence);
+}
+
+int
+Executable(char *file)
+{
+	Dir *statbuf;
+	int ret;
+
+	statbuf = dirstat(file);
+	if(statbuf == nil)
+		return 0;
+	ret = ((statbuf->mode&0111)!=0 && (statbuf->mode&DMDIR)==0);
+	free(statbuf);
+	return ret;
+}
+
+int
+Open(char *file, int mode)
+{
+	static int tab[] = {OREAD,OWRITE,ORDWR,OREAD|ORCLOSE};
+	return open(file, tab[mode&3]);
+}
+
+void
+Close(int fd)
+{
+	close(fd);
+}
+
+int
+Creat(char *file)
+{
+	return create(file, OWRITE, 0666L);
+}
+
+int
+Dup(int a, int b)
+{
+	return dup(a, b);
+}
+
+int
+Dup1(int a)
+{
+	return dup(a, -1);
+}
+
+void
+Exit(void)
+{
+	Updenv();
+	exits(truestatus()?"":getstatus());
+}
+
+int
+Eintr(void)
+{
+	return interrupted;
+}
+
+void
+Noerror(void)
+{
+	interrupted = 0;
+}
+
+int
+Isatty(int fd)
+{
+	char buf[64];
+
+	if(fd2path(fd, buf, sizeof buf) != 0)
+		return 0;
+	/* might be /mnt/term/dev/cons */
+	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
+}
+
+void
+Abort(void)
+{
+	abort();
+}
+
+static int newwdir;
+
+int
+Chdir(char *dir)
+{
+	newwdir = 1;
+	return chdir(dir);
+}
+
+void
+Prompt(char *s)
+{
+	pstr(err, s);
+	flushio(err);
+
+	if(newwdir){
+		char dir[4096];
+		int fd;
+		if((fd=Creat("/dev/wdir"))>=0){
+			getwd(dir, sizeof(dir));
+			Write(fd, dir, strlen(dir));
+			Close(fd);
+		}
+		newwdir = 0;
+	}
+}
--- /dev/null
+++ b/librc/rc.1
@@ -1,0 +1,1025 @@
+.TH RC 1
+.SH NAME
+rc, cd, eval, exec, exit, flag, rfork, shift, wait, whatis, ., ~ \- command language
+.SH SYNOPSIS
+.B rc
+[
+.B -srdiIlxebpvV
+]
+[
+.B -c
+.I command
+]
+[
+.B -m
+.I initial
+]
+[
+.I file
+[
+.I arg ...
+]]
+.SH DESCRIPTION
+.I Rc
+is the Plan 9 shell.
+It executes command lines read from a terminal or a file or, with the
+.B -c
+flag, from
+.I rc's
+argument list.
+.SS Command Lines
+A command line is a sequence of commands, separated by ampersands or semicolons
+.RB ( &
+or
+.BR ; ),
+terminated by a newline.
+The commands are executed in sequence
+from left to right.
+.I Rc
+does not wait for a command followed by
+.B &
+to finish executing before starting
+the following command.
+Whenever a command followed by
+.B &
+is executed, its process id is assigned to the
+.I rc
+variable
+.BR $apid .
+Whenever a command
+.I not
+followed by
+.B &
+exits or is terminated, the
+.I rc
+variable
+.B $status
+gets the process's wait message (see
+.IR wait (2));
+it will be the null string if the command was successful.
+.PP
+A long command line may be continued on subsequent lines by typing
+a backslash
+.RB ( \e )
+followed by a newline.
+This sequence is treated as though it were a blank.
+Backslash is not otherwise a special character.
+.PP
+A number-sign
+.RB ( # )
+and any following characters up to (but not including) the next newline
+are ignored, except in quotation marks.
+.SS Simple Commands
+A simple command is a sequence of arguments interspersed with I/O redirections.
+If the first argument is the name of an
+.I rc
+function or of one of
+.I rc's
+built-in commands, it is executed by
+.IR rc .
+Otherwise if the name starts with a slash
+.RB ( / ),
+it must be the path name of the program to be executed.
+Names containing no initial slash are searched for in
+a list of directory names stored in
+.BR $path .
+The first executable file of the given name found
+in a directory in
+.B $path
+is the program to be executed.
+To be executable, the user must have execute permission (see
+.IR stat (2))
+and the file must be either an executable binary
+for the current machine's CPU type, or a shell script.
+Shell scripts begin with a line containing the full path name of a shell
+(usually
+.BR /bin/rc ),
+prefixed by
+.LR #! .
+.PP
+The first word of a simple command cannot be a keyword unless it is
+quoted or otherwise disguised.
+The keywords are
+.EX
+	for in while if not switch fn ~ ! @
+.EE
+.SS Arguments and Variables
+A number of constructions may be used where
+.I rc's
+syntax requires an argument to appear.
+In many cases a construction's
+value will be a list of arguments rather than a single string.
+.PP
+The simplest kind of argument is the unquoted word:
+a sequence of one or more characters none of which is a blank, tab,
+newline, or any of the following:
+.EX
+	# ; & | ^ $ = ` ' { } ( ) < >
+.EE
+An unquoted word that contains any of the characters
+.B *
+.B ?
+.B [
+is a pattern for matching against file names.
+The character
+.B *
+matches any sequence of characters,
+.B ?
+matches any single character, and
+.BI [ class ]
+matches any character in the
+.IR class .
+If the first character of
+.I class
+is
+.BR ~ ,
+the class is complemented.
+The
+.I class
+may also contain pairs of characters separated by
+.BR - ,
+standing for all characters lexically between the two.
+The character
+.B /
+must appear explicitly in a pattern, as must the
+first character of the path name components
+.B .
+and
+.BR .. .
+A pattern is replaced by a list of arguments, one for each path name matched,
+except that a pattern matching no names is not replaced by the empty list,
+but rather stands for itself.
+Pattern matching is done after all other
+operations.
+Thus,
+.EX
+	x=/tmp echo $x^/*.c
+.EE
+matches
+.BR /tmp/*.c ,
+rather than matching
+.B "/*.c
+and then prefixing
+.BR /tmp .
+.PP
+A quoted word is a sequence of characters surrounded by single quotes
+.RB ( ' ).
+A single quote is represented in a quoted word by a pair of quotes
+.RB ( '' ).
+.PP
+Each of the following is an argument.
+.PD 0
+.HP
+.BI ( arguments )
+.br
+The value of a sequence of arguments enclosed in parentheses is
+a list comprising the members of each element of the sequence.
+Argument lists have no recursive structure, although their syntax may
+suggest it.
+The following are entirely equivalent:
+.EX
+	echo hi there everybody
+	((echo) (hi there) everybody)
+.EE
+.HP
+.BI $ argument
+.HP
+.BI $ argument ( subscript )
+.br
+The
+.I argument
+after the
+.B $
+is the name of a variable whose value is substituted.
+Multiple levels
+of indirection are possible, but of questionable utility.
+Variable values
+are lists of strings.
+If
+.I argument
+is a number
+.IR n ,
+the value is the
+.IR n th
+element of
+.BR $* ,
+unless
+.B $*
+doesn't have
+.I n
+elements, in which case the value is empty.
+If
+.I argument
+is followed by a parenthesized list of subscripts, the
+value substituted is a list composed of the requested elements (origin 1).
+The parenthesis must follow the variable name with no spaces.
+Subscripts can also take the form
+.IB m - n
+or
+.IB m -
+to indicate a sequence of elements.
+Assignments to variables are described below.
+.HP
+.BI $# argument
+.br
+The value is the number of elements in the named variable.
+A variable
+never assigned a value has zero elements.
+.HP
+$"\c
+.I argument
+.br
+The value is a single string containing the components of the named variable
+separated by spaces.  A variable with zero elements yields the empty string.
+.HP
+.BI `{ command }
+.HP
+.BI ` "split " { command }
+.br
+.I rc
+executes the
+.I command
+and reads its standard output, splitting it into a list of arguments,
+using characters in
+.B $ifs
+as separators.
+If
+.B $ifs
+is not otherwise set, its value is
+.BR "'\ \et\en'" .
+In the second form of the command, split is used instead of
+.BR $ifs .
+.HP
+.BI <{ command }
+.HP
+.BI >{ command }
+.br
+The
+.I command
+is executed asynchronously with its standard output or standard input
+connected to a pipe.
+The value of the argument is the name of a file
+referring to the other end of the pipe.
+This allows the construction of
+non-linear pipelines.
+For example, the following runs two commands
+.B old
+and
+.B new
+and uses
+.B cmp
+to compare their outputs
+.EX
+	cmp <{old} <{new}
+.EE
+.HP
+.IB argument ^ argument
+.br
+The
+.B ^
+operator concatenates its two operands.
+If the two operands
+have the same number of components, they are concatenated pairwise.
+If not,
+then one operand must have one component, and the other must be non-empty,
+and concatenation is distributive.
+.PD
+.SS Free Carets
+In most circumstances,
+.I rc
+will insert the
+.B ^
+operator automatically between words that are not separated by white space.
+Whenever one of
+.B $
+.B '
+.B `
+follows a quoted or unquoted word or an unquoted word follows a quoted word
+with no intervening blanks or tabs,
+a
+.B ^
+is inserted between the two.
+If an unquoted word immediately follows a
+.BR $ 
+and contains a character other than an alphanumeric, underscore,
+or
+.BR * ,
+a
+.B ^
+is inserted before the first such character.
+Thus
+.IP
+.B cc -$flags $stem.c
+.LP
+is equivalent to
+.IP
+.B cc -^$flags $stem^.c
+.SS I/O Redirections
+The sequence
+.BI > file
+redirects the standard output file (file descriptor 1, normally the
+terminal) to the named
+.IR file ;
+.BI >> file
+appends standard output to the file.
+The standard input file (file descriptor 0, also normally the terminal)
+may be redirected from a file by the sequence
+.BI < file \f1,
+or from an inline `here document'
+by the sequence
+.BI << eof-marker\f1.
+The contents of a here document are lines of text taken from the command
+input stream up to a line containing nothing but the
+.IR eof-marker ,
+which may be either a quoted or unquoted word.
+If
+.I eof-marker
+is unquoted, variable names of the form
+.BI $ word
+have their values substituted from
+.I rc's
+environment.
+If
+.BI $ word
+is followed by a caret
+.RB ( ^ ),
+the caret is deleted.
+If
+.I eof-marker
+is quoted, no substitution occurs.
+The standard input file
+may also be redirected from a file by the sequence
+.BI <> file \f1,
+which opens
+.I file
+exactly once, for reading and writing.
+.PP
+Redirections may be applied to a file-descriptor other than standard input
+or output by qualifying the redirection operator
+with a number in square brackets.
+For example, the diagnostic output (file descriptor 2)
+may be redirected by writing
+.BR "cc junk.c >[2]junk" .
+.PP
+A file descriptor may be redirected to an already open descriptor by writing
+.BI >[ fd0 = fd1 ],
+.BI <>[ fd0 = fd1 ],
+or
+.BI <[ fd0 = fd1 ]\f1.
+.I Fd1
+is a previously opened file descriptor and
+.I fd0
+becomes a new copy (in the sense of 
+.IR dup (2))
+of it.
+A file descriptor may be closed by writing
+.BI >[ fd0 =]
+or
+.BI <[ fd0 =]\f1.
+.PP
+Redirections are executed from left to right.
+Therefore,
+.B cc junk.c >/dev/null >[2=1]
+and
+.B cc junk.c >[2=1] >/dev/null
+have different effects: the first puts standard output in
+.BR /dev/null
+and then puts diagnostic output in the same place, where the second
+directs diagnostic output to the terminal and sends standard output to
+.BR /dev/null .
+.PP
+.B newconn <>/net/tcp/clone >[1=0]
+opens
+.B /net/tcp/clone
+exactly once for reading and writing and puts it on standard input and output.
+.B lpd <>[3]/net/tcp/42/data
+opens
+.B /net/tcp/42/data
+exactly once for reading and writing and puts it on file descriptor 3.
+.SS Compound Commands
+A pair of commands separated by a pipe operator
+.RB ( | )
+is a command.
+The standard output of the left command is sent through a pipe
+to the standard input of the right command.
+The pipe operator may be decorated
+to use different file descriptors.
+.BI |[ fd ]
+connects the output end of the pipe to file descriptor
+.I fd
+rather than 1.
+.BI |[ fd0 = fd1 ]
+connects output to
+.I fd1
+of the left command and input to
+.I fd0
+of the right command.
+.PP
+A pair of commands separated by
+.B &&
+or
+.B ||
+is a command.
+In either case, the left command is executed and its exit status examined.
+If the operator is
+.B &&
+the right command is executed if the left command's status is null.
+.B ||
+causes the right command to be executed if the left command's status is non-null.
+.PP
+The exit status of a command may be inverted (non-null is changed to null, null
+is changed to non-null) by preceding it with a
+.BR ! .
+.PP
+The
+.B |
+operator has highest precedence, and is left-associative (i.e. binds tighter
+to the left than the right).
+.B !
+has intermediate precedence, and
+.B &&
+and
+.B ||
+have the lowest precedence.
+.PP
+The unary
+.B @
+operator, with precedence equal to
+.BR ! ,
+causes its operand to be executed in a subshell.
+.PP
+Each of the following is a command.
+.PD 0
+.HP
+.B if (
+.I list
+.B )
+.I command
+.br
+A
+.I list
+is a sequence of commands, separated by
+.BR & ,
+.BR ; ,
+or newline.
+It is executed and
+if its exit status is null, the
+.I command
+is executed.
+.HP
+.B if not
+.I command
+.br
+The immediately preceding command must have been
+.BI if( list )
+.IR command .
+If its condition was non-zero, the
+.I command
+is executed.
+.HP
+.BI for( name
+.B in
+.IB arguments )
+.I command
+.HP
+.BI for( name )
+.I command
+.br
+The
+.I command
+is executed once for each
+.IR argument 
+with that argument assigned to
+.IR name .
+If the argument list is omitted,
+.B $*
+is used.
+.HP
+.BI while( list )
+.I command
+.br
+The
+.I list
+is executed repeatedly until its exit status is non-null.
+Each time it returns null status, the
+.I command
+is executed.
+An empty
+.I list
+is taken to give null status.
+.HP
+.BI "switch(" argument "){" list }
+.br
+The
+.IR list
+is searched for simple commands beginning with the word
+.BR case .
+(The search is only at the `top level' of the
+.IR list .
+That is,
+.B cases
+in nested constructs are not found.)
+.I Argument
+is matched against each word following
+.B case
+using the pattern-matching algorithm described above, except that
+.B /
+and the first characters of
+.B .
+and
+.B ..
+need not be matched explicitly.
+When a match is found, commands in the list are executed up to the next
+following
+.B case
+command (at the top level) or the closing brace.
+.HP
+.BI { list }
+.br
+Braces serve to alter the grouping of commands implied by operator
+priorities.
+The
+.I body
+is a sequence of commands separated by
+.BR & ,
+.BR ; ,
+or newline.
+.HP
+.BI "fn " name { list }
+.HP
+.BI "fn " name
+.br
+The first form defines a function with the given
+.IR name .
+Subsequently, whenever a command whose first argument is
+.I name
+is encountered, the current value of
+the remainder of the command's argument list will be assigned to
+.BR $* ,
+after saving its current value, and
+.I rc
+will execute the
+.IR list .
+The second form removes
+.IR name 's
+function definition.
+.HP
+.BI "fn " note { list }
+.br
+.HP
+.BI "fn " note
+.br
+A function with a special name will be called when
+.I rc
+receives a corresponding note; see
+.IR notify (2).
+The valid note names (and corresponding notes) are
+.B sighup
+.RB ( hangup ),
+.B sigint
+.RB ( interrupt ),
+.BR sigalrm
+.RB ( alarm ),
+and
+.B sigfpe
+(floating point trap).
+By default
+.I rc
+exits on receiving any signal, except when run interactively,
+in which case interrupts and quits normally cause
+.I rc
+to stop whatever it's doing and start reading a new command.
+The second form causes
+.I rc
+to handle a signal in the default manner.
+.I Rc
+recognizes an artificial note,
+.BR sigexit ,
+which occurs when
+.I rc
+is about to finish executing.
+.HP
+.IB name = "argument command"
+.br
+Any command may be preceded by a sequence of assignments
+interspersed with redirections.
+The assignments remain in effect until the end of the command, unless
+the command is empty (i.e. the assignments stand alone), in which case
+they are effective until rescinded by later assignments.
+.PD
+.SS Built-in Commands
+These commands are executed internally by
+.IR rc ,
+usually because their execution changes or depends on
+.IR rc 's
+internal state.
+.PD 0
+.HP
+.BI . " [-biq] file ..."
+.br
+Execute commands from
+.IR file .
+.B $*
+is set for the duration to the remainder of the argument list following
+.IR file .
+.I File
+is searched for using
+.BR $path .
+The flags
+.B -b
+and
+.B -i
+can be set for the new commands
+(see description below).
+The
+.B -q
+flag suppresses errors,
+inhibiting the effect of
+.B -e
+and
+.B -v
+flags of the main interpreter.
+.HP
+.BI builtin " command ..."
+.br
+Execute
+.I command
+as usual except that any function named
+.I command
+is ignored in favor of the built-in meaning.
+.HP
+.BI "cd [" dir "]"
+.br
+Change the current directory to
+.IR dir .
+The default argument is
+.BR $home .
+.I dir
+is searched for in each of the directories mentioned in
+.BR $cdpath .
+.HP
+.BI "eval [" "arg ..." "]"
+.br
+The arguments are concatenated separated by spaces into a single string,
+read as input to
+.IR rc ,
+and executed.
+.HP
+.BI "exec [" "command ..." "]"
+.br
+This instance of
+.I rc
+replaces itself with the given (non-built-in)
+.IR command .
+.HP
+.BI "flag " f " [+-]"
+.br
+Either set
+.RB ( + ),
+clear
+.RB ( - ),
+or test (neither
+.B +
+nor
+.BR - )
+the flag
+.IR f ,
+where
+.I f
+is a single character, one of the command line flags (see Invocation, below).
+.HP
+.BI "exit [" status "]"
+.br
+Exit with the given exit status.
+If none is given, the current value of
+.B $status
+is used.
+.HP
+.BR "rfork " [ nNeEsfFm ]
+.br
+Become a new process group using
+.BI rfork( flags )
+where
+.I flags
+is composed of the bitwise OR of the
+.B rfork
+flags specified by the option letters
+(see
+.IR fork (2)).
+If no
+.I flags
+are given, they default to
+.BR ens .
+The
+.I flags
+and their meanings are:
+.B n
+is
+.BR RFNAMEG ;
+.B N
+is
+.BR RFCNAMEG ;
+.B e
+is
+.BR RFENVG ;
+.B E
+is
+.BR RFCENVG ;
+.B s
+is
+.BR RFNOTEG ;
+.B f
+is
+.BR RFFDG ;
+.B F
+is
+.BR RFCFDG ;
+and
+.B m
+is
+.BR RFNOMNT .
+.HP
+.BI "shift [" n "]"
+.br
+Delete the first
+.IR n
+(default 1)
+elements of
+.BR $* .
+.HP
+.BI "wait [" pid "]"
+.br
+Wait for the process with the given
+.I pid
+to exit.
+If no
+.I pid
+is given, all outstanding processes are waited for.
+.HP
+.BI whatis " name ..."
+.br
+Print the value of each
+.I name
+in a form suitable for input to
+.IR rc .
+The output is
+an assignment to any variable,
+the definition of any function,
+a call to
+.B builtin
+for any built-in command, or
+the completed pathname of any executable file.
+.HP
+.BI ~ " subject pattern ..."
+.br
+The
+.I subject
+is matched against each
+.I pattern
+in sequence.
+If it matches any pattern,
+.B $status
+is set to zero.
+Otherwise,
+.B $status
+is set to one.
+Patterns are the same as for file name matching, except that
+.B /
+and the first character of
+.B .
+and
+.B ..
+need not be matched explicitly.
+The
+.I patterns
+are not subjected to
+file name matching before the
+.B ~
+command is executed, so they need not be enclosed in quotation marks.
+.PD
+.SS Environment
+The
+.I environment
+is a list of strings made available to executing binaries by the
+.B env
+device
+(see
+.IR env (3)).
+.I Rc
+creates an environment entry for each variable whose value is non-empty,
+and for each function.
+The string for a variable entry has the variable's name followed by
+.B =
+and its value.
+If the value has more than one component, these
+are separated by nul
+.RB ( '\e000' )
+characters.
+The string for a function is just the
+.I rc
+input that defines the function.
+The name of a function in the environment is the function name
+preceded by
+.LR fn# .
+.PP
+When
+.I rc
+starts executing it reads variable and function definitions from its
+environment.
+.SS Special Variables
+The following variables are set or used by
+.IR rc .
+.PD 0
+.TP \w'\fL$promptXX'u
+.B $*
+Set to
+.IR rc 's
+argument list during initialization.
+Whenever a
+.B .
+command or a function is executed, the current value is saved and
+.B $*
+receives the new argument list.
+The saved value is restored on completion of the
+.B .
+or function.
+.TP
+.B $apid
+Whenever a process is started asynchronously with
+.BR & ,
+.B $apid
+is set to its process id.
+.TP
+.B $home
+The default directory for
+.BR cd .
+.TP
+.B $ifs
+The input field separators used in backquote substitutions.
+If
+.B $ifs
+is not otherwise set, its value is
+.BR "'\ \et\en'" .
+.TP
+.B $path
+The search path used to find commands and input files
+for the
+.B .
+command.
+If not set in the environment, it is initialized by
+.BR "path=(.\ /bin)" .
+Its use is discouraged; instead use
+.IR bind (1)
+to build a
+.B /bin
+containing what's needed.
+.TP
+.B $pid
+Set during initialization to
+.IR rc 's
+process id.
+.TP
+.B $prompt
+When
+.I rc
+is run interactively, the first component of
+.B $prompt
+is printed before reading each command.
+The second component is printed whenever a newline is typed and more lines
+are required to complete the command.
+If not set in the environment, it is initialized by
+.BR "prompt=('%\ '\ '\ ')" .
+.TP
+.B $status
+Set to the wait message of the last-executed program.
+(unless started with
+.BR &).
+.B !
+and
+.B ~
+also change
+.BR $status .
+Its value is used to control execution in
+.BR && ,
+.BR || ,
+.B if
+and
+.B while
+commands.
+When
+.I rc
+exits at end-of-file of its input or on executing an
+.B exit
+command with no argument,
+.B $status
+is its exit status.
+.PD
+.SS Invocation
+If
+.I rc
+is started with no arguments it reads commands from standard input.
+Otherwise its first non-flag argument is the name of a file from which
+to read commands (but see
+.B -c
+below).
+Subsequent arguments become the initial value of
+.BR $* .
+.I Rc
+accepts the following command-line flags.
+.PD 0
+.TP \w'\fL-c\ \fIstring\fLXX'u
+.BI -c " string"
+Commands are read from
+.IR string .
+.TP
+.B -s
+Print out exit status after any command where the status is non-null.
+.TP
+.B -e
+Exit if
+.B $status
+is non-null after executing a simple command.
+.TP
+.B -i
+If
+.B -i
+is present, or
+.I rc
+is given no arguments and its standard input is a terminal,
+it runs interactively.
+Commands are prompted for using
+.BR $prompt .
+.TP
+.B -I
+Makes sure
+.I rc
+is not run interactively.
+.TP
+.B -l
+If
+.B -l
+is given or the first character of argument zero is
+.BR - ,
+.I rc
+reads commands from
+.BR $home/lib/profile ,
+if it exists, before reading its normal input.
+.TP
+.B -m
+Read commands to initialize
+.I rc
+from
+.I initial
+instead of from
+.BR /rc/lib/rcmain .
+.TP
+.B -p
+A no-op.
+.TP
+.B -d
+A no-op.
+.TP
+.B -v
+Echo input on file descriptor 2 as it is read.
+.TP
+.B -x
+Print each simple command before executing it.
+.TP
+.B -r
+Print debugging information (internal form of commands
+as they are executed).
+.TP
+.B -b
+Compile the command file as a whole before executing.
+This allows syntax checking of the whole file.
+.PD
+.SH FILES
+.TF $home/lib/profile
+.TP
+.B $home/lib/profile
+the user's local rc start script
+.TF /rc/lib/rcmain
+.TP
+.B /rc/lib/rcmain
+System rc start script
+.TF /rc/lib/rcmain.local
+.TP
+.B /rc/lib/rcmain.local
+Site specific system rc start script
+.SH SOURCE
+.B /sys/src/cmd/rc
+.SH "SEE ALSO"
+Tom Duff,
+``Rc \- The Plan 9 Shell''.
+.SH BUGS
+There should be a way to match patterns against whole lists rather than
+just single strings.
+.PP
+Using
+.B ~
+to check the value of
+.B $status
+changes
+.BR $status .
+.PP
+Free carets don't get inserted next to keywords.
--- /dev/null
+++ b/librc/rc.h
@@ -1,0 +1,166 @@
+/*
+ * Plan9 is defined for plan 9
+ * otherwise its UNIX.
+ * Please don't litter the code with ifdefs.  The three below (and one in
+ * getflags) should be enough.
+ */
+#ifdef Plan9
+#include <u.h>
+#include <libc.h>
+#define NSIG	32
+#define	SIGINT	2
+#define	SIGQUIT	3
+#else
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#ifndef NSIG
+#define NSIG 32
+#endif
+#endif
+
+#define	YYMAXDEPTH	500
+#ifndef PAREN
+#include "y.tab.h"
+#endif
+typedef struct tree tree;
+typedef struct word word;
+typedef struct io io;
+typedef union code code;
+typedef struct var var;
+typedef struct list list;
+typedef struct lexer lexer;
+typedef struct redir redir;
+typedef struct thread thread;
+typedef struct builtin builtin;
+
+#pragma incomplete word
+#pragma incomplete io
+
+struct tree{
+	int	type;
+	int	rtype, fd0, fd1;	/* details of REDIR PIPE DUP tokens */
+	int	line;
+	char	glob;			/* 0=string, 1=glob, 2=pattern see globprop() and noglobs() */
+	char	quoted;
+	char	iskw;
+	char	*str;
+	tree	*child[3];
+	tree	*next;
+};
+tree *newtree(void);
+tree *token(char*, int), *klook(char*), *tree1(int, tree*);
+tree *tree2(int, tree*, tree*), *tree3(int, tree*, tree*, tree*);
+tree *mung1(tree*, tree*), *mung2(tree*, tree*, tree*);
+tree *mung3(tree*, tree*, tree*, tree*), *epimung(tree*, tree*);
+tree *simplemung(tree*);
+tree *globprop(tree*);
+char *fnstr(tree*);
+
+/*
+ * The first word of any code vector is a reference count
+ * and the second word is a string for srcfile().
+ * Code starts at pc 2. The last code word must be a zero
+ * terminator for codefree().
+ * Always create a new reference to a code vector by calling codecopy(.).
+ * Always call codefree(.) when deleting a reference.
+ */
+union code{
+	void	(*f)(void);
+	int	i;
+	char	*s;
+};
+
+#define	NTOK	8192
+
+struct lexer{
+	io	*input;
+	char	*file;
+	int	line;
+
+	char	*prolog;
+	char	*epilog;
+
+	int	peekc;
+	int	future;
+	int	lastc;
+
+	char	eof;
+	char	inquote;
+	char	incomm;
+	char	lastword;	/* was the last token read a word or compound word terminator? */
+	char	lastdol;	/* was the last token read '$' or '$#' or '"'? */
+	char	iflast;		/* static `if not' checking */
+
+	char	qflag;
+
+	char	tok[NTOK];
+};
+extern lexer *lex;		/* current lexer */
+lexer *newlexer(io*, char*);
+void freelexer(lexer*);
+
+#define	APPEND	1
+#define	WRITE	2
+#define	READ	3
+#define	HERE	4
+#define	DUPFD	5
+#define	CLOSE	6
+#define RDWR	7
+
+struct var{
+	var	*next;		/* next on hash or local list */
+	word	*val;		/* value */
+	code	*fn;		/* pointer to function's code vector */
+	int	pc;		/* pc of start of function */
+	char	fnchanged;
+	char	changed;
+	char	name[];
+};
+var *vlook(char*), *gvlook(char*), *newvar(char*, var*);
+void setvar(char*, word*), freevar(var*);
+
+#define	NVAR	521
+extern var *gvar[NVAR];		/* hash for globals */
+
+#define	new(type)	((type *)emalloc(sizeof(type)))
+
+void *emalloc(long);
+void *erealloc(void *, long);
+char *estrdup(char*);
+
+/*
+ * Glob character escape in strings:
+ *	In a string, GLOB must be followed by *?[ or GLOB.
+ *	GLOB* matches any string
+ *	GLOB? matches any single character
+ *	GLOB[...] matches anything in the brackets
+ *	GLOBGLOB matches GLOB
+ */
+#define	GLOB	((char)0x01)
+/*
+ * Is c the first character of a utf sequence?
+ */
+#define	onebyte(c)	(((c)&0x80)==0x00)
+#define twobyte(c)	(((c)&0xe0)==0xc0)
+#define threebyte(c)	(((c)&0xf0)==0xe0)
+#define fourbyte(c)	(((c)&0xf8)==0xf0)
+#define xbyte(c)	(((c)&0xc0)==0x80)
+
+extern char *argv0;
+extern int nerror;		/* number of errors encountered during compilation */
+extern int doprompt;		/* is it time for a prompt? */
+extern io *err;
+
+/*
+ * Which fds are the reading/writing end of a pipe?
+ * Unfortunately, this can vary from system to system.
+ * 9th edition Unix doesn't care, the following defines
+ * work on plan 9.
+ */
+#define	PRD	0
+#define	PWR	1
+extern char Rcmain[], Fdprefix[];
+extern char *Signame[];
--- /dev/null
+++ b/librc/rcmain.plan9
@@ -1,0 +1,41 @@
+# rcmain: Plan 9 version
+if(~ $#home 0) home=/
+if(~ $#ifs 0) ifs=' 	
+'
+switch($#prompt){
+case 0
+	prompt=('% ' '	')
+case 1
+	prompt=($prompt '	')
+}
+if(~ $rcname ?.out) prompt=('broken! ' '	')
+if(flag p) path=/bin
+if not{
+	for(i in '/env/fn#'*){
+		. -bq $i
+	}
+	if(~ $#path 0) path=(/bin .)
+}
+fn sigexit
+if(! ~ $#cflag 0){
+	if(flag l){
+		. -q /rc/lib/rcmain.local
+		. -q $home/lib/profile
+	}
+	status=''
+	eval $cflag
+}
+if not if(flag i){
+	if(flag l){
+		. -q /rc/lib/rcmain.local
+		. -q $home/lib/profile
+	}
+	status=''
+	if(! ~ $#* 0) . $*
+	. -i '#d/0'
+}
+if not if(~ $#* 0) . '#d/0'
+if not{
+	status=''
+	. $*
+}
--- /dev/null
+++ b/librc/rcmain.unix
@@ -1,0 +1,38 @@
+# rcmain: unix version
+if(~ $#home 0) home=$HOME
+if(~ $#ifs 0) ifs=' 	
+'
+profile=$home/.rcrc
+switch($#prompt){
+case 0
+	prompt=('% ' '	')
+case 1
+	prompt=($prompt '	')
+}
+if(~ $rcname ?.out) prompt=('broken! ' '	')
+if(flag p) path=/bin
+if not {
+	finit
+	if(~ $#path 0) path=(. /bin /usr/bin /usr/local/bin)
+}
+fn sigexit
+if(! ~ $#cflag 0){
+	if(flag l) {
+		. -q $profile
+	}
+	status=''
+	eval $cflag
+}
+if not if(flag i){
+	if(flag l) {
+		. -q $profile
+	}
+	status=''
+	if(! ~ $#* 0) . $*
+	. -i /dev/fd/0
+}
+if not if(~ $#* 0) . /dev/fd/0
+if not{
+	status=''
+	. $*
+}
--- /dev/null
+++ b/librc/simple.c
@@ -1,0 +1,539 @@
+/*
+ * Maybe `simple' is a misnomer.
+ */
+#include "rc.h"
+#include "getflags.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+
+/*
+ * Search through the following code to see if we're just going to exit.
+ */
+int
+exitnext(void){
+	int i=ifnot;
+	thread *p=runq;
+	code *c;
+loop:
+	c=&p->code[p->pc];
+	while(1){
+		if(c->f==Xpopredir || c->f==Xunlocal)
+			c++;
+		else if(c->f==Xsrcline)
+			c += 2;
+		else if(c->f==Xwastrue){
+			c++;
+			i=0;
+		}
+		else if(c->f==Xifnot){
+			if(i)
+				c += 2;
+			else
+				c = &p->code[c[1].i];
+		}
+		else if(c->f==Xreturn){
+			p = p->ret;
+			if(p==0)
+				return 1;
+			goto loop;
+		}else
+			break;
+	}
+	return c->f==Xexit;
+}
+
+void (*builtinfunc(char *name))(void)
+{
+	extern builtin Builtin[];
+	builtin *bp;
+
+	for(bp = Builtin;bp->name;bp++)
+		if(strcmp(name, bp->name)==0)
+			return bp->fnc;
+	return 0;
+}
+
+void
+Xsimple(void)
+{
+	void (*f)(void);
+	word *a;
+	var *v;
+	int pid;
+
+	a = runq->argv->words;
+	if(a==0){
+		Xerror1("empty argument list");
+		return;
+	}
+	if(flag['x'])
+		pfmt(err, "%v\n", a); /* wrong, should do redirs */
+	v = gvlook(a->word);
+	if(v->fn)
+		execfunc(v);
+	else{
+		if(strcmp(a->word, "builtin")==0){
+			a = a->next;
+			if(a==0){
+				Xerror1("builtin: empty argument list");
+				return;
+			}
+			popword();	/* "builtin" */
+		}
+		f = builtinfunc(a->word);
+		if(f){
+			(*f)();
+			return;
+		}
+		if(exitnext()){
+			/* fork and wait is redundant */
+			pushword("exec");
+			execexec();
+			/* does not return */
+		}
+		else{
+			if((pid = execforkexec()) < 0){
+				Xerror2("try again", Errstr());
+				return;
+			}
+			poplist();
+
+			/* interrupts don't get us out */
+			while(Waitfor(pid) < 0)
+				;
+		}
+	}
+}
+
+static void
+doredir(redir *rp)
+{
+	if(rp){
+		doredir(rp->next);
+		switch(rp->type){
+		case ROPEN:
+			if(rp->from!=rp->to){
+				Dup(rp->from, rp->to);
+				Close(rp->from);
+			}
+			break;
+		case RDUP:
+			Dup(rp->from, rp->to);
+			break;
+		case RCLOSE:
+			Close(rp->from);
+			break;
+		}
+	}
+}
+
+word*
+searchpath(char *w, char *v)
+{
+	static struct word nullpath = { "", 0 };
+	word *path;
+
+	if(w[0] && w[0] != '/' && w[0] != '#' &&
+	  (w[0] != '.' || (w[1] && w[1] != '/' && (w[1] != '.' || w[2] && w[2] != '/')))){
+		path = vlook(v)->val;
+		if(path)
+			return path;
+	}
+	return &nullpath;
+}
+
+char*
+makepath(char *dir, char *file)
+{
+	char *path;
+	int m, n = strlen(dir);
+	if(n==0) return estrdup(file);
+	while (n > 0 && dir[n-1]=='/') n--;
+	while (file[0]=='/') file++;
+	m = strlen(file);
+	path = emalloc(n + m + 2);
+	if(n>0) memmove(path, dir, n);
+	path[n++]='/';
+	memmove(path+n, file, m+1);
+	return path;
+}
+
+static char**
+mkargv(word *a)
+{
+	char **argv = (char **)emalloc((count(a)+2)*sizeof(char *));
+	char **argp = argv+1;
+	for(;a;a = a->next) *argp++=a->word;
+	*argp = 0;
+	return argv;
+}
+
+void
+execexec(void)
+{
+	char **argv;
+	word *path;
+
+	popword();	/* "exec" */
+	if(runq->argv->words==0){
+		Xerror1("exec: empty argument list");
+		return;
+	}
+	argv = mkargv(runq->argv->words);
+	Updenv();
+	doredir(runq->redir);
+	for(path = searchpath(argv[1], "path"); path; path = path->next){
+		argv[0] = makepath(path->word, argv[1]);
+		Exec(argv);
+	}
+	setstatus(Errstr());
+	pfln(err, srcfile(runq), runq->line);
+	pfmt(err, ": %s: %s\n", argv[1], getstatus());
+	Xexit();
+}
+
+void
+execfunc(var *func)
+{
+	popword();	/* name */
+	startfunc(func, Poplist(), runq->local, runq->redir);
+}
+
+void
+execcd(void)
+{
+	word *a = runq->argv->words;
+	word *cdpath;
+	char *dir;
+
+	setstatus("can't cd");
+	switch(count(a)){
+	default:
+		pfmt(err, "Usage: cd [directory]\n");
+		break;
+	case 2:
+		a = a->next;
+		for(cdpath = searchpath(a->word, "cdpath"); cdpath; cdpath = cdpath->next){
+			dir = makepath(cdpath->word, a->word);
+			if(Chdir(dir)>=0){
+				if(cdpath->word[0] != '\0' && strcmp(cdpath->word, ".") != 0)
+					pfmt(err, "%s\n", dir);
+				free(dir);
+				setstatus("");
+				break;
+			}
+			free(dir);
+		}
+		if(cdpath==0)
+			pfmt(err, "Can't cd %s: %s\n", a->word, Errstr());
+		break;
+	case 1:
+		a = vlook("home")->val;
+		if(a){
+			if(Chdir(a->word)>=0)
+				setstatus("");
+			else
+				pfmt(err, "Can't cd %s: %s\n", a->word, Errstr());
+		}
+		else
+			pfmt(err, "Can't cd -- $home empty\n");
+		break;
+	}
+	poplist();
+}
+
+void
+execexit(void)
+{
+	switch(count(runq->argv->words)){
+	default:
+		pfmt(err, "Usage: exit [status]\nExiting anyway\n");
+	case 2:
+		setstatus(runq->argv->words->next->word);
+	case 1:	Xexit();
+	}
+}
+
+void
+execshift(void)
+{
+	int n;
+	word *a;
+	var *star;
+	switch(count(runq->argv->words)){
+	default:
+		pfmt(err, "Usage: shift [n]\n");
+		setstatus("shift usage");
+		poplist();
+		return;
+	case 2:
+		n = atoi(runq->argv->words->next->word);
+		break;
+	case 1:
+		n = 1;
+		break;
+	}
+	star = vlook("*");
+	for(;n>0 && star->val;--n){
+		a = star->val->next;
+		free(Freeword(star->val));
+		star->val = a;
+		star->changed = 1;
+	}
+	setstatus("");
+	poplist();
+}
+
+int
+mapfd(int fd)
+{
+	redir *rp;
+	for(rp = runq->redir;rp;rp = rp->next){
+		switch(rp->type){
+		case RCLOSE:
+			if(rp->from==fd)
+				fd=-1;
+			break;
+		case RDUP:
+		case ROPEN:
+			if(rp->to==fd)
+				fd = rp->from;
+			break;
+		}
+	}
+	return fd;
+}
+
+void
+execcmds(io *input, char *file, var *local, redir *redir)
+{
+	static union code rdcmds[5];
+
+	if(rdcmds[0].i==0){
+		rdcmds[0].i = 1;
+		rdcmds[1].s="*rdcmds*";
+		rdcmds[2].f = Xrdcmds;
+		rdcmds[3].f = Xreturn;
+		rdcmds[4].f = 0;
+	}
+
+	if(exitnext()) turfstack(local);
+
+	start(rdcmds, 2, local, redir);
+	runq->lex = newlexer(input, file);
+}
+
+void
+execeval(void)
+{
+	char *cmds;
+	int len;
+	io *f;
+
+	popword();	/* "eval" */
+
+	if(runq->argv->words==0){
+		Xerror1("Usage: eval cmd ...");
+		return;
+	}
+	Xqw();		/* make into single word */
+	cmds = Popword();
+	len = strlen(cmds);
+	cmds[len++] = '\n';
+	poplist();
+
+	f = openiostr();
+	pfln(f, srcfile(runq), runq->line);
+	pstr(f, " *eval*");
+
+	execcmds(openiocore(cmds, len), closeiostr(f), runq->local, runq->redir);
+}
+
+void
+execdot(void)
+{
+	int fd, bflag, iflag, qflag;
+	word *path, *argv;
+	char *file;
+
+	popword();	/* "." */
+
+	bflag = iflag = qflag = 0;
+	while(runq->argv->words && runq->argv->words->word[0]=='-'){
+		char *f = runq->argv->words->word+1;
+		if(*f == '-'){
+			popword();
+			break;
+		}
+		for(; *f; f++){
+			switch(*f){
+			case 'b':
+				bflag = 1;
+				continue;
+			case 'i':
+				iflag = 1;
+				continue;
+			case 'q':
+				qflag = 1;
+				continue;
+			}
+			goto Usage;
+		}
+		popword();
+	}
+
+	/* get input file */
+	if(runq->argv->words==0){
+Usage:
+		Xerror1("Usage: . [-biq] file [arg ...]");
+		return;
+	}
+	argv = Poplist();
+		
+	file = 0;
+	fd = -1;
+	for(path = searchpath(argv->word, "path"); path; path = path->next){
+		file = makepath(path->word, argv->word);
+		fd = Open(file, 0);
+		if(fd >= 0)
+			break;
+		if(strcmp(file, "/dev/stdin")==0){	/* for sun & ucb */
+			fd = Dup1(0);
+			if(fd>=0)
+				break;
+		}
+		free(file);
+	}
+	if(fd<0){
+		if(!qflag)
+			Xerror3(". can't open", argv->word, Errstr());
+		freewords(argv);
+		return;
+	}
+
+	execcmds(openiofd(fd), file, (var*)0, runq->redir);
+	pushredir(RCLOSE, fd, 0);
+	runq->lex->qflag = qflag;
+	runq->iflag = iflag;
+	if(iflag || !bflag && flag['b']==0){
+		runq->lex->peekc=EOF;
+		runq->lex->epilog="";
+	}
+
+	runq->local = newvar("*", runq->local);
+	runq->local->val = argv->next;
+	argv->next=0;
+	runq->local->changed = 1;
+
+	runq->local = newvar("0", runq->local);
+	runq->local->val = argv;
+	runq->local->changed = 1;
+}
+
+void
+execflag(void)
+{
+	char *letter, *val;
+	switch(count(runq->argv->words)){
+	case 2:
+		setstatus(flag[(unsigned char)runq->argv->words->next->word[0]]?"":"flag not set");
+		break;
+	case 3:
+		letter = runq->argv->words->next->word;
+		val = runq->argv->words->next->next->word;
+		if(strlen(letter)==1){
+			if(strcmp(val, "+")==0){
+				flag[(unsigned char)letter[0]] = flagset;
+				setstatus("");
+				break;
+			}
+			if(strcmp(val, "-")==0){
+				flag[(unsigned char)letter[0]] = 0;
+				setstatus("");
+				break;
+			}
+		}
+	default:
+		Xerror1("Usage: flag [letter] [+-]");
+		return;
+	}
+	poplist();
+}
+
+void
+execwhatis(void){	/* mildly wrong -- should fork before writing */
+	word *a, *b, *path;
+	var *v;
+	char *file;
+	io *out;
+	int found, sep;
+	a = runq->argv->words->next;
+	if(a==0){
+		Xerror1("Usage: whatis name ...");
+		return;
+	}
+	setstatus("");
+	out = openiofd(mapfd(1));
+	for(;a;a = a->next){
+		v = vlook(a->word);
+		if(v->val){
+			pfmt(out, "%s=", a->word);
+			if(v->val->next==0)
+				pfmt(out, "%q\n", v->val->word);
+			else{
+				sep='(';
+				for(b = v->val;b && b->word;b = b->next){
+					pfmt(out, "%c%q", sep, b->word);
+					sep=' ';
+				}
+				pstr(out, ")\n");
+			}
+			found = 1;
+		}
+		else
+			found = 0;
+		v = gvlook(a->word);
+		if(v->fn)
+			pfmt(out, "fn %q %s\n", v->name, v->fn[v->pc-1].s);
+		else{
+			if(builtinfunc(a->word))
+				pfmt(out, "builtin %s\n", a->word);
+			else {
+				for(path = searchpath(a->word, "path"); path; path = path->next){
+					file = makepath(path->word, a->word);
+					if(Executable(file)){
+						pfmt(out, "%s\n", file);
+						free(file);
+						break;
+					}
+					free(file);
+				}
+				if(!path && !found){
+					pfmt(err, "%s: not found\n", a->word);
+					setstatus("not found");
+				}
+			}
+		}
+		flushio(out);
+	}
+	poplist();
+	free(closeiostr(out));	/* don't close fd */
+}
+
+void
+execwait(void)
+{
+	switch(count(runq->argv->words)){
+	default:
+		Xerror1("Usage: wait [pid]");
+		return;
+	case 2:
+		Waitfor(atoi(runq->argv->words->next->word));
+		break;
+	case 1:
+		Waitfor(-1);
+		break;
+	}
+	poplist();
+}
--- /dev/null
+++ b/librc/subr.c
@@ -1,0 +1,74 @@
+#include "rc.h"
+#include "io.h"
+#include "fns.h"
+
+void *
+emalloc(long n)
+{
+	void *p = malloc(n);
+	if(p==0)
+		panic("Can't malloc %d bytes", n);
+	return p;
+}
+
+void*
+erealloc(void *p, long n)
+{
+	p = realloc(p, n);
+	if(p==0 && n!=0)
+		panic("Can't realloc %d bytes\n", n);
+	return p;
+}
+
+char*
+estrdup(char *s)
+{
+	int n = strlen(s)+1;
+	char *d = emalloc(n);
+	memmove(d, s, n);
+	return d;
+}
+
+void
+pfln(io *fd, char *file, int line)
+{
+	if(file && line)
+		pfmt(fd, "%s:%d", file, line);
+	else if(file)
+		pstr(fd, file);
+	else
+		pstr(fd, argv0);
+}
+
+static char *bp;
+
+static void
+iacvt(int n)
+{
+	if(n<0){
+		*bp++='-';
+		n=-n;	/* doesn't work for n==-inf */
+	}
+	if(n/10)
+		iacvt(n/10);
+	*bp++=n%10+'0';
+}
+
+void
+inttoascii(char *s, int n)
+{
+	bp = s;
+	iacvt(n);
+	*bp='\0';
+}
+
+void
+panic(char *s, int n)
+{
+	pfmt(err, "%s: ", argv0);
+	pfmt(err, s, n);
+	pchr(err, '\n');
+	flushio(err);
+
+	Abort();
+}
--- /dev/null
+++ b/librc/trap.c
@@ -1,0 +1,34 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+#include "io.h"
+
+int ntrap;
+int trap[NSIG];
+
+void
+dotrap(void)
+{
+	int i;
+	var *trapreq;
+	word *starval;
+	starval = vlook("*")->val;
+	while(ntrap) for(i = 0;i<NSIG;i++) while(trap[i]){
+		--trap[i];
+		--ntrap;
+		if(getpid()!=mypid) Exit();
+		trapreq = vlook(Signame[i]);
+		if(trapreq->fn)
+			startfunc(trapreq, copywords(starval, (word*)0), (var*)0, (redir*)0);
+		else if(i==SIGINT || i==SIGQUIT){
+			/*
+			 * run the stack down until we uncover the
+			 * command reading loop.  Xreturn will exit
+			 * if there is none (i.e. if this is not
+			 * an interactive rc.)
+			 */
+			while(!runq->iflag) Xreturn();
+		}
+		else Exit();
+	}
+}
--- /dev/null
+++ b/librc/tree.c
@@ -1,0 +1,190 @@
+#include "rc.h"
+#include "io.h"
+#include "fns.h"
+
+/*
+ * create and clear a new tree node, and add it
+ * to the node list.
+ */
+static tree *treefree, *treenodes;
+
+tree*
+newtree(void)
+{
+	tree *t;
+
+	t = treefree;
+	if(t==0)
+		t = new(tree);
+	else
+		treefree = t->next;
+	t->quoted = 0;
+	t->glob = 0;
+	t->iskw = 0;
+	t->str = 0;
+	t->child[0] = t->child[1] = t->child[2] = 0;
+	t->line = lex->line;
+	t->next = treenodes;
+	treenodes = t;
+	return t;
+}
+
+void
+freenodes(void)
+{
+	tree *t;
+
+	t = treenodes;
+	while(t){
+		if(t->str){
+			free(t->str);
+			t->str = 0;
+		}
+		t->child[0] = t->child[1] = t->child[2] = 0;
+		if(t->next==0){
+			t->next = treefree;
+			treefree = treenodes;
+			break;
+		}
+		t = t->next;
+	}
+	treenodes = 0;
+}
+
+tree*
+tree1(int type, tree *c0)
+{
+	return tree3(type, c0, (tree *)0, (tree *)0);
+}
+
+tree*
+tree2(int type, tree *c0, tree *c1)
+{
+	return tree3(type, c0, c1, (tree *)0);
+}
+
+tree*
+tree3(int type, tree *c0, tree *c1, tree *c2)
+{
+	tree *t;
+	if(type==';'){
+		if(c0==0)
+			return c1;
+		if(c1==0)
+			return c0;
+	}
+	t = newtree();
+	t->type = type;
+	t->child[0] = c0;
+	t->child[1] = c1;
+	t->child[2] = c2;
+
+	if(c0)
+		t->line = c0->line;
+	else if(c1)
+		t->line = c1->line;
+	else if(c2)
+		t->line = c2->line;
+	return t;
+}
+
+tree*
+mung1(tree *t, tree *c0)
+{
+	t->child[0] = c0;
+	return t;
+}
+
+tree*
+mung2(tree *t, tree *c0, tree *c1)
+{
+	t->child[0] = c0;
+	t->child[1] = c1;
+	return t;
+}
+
+tree*
+mung3(tree *t, tree *c0, tree *c1, tree *c2)
+{
+	t->child[0] = c0;
+	t->child[1] = c1;
+	t->child[2] = c2;
+	return t;
+}
+
+tree*
+epimung(tree *comp, tree *epi)
+{
+	tree *p;
+	if(epi==0)
+		return comp;
+	for(p = epi;p->child[1];p = p->child[1]);
+	p->child[1] = comp;
+	return epi;
+}
+
+/*
+ * Add a SIMPLE node at the root of t and percolate all the redirections
+ * up to the root.
+ */
+tree*
+simplemung(tree *t)
+{
+	tree *u;
+
+	t = tree1(SIMPLE, t);
+	t->str = fnstr(t);
+	for(u = t->child[0];u->type==ARGLIST;u = u->child[0]){
+		if(u->child[1]->type==DUP
+		|| u->child[1]->type==REDIR){
+			u->child[1]->child[1] = t;
+			t = u->child[1];
+			u->child[1] = 0;
+		}
+	}
+	return t;
+}
+
+char*
+fnstr(tree *t)
+{
+	io *f = openiostr();
+	pfmt(f, "%t", t);
+	return closeiostr(f);
+}
+
+tree*
+globprop(tree *t)
+{
+	tree *c0 = t->child[0];
+	tree *c1 = t->child[1];
+	if(c1==0){
+		while(c0 && c0->type==WORDS){
+			c1 = c0->child[1];
+			if(c1 && c1->glob){
+				c1->glob=2;
+				t->glob=1;
+			}
+			c0 = c0->child[0];
+		}
+	} else {
+		if(c0->glob){
+			c0->glob=2;
+			t->glob=1;
+		}
+		if(c1->glob){
+			c1->glob=2;
+			t->glob=1;
+		}
+	}
+	return t;
+}
+
+tree*
+token(char *str, int type)
+{
+	tree *t = newtree();
+	t->str = estrdup(str);
+	t->type = type;
+	return t;
+}
--- /dev/null
+++ b/librc/unix.c
@@ -1,0 +1,420 @@
+/*
+ * Unix versions of system-specific functions
+ *	By convention, exported routines herein have names beginning with an
+ *	upper case letter.
+ */
+#include "rc.h"
+#include "exec.h"
+#include "io.h"
+#include "fns.h"
+#include "getflags.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/wait.h>
+
+static void execfinit(void);
+
+builtin Builtin[] = {
+	"cd",		execcd,
+	"whatis",	execwhatis,
+	"eval",		execeval,
+	"exec",		execexec,	/* but with popword first */
+	"exit",		execexit,
+	"shift",	execshift,
+	"wait",		execwait,
+	".",		execdot,
+	"flag",		execflag,
+	"finit",	execfinit,
+	0
+};
+
+char Rcmain[] = PREFIX "/lib/rcmain";
+char Fdprefix[] = "/dev/fd/";
+
+char *Signame[NSIG];
+
+#define SEP '\1'
+extern char **environ;
+static char **envp;
+
+static void
+Xrdfn(void)
+{
+	char *s;
+	int len;
+
+	for(;*envp;envp++){
+		for(s=*envp;*s && *s!='(' && *s!='=';s++);
+		switch(*s){
+		case '(':		/* Bourne again */
+			if(strncmp(s, "()fn ", 5)!=0)
+				continue;
+			s=estrdup(s+2);
+			len=strlen(s);
+			s[len++]='\n';
+			envp++;
+			runq->pc--;	/* re-execute */
+			execcmds(openiocore(s, len), estrdup("*environ*"), runq->local, runq->redir);
+			runq->lex->qflag = 1;
+			return;
+		default:
+			continue;
+		}
+	}
+}
+
+static void
+execfinit(void)
+{
+	static union code rdfns[5];
+	if(rdfns[0].i==0){
+		rdfns[0].i = 1;
+		rdfns[1].s = "*rdfns*";
+		rdfns[2].f = Xrdfn;
+		rdfns[3].f = Xreturn;
+		rdfns[4].f = 0;
+	}
+	poplist();
+	envp=environ;
+	start(rdfns, 2, runq->local, runq->redir);
+}
+
+static int
+cmpenv(const void *aa, const void *ab)
+{
+	return strcmp(*(char**)aa, *(char**)ab);
+}
+
+static char**
+mkenv(void)
+{
+	char **env, **ep, *p, *q;
+	struct var **h, *v;
+	struct word *a;
+	int nvar = 0, nchr = 0, sep;
+
+	/*
+	 * Slightly kludgy loops look at locals then globals.
+	 * locals no longer exist - geoff
+	 */
+	for(h = gvar-1; h != &gvar[NVAR]; h++)
+	for(v = h >= gvar? *h: runq->local; v ;v = v->next){
+		if((v==vlook(v->name)) && v->val){
+			nvar++;
+			nchr+=strlen(v->name)+1;
+			for(a = v->val;a;a = a->next)
+				nchr+=strlen(a->word)+1;
+		}
+		if(v->fn){
+			nvar++;
+			nchr+=strlen(v->name)+strlen(v->fn[v->pc-1].s)+8;
+		}
+	}
+	env = (char **)emalloc((nvar+1)*sizeof(char *)+nchr);
+	ep = env;
+	p = (char *)&env[nvar+1];
+	for(h = gvar-1; h != &gvar[NVAR]; h++)
+	for(v = h >= gvar? *h: runq->local;v;v = v->next){
+		if((v==vlook(v->name)) && v->val){
+			*ep++=p;
+			q = v->name;
+			while(*q) *p++=*q++;
+			sep='=';
+			for(a = v->val;a;a = a->next){
+				*p++=sep;
+				sep = SEP;
+				q = a->word;
+				while(*q) *p++=*q++;
+			}
+			*p++='\0';
+		}
+		if(v->fn){
+			*ep++=p;
+			*p++='#'; *p++='('; *p++=')';	/* to fool Bourne */
+			*p++='f'; *p++='n'; *p++=' ';
+			q = v->name;
+			while(*q) *p++=*q++;
+			*p++=' ';
+			q = v->fn[v->pc-1].s;
+			while(*q) *p++=*q++;
+			*p++='\0';
+		}
+	}
+	*ep = 0;
+	qsort((void *)env, nvar, sizeof ep[0], cmpenv);
+	return env;	
+}
+
+static word*
+envval(char *s)
+{
+	char *t, c;
+	word *v;
+	for(t=s;*t&&*t!=SEP;t++);
+	c=*t;
+	*t='\0';
+	v=newword(s, c=='\0'?(word*)0:envval(t+1));
+	*t=c;
+	return v;
+}
+
+void
+Vinit(void)
+{
+	char *s;
+
+	for(envp=environ;*envp;envp++){
+		for(s=*envp;*s && *s!='(' && *s!='=';s++);
+		switch(*s){
+		case '=':
+			*s='\0';
+			setvar(*envp, envval(s+1));
+			*s='=';
+			break;
+		default: continue;
+		}
+	}
+}
+
+static void
+sighandler(int sig)
+{
+	trap[sig]++;
+	ntrap++;
+}
+
+void
+Trapinit(void)
+{
+	int i;
+
+	Signame[0] = "sigexit";
+
+#ifdef SIGINT
+	Signame[SIGINT] = "sigint";
+#endif
+#ifdef SIGTERM
+	Signame[SIGTERM] = "sigterm";
+#endif
+#ifdef SIGHUP
+	Signame[SIGHUP] = "sighup";
+#endif
+#ifdef SIGQUIT
+	Signame[SIGQUIT] = "sigquit";
+#endif
+#ifdef SIGPIPE
+	Signame[SIGPIPE] = "sigpipe";
+#endif
+#ifdef SIGUSR1
+	Signame[SIGUSR1] = "sigusr1";
+#endif
+#ifdef SIGUSR2
+	Signame[SIGUSR2] = "sigusr2";
+#endif
+#ifdef SIGBUS
+	Signame[SIGBUS] = "sigbus";
+#endif
+#ifdef SIGWINCH
+	Signame[SIGWINCH] = "sigwinch";
+#endif
+
+	for(i=1; i<NSIG; i++) if(Signame[i]){
+#ifdef SA_RESTART
+		struct sigaction a;
+
+		sigaction(i, NULL, &a);
+		a.sa_flags &= ~SA_RESTART;
+		a.sa_handler = sighandler;
+		sigaction(i, &a, NULL);
+#else
+		signal(i, sighandler);
+#endif
+	}
+}
+
+char*
+Errstr(void)
+{
+	return strerror(errno);
+}
+
+int
+Waitfor(int pid)
+{
+	thread *p;
+	char num[12];
+	int wpid, status;
+
+	if(pid >= 0 && !havewaitpid(pid))
+		return 0;
+	while((wpid = wait(&status))!=-1){
+		delwaitpid(wpid);
+		inttoascii(num, WIFSIGNALED(status)?WTERMSIG(status)+1000:WEXITSTATUS(status));
+		if(wpid==pid){
+			setstatus(num);
+			return 0;
+		}
+		for(p = runq->ret;p;p = p->ret)
+			if(p->pid==wpid){
+				p->pid=-1;
+				p->status = estrdup(num);
+				break;
+			}
+	}
+	if(Eintr()) return -1;
+	return 0;
+}
+
+static char **nextenv;
+
+void
+Updenv(void)
+{
+	if(nextenv){
+		free(nextenv);
+		nextenv = NULL;
+	}
+	if(err)
+		flushio(err);
+}
+
+void
+Exec(char **argv)
+{
+	if(nextenv==NULL) nextenv=mkenv();
+	execve(argv[0], argv+1, nextenv);
+}
+
+int
+Fork(void)
+{
+	Updenv();
+	return fork();
+}
+
+void*
+Opendir(char *name)
+{
+	return opendir(name);
+}
+
+char*
+Readdir(void *arg, int onlydirs)
+{
+	DIR *rd = arg;
+	struct dirent *ent = readdir(rd);
+	if(ent == NULL)
+		return 0;
+	return ent->d_name;
+}
+
+void
+Closedir(void *arg)
+{
+	DIR *rd = arg;
+	closedir(rd);
+}
+
+long
+Write(int fd, void *buf, long cnt)
+{
+	return write(fd, buf, cnt);
+}
+
+long
+Read(int fd, void *buf, long cnt)
+{
+	return read(fd, buf, cnt);
+}
+
+long
+Seek(int fd, long cnt, long whence)
+{
+	return lseek(fd, cnt, whence);
+}
+
+int
+Executable(char *file)
+{
+	return access(file, 01)==0;
+}
+
+int
+Open(char *file, int mode)
+{
+	static int tab[] = {O_RDONLY,O_WRONLY,O_RDWR,O_RDONLY};
+	int fd = open(file, tab[mode&3]);
+	if(fd >= 0 && mode == 3)
+		unlink(file);
+	return fd;
+}
+
+void
+Close(int fd)
+{
+	close(fd);
+}
+
+int
+Creat(char *file)
+{
+	return creat(file, 0666L);
+}
+
+int
+Dup(int a, int b)
+{
+	return dup2(a, b);
+}
+
+int
+Dup1(int a)
+{
+	return dup(a);
+}
+
+void
+Exit(void)
+{
+	Updenv();
+	exit(truestatus()?0:1);
+}
+
+int
+Eintr(void)
+{
+	return errno==EINTR;
+}
+
+void
+Noerror(void)
+{
+	errno=0;
+}
+
+int
+Isatty(int fd)
+{
+	return isatty(fd);
+}
+
+void
+Abort(void)
+{
+	abort();
+}
+
+int
+Chdir(char *dir)
+{
+	return chdir(dir);
+}
+
+void
+Prompt(char *s)
+{
+	pstr(err, s);
+	flushio(err);
+}
--- /dev/null
+++ b/librc/var.c
@@ -1,0 +1,109 @@
+#include "rc.h"
+#include "exec.h"
+#include "fns.h"
+
+var *gvar[NVAR];
+
+int
+hash(char *s, int n)
+{
+	int h = 0, i = 1;
+	while(*s) h+=*s++*i++;
+	h%=n;
+	return h<0?h+n:h;
+}
+#define	NKW	30
+struct kw{
+	char *name;
+	int type;
+	struct kw *next;
+}*kw[NKW];
+
+void
+kenter(int type, char *name)
+{
+	int h = hash(name, NKW);
+	struct kw *p = new(struct kw);
+	p->type = type;
+	p->name = name;
+	p->next = kw[h];
+	kw[h] = p;
+}
+
+void
+kinit(void)
+{
+	kenter(FOR, "for");
+	kenter(IN, "in");
+	kenter(WHILE, "while");
+	kenter(IF, "if");
+	kenter(NOT, "not");
+	kenter(TWIDDLE, "~");
+	kenter(BANG, "!");
+	kenter(SUBSHELL, "@");
+	kenter(SWITCH, "switch");
+	kenter(FN, "fn");
+}
+
+tree*
+klook(char *name)
+{
+	struct kw *p;
+	tree *t = token(name, WORD);
+	for(p = kw[hash(name, NKW)];p;p = p->next)
+		if(strcmp(p->name, name)==0){
+			t->type = p->type;
+			t->iskw = 1;
+			break;
+		}
+	return t;
+}
+
+var*
+newvar(char *name, var *next)
+{
+	int n = strlen(name)+1;
+	var *v = emalloc(sizeof(var)+n);
+	memmove(v->name, name, n);
+	v->next = next;
+	v->val = 0;
+	v->fn = 0;
+	v->changed = 0;
+	v->fnchanged = 0;
+	return v;
+}
+
+var*
+gvlook(char *name)
+{
+	int h = hash(name, NVAR);
+	var *v;
+	for(v = gvar[h];v;v = v->next) if(strcmp(v->name, name)==0) return v;
+	return gvar[h] = newvar(name, gvar[h]);
+}
+
+var*
+vlook(char *name)
+{
+	var *v;
+	if(runq)
+		for(v = runq->local;v;v = v->next)
+			if(strcmp(v->name, name)==0) return v;
+	return gvlook(name);
+}
+
+void
+setvar(char *name, word *val)
+{
+	var *v = vlook(name);
+	freewords(v->val);
+	v->val = val;
+	v->changed = 1;
+}
+
+void
+freevar(var *v)
+{
+	freewords(v->val);
+	free(v);
+}
--- /dev/null
+++ b/librc/y.tab.c
@@ -1,0 +1,1989 @@
+/* A Bison parser, made by GNU Bison 2.3.  */
+
+/* Skeleton implementation for Bison's Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+   simplifying the original so-called "semantic" parser.  */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+   infringing on user name space.  This should be done even for local
+   variables, as they might otherwise be expanded by user macros.
+   There are some unavoidable exceptions within include files to
+   define necessary library symbols; they are noted "INFRINGES ON
+   USER NAME SPACE" below.  */
+
+/* Identify Bison output.  */
+#define YYBISON 1
+
+/* Bison version.  */
+#define YYBISON_VERSION "2.3"
+
+/* Skeleton name.  */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers.  */
+#define YYPURE 0
+
+/* Using locations.  */
+#define YYLSP_NEEDED 0
+
+
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     FOR = 258,
+     IN = 259,
+     WHILE = 260,
+     IF = 261,
+     NOT = 262,
+     TWIDDLE = 263,
+     BANG = 264,
+     SUBSHELL = 265,
+     SWITCH = 266,
+     FN = 267,
+     WORD = 268,
+     REDIR = 269,
+     DUP = 270,
+     PIPE = 271,
+     SUB = 272,
+     SIMPLE = 273,
+     ARGLIST = 274,
+     WORDS = 275,
+     BRACE = 276,
+     PAREN = 277,
+     PCMD = 278,
+     PIPEFD = 279,
+     OROR = 280,
+     ANDAND = 281,
+     COUNT = 282
+   };
+#endif
+/* Tokens.  */
+#define FOR 258
+#define IN 259
+#define WHILE 260
+#define IF 261
+#define NOT 262
+#define TWIDDLE 263
+#define BANG 264
+#define SUBSHELL 265
+#define SWITCH 266
+#define FN 267
+#define WORD 268
+#define REDIR 269
+#define DUP 270
+#define PIPE 271
+#define SUB 272
+#define SIMPLE 273
+#define ARGLIST 274
+#define WORDS 275
+#define BRACE 276
+#define PAREN 277
+#define PCMD 278
+#define PIPEFD 279
+#define OROR 280
+#define ANDAND 281
+#define COUNT 282
+
+
+
+
+/* Copy the first part of user declarations.  */
+#line 12 "syn.y"
+
+#include "rc.h"
+#include "fns.h"
+
+
+/* Enabling traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+
+/* Enabling verbose error messages.  */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+/* Enabling the token table.  */
+#ifndef YYTOKEN_TABLE
+# define YYTOKEN_TABLE 0
+#endif
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+#line 16 "syn.y"
+{
+	struct tree *tree;
+}
+/* Line 193 of yacc.c.  */
+#line 159 "y.tab.c"
+	YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+
+
+/* Copy the second part of user declarations.  */
+
+
+/* Line 216 of yacc.c.  */
+#line 172 "y.tab.c"
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#elif (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+typedef signed char yytype_int8;
+#else
+typedef short int yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+#  define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+#  define YYSIZE_T size_t
+# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#  include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYSIZE_T size_t
+# else
+#  define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+#  if ENABLE_NLS
+#   include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+#   define YY_(msgid) dgettext ("bison-runtime", msgid)
+#  endif
+# endif
+# ifndef YY_
+#  define YY_(msgid) msgid
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E.  */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(e) ((void) (e))
+#else
+# define YYUSE(e) /* empty */
+#endif
+
+/* Identity function, used to suppress warnings about constant conditions.  */
+#ifndef lint
+# define YYID(n) (n)
+#else
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static int
+YYID (int i)
+#else
+static int
+YYID (i)
+    int i;
+#endif
+{
+  return i;
+}
+#endif
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols.  */
+
+# ifdef YYSTACK_USE_ALLOCA
+#  if YYSTACK_USE_ALLOCA
+#   ifdef __GNUC__
+#    define YYSTACK_ALLOC __builtin_alloca
+#   elif defined __BUILTIN_VA_ARG_INCR
+#    include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+#   elif defined _AIX
+#    define YYSTACK_ALLOC __alloca
+#   elif defined _MSC_VER
+#    include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+#    define alloca _alloca
+#   else
+#    define YYSTACK_ALLOC alloca
+#    if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#     include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#     ifndef _STDLIB_H
+#      define _STDLIB_H 1
+#     endif
+#    endif
+#   endif
+#  endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+   /* Pacify GCC's `empty if-body' warning.  */
+#  define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0))
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+    /* The OS might guarantee only one guard page at the bottom of the stack,
+       and a page size can be as small as 4096 bytes.  So we cannot safely
+       invoke alloca (N) if N exceeds 4096.  Use a slightly smaller number
+       to allow for a few compiler-allocated temporary stack slots.  */
+#   define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+#  endif
+# else
+#  define YYSTACK_ALLOC YYMALLOC
+#  define YYSTACK_FREE YYFREE
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+#   define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+#  endif
+#  if (defined __cplusplus && ! defined _STDLIB_H \
+       && ! ((defined YYMALLOC || defined malloc) \
+	     && (defined YYFREE || defined free)))
+#   include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#   ifndef _STDLIB_H
+#    define _STDLIB_H 1
+#   endif
+#  endif
+#  ifndef YYMALLOC
+#   define YYMALLOC malloc
+#   if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+#  ifndef YYFREE
+#   define YYFREE free
+#   if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+     && (! defined __cplusplus \
+	 || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member.  */
+union yyalloc
+{
+  yytype_int16 yyss;
+  YYSTYPE yyvs;
+  };
+
+/* The size of the maximum gap between one aligned stack and the next.  */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+   N elements.  */
+# define YYSTACK_BYTES(N) \
+     ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+      + YYSTACK_GAP_MAXIMUM)
+
+/* Copy COUNT objects from FROM to TO.  The source and destination do
+   not overlap.  */
+# ifndef YYCOPY
+#  if defined __GNUC__ && 1 < __GNUC__
+#   define YYCOPY(To, From, Count) \
+      __builtin_memcpy (To, From, (Count) * sizeof (*(From)))
+#  else
+#   define YYCOPY(To, From, Count)		\
+      do					\
+	{					\
+	  YYSIZE_T yyi;				\
+	  for (yyi = 0; yyi < (Count); yyi++)	\
+	    (To)[yyi] = (From)[yyi];		\
+	}					\
+      while (YYID (0))
+#  endif
+# endif
+
+/* Relocate STACK from its old location to the new one.  The
+   local variables YYSIZE and YYSTACKSIZE give the old and new number of
+   elements in the stack, and YYPTR gives the new location of the
+   stack.  Advance YYPTR to a properly aligned location for the next
+   stack.  */
+# define YYSTACK_RELOCATE(Stack)					\
+    do									\
+      {									\
+	YYSIZE_T yynewbytes;						\
+	YYCOPY (&yyptr->Stack, Stack, yysize);				\
+	Stack = &yyptr->Stack;						\
+	yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+	yyptr += yynewbytes / sizeof (*yyptr);				\
+      }									\
+    while (YYID (0))
+
+#endif
+
+/* YYFINAL -- State number of the termination state.  */
+#define YYFINAL  63
+/* YYLAST -- Last index in YYTABLE.  */
+#define YYLAST   347
+
+/* YYNTOKENS -- Number of terminals.  */
+#define YYNTOKENS  40
+/* YYNNTS -- Number of nonterminals.  */
+#define YYNNTS  24
+/* YYNRULES -- Number of rules.  */
+#define YYNRULES  72
+/* YYNRULES -- Number of states.  */
+#define YYNSTATES  118
+
+/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX.  */
+#define YYUNDEFTOK  2
+#define YYMAXUTOK   282
+
+#define YYTRANSLATE(YYX)						\
+  ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX.  */
+static const yytype_uint8 yytranslate[] =
+{
+       0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+      32,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,    30,     2,    29,     2,    34,     2,
+      37,    25,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,    33,
+       2,    38,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,    28,     2,    39,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,    35,     2,    36,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
+       5,     6,     7,     8,     9,    10,    11,    12,    13,    14,
+      15,    16,    17,    18,    19,    20,    21,    22,    23,    24,
+      26,    27,    31
+};
+
+#if YYDEBUG
+/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in
+   YYRHS.  */
+static const yytype_uint8 yyprhs[] =
+{
+       0,     0,     3,     4,     7,     9,    12,    14,    17,    20,
+      23,    25,    28,    32,    36,    40,    41,    44,    47,    49,
+      50,    53,    54,    59,    60,    65,    66,    75,    76,    83,
+      84,    89,    90,    95,    97,   101,   105,   109,   113,   116,
+     119,   122,   125,   129,   132,   134,   137,   140,   142,   146,
+     148,   150,   154,   157,   163,   166,   169,   171,   174,   178,
+     182,   185,   187,   189,   191,   193,   195,   197,   199,   201,
+     203,   205,   206
+};
+
+/* YYRHS -- A `-1'-separated list of the rules' RHS.  */
+static const yytype_int8 yyrhs[] =
+{
+      41,     0,    -1,    -1,    42,    32,    -1,    51,    -1,    44,
+      42,    -1,    51,    -1,    45,    43,    -1,    51,    33,    -1,
+      51,    34,    -1,    44,    -1,    51,    32,    -1,    35,    43,
+      36,    -1,    37,    43,    25,    -1,    59,    38,    60,    -1,
+      -1,    50,    49,    -1,    14,    60,    -1,    15,    -1,    -1,
+      46,    49,    -1,    -1,     6,    47,    52,    51,    -1,    -1,
+       6,     7,    53,    51,    -1,    -1,     3,    37,    60,     4,
+      63,    25,    54,    51,    -1,    -1,     3,    37,    60,    25,
+      55,    51,    -1,    -1,     5,    47,    56,    51,    -1,    -1,
+      11,    60,    57,    46,    -1,    58,    -1,     8,    60,    63,
+      -1,    51,    27,    51,    -1,    51,    26,    51,    -1,    51,
+      16,    51,    -1,    50,    51,    -1,    48,    51,    -1,     9,
+      51,    -1,    10,    51,    -1,    12,    63,    46,    -1,    12,
+      63,    -1,    59,    -1,    58,    60,    -1,    58,    50,    -1,
+      61,    -1,    59,    28,    60,    -1,    62,    -1,    61,    -1,
+      60,    28,    60,    -1,    29,    60,    -1,    29,    60,    17,
+      63,    25,    -1,    30,    60,    -1,    31,    60,    -1,    13,
+      -1,    39,    46,    -1,    39,    60,    46,    -1,    37,    63,
+      25,    -1,    14,    46,    -1,     3,    -1,     4,    -1,     5,
+      -1,     6,    -1,     7,    -1,     8,    -1,     9,    -1,    10,
+      -1,    11,    -1,    12,    -1,    -1,    63,    60,    -1
+};
+
+/* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
+static const yytype_uint8 yyrline[] =
+{
+       0,    24,    24,    25,    26,    27,    28,    29,    30,    31,
+      32,    33,    34,    35,    36,    37,    38,    39,    40,    41,
+      42,    43,    43,    45,    45,    46,    46,    56,    56,    58,
+      58,    60,    60,    62,    63,    64,    65,    66,    67,    68,
+      69,    70,    71,    72,    73,    74,    75,    76,    77,    78,
+      79,    80,    81,    82,    83,    84,    85,    86,    87,    88,
+      89,    90,    90,    90,    90,    90,    90,    90,    90,    90,
+      90,    91,    92
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+   First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
+static const char *const yytname[] =
+{
+  "$end", "error", "$undefined", "FOR", "IN", "WHILE", "IF", "NOT",
+  "TWIDDLE", "BANG", "SUBSHELL", "SWITCH", "FN", "WORD", "REDIR", "DUP",
+  "PIPE", "SUB", "SIMPLE", "ARGLIST", "WORDS", "BRACE", "PAREN", "PCMD",
+  "PIPEFD", "')'", "OROR", "ANDAND", "'^'", "'$'", "'\"'", "COUNT",
+  "'\\n'", "';'", "'&'", "'{'", "'}'", "'('", "'='", "'`'", "$accept",
+  "rc", "line", "body", "cmdsa", "cmdsan", "brace", "paren", "assign",
+  "epilog", "redir", "cmd", "@1", "@2", "@3", "@4", "@5", "@6", "simple",
+  "first", "word", "comword", "keyword", "words", 0
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to
+   token YYLEX-NUM.  */
+static const yytype_uint16 yytoknum[] =
+{
+       0,   256,   257,   258,   259,   260,   261,   262,   263,   264,
+     265,   266,   267,   268,   269,   270,   271,   272,   273,   274,
+     275,   276,   277,   278,   279,    41,   280,   281,    94,    36,
+      34,   282,    10,    59,    38,   123,   125,    40,    61,    96
+};
+# endif
+
+/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives.  */
+static const yytype_uint8 yyr1[] =
+{
+       0,    40,    41,    41,    42,    42,    43,    43,    44,    44,
+      45,    45,    46,    47,    48,    49,    49,    50,    50,    51,
+      51,    52,    51,    53,    51,    54,    51,    55,    51,    56,
+      51,    57,    51,    51,    51,    51,    51,    51,    51,    51,
+      51,    51,    51,    51,    58,    58,    58,    59,    59,    60,
+      60,    60,    61,    61,    61,    61,    61,    61,    61,    61,
+      61,    62,    62,    62,    62,    62,    62,    62,    62,    62,
+      62,    63,    63
+};
+
+/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN.  */
+static const yytype_uint8 yyr2[] =
+{
+       0,     2,     0,     2,     1,     2,     1,     2,     2,     2,
+       1,     2,     3,     3,     3,     0,     2,     2,     1,     0,
+       2,     0,     4,     0,     4,     0,     8,     0,     6,     0,
+       4,     0,     4,     1,     3,     3,     3,     3,     2,     2,
+       2,     2,     3,     2,     1,     2,     2,     1,     3,     1,
+       1,     3,     2,     5,     2,     2,     1,     2,     3,     3,
+       2,     1,     1,     1,     1,     1,     1,     1,     1,     1,
+       1,     0,     2
+};
+
+/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
+   STATE-NUM when YYTABLE doesn't specify something else to do.  Zero
+   means the default is an error.  */
+static const yytype_uint8 yydefact[] =
+{
+      19,     0,     0,     0,     0,    19,    19,     0,    71,    56,
+       0,    18,     0,     0,     0,    19,    71,     0,     0,     0,
+      19,    15,    19,    19,     4,    33,    44,    47,     0,    19,
+      29,    23,    21,    61,    62,    63,    64,    65,    66,    67,
+      68,    69,    70,     0,    71,    50,    49,    40,    41,    31,
+      43,    60,    17,    52,    54,    55,     0,    10,    19,     6,
+       0,    57,     0,     1,     3,     5,     0,    20,    15,    39,
+      38,    19,    19,    19,     8,     9,    46,    45,     0,     0,
+       0,     0,    19,    19,    19,     0,    34,     0,    42,    72,
+      71,    12,     7,    11,    59,    58,    16,    37,    36,    35,
+      48,    14,    71,    27,    13,    30,    24,    22,    51,    32,
+       0,     0,    19,    53,    25,    28,    19,    26
+};
+
+/* YYDEFGOTO[NTERM-NUM].  */
+static const yytype_int8 yydefgoto[] =
+{
+      -1,    18,    19,    56,    57,    58,    21,    30,    22,    67,
+      23,    59,    84,    83,   116,   112,    82,    87,    25,    26,
+      89,    45,    46,    50
+};
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+   STATE-NUM.  */
+#define YYPACT_NINF -28
+static const yytype_int16 yypact[] =
+{
+     122,   -27,   -13,    -3,   296,   308,   308,   296,   -28,   -28,
+     135,   -28,   296,   296,   296,   308,   -28,   135,    25,     5,
+     308,     4,   308,   308,    84,   172,   -26,   -28,   296,   308,
+     -28,   -28,   -28,   -28,   -28,   -28,   -28,   -28,   -28,   -28,
+     -28,   -28,   -28,     9,    15,   -28,   -28,    20,    20,    15,
+     135,   -28,    15,    30,   -28,   -28,    13,   -28,   308,    36,
+     185,   -28,   -19,   -28,   -28,   -28,   296,   -28,     4,    20,
+      20,   308,   308,   308,   -28,   -28,   -28,    15,   296,   296,
+      23,    32,   308,   308,   308,   296,   296,     9,   -28,    15,
+     -28,   -28,   -28,   -28,   -28,   -28,   -28,   -28,    20,    20,
+     -28,    15,   -28,   -28,   -28,    38,    38,    38,   -28,   -28,
+     222,   259,   308,   -28,   -28,    38,   308,    38
+};
+
+/* YYPGOTO[NTERM-NUM].  */
+static const yytype_int8 yypgoto[] =
+{
+     -28,   -28,    35,   -12,     1,   -28,    16,    57,   -28,    -7,
+     -18,     8,   -28,   -28,   -28,   -28,   -28,   -28,   -28,   -28,
+      28,     0,   -28,    -5
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]].  What to do in state STATE-NUM.  If
+   positive, shift that token.  If negative, reduce the rule which
+   number is the opposite.  If zero, do what YYDEFACT says.
+   If YYTABLE_NINF, syntax error.  */
+#define YYTABLE_NINF -3
+static const yytype_int8 yytable[] =
+{
+      27,    20,    78,    68,    31,    27,    27,    76,    24,    85,
+      28,    60,    79,    47,    48,    27,    15,    81,    66,    11,
+      27,    20,    27,    27,    29,    63,    51,   102,    24,    27,
+      69,    70,    44,    61,    29,    49,    71,    64,    52,    86,
+      53,    54,    55,    85,    15,    62,    92,    90,   103,    91,
+      68,    85,    71,    77,    71,    65,    80,   104,    27,    51,
+      32,    96,    72,    73,    72,    73,    88,     0,    93,    74,
+      75,    27,    27,    27,     0,     0,     0,     0,    95,    97,
+      98,    99,    27,    27,    27,   110,     0,     0,     0,     0,
+     105,   106,   107,     0,    52,     0,     0,   111,     0,     0,
+      71,     0,     0,   109,     0,     0,   100,   101,     0,     0,
+      72,    73,    27,   108,     0,     0,    27,    74,    75,     0,
+     115,     0,    -2,     0,   117,     1,     0,     2,     3,     0,
+       4,     5,     6,     7,     8,     9,    10,    11,    33,    34,
+      35,    36,    37,    38,    39,    40,    41,    42,     9,    43,
+       0,    12,    13,    14,     0,     0,     0,    15,     0,    16,
+       0,    17,     0,     0,    12,    13,    14,     0,     0,     0,
+      15,     0,    16,     0,    17,    33,    34,    35,    36,    37,
+      38,    39,    40,    41,    42,     9,    10,    11,    33,    34,
+      35,    36,    37,    38,    39,    40,    41,    42,     9,    43,
+       0,    12,    13,    14,     0,     0,     0,     0,     0,    16,
+      94,    17,     0,     0,    12,    13,    14,     0,     0,     0,
+       0,     0,    16,     0,    17,    33,    34,    35,    36,    37,
+      38,    39,    40,    41,    42,     9,    43,     0,     0,     0,
+       0,     0,     0,     0,     0,     0,     0,   113,     0,     0,
+       0,    12,    13,    14,     0,     0,     0,     0,     0,    16,
+       0,    17,    33,    34,    35,    36,    37,    38,    39,    40,
+      41,    42,     9,    43,     0,     0,     0,     0,     0,     0,
+       0,     0,     0,     0,   114,     0,     0,     0,    12,    13,
+      14,     0,     0,     0,     0,     0,    16,     0,    17,    33,
+      34,    35,    36,    37,    38,    39,    40,    41,    42,     9,
+      43,     1,     0,     2,     3,     0,     4,     5,     6,     7,
+       8,     9,    10,    11,     0,    12,    13,    14,     0,     0,
+       0,     0,     0,    16,     0,    17,     0,    12,    13,    14,
+       0,     0,     0,    15,     0,    16,     0,    17
+};
+
+static const yytype_int8 yycheck[] =
+{
+       0,     0,    28,    21,     7,     5,     6,    25,     0,    28,
+      37,    16,    38,     5,     6,    15,    35,    29,    14,    15,
+      20,    20,    22,    23,    37,     0,    10,     4,    20,    29,
+      22,    23,     4,    17,    37,     7,    16,    32,    10,    44,
+      12,    13,    14,    28,    35,    17,    58,    17,    25,    36,
+      68,    28,    16,    25,    16,    20,    28,    25,    58,    43,
+       3,    68,    26,    27,    26,    27,    50,    -1,    32,    33,
+      34,    71,    72,    73,    -1,    -1,    -1,    -1,    62,    71,
+      72,    73,    82,    83,    84,    90,    -1,    -1,    -1,    -1,
+      82,    83,    84,    -1,    66,    -1,    -1,   102,    -1,    -1,
+      16,    -1,    -1,    87,    -1,    -1,    78,    79,    -1,    -1,
+      26,    27,   112,    85,    -1,    -1,   116,    33,    34,    -1,
+     112,    -1,     0,    -1,   116,     3,    -1,     5,     6,    -1,
+       8,     9,    10,    11,    12,    13,    14,    15,     3,     4,
+       5,     6,     7,     8,     9,    10,    11,    12,    13,    14,
+      -1,    29,    30,    31,    -1,    -1,    -1,    35,    -1,    37,
+      -1,    39,    -1,    -1,    29,    30,    31,    -1,    -1,    -1,
+      35,    -1,    37,    -1,    39,     3,     4,     5,     6,     7,
+       8,     9,    10,    11,    12,    13,    14,    15,     3,     4,
+       5,     6,     7,     8,     9,    10,    11,    12,    13,    14,
+      -1,    29,    30,    31,    -1,    -1,    -1,    -1,    -1,    37,
+      25,    39,    -1,    -1,    29,    30,    31,    -1,    -1,    -1,
+      -1,    -1,    37,    -1,    39,     3,     4,     5,     6,     7,
+       8,     9,    10,    11,    12,    13,    14,    -1,    -1,    -1,
+      -1,    -1,    -1,    -1,    -1,    -1,    -1,    25,    -1,    -1,
+      -1,    29,    30,    31,    -1,    -1,    -1,    -1,    -1,    37,
+      -1,    39,     3,     4,     5,     6,     7,     8,     9,    10,
+      11,    12,    13,    14,    -1,    -1,    -1,    -1,    -1,    -1,
+      -1,    -1,    -1,    -1,    25,    -1,    -1,    -1,    29,    30,
+      31,    -1,    -1,    -1,    -1,    -1,    37,    -1,    39,     3,
+       4,     5,     6,     7,     8,     9,    10,    11,    12,    13,
+      14,     3,    -1,     5,     6,    -1,     8,     9,    10,    11,
+      12,    13,    14,    15,    -1,    29,    30,    31,    -1,    -1,
+      -1,    -1,    -1,    37,    -1,    39,    -1,    29,    30,    31,
+      -1,    -1,    -1,    35,    -1,    37,    -1,    39
+};
+
+/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+   symbol of state STATE-NUM.  */
+static const yytype_uint8 yystos[] =
+{
+       0,     3,     5,     6,     8,     9,    10,    11,    12,    13,
+      14,    15,    29,    30,    31,    35,    37,    39,    41,    42,
+      44,    46,    48,    50,    51,    58,    59,    61,    37,    37,
+      47,     7,    47,     3,     4,     5,     6,     7,     8,     9,
+      10,    11,    12,    14,    60,    61,    62,    51,    51,    60,
+      63,    46,    60,    60,    60,    60,    43,    44,    45,    51,
+      63,    46,    60,     0,    32,    42,    14,    49,    50,    51,
+      51,    16,    26,    27,    33,    34,    50,    60,    28,    38,
+      60,    43,    56,    53,    52,    28,    63,    57,    46,    60,
+      17,    36,    43,    32,    25,    46,    49,    51,    51,    51,
+      60,    60,     4,    25,    25,    51,    51,    51,    60,    46,
+      63,    63,    55,    25,    25,    51,    54,    51
+};
+
+#define yyerrok		(yyerrstatus = 0)
+#define yyclearin	(yychar = YYEMPTY)
+#define YYEMPTY		(-2)
+#define YYEOF		0
+
+#define YYACCEPT	goto yyacceptlab
+#define YYABORT		goto yyabortlab
+#define YYERROR		goto yyerrorlab
+
+
+/* Like YYERROR except do call yyerror.  This remains here temporarily
+   to ease the transition to the new meaning of YYERROR, for GCC.
+   Once GCC version 2 has supplanted version 1, this can go.  */
+
+#define YYFAIL		goto yyerrlab
+
+#define YYRECOVERING()  (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value)					\
+do								\
+  if (yychar == YYEMPTY && yylen == 1)				\
+    {								\
+      yychar = (Token);						\
+      yylval = (Value);						\
+      yytoken = YYTRANSLATE (yychar);				\
+      YYPOPSTACK (1);						\
+      goto yybackup;						\
+    }								\
+  else								\
+    {								\
+      yyerror (YY_("syntax error: cannot back up")); \
+      YYERROR;							\
+    }								\
+while (YYID (0))
+
+
+#define YYTERROR	1
+#define YYERRCODE	256
+
+
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+   If N is 0, then set CURRENT to the empty location which ends
+   the previous symbol: RHS[0] (always defined).  */
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K])
+#ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N)				\
+    do									\
+      if (YYID (N))                                                    \
+	{								\
+	  (Current).first_line   = YYRHSLOC (Rhs, 1).first_line;	\
+	  (Current).first_column = YYRHSLOC (Rhs, 1).first_column;	\
+	  (Current).last_line    = YYRHSLOC (Rhs, N).last_line;		\
+	  (Current).last_column  = YYRHSLOC (Rhs, N).last_column;	\
+	}								\
+      else								\
+	{								\
+	  (Current).first_line   = (Current).last_line   =		\
+	    YYRHSLOC (Rhs, 0).last_line;				\
+	  (Current).first_column = (Current).last_column =		\
+	    YYRHSLOC (Rhs, 0).last_column;				\
+	}								\
+    while (YYID (0))
+#endif
+
+
+/* YY_LOCATION_PRINT -- Print the location on the stream.
+   This macro was not mandated originally: define only if we know
+   we won't break user code: when these are the locations we know.  */
+
+#ifndef YY_LOCATION_PRINT
+# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL
+#  define YY_LOCATION_PRINT(File, Loc)			\
+     fprintf (File, "%d.%d-%d.%d",			\
+	      (Loc).first_line, (Loc).first_column,	\
+	      (Loc).last_line,  (Loc).last_column)
+# else
+#  define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+# endif
+#endif
+
+
+/* YYLEX -- calling `yylex' with the right arguments.  */
+
+#ifdef YYLEX_PARAM
+# define YYLEX yylex (YYLEX_PARAM)
+#else
+# define YYLEX yylex ()
+#endif
+
+/* Enable debugging if requested.  */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+#  include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args)			\
+do {						\
+  if (yydebug)					\
+    YYFPRINTF Args;				\
+} while (YYID (0))
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)			  \
+do {									  \
+  if (yydebug)								  \
+    {									  \
+      YYFPRINTF (stderr, "%s ", Title);					  \
+      yy_symbol_print (stderr,						  \
+		  Type, Value); \
+      YYFPRINTF (stderr, "\n");						  \
+    }									  \
+} while (YYID (0))
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+#else
+static void
+yy_symbol_value_print (yyoutput, yytype, yyvaluep)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+#endif
+{
+  if (!yyvaluep)
+    return;
+# ifdef YYPRINT
+  if (yytype < YYNTOKENS)
+    YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# else
+  YYUSE (yyoutput);
+# endif
+  switch (yytype)
+    {
+      default:
+	break;
+    }
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+#else
+static void
+yy_symbol_print (yyoutput, yytype, yyvaluep)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+#endif
+{
+  if (yytype < YYNTOKENS)
+    YYFPRINTF (yyoutput, "token %s (", yytname[yytype]);
+  else
+    YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]);
+
+  yy_symbol_value_print (yyoutput, yytype, yyvaluep);
+  YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included).                                                   |
+`------------------------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_stack_print (yytype_int16 *bottom, yytype_int16 *top)
+#else
+static void
+yy_stack_print (bottom, top)
+    yytype_int16 *bottom;
+    yytype_int16 *top;
+#endif
+{
+  YYFPRINTF (stderr, "Stack now");
+  for (; bottom <= top; ++bottom)
+    YYFPRINTF (stderr, " %d", *bottom);
+  YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top)				\
+do {								\
+  if (yydebug)							\
+    yy_stack_print ((Bottom), (Top));				\
+} while (YYID (0))
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced.  |
+`------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_reduce_print (YYSTYPE *yyvsp, int yyrule)
+#else
+static void
+yy_reduce_print (yyvsp, yyrule)
+    YYSTYPE *yyvsp;
+    int yyrule;
+#endif
+{
+  int yynrhs = yyr2[yyrule];
+  int yyi;
+  unsigned long int yylno = yyrline[yyrule];
+  YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+	     yyrule - 1, yylno);
+  /* The symbols being reduced.  */
+  for (yyi = 0; yyi < yynrhs; yyi++)
+    {
+      fprintf (stderr, "   $%d = ", yyi + 1);
+      yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi],
+		       &(yyvsp[(yyi + 1) - (yynrhs)])
+		       		       );
+      fprintf (stderr, "\n");
+    }
+}
+
+# define YY_REDUCE_PRINT(Rule)		\
+do {					\
+  if (yydebug)				\
+    yy_reduce_print (yyvsp, Rule); \
+} while (YYID (0))
+
+/* Nonzero means print parse trace.  It is left uninitialized so that
+   multiple parsers can coexist.  */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks.  */
+#ifndef	YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+   if the built-in stack extension method is used).
+
+   Do not make this value too large; the results are undefined if
+   YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+   evaluated with infinite-precision integer arithmetic.  */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+#  if defined __GLIBC__ && defined _STRING_H
+#   define yystrlen strlen
+#  else
+/* Return the length of YYSTR.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static YYSIZE_T
+yystrlen (const char *yystr)
+#else
+static YYSIZE_T
+yystrlen (yystr)
+    const char *yystr;
+#endif
+{
+  YYSIZE_T yylen;
+  for (yylen = 0; yystr[yylen]; yylen++)
+    continue;
+  return yylen;
+}
+#  endif
+# endif
+
+# ifndef yystpcpy
+#  if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+#   define yystpcpy stpcpy
+#  else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+   YYDEST.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+#else
+static char *
+yystpcpy (yydest, yysrc)
+    char *yydest;
+    const char *yysrc;
+#endif
+{
+  char *yyd = yydest;
+  const char *yys = yysrc;
+
+  while ((*yyd++ = *yys++) != '\0')
+    continue;
+
+  return yyd - 1;
+}
+#  endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+   quotes and backslashes, so that it's suitable for yyerror.  The
+   heuristic is that double-quoting is unnecessary unless the string
+   contains an apostrophe, a comma, or backslash (other than
+   backslash-backslash).  YYSTR is taken from yytname.  If YYRES is
+   null, do not copy; instead, return the length of what the result
+   would have been.  */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+  if (*yystr == '"')
+    {
+      YYSIZE_T yyn = 0;
+      char const *yyp = yystr;
+
+      for (;;)
+	switch (*++yyp)
+	  {
+	  case '\'':
+	  case ',':
+	    goto do_not_strip_quotes;
+
+	  case '\\':
+	    if (*++yyp != '\\')
+	      goto do_not_strip_quotes;
+	    /* Fall through.  */
+	  default:
+	    if (yyres)
+	      yyres[yyn] = *yyp;
+	    yyn++;
+	    break;
+
+	  case '"':
+	    if (yyres)
+	      yyres[yyn] = '\0';
+	    return yyn;
+	  }
+    do_not_strip_quotes: ;
+    }
+
+  if (! yyres)
+    return yystrlen (yystr);
+
+  return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into YYRESULT an error message about the unexpected token
+   YYCHAR while in state YYSTATE.  Return the number of bytes copied,
+   including the terminating null byte.  If YYRESULT is null, do not
+   copy anything; just return the number of bytes that would be
+   copied.  As a special case, return 0 if an ordinary "syntax error"
+   message will do.  Return YYSIZE_MAXIMUM if overflow occurs during
+   size calculation.  */
+static YYSIZE_T
+yysyntax_error (char *yyresult, int yystate, int yychar)
+{
+  int yyn = yypact[yystate];
+
+  if (! (YYPACT_NINF < yyn && yyn <= YYLAST))
+    return 0;
+  else
+    {
+      int yytype = YYTRANSLATE (yychar);
+      YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]);
+      YYSIZE_T yysize = yysize0;
+      YYSIZE_T yysize1;
+      int yysize_overflow = 0;
+      enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+      char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+      int yyx;
+
+# if 0
+      /* This is so xgettext sees the translatable formats that are
+	 constructed on the fly.  */
+      YY_("syntax error, unexpected %s");
+      YY_("syntax error, unexpected %s, expecting %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s or %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s");
+# endif
+      char *yyfmt;
+      char const *yyf;
+      static char const yyunexpected[] = "syntax error, unexpected %s";
+      static char const yyexpecting[] = ", expecting %s";
+      static char const yyor[] = " or %s";
+      char yyformat[sizeof yyunexpected
+		    + sizeof yyexpecting - 1
+		    + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2)
+		       * (sizeof yyor - 1))];
+      char const *yyprefix = yyexpecting;
+
+      /* Start YYX at -YYN if negative to avoid negative indexes in
+	 YYCHECK.  */
+      int yyxbegin = yyn < 0 ? -yyn : 0;
+
+      /* Stay within bounds of both yycheck and yytname.  */
+      int yychecklim = YYLAST - yyn + 1;
+      int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+      int yycount = 1;
+
+      yyarg[0] = yytname[yytype];
+      yyfmt = yystpcpy (yyformat, yyunexpected);
+
+      for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+	if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR)
+	  {
+	    if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+	      {
+		yycount = 1;
+		yysize = yysize0;
+		yyformat[sizeof yyunexpected - 1] = '\0';
+		break;
+	      }
+	    yyarg[yycount++] = yytname[yyx];
+	    yysize1 = yysize + yytnamerr (0, yytname[yyx]);
+	    yysize_overflow |= (yysize1 < yysize);
+	    yysize = yysize1;
+	    yyfmt = yystpcpy (yyfmt, yyprefix);
+	    yyprefix = yyor;
+	  }
+
+      yyf = YY_(yyformat);
+      yysize1 = yysize + yystrlen (yyf);
+      yysize_overflow |= (yysize1 < yysize);
+      yysize = yysize1;
+
+      if (yysize_overflow)
+	return YYSIZE_MAXIMUM;
+
+      if (yyresult)
+	{
+	  /* Avoid sprintf, as that infringes on the user's name space.
+	     Don't have undefined behavior even if the translation
+	     produced a string with the wrong number of "%s"s.  */
+	  char *yyp = yyresult;
+	  int yyi = 0;
+	  while ((*yyp = *yyf) != '\0')
+	    {
+	      if (*yyp == '%' && yyf[1] == 's' && yyi < yycount)
+		{
+		  yyp += yytnamerr (yyp, yyarg[yyi++]);
+		  yyf += 2;
+		}
+	      else
+		{
+		  yyp++;
+		  yyf++;
+		}
+	    }
+	}
+      return yysize;
+    }
+}
+#endif /* YYERROR_VERBOSE */
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol.  |
+`-----------------------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
+#else
+static void
+yydestruct (yymsg, yytype, yyvaluep)
+    const char *yymsg;
+    int yytype;
+    YYSTYPE *yyvaluep;
+#endif
+{
+  YYUSE (yyvaluep);
+
+  if (!yymsg)
+    yymsg = "Deleting";
+  YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+  switch (yytype)
+    {
+
+      default:
+	break;
+    }
+}
+
+
+/* Prevent warnings from -Wmissing-prototypes.  */
+
+#ifdef YYPARSE_PARAM
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void *YYPARSE_PARAM);
+#else
+int yyparse ();
+#endif
+#else /* ! YYPARSE_PARAM */
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void);
+#else
+int yyparse ();
+#endif
+#endif /* ! YYPARSE_PARAM */
+
+
+
+/* The look-ahead symbol.  */
+int yychar;
+
+/* The semantic value of the look-ahead symbol.  */
+YYSTYPE yylval;
+
+/* Number of syntax errors so far.  */
+int yynerrs;
+
+
+
+/*----------.
+| yyparse.  |
+`----------*/
+
+#ifdef YYPARSE_PARAM
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void *YYPARSE_PARAM)
+#else
+int
+yyparse (YYPARSE_PARAM)
+    void *YYPARSE_PARAM;
+#endif
+#else /* ! YYPARSE_PARAM */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void)
+#else
+int
+yyparse ()
+
+#endif
+#endif
+{
+  
+  int yystate;
+  int yyn;
+  int yyresult;
+  /* Number of tokens to shift before error messages enabled.  */
+  int yyerrstatus;
+  /* Look-ahead token as an internal (translated) token number.  */
+  int yytoken = 0;
+#if YYERROR_VERBOSE
+  /* Buffer for error messages, and its allocated size.  */
+  char yymsgbuf[128];
+  char *yymsg = yymsgbuf;
+  YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+  /* Three stacks and their tools:
+     `yyss': related to states,
+     `yyvs': related to semantic values,
+     `yyls': related to locations.
+
+     Refer to the stacks thru separate pointers, to allow yyoverflow
+     to reallocate them elsewhere.  */
+
+  /* The state stack.  */
+  yytype_int16 yyssa[YYINITDEPTH];
+  yytype_int16 *yyss = yyssa;
+  yytype_int16 *yyssp;
+
+  /* The semantic value stack.  */
+  YYSTYPE yyvsa[YYINITDEPTH];
+  YYSTYPE *yyvs = yyvsa;
+  YYSTYPE *yyvsp;
+
+
+
+#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N))
+
+  YYSIZE_T yystacksize = YYINITDEPTH;
+
+  /* The variables used to return semantic value and location from the
+     action routines.  */
+  YYSTYPE yyval;
+
+
+  /* The number of symbols on the RHS of the reduced rule.
+     Keep to zero when no symbol should be popped.  */
+  int yylen = 0;
+
+  YYDPRINTF ((stderr, "Starting parse\n"));
+
+  yystate = 0;
+  yyerrstatus = 0;
+  yynerrs = 0;
+  yychar = YYEMPTY;		/* Cause a token to be read.  */
+
+  /* Initialize stack pointers.
+     Waste one element of value and location stack
+     so that they stay on the same level as the state stack.
+     The wasted elements are never initialized.  */
+
+  yyssp = yyss;
+  yyvsp = yyvs;
+
+  goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate.  |
+`------------------------------------------------------------*/
+ yynewstate:
+  /* In all cases, when you get here, the value and location stacks
+     have just been pushed.  So pushing a state here evens the stacks.  */
+  yyssp++;
+
+ yysetstate:
+  *yyssp = yystate;
+
+  if (yyss + yystacksize - 1 <= yyssp)
+    {
+      /* Get the current used size of the three stacks, in elements.  */
+      YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+      {
+	/* Give user a chance to reallocate the stack.  Use copies of
+	   these so that the &'s don't force the real ones into
+	   memory.  */
+	YYSTYPE *yyvs1 = yyvs;
+	yytype_int16 *yyss1 = yyss;
+
+
+	/* Each stack pointer address is followed by the size of the
+	   data in use in that stack, in bytes.  This used to be a
+	   conditional around just the two extra args, but that might
+	   be undefined if yyoverflow is a macro.  */
+	yyoverflow (YY_("memory exhausted"),
+		    &yyss1, yysize * sizeof (*yyssp),
+		    &yyvs1, yysize * sizeof (*yyvsp),
+
+		    &yystacksize);
+
+	yyss = yyss1;
+	yyvs = yyvs1;
+      }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+      goto yyexhaustedlab;
+# else
+      /* Extend the stack our own way.  */
+      if (YYMAXDEPTH <= yystacksize)
+	goto yyexhaustedlab;
+      yystacksize *= 2;
+      if (YYMAXDEPTH < yystacksize)
+	yystacksize = YYMAXDEPTH;
+
+      {
+	yytype_int16 *yyss1 = yyss;
+	union yyalloc *yyptr =
+	  (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+	if (! yyptr)
+	  goto yyexhaustedlab;
+	YYSTACK_RELOCATE (yyss);
+	YYSTACK_RELOCATE (yyvs);
+
+#  undef YYSTACK_RELOCATE
+	if (yyss1 != yyssa)
+	  YYSTACK_FREE (yyss1);
+      }
+# endif
+#endif /* no yyoverflow */
+
+      yyssp = yyss + yysize - 1;
+      yyvsp = yyvs + yysize - 1;
+
+
+      YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+		  (unsigned long int) yystacksize));
+
+      if (yyss + yystacksize - 1 <= yyssp)
+	YYABORT;
+    }
+
+  YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+  goto yybackup;
+
+/*-----------.
+| yybackup.  |
+`-----------*/
+yybackup:
+
+  /* Do appropriate processing given the current state.  Read a
+     look-ahead token if we need one and don't already have one.  */
+
+  /* First try to decide what to do without reference to look-ahead token.  */
+  yyn = yypact[yystate];
+  if (yyn == YYPACT_NINF)
+    goto yydefault;
+
+  /* Not known => get a look-ahead token if don't already have one.  */
+
+  /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol.  */
+  if (yychar == YYEMPTY)
+    {
+      YYDPRINTF ((stderr, "Reading a token: "));
+      yychar = YYLEX;
+    }
+
+  if (yychar <= YYEOF)
+    {
+      yychar = yytoken = YYEOF;
+      YYDPRINTF ((stderr, "Now at end of input.\n"));
+    }
+  else
+    {
+      yytoken = YYTRANSLATE (yychar);
+      YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+    }
+
+  /* If the proper action on seeing token YYTOKEN is to reduce or to
+     detect an error, take that action.  */
+  yyn += yytoken;
+  if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+    goto yydefault;
+  yyn = yytable[yyn];
+  if (yyn <= 0)
+    {
+      if (yyn == 0 || yyn == YYTABLE_NINF)
+	goto yyerrlab;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+  /* Count tokens shifted since error; after three, turn off error
+     status.  */
+  if (yyerrstatus)
+    yyerrstatus--;
+
+  /* Shift the look-ahead token.  */
+  YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+  /* Discard the shifted token unless it is eof.  */
+  if (yychar != YYEOF)
+    yychar = YYEMPTY;
+
+  yystate = yyn;
+  *++yyvsp = yylval;
+
+  goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state.  |
+`-----------------------------------------------------------*/
+yydefault:
+  yyn = yydefact[yystate];
+  if (yyn == 0)
+    goto yyerrlab;
+  goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction.  |
+`-----------------------------*/
+yyreduce:
+  /* yyn is the number of a rule to reduce with.  */
+  yylen = yyr2[yyn];
+
+  /* If YYLEN is nonzero, implement the default value of the action:
+     `$$ = $1'.
+
+     Otherwise, the following line sets YYVAL to garbage.
+     This behavior is undocumented and Bison
+     users should not rely upon it.  Assigning to YYVAL
+     unconditionally makes the parser a bit smaller, and it avoids a
+     GCC warning that YYVAL may be used uninitialized.  */
+  yyval = yyvsp[1-yylen];
+
+
+  YY_REDUCE_PRINT (yyn);
+  switch (yyn)
+    {
+        case 2:
+#line 24 "syn.y"
+    { return 1;}
+    break;
+
+  case 3:
+#line 25 "syn.y"
+    {readhere(lex->input); return !compile((yyvsp[(1) - (2)].tree));}
+    break;
+
+  case 5:
+#line 27 "syn.y"
+    {(yyval.tree)=tree2(';', (yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 7:
+#line 29 "syn.y"
+    {(yyval.tree)=tree2(';', (yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 9:
+#line 31 "syn.y"
+    {(yyval.tree)=tree1('&', (yyvsp[(1) - (2)].tree));}
+    break;
+
+  case 11:
+#line 33 "syn.y"
+    {readhere(lex->input);}
+    break;
+
+  case 12:
+#line 34 "syn.y"
+    {(yyval.tree)=tree1(BRACE, (yyvsp[(2) - (3)].tree));}
+    break;
+
+  case 13:
+#line 35 "syn.y"
+    {(yyval.tree)=tree1(PCMD, (yyvsp[(2) - (3)].tree));}
+    break;
+
+  case 14:
+#line 36 "syn.y"
+    {(yyval.tree)=tree2('=', (yyvsp[(1) - (3)].tree), (yyvsp[(3) - (3)].tree));}
+    break;
+
+  case 15:
+#line 37 "syn.y"
+    {(yyval.tree)=0;}
+    break;
+
+  case 16:
+#line 38 "syn.y"
+    {(yyval.tree)=mung2((yyvsp[(1) - (2)].tree), (yyvsp[(1) - (2)].tree)->child[0], (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 17:
+#line 39 "syn.y"
+    {(yyval.tree)=mung1((yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree)); if((yyval.tree)->rtype==HERE) heredoc((yyval.tree));}
+    break;
+
+  case 19:
+#line 41 "syn.y"
+    {(yyval.tree)=0;}
+    break;
+
+  case 20:
+#line 42 "syn.y"
+    {(yyval.tree)=epimung((yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 21:
+#line 43 "syn.y"
+    {skipnl();}
+    break;
+
+  case 22:
+#line 44 "syn.y"
+    {(yyval.tree)=mung2((yyvsp[(1) - (4)].tree), (yyvsp[(2) - (4)].tree), (yyvsp[(4) - (4)].tree));}
+    break;
+
+  case 23:
+#line 45 "syn.y"
+    {skipnl();}
+    break;
+
+  case 24:
+#line 45 "syn.y"
+    {(yyval.tree)=mung1((yyvsp[(2) - (4)].tree), (yyvsp[(4) - (4)].tree));}
+    break;
+
+  case 25:
+#line 46 "syn.y"
+    {skipnl();}
+    break;
+
+  case 26:
+#line 55 "syn.y"
+    {(yyval.tree)=mung3((yyvsp[(1) - (8)].tree), (yyvsp[(3) - (8)].tree), (yyvsp[(5) - (8)].tree) ? (yyvsp[(5) - (8)].tree) : tree1(PAREN, (yyvsp[(5) - (8)].tree)), (yyvsp[(8) - (8)].tree));}
+    break;
+
+  case 27:
+#line 56 "syn.y"
+    {skipnl();}
+    break;
+
+  case 28:
+#line 57 "syn.y"
+    {(yyval.tree)=mung3((yyvsp[(1) - (6)].tree), (yyvsp[(3) - (6)].tree), (tree*)0, (yyvsp[(6) - (6)].tree));}
+    break;
+
+  case 29:
+#line 58 "syn.y"
+    {skipnl();}
+    break;
+
+  case 30:
+#line 59 "syn.y"
+    {(yyval.tree)=mung2((yyvsp[(1) - (4)].tree), (yyvsp[(2) - (4)].tree), (yyvsp[(4) - (4)].tree));}
+    break;
+
+  case 31:
+#line 60 "syn.y"
+    {skipnl();}
+    break;
+
+  case 32:
+#line 61 "syn.y"
+    {(yyval.tree)=tree2(SWITCH, (yyvsp[(2) - (4)].tree), (yyvsp[(4) - (4)].tree));}
+    break;
+
+  case 33:
+#line 62 "syn.y"
+    {(yyval.tree)=simplemung((yyvsp[(1) - (1)].tree));}
+    break;
+
+  case 34:
+#line 63 "syn.y"
+    {(yyval.tree)=mung2((yyvsp[(1) - (3)].tree), (yyvsp[(2) - (3)].tree), (yyvsp[(3) - (3)].tree));}
+    break;
+
+  case 35:
+#line 64 "syn.y"
+    {(yyval.tree)=tree2(ANDAND, (yyvsp[(1) - (3)].tree), (yyvsp[(3) - (3)].tree));}
+    break;
+
+  case 36:
+#line 65 "syn.y"
+    {(yyval.tree)=tree2(OROR, (yyvsp[(1) - (3)].tree), (yyvsp[(3) - (3)].tree));}
+    break;
+
+  case 37:
+#line 66 "syn.y"
+    {(yyval.tree)=mung2((yyvsp[(2) - (3)].tree), (yyvsp[(1) - (3)].tree), (yyvsp[(3) - (3)].tree));}
+    break;
+
+  case 38:
+#line 67 "syn.y"
+    {(yyval.tree)=mung2((yyvsp[(1) - (2)].tree), (yyvsp[(1) - (2)].tree)->child[0], (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 39:
+#line 68 "syn.y"
+    {(yyval.tree)=mung3((yyvsp[(1) - (2)].tree), (yyvsp[(1) - (2)].tree)->child[0], (yyvsp[(1) - (2)].tree)->child[1], (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 40:
+#line 69 "syn.y"
+    {(yyval.tree)=mung1((yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 41:
+#line 70 "syn.y"
+    {(yyval.tree)=mung1((yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 42:
+#line 71 "syn.y"
+    {(yyval.tree)=tree2(FN, (yyvsp[(2) - (3)].tree), (yyvsp[(3) - (3)].tree));}
+    break;
+
+  case 43:
+#line 72 "syn.y"
+    {(yyval.tree)=tree1(FN, (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 45:
+#line 74 "syn.y"
+    {(yyval.tree)=globprop(tree2(ARGLIST, (yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree)));}
+    break;
+
+  case 46:
+#line 75 "syn.y"
+    {(yyval.tree)=globprop(tree2(ARGLIST, (yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree)));}
+    break;
+
+  case 48:
+#line 77 "syn.y"
+    {(yyval.tree)=globprop(tree2('^', (yyvsp[(1) - (3)].tree), (yyvsp[(3) - (3)].tree)));}
+    break;
+
+  case 49:
+#line 78 "syn.y"
+    {lex->lastword=1; (yyvsp[(1) - (1)].tree)->type=WORD;}
+    break;
+
+  case 51:
+#line 80 "syn.y"
+    {(yyval.tree)=globprop(tree2('^', (yyvsp[(1) - (3)].tree), (yyvsp[(3) - (3)].tree)));}
+    break;
+
+  case 52:
+#line 81 "syn.y"
+    {(yyval.tree)=tree1('$', (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 53:
+#line 82 "syn.y"
+    {(yyval.tree)=tree2(SUB, (yyvsp[(2) - (5)].tree), (yyvsp[(4) - (5)].tree));}
+    break;
+
+  case 54:
+#line 83 "syn.y"
+    {(yyval.tree)=tree1('"', (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 55:
+#line 84 "syn.y"
+    {(yyval.tree)=tree1(COUNT, (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 57:
+#line 86 "syn.y"
+    {(yyval.tree)=tree2('`', (tree*)0, (yyvsp[(2) - (2)].tree));}
+    break;
+
+  case 58:
+#line 87 "syn.y"
+    {(yyval.tree)=tree2('`', (yyvsp[(2) - (3)].tree), (yyvsp[(3) - (3)].tree));}
+    break;
+
+  case 59:
+#line 88 "syn.y"
+    {(yyval.tree)=globprop(tree1(PAREN, (yyvsp[(2) - (3)].tree)));}
+    break;
+
+  case 60:
+#line 89 "syn.y"
+    {(yyval.tree)=mung1((yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree)); (yyval.tree)->type=PIPEFD;}
+    break;
+
+  case 71:
+#line 91 "syn.y"
+    {(yyval.tree)=(tree*)0;}
+    break;
+
+  case 72:
+#line 92 "syn.y"
+    {(yyval.tree)=tree2(WORDS, (yyvsp[(1) - (2)].tree), (yyvsp[(2) - (2)].tree));}
+    break;
+
+
+/* Line 1267 of yacc.c.  */
+#line 1776 "y.tab.c"
+      default: break;
+    }
+  YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+
+  *++yyvsp = yyval;
+
+
+  /* Now `shift' the result of the reduction.  Determine what state
+     that goes to, based on the state we popped back to and the rule
+     number reduced by.  */
+
+  yyn = yyr1[yyn];
+
+  yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+  if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+    yystate = yytable[yystate];
+  else
+    yystate = yydefgoto[yyn - YYNTOKENS];
+
+  goto yynewstate;
+
+
+/*------------------------------------.
+| yyerrlab -- here on detecting error |
+`------------------------------------*/
+yyerrlab:
+  /* If not already recovering from an error, report this error.  */
+  if (!yyerrstatus)
+    {
+      ++yynerrs;
+#if ! YYERROR_VERBOSE
+      yyerror (YY_("syntax error"));
+#else
+      {
+	YYSIZE_T yysize = yysyntax_error (0, yystate, yychar);
+	if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM)
+	  {
+	    YYSIZE_T yyalloc = 2 * yysize;
+	    if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM))
+	      yyalloc = YYSTACK_ALLOC_MAXIMUM;
+	    if (yymsg != yymsgbuf)
+	      YYSTACK_FREE (yymsg);
+	    yymsg = (char *) YYSTACK_ALLOC (yyalloc);
+	    if (yymsg)
+	      yymsg_alloc = yyalloc;
+	    else
+	      {
+		yymsg = yymsgbuf;
+		yymsg_alloc = sizeof yymsgbuf;
+	      }
+	  }
+
+	if (0 < yysize && yysize <= yymsg_alloc)
+	  {
+	    (void) yysyntax_error (yymsg, yystate, yychar);
+	    yyerror (yymsg);
+	  }
+	else
+	  {
+	    yyerror (YY_("syntax error"));
+	    if (yysize != 0)
+	      goto yyexhaustedlab;
+	  }
+      }
+#endif
+    }
+
+
+
+  if (yyerrstatus == 3)
+    {
+      /* If just tried and failed to reuse look-ahead token after an
+	 error, discard it.  */
+
+      if (yychar <= YYEOF)
+	{
+	  /* Return failure if at end of input.  */
+	  if (yychar == YYEOF)
+	    YYABORT;
+	}
+      else
+	{
+	  yydestruct ("Error: discarding",
+		      yytoken, &yylval);
+	  yychar = YYEMPTY;
+	}
+    }
+
+  /* Else will try to reuse look-ahead token after shifting the error
+     token.  */
+  goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR.  |
+`---------------------------------------------------*/
+yyerrorlab:
+
+  /* Pacify compilers like GCC when the user code never invokes
+     YYERROR and the label yyerrorlab therefore never appears in user
+     code.  */
+  if (/*CONSTCOND*/ 0)
+     goto yyerrorlab;
+
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYERROR.  */
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+  yystate = *yyssp;
+  goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR.  |
+`-------------------------------------------------------------*/
+yyerrlab1:
+  yyerrstatus = 3;	/* Each real token shifted decrements this.  */
+
+  for (;;)
+    {
+      yyn = yypact[yystate];
+      if (yyn != YYPACT_NINF)
+	{
+	  yyn += YYTERROR;
+	  if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+	    {
+	      yyn = yytable[yyn];
+	      if (0 < yyn)
+		break;
+	    }
+	}
+
+      /* Pop the current state because it cannot handle the error token.  */
+      if (yyssp == yyss)
+	YYABORT;
+
+
+      yydestruct ("Error: popping",
+		  yystos[yystate], yyvsp);
+      YYPOPSTACK (1);
+      yystate = *yyssp;
+      YY_STACK_PRINT (yyss, yyssp);
+    }
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+  *++yyvsp = yylval;
+
+
+  /* Shift the error token.  */
+  YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+  yystate = yyn;
+  goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here.  |
+`-------------------------------------*/
+yyacceptlab:
+  yyresult = 0;
+  goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here.  |
+`-----------------------------------*/
+yyabortlab:
+  yyresult = 1;
+  goto yyreturn;
+
+#ifndef yyoverflow
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here.  |
+`-------------------------------------------------*/
+yyexhaustedlab:
+  yyerror (YY_("memory exhausted"));
+  yyresult = 2;
+  /* Fall through.  */
+#endif
+
+yyreturn:
+  if (yychar != YYEOF && yychar != YYEMPTY)
+     yydestruct ("Cleanup: discarding lookahead",
+		 yytoken, &yylval);
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYABORT or YYACCEPT.  */
+  YYPOPSTACK (yylen);
+  YY_STACK_PRINT (yyss, yyssp);
+  while (yyssp != yyss)
+    {
+      yydestruct ("Cleanup: popping",
+		  yystos[*yyssp], yyvsp);
+      YYPOPSTACK (1);
+    }
+#ifndef yyoverflow
+  if (yyss != yyssa)
+    YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+  if (yymsg != yymsgbuf)
+    YYSTACK_FREE (yymsg);
+#endif
+  /* Make sure YYID is used.  */
+  return YYID (yyresult);
+}
+
+
+
--- /dev/null
+++ b/librc/y.tab.h
@@ -1,0 +1,114 @@
+/* A Bison parser, made by GNU Bison 2.3.  */
+
+/* Skeleton interface for Bison's Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     FOR = 258,
+     IN = 259,
+     WHILE = 260,
+     IF = 261,
+     NOT = 262,
+     TWIDDLE = 263,
+     BANG = 264,
+     SUBSHELL = 265,
+     SWITCH = 266,
+     FN = 267,
+     WORD = 268,
+     REDIR = 269,
+     DUP = 270,
+     PIPE = 271,
+     SUB = 272,
+     SIMPLE = 273,
+     ARGLIST = 274,
+     WORDS = 275,
+     BRACE = 276,
+     PAREN = 277,
+     PCMD = 278,
+     PIPEFD = 279,
+     OROR = 280,
+     ANDAND = 281,
+     COUNT = 282
+   };
+#endif
+/* Tokens.  */
+#define FOR 258
+#define IN 259
+#define WHILE 260
+#define IF 261
+#define NOT 262
+#define TWIDDLE 263
+#define BANG 264
+#define SUBSHELL 265
+#define SWITCH 266
+#define FN 267
+#define WORD 268
+#define REDIR 269
+#define DUP 270
+#define PIPE 271
+#define SUB 272
+#define SIMPLE 273
+#define ARGLIST 274
+#define WORDS 275
+#define BRACE 276
+#define PAREN 277
+#define PCMD 278
+#define PIPEFD 279
+#define OROR 280
+#define ANDAND 281
+#define COUNT 282
+
+
+
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+#line 16 "syn.y"
+{
+	struct tree *tree;
+}
+/* Line 1529 of yacc.c.  */
+#line 107 "y.tab.h"
+	YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+extern YYSTYPE yylval;
+
--- /dev/null
+++ b/libsec/Makefile
@@ -1,0 +1,28 @@
+ROOT=..
+include ../Make.config
+LIB=libsec.a
+
+OFILES=\
+	aes.$O aesni.$O aesCBC.$O aes_gcm.$O\
+	poly1305.$O chacha.$O chachablock.$O ccpoly.$O\
+	des.$O des3CBC.$O desmodes.$O\
+	ecc.$O jacobian.$O secp256k1.$O secp256r1.$O secp384r1.$O\
+	curve25519.$O curve25519_dh.$O\
+	genrandom.$O fastrand.$O nfastrand.$O prng.$O\
+	hmac.$O hkdf.$O pbkdf2.$O\
+	rsaalloc.$O rsadecrypt.$O rsaencrypt.$O\
+	dh.$O\
+	rc4.$O md5.$O md5block.$O\
+	sha1.$O sha2_128.$O sha2_64.$O\
+	sha1block.$O sha2block128.$O sha2block64.$O\
+	tlshand.$O x509.$O\
+	tsmemcmp.$O\
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
--- /dev/null
+++ b/libsec/aes.c
@@ -1,0 +1,1257 @@
+/*
+ * 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 "os.h"
+#include <libsec.h>
+
+typedef uchar	u8;
+typedef ulong	u32;
+
+#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); }
+
+#define FULL_UNROLL
+
+/*
+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 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 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 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 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 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 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 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 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 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 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 u32 rcon[] = {
+	0x01000000, 0x02000000, 0x04000000, 0x08000000,
+	0x10000000, 0x20000000, 0x40000000, 0x80000000,
+	0x1B000000, 0x36000000,
+	/* for 128-bit blocks, Rijndael never uses more than 10 rcon values */
+};
+
+/*
+ * Expand the cipher key into the encryption key schedule.
+ *
+ * @return	the number of rounds for the given cipher key size.
+ */
+static int
+setupEnc(ulong rk[/*4*(Nr + 1)*/], uchar key[], int nkey)
+{
+	int i = 0;
+	u32 temp;
+
+	rk[0] = GETU32(key     );
+	rk[1] = GETU32(key +  4);
+	rk[2] = GETU32(key +  8);
+	rk[3] = GETU32(key + 12);
+	if (nkey == 16) {
+		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(key + 16);
+	rk[5] = GETU32(key + 20);
+	if (nkey == 24) {
+		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(key + 24);
+	rk[7] = GETU32(key + 28);
+	if (nkey == 32) {
+		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 encryption and decryption key schedules.
+ *
+ * @return	the number of rounds for the given cipher key size.
+ */
+static int
+AESsetup(ulong erk[/* 4*(Nr + 1) */], ulong drk[/* 4*(Nr + 1) */], uchar key[], int nkey)
+{
+	int Nr, i;
+
+	/* expand the cipher key: */
+	Nr = setupEnc(erk, key, nkey);
+
+	/*
+	 * 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;
+}
+
+/* using round keys in rk, perform Nr rounds of encrypting pt into ct */
+static void
+AESencrypt(ulong rk[/* 4*(Nr + 1) */], int Nr, uchar pt[16], uchar ct[16])
+{
+	ulong 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
+AESdecrypt(ulong rk[/* 4*(Nr + 1) */], int Nr, uchar ct[16], uchar pt[16])
+{
+	ulong 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);
+}
+
+void (*aes_encrypt)(ulong rk[], int Nr, uchar pt[16], uchar ct[16]) = AESencrypt;
+void (*aes_decrypt)(ulong rk[], int Nr, uchar ct[16], uchar pt[16]) = AESdecrypt;
+
+void
+setupAESstate(AESstate *s, uchar key[], int nkey, uchar *ivec)
+{
+	static int (*aes_setup)(ulong erk[/* 4*(Nr + 1) */], ulong drk[/* 4*(Nr + 1) */], uchar key[], int nkey);
+
+	if(aes_setup == nil){
+		extern void *aesni_init(void);
+		if((aes_setup = aesni_init()) == nil)
+			aes_setup = AESsetup;
+	}
+	memset(s, 0, sizeof(*s));
+	if(nkey > AESmaxkey)
+		nkey = AESmaxkey;
+	memmove(s->key, key, nkey);
+	s->keybytes = nkey;
+	s->ekey = s->storage+16 - ((s->storage - (uchar*)0) & 15);
+	s->dkey = (uchar*)s->ekey + 16*(AESmaxrounds+1);
+	s->rounds = (*aes_setup)(s->ekey, s->dkey, s->key, nkey);
+	if(ivec != nil)
+		memmove(s->ivec, ivec, AESbsize);
+	if(s->rounds != 0)
+		s->setup = 0xcafebabe;
+}
--- /dev/null
+++ b/libsec/aesCBC.c
@@ -1,0 +1,60 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+ * 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++;
+		aes_encrypt(s->ekey, s->rounds, p, q);
+		memmove(s->ivec, q, AESbsize);
+		memmove(p, q, AESbsize);
+		p += AESbsize;
+	}
+
+	if(len > 0){
+		ip = s->ivec;
+		aes_encrypt(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);
+		aes_decrypt(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;
+		aes_encrypt(s->ekey, s->rounds, ip, q);
+		memmove(s->ivec, q, AESbsize);
+		for(eip = ip+len; ip < eip; )
+			*p++ ^= *ip++;
+	}
+}
--- /dev/null
+++ b/libsec/aesCFB.c
@@ -1,0 +1,50 @@
+#include "os.h"
+#include <libsec.h>
+
+typedef ulong u32;
+
+void
+aesCFBencrypt(uchar *p, int len, AESstate *s)
+{
+	u32 a, o = s->offset;
+
+	while(len > 0){
+		if(o % 16){
+		Odd:
+			a = (s->ivec[o++ % 16] ^= *p), *p++ = a, len--;
+			continue;
+		}
+		aes_encrypt(s->ekey, s->rounds, s->ivec, s->ivec);
+		if(len < 16 || ((p-(uchar*)0) & 3) != 0)
+			goto Odd;
+		((u32*)p)[0] = (((u32*)s->ivec)[0] ^= ((u32*)p)[0]);
+		((u32*)p)[1] = (((u32*)s->ivec)[1] ^= ((u32*)p)[1]);
+		((u32*)p)[2] = (((u32*)s->ivec)[2] ^= ((u32*)p)[2]);
+		((u32*)p)[3] = (((u32*)s->ivec)[3] ^= ((u32*)p)[3]);
+		o += 16, p += 16, len -= 16;
+	}
+	s->offset = o;
+}
+
+void
+aesCFBdecrypt(uchar *p, int len, AESstate *s)
+{
+	u32 a, o = s->offset;
+
+	while(len > 0){
+		if(o % 16){
+		Odd:
+			a = *p, *p++ ^= s->ivec[o % 16], s->ivec[o++ % 16] = a, len--;
+			continue;
+		}
+		aes_encrypt(s->ekey, s->rounds, s->ivec, s->ivec);
+		if(len < 16 || ((p-(uchar*)0) & 3) != 0)
+			goto Odd;
+		a = ((u32*)p)[0], ((u32*)p)[0] ^= ((u32*)s->ivec)[0], ((u32*)s->ivec)[0] = a;
+		a = ((u32*)p)[1], ((u32*)p)[1] ^= ((u32*)s->ivec)[1], ((u32*)s->ivec)[1] = a;
+		a = ((u32*)p)[2], ((u32*)p)[2] ^= ((u32*)s->ivec)[2], ((u32*)s->ivec)[2] = a;
+		a = ((u32*)p)[3], ((u32*)p)[3] ^= ((u32*)s->ivec)[3], ((u32*)s->ivec)[3] = a;
+		o += 16, p += 16, len -= 16;
+	}
+	s->offset = o;
+}
--- /dev/null
+++ b/libsec/aesOFB.c
@@ -1,0 +1,28 @@
+#include "os.h"
+#include <libsec.h>
+
+typedef ulong u32;
+
+void
+aesOFBencrypt(uchar *p, int len, AESstate *s)
+{
+	u32 o = s->offset;
+
+	while(len > 0){
+		if(o % 16){
+		Odd:
+			*p++ ^= s->ivec[o++ % 16], len--;
+			continue;
+		}
+		aes_encrypt(s->ekey, s->rounds, s->ivec, s->ivec);
+		if(len < 16 || ((p-(uchar*)0) & 3) != 0)
+			goto Odd;
+		((u32*)p)[0] ^= ((u32*)s->ivec)[0];
+		((u32*)p)[1] ^= ((u32*)s->ivec)[1];
+		((u32*)p)[2] ^= ((u32*)s->ivec)[2];
+		((u32*)p)[3] ^= ((u32*)s->ivec)[3];
+		o += 16, p += 16, len -= 16;
+	}
+	s->offset = o;
+}
+
--- /dev/null
+++ b/libsec/aes_gcm.c
@@ -1,0 +1,199 @@
+#include "os.h"
+#include <libsec.h>
+
+static void
+load128(uchar b[16], ulong W[4])
+{
+	W[0] = (ulong)b[15] | (ulong)b[14]<<8 | (ulong)b[13]<<16 | (ulong)b[12]<<24;
+	W[1] = (ulong)b[11] | (ulong)b[10]<<8 | (ulong)b[ 9]<<16 | (ulong)b[ 8]<<24;
+	W[2] = (ulong)b[ 7] | (ulong)b[ 6]<<8 | (ulong)b[ 5]<<16 | (ulong)b[ 4]<<24;
+	W[3] = (ulong)b[ 3] | (ulong)b[ 2]<<8 | (ulong)b[ 1]<<16 | (ulong)b[ 0]<<24;
+}
+
+static void
+store128(ulong W[4], uchar b[16])
+{
+	b[15] = W[0], b[14] = W[0]>>8, b[13] = W[0]>>16, b[12] = W[0]>>24;
+	b[11] = W[1], b[10] = W[1]>>8, b[ 9] = W[1]>>16, b[ 8] = W[1]>>24;
+	b[ 7] = W[2], b[ 6] = W[2]>>8, b[ 5] = W[2]>>16, b[ 4] = W[2]>>24;
+	b[ 3] = W[3], b[ 2] = W[3]>>8, b[ 1] = W[3]>>16, b[ 0] = W[3]>>24;
+}
+
+static void
+gfmul(ulong X[4], ulong Y[4], ulong Z[4])
+{
+	long m, i;
+
+	Z[0] = Z[1] = Z[2] = Z[3] = 0;
+	for(i=127; i>=0; i--){
+		m = ((long)Y[i>>5] << (31-(i&31))) >> 31;
+		Z[0] ^= X[0] & m;
+		Z[1] ^= X[1] & m;
+		Z[2] ^= X[2] & m;
+		Z[3] ^= X[3] & m;
+		m = ((long)X[0]<<31) >> 31;
+		X[0] = X[0]>>1 | X[1]<<31;
+		X[1] = X[1]>>1 | X[2]<<31;
+		X[2] = X[2]>>1 | X[3]<<31;
+		X[3] = X[3]>>1 ^ (0xE1000000 & m);
+	}
+}
+
+static void
+prepareM(ulong H[4], ulong M[16][256][4])
+{
+	ulong X[4], i, j;
+
+	for(i=0; i<16; i++){
+		for(j=0; j<256; j++){
+			X[0] = X[1] = X[2] = X[3] = 0;
+			X[i>>2] = j<<((i&3)<<3);
+			gfmul(X, H, M[i][j]);
+		}
+	}
+}
+
+static void
+ghash1(AESGCMstate *s, ulong X[4], ulong Y[4])
+{
+	ulong *Xi, i;
+
+	X[0] ^= Y[0], X[1] ^= Y[1], X[2] ^= Y[2], X[3] ^= Y[3];
+	if(0){
+		gfmul(X, s->H, Y);
+		return;
+	}
+
+	Y[0] = Y[1] = Y[2] = Y[3] = 0;
+	for(i=0; i<16; i++){
+		Xi = s->M[i][(X[i>>2]>>((i&3)<<3))&0xFF];
+		Y[0] ^= Xi[0];
+		Y[1] ^= Xi[1];
+		Y[2] ^= Xi[2];
+		Y[3] ^= Xi[3];
+	}
+}
+
+static void
+ghashn(AESGCMstate *s, uchar *dat, ulong len, ulong Y[4])
+{
+	uchar tmp[16];
+	ulong X[4];
+
+	while(len >= 16){
+		load128(dat, X);
+		ghash1(s, X, Y);
+		dat += 16, len -= 16;
+	}
+	if(len > 0){
+		memmove(tmp, dat, len);
+		memset(tmp+len, 0, 16-len);
+		load128(tmp, X);
+		ghash1(s, X, Y);
+	}
+}
+
+static ulong
+aesxctr1(AESstate *s, uchar ctr[AESbsize], uchar *dat, ulong len)
+{
+	uchar tmp[AESbsize];
+	ulong i;
+
+	aes_encrypt(s->ekey, s->rounds, ctr, tmp);
+	if(len > AESbsize)
+		len = AESbsize;
+	for(i=0; i<len; i++)
+		dat[i] ^= tmp[i];
+	return len;
+}
+
+static void
+aesxctrn(AESstate *s, uchar *dat, ulong len)
+{
+	uchar ctr[AESbsize];
+	ulong i;
+
+	memmove(ctr, s->ivec, AESbsize);
+	while(len > 0){
+		for(i=AESbsize-1; i>=AESbsize-4; i--)
+			if(++ctr[i] != 0)
+				break;
+
+		if(aesxctr1(s, ctr, dat, len) < AESbsize)
+			break;
+		dat += AESbsize;
+		len -= AESbsize;
+	}
+}
+
+void
+aesgcm_setiv(AESGCMstate *s, uchar *iv, int ivlen)
+{
+	if(ivlen == 96/8){
+		memmove(s->a.ivec, iv, ivlen);
+		memset(s->a.ivec+ivlen, 0, AESbsize-ivlen);
+		s->a.ivec[AESbsize-1] = 1;
+	} else {
+		ulong L[4], Y[4] = {0};
+
+		ghashn(s, iv, ivlen, Y);
+		L[0] = ivlen << 3;
+		L[1] = ivlen >> 29;
+		L[2] = L[3] = 0;
+		ghash1(s, L, Y);
+		store128(Y, s->a.ivec);
+	}
+}
+
+void
+setupAESGCMstate(AESGCMstate *s, uchar *key, int keylen, uchar *iv, int ivlen)
+{
+	setupAESstate(&s->a, key, keylen, nil);
+
+	memset(s->a.ivec, 0, AESbsize);
+	aes_encrypt(s->a.ekey, s->a.rounds, s->a.ivec, s->a.ivec);
+	load128(s->a.ivec, s->H);
+	memset(s->a.ivec, 0, AESbsize);
+	prepareM(s->H, s->M);
+
+	if(iv != nil && ivlen > 0)
+		aesgcm_setiv(s, iv, ivlen);
+}
+
+void
+aesgcm_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], AESGCMstate *s)
+{
+	ulong L[4], Y[4] = {0};
+
+	ghashn(s, aad, naad, Y);
+	aesxctrn(&s->a, dat, ndat);
+	ghashn(s, dat, ndat, Y);
+	L[0] = ndat << 3;
+	L[1] = ndat >> 29;
+	L[2] = naad << 3;
+	L[3] = naad >> 29;
+	ghash1(s, L, Y);
+	store128(Y, tag);
+	aesxctr1(&s->a, s->a.ivec, tag, 16);
+}
+
+int
+aesgcm_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], AESGCMstate *s)
+{
+	ulong L[4], Y[4] = {0};
+	uchar tmp[16];
+
+	ghashn(s, aad, naad, Y);
+	ghashn(s, dat, ndat, Y);
+	L[0] = ndat << 3;
+	L[1] = ndat >> 29;
+	L[2] = naad << 3;
+	L[3] = naad >> 29;
+	ghash1(s, L, Y);
+	store128(Y, tmp);
+	aesxctr1(&s->a, s->a.ivec, tmp, 16);
+	if(tsmemcmp(tag, tmp, 16) != 0)
+		return -1;
+	aesxctrn(&s->a, dat, ndat);
+	return 0;
+}
--- /dev/null
+++ b/libsec/aes_xts.c
@@ -1,0 +1,83 @@
+#include "os.h"
+#include <libsec.h>
+
+/* little-endian data order */
+#define	GET4(p)		((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
+#define	PUT4(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24
+
+static void
+gf_mulx(uchar *x)
+{
+	ulong t0, t1, t2, t3, t4;
+
+	t0 = GET4(x);
+	t1 = GET4(x+4);
+	t2 = GET4(x+8);
+	t3 = GET4(x+12);
+
+	t4 =             (t3 >> 31);
+	t3 = (t3 << 1) | (t2 >> 31);
+	t2 = (t2 << 1) | (t1 >> 31);
+	t1 = (t1 << 1) | (t0 >> 31);
+	t0 = (t0 << 1) ^ (t4*135);
+
+	PUT4(x, t0);
+	PUT4(x+4, t1);
+	PUT4(x+8, t2);
+	PUT4(x+12, t3);
+}
+
+static void
+xor128(uchar *o, uchar *i1, uchar *i2)
+{
+	int i;
+
+	for(i=0; i<16; i++)
+		o[i] = i1[i] ^ i2[i];
+}
+
+static void
+setupT(AESstate *tweak, uvlong sectorNumber, uchar T[AESbsize])
+{
+	PUT4(T+0, (ulong)sectorNumber), sectorNumber >>= 32;
+	PUT4(T+4, (ulong)sectorNumber);
+	PUT4(T+8, 0);
+	PUT4(T+12, 0);
+	aes_encrypt(tweak->ekey, tweak->rounds, T, T);
+}
+
+void
+aes_xts_encrypt(AESstate *tweak, AESstate *ecb,
+	uvlong sectorNumber, uchar *input, uchar *output, ulong len)
+{
+	uchar T[AESbsize], x[AESbsize];
+	
+	if(len % AESbsize)
+		abort();
+
+	setupT(tweak, sectorNumber, T);
+	for (; len > 0; len -= AESbsize, input += AESbsize, output += AESbsize) {
+		xor128(x, input, T);
+		aes_encrypt(ecb->ekey, ecb->rounds, x, x);
+		xor128(output, x, T);
+		gf_mulx(T);
+	}
+}
+
+void
+aes_xts_decrypt(AESstate *tweak, AESstate *ecb,
+	uvlong sectorNumber, uchar *input, uchar *output, ulong len)
+{
+	uchar T[AESbsize], x[AESbsize];
+	
+	if(len % AESbsize)
+		abort();
+
+	setupT(tweak, sectorNumber, T);
+	for (; len > 0; len -= AESbsize, input += AESbsize, output += AESbsize) {
+		xor128(x, input, T);
+		aes_decrypt(ecb->dkey, ecb->rounds, x, x);
+		xor128(output, x, T);
+		gf_mulx(T);
+	}
+}
--- /dev/null
+++ b/libsec/aesni.c
@@ -1,0 +1,5 @@
+void*
+aesni_init(void)
+{
+	return 0;
+}
--- /dev/null
+++ b/libsec/blowfish.c
@@ -1,0 +1,524 @@
+#include "os.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 *);
+
+/*
+ * Endianess agnostic functions to convert a 
+ * block (8-byte buffer) to a u32int array and 
+ * viceversa.
+ */
+
+static void
+buf2ints(uchar *p, u32int *b)
+{
+	b[0] =  p[0]<<24 | p[1]<<16  | p[2]<<8 | p[3];
+	b[1] =  p[4]<<24 | p[5]<<16  | p[6]<<8 | p[7];
+}
+
+static void
+ints2buf(u32int *b, uchar *p)
+{
+	u32int u;
+
+	u = b[0];
+	p[0] = u>>24;
+	p[1] = u>>16;
+	p[2] = u>>8;
+	p[3] = u;
+
+	u = b[1];
+	p[4] = u>>24;
+	p[5] = u>>16;
+	p[6] = u>>8;
+	p[7] = u;
+}
+
+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;
+	u32int bo[2], bi[2];
+
+	assert((n & 7) == 0);
+
+	buf2ints(s->ivec, bo);
+	for(i=0; i < n; i += 8, buf += 8) {
+		buf2ints(buf, bi);
+
+		bi[0] ^= bo[0];
+		bi[1] ^= bo[1];
+
+		bfencrypt(bi, s);
+
+		bo[0] = bi[0];
+		bo[1] = bi[1];
+
+		ints2buf(bi, buf);
+	}
+	ints2buf(bo, s->ivec);
+	return;
+}
+
+void
+bfCBCdecrypt(uchar *buf, int n, BFstate *s)
+{
+	int i;
+	u32int  bo[2], bi[2], xr[2];
+
+	assert((n & 7) == 0);
+
+	buf2ints(s->ivec, bo);
+	for(i=0; i < n; i += 8, buf += 8) {
+		buf2ints(buf, bi);
+
+		xr[0] = bi[0];
+		xr[1] = bi[1];
+
+		bfdecrypt(bi, s);
+
+		bo[0] ^= bi[0];
+		bo[1] ^= bi[1];
+
+		ints2buf(bo, buf);
+
+		bo[0] = xr[0];
+		bo[1] = xr[1];
+	}
+	ints2buf(bo, s->ivec);
+	return;
+}
+
+void
+bfECBencrypt(uchar *buf, int n, BFstate *s)
+{
+	int i;
+	u32int b[2];
+
+	for(i=0; i < n; i += 8, buf += 8) {
+		buf2ints(buf, b);
+		bfencrypt(b, s);
+		ints2buf(b, buf);
+	}
+
+	return;
+}
+
+void
+bfECBdecrypt(uchar *buf, int n, BFstate *s)
+{
+	int i;
+	u32int b[2];
+
+	for(i=0; i < n; i += 8, buf += 8) {
+		buf2ints(buf, b);
+		bfdecrypt(b, s);
+		ints2buf(b, buf);
+	}
+
+	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/ccpoly.c
@@ -1,0 +1,90 @@
+#include "os.h"
+#include <libsec.h>
+
+static void
+ccpolyotk(Chachastate *cs, DigestState *ds)
+{
+	uchar otk[ChachaBsize];
+
+	memset(ds, 0, sizeof(*ds));
+	memset(otk, 0, 32);
+	chacha_setblock(cs, 0);
+	chacha_encrypt(otk, ChachaBsize, cs);
+	poly1305(nil, 0, otk, 32, nil, ds);
+}
+
+static void
+ccpolypad(uchar *buf, ulong nbuf, DigestState *ds)
+{
+	static uchar zeros[16] = {0};
+	ulong npad;
+
+	if(nbuf == 0)
+		return;
+	poly1305(buf, nbuf, nil, 0, nil, ds);
+	npad = nbuf % 16;
+	if(npad == 0)
+		return;
+	poly1305(zeros, 16 - npad, nil, 0, nil, ds);
+}
+
+static void
+ccpolylen(ulong n, uchar tag[16], DigestState *ds)
+{
+	uchar info[8];
+
+	info[0] = n;
+	info[1] = n>>8;
+	info[2] = n>>16;
+	info[3] = n>>24;
+	info[4] = 0;
+	info[5] = 0;
+	info[6] = 0;
+	info[7] = 0;
+	poly1305(info, 8, nil, 0, tag, ds);
+}
+
+void
+ccpoly_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs)
+{
+	DigestState ds;
+
+	ccpolyotk(cs, &ds);
+	if(cs->ivwords == 2){
+		poly1305(aad, naad, nil, 0, nil, &ds);
+		ccpolylen(naad, nil, &ds);
+		chacha_encrypt(dat, ndat, cs);
+		poly1305(dat, ndat, nil, 0, nil, &ds);
+		ccpolylen(ndat, tag, &ds);
+	} else {
+		ccpolypad(aad, naad, &ds);
+		chacha_encrypt(dat, ndat, cs);
+		ccpolypad(dat, ndat, &ds);
+		ccpolylen(naad, nil, &ds);
+		ccpolylen(ndat, tag, &ds);
+	}
+}
+
+int
+ccpoly_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs)
+{
+	DigestState ds;
+	uchar tmp[16];
+
+	ccpolyotk(cs, &ds);
+	if(cs->ivwords == 2){
+		poly1305(aad, naad, nil, 0, nil, &ds);
+		ccpolylen(naad, nil, &ds);
+		poly1305(dat, ndat, nil, 0, nil, &ds);
+		ccpolylen(ndat, tmp, &ds);
+	} else {
+		ccpolypad(aad, naad, &ds);
+		ccpolypad(dat, ndat, &ds);
+		ccpolylen(naad, nil, &ds);
+		ccpolylen(ndat, tmp, &ds);
+	}
+	if(tsmemcmp(tag, tmp, 16) != 0)
+		return -1;
+	chacha_encrypt(dat, ndat, cs);
+	return 0;
+}
--- /dev/null
+++ b/libsec/chacha.c
@@ -1,0 +1,222 @@
+/*
+Adapted from chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+
+modified for use in Plan 9 and Inferno (no algorithmic changes),
+and including the changes to block number and nonce defined in RFC7539
+*/
+
+#include "os.h"
+#include <libsec.h>
+
+/* from chachablock.$O */
+extern void _chachablock(u32int x[16], int rounds);
+
+/* little-endian data order */
+#define	GET4(p)		((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
+#define	PUT4(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24
+
+#define ENCRYPT(s, x, y, d) {\
+	u32int v; \
+	v = GET4(s); \
+	v ^= (x)+(y); \
+	PUT4(d, v); \
+}
+
+static uchar sigma[16] = "expand 32-byte k";
+static uchar tau[16] = "expand 16-byte k";
+
+static void
+load(u32int *d, uchar *s, int nw)
+{
+	int i;
+
+	for(i = 0; i < nw; i++, s+=4)
+		d[i] = GET4(s);
+}
+
+void
+setupChachastate(Chachastate *s, uchar *key, ulong keylen, uchar *iv, ulong ivlen, int rounds)
+{
+	if(keylen != 256/8 && keylen != 128/8)
+		sysfatal("invalid chacha key length");
+	if(ivlen != 64/8 && ivlen != 96/8
+	&& ivlen != 128/8 && ivlen != 192/8)	/* hchacha, xchacha */
+		sysfatal("invalid chacha iv length");
+	if(rounds == 0)
+		rounds = 20;
+	s->rounds = rounds;
+	if(keylen == 256/8) { /* recommended */
+		load(&s->input[0], sigma, 4);
+		load(&s->input[4], key, 8);
+	}else{
+		load(&s->input[0], tau, 4);
+		load(&s->input[4], key, 4);
+		load(&s->input[8], key, 4);
+	}
+	s->xkey[0] = s->input[4];
+	s->xkey[1] = s->input[5];
+	s->xkey[2] = s->input[6];
+	s->xkey[3] = s->input[7];
+	s->xkey[4] = s->input[8];
+	s->xkey[5] = s->input[9];
+	s->xkey[6] = s->input[10];
+	s->xkey[7] = s->input[11];
+
+	s->ivwords = ivlen/4;
+	s->input[12] = 0;
+	s->input[13] = 0;
+	if(iv == nil){
+		s->input[14] = 0;
+		s->input[15] = 0;
+	}else
+		chacha_setiv(s, iv);
+}
+
+static void
+hchachablock(uchar h[32], Chachastate *s)
+{
+	u32int x[16];
+
+	x[0] = s->input[0];
+	x[1] = s->input[1];
+	x[2] = s->input[2];
+	x[3] = s->input[3];
+	x[4] = s->input[4];
+	x[5] = s->input[5];
+	x[6] = s->input[6];
+	x[7] = s->input[7];
+	x[8] = s->input[8];
+	x[9] = s->input[9];
+	x[10] = s->input[10];
+	x[11] = s->input[11];
+	x[12] = s->input[12];
+	x[13] = s->input[13];
+	x[14] = s->input[14];
+	x[15] = s->input[15];
+
+	_chachablock(x, s->rounds);
+
+	PUT4(h+0*4, x[0]);
+	PUT4(h+1*4, x[1]);
+	PUT4(h+2*4, x[2]);
+	PUT4(h+3*4, x[3]);
+	PUT4(h+4*4, x[12]);
+	PUT4(h+5*4, x[13]);
+	PUT4(h+6*4, x[14]);
+	PUT4(h+7*4, x[15]);
+}
+
+void
+chacha_setiv(Chachastate *s, uchar *iv)
+{
+	if(s->ivwords == 192/32){
+		/* xchacha with 192-bit iv */
+		u32int counter[2];
+		uchar h[32];
+
+		s->input[4] = s->xkey[0];
+		s->input[5] = s->xkey[1];
+		s->input[6] = s->xkey[2];
+		s->input[7] = s->xkey[3];
+		s->input[8] = s->xkey[4];
+		s->input[9] = s->xkey[5];
+		s->input[10] = s->xkey[6];
+		s->input[11] = s->xkey[7];
+
+		counter[0] = s->input[12];
+		counter[1] = s->input[13];
+
+		load(&s->input[12], iv, 4);
+
+		hchachablock(h, s);
+		load(&s->input[4], h, 8);
+		memset(h, 0, 32);
+
+		s->input[12] = counter[0];
+		s->input[13] = counter[1];
+
+		load(&s->input[14], iv+16, 2);
+		return;
+	}
+	load(&s->input[16 - s->ivwords], iv, s->ivwords);
+}
+
+void
+chacha_setblock(Chachastate *s, u64int blockno)
+{
+	s->input[12] = blockno;
+	if(s->ivwords != 3)
+		s->input[13] = blockno>>32;
+}
+
+static void
+encryptblock(Chachastate *s, uchar *src, uchar *dst)
+{
+	u32int x[16];
+	int i;
+
+	x[0] = s->input[0];
+	x[1] = s->input[1];
+	x[2] = s->input[2];
+	x[3] = s->input[3];
+	x[4] = s->input[4];
+	x[5] = s->input[5];
+	x[6] = s->input[6];
+	x[7] = s->input[7];
+	x[8] = s->input[8];
+	x[9] = s->input[9];
+	x[10] = s->input[10];
+	x[11] = s->input[11];
+	x[12] = s->input[12];
+	x[13] = s->input[13];
+	x[14] = s->input[14];
+	x[15] = s->input[15];
+	_chachablock(x, s->rounds);
+
+	for(i=0; i<nelem(x); i+=4){
+		ENCRYPT(src, x[i], s->input[i], dst);
+		ENCRYPT(src+4, x[i+1], s->input[i+1], dst+4);
+		ENCRYPT(src+8, x[i+2], s->input[i+2], dst+8);
+		ENCRYPT(src+12, x[i+3], s->input[i+3], dst+12);
+		src += 16;
+		dst += 16;
+	}
+
+	if(++s->input[12] == 0 && s->ivwords != 3)
+		s->input[13]++;
+}
+
+void
+chacha_encrypt2(uchar *src, uchar *dst, ulong bytes, Chachastate *s)
+{
+	uchar tmp[ChachaBsize];
+
+	for(; bytes >= ChachaBsize; bytes -= ChachaBsize){
+		encryptblock(s, src, dst);
+		src += ChachaBsize;
+		dst += ChachaBsize;
+	}
+	if(bytes > 0){
+		memmove(tmp, src, bytes);
+		encryptblock(s, tmp, tmp);
+		memmove(dst, tmp, bytes);
+	}
+}
+
+void
+chacha_encrypt(uchar *buf, ulong bytes, Chachastate *s)
+{
+	chacha_encrypt2(buf, buf, bytes, s);
+}
+
+void
+hchacha(uchar h[32], uchar *key, ulong keylen, uchar nonce[16], int rounds)
+{
+	Chachastate s[1];
+
+	setupChachastate(s, key, keylen, nonce, 16, rounds);
+	hchachablock(h, s);
+	memset(s, 0, sizeof(s));
+}
--- /dev/null
+++ b/libsec/chachablock.c
@@ -1,0 +1,29 @@
+#include "os.h"
+
+#define ROTATE(v,c) ((u32int)((v) << (c)) | ((v) >> (32 - (c))))
+
+#define QUARTERROUND(ia,ib,ic,id) { \
+	u32int a, b, c, d, t; \
+	a = x[ia]; b = x[ib]; c = x[ic]; d = x[id]; \
+	a += b; t = d^a; d = ROTATE(t,16); \
+	c += d; t = b^c; b = ROTATE(t,12); \
+	a += b; t = d^a; d = ROTATE(t, 8); \
+	c += d; t = b^c; b = ROTATE(t, 7); \
+	x[ia] = a; x[ib] = b; x[ic] = c; x[id] = d; \
+}
+
+void
+_chachablock(u32int x[16], int rounds)
+{
+	for(; rounds > 0; rounds -= 2) {
+		QUARTERROUND(0, 4, 8,12)
+		QUARTERROUND(1, 5, 9,13)
+		QUARTERROUND(2, 6,10,14)
+		QUARTERROUND(3, 7,11,15)
+
+		QUARTERROUND(0, 5,10,15)
+		QUARTERROUND(1, 6,11,12)
+		QUARTERROUND(2, 7, 8,13)
+		QUARTERROUND(3, 4, 9,14)
+	}
+}
--- /dev/null
+++ b/libsec/curve25519.c
@@ -1,0 +1,570 @@
+/* Copyright 2008, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 COPYRIGHT
+ * OWNER 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.
+ *
+ * curve25519: Curve25519 elliptic curve, public key function
+ *
+ * http://code.google.com/p/curve25519-donna/
+ *
+ * Adam Langley <agl@imperialviolet.org>
+ *
+ * Derived from public domain C code by Daniel J. Bernstein <djb@cr.yp.to>
+ *
+ * More information about curve25519 can be found here
+ *   http://cr.yp.to/ecdh.html
+ *
+ * djb's sample implementation of curve25519 is written in a special assembly
+ * language called qhasm and uses the floating point registers.
+ *
+ * This is, almost, a clean room reimplementation from the curve25519 paper. It
+ * uses many of the tricks described therein. Only the crecip function is taken
+ * from the sample implementation.
+ */
+#include "os.h"
+#include <libsec.h>
+
+typedef vlong felem;
+
+/* Sum two numbers: output += in */
+static void fsum(felem *output, felem *in) {
+  unsigned i;
+  for (i = 0; i < 10; i += 2) {
+    output[0+i] = (output[0+i] + in[0+i]);
+    output[1+i] = (output[1+i] + in[1+i]);
+  }
+}
+
+/* Find the difference of two numbers: output = in - output
+ * (note the order of the arguments!)
+ */
+static void fdifference(felem *output, felem *in) {
+  unsigned i;
+  for (i = 0; i < 10; ++i) {
+    output[i] = (in[i] - output[i]);
+  }
+}
+
+/* Multiply a number my a scalar: output = in * scalar */
+static void fscalar_product(felem *output, felem *in, felem scalar) {
+  unsigned i;
+  for (i = 0; i < 10; ++i) {
+    output[i] = in[i] * scalar;
+  }
+}
+
+/* Multiply two numbers: output = in2 * in
+ *
+ * output must be distinct to both inputs. The inputs are reduced coefficient
+ * form, the output is not.
+ */
+static void fproduct(felem *output, felem *in2, felem *in) {
+  output[0] =      in2[0] * in[0];
+  output[1] =      in2[0] * in[1] +
+                   in2[1] * in[0];
+  output[2] =  2 * in2[1] * in[1] +
+                   in2[0] * in[2] +
+                   in2[2] * in[0];
+  output[3] =      in2[1] * in[2] +
+                   in2[2] * in[1] +
+                   in2[0] * in[3] +
+                   in2[3] * in[0];
+  output[4] =      in2[2] * in[2] +
+               2 * (in2[1] * in[3] +
+                    in2[3] * in[1]) +
+                   in2[0] * in[4] +
+                   in2[4] * in[0];
+  output[5] =      in2[2] * in[3] +
+                   in2[3] * in[2] +
+                   in2[1] * in[4] +
+                   in2[4] * in[1] +
+                   in2[0] * in[5] +
+                   in2[5] * in[0];
+  output[6] =  2 * (in2[3] * in[3] +
+                    in2[1] * in[5] +
+                    in2[5] * in[1]) +
+                   in2[2] * in[4] +
+                   in2[4] * in[2] +
+                   in2[0] * in[6] +
+                   in2[6] * in[0];
+  output[7] =      in2[3] * in[4] +
+                   in2[4] * in[3] +
+                   in2[2] * in[5] +
+                   in2[5] * in[2] +
+                   in2[1] * in[6] +
+                   in2[6] * in[1] +
+                   in2[0] * in[7] +
+                   in2[7] * in[0];
+  output[8] =      in2[4] * in[4] +
+               2 * (in2[3] * in[5] +
+                    in2[5] * in[3] +
+                    in2[1] * in[7] +
+                    in2[7] * in[1]) +
+                   in2[2] * in[6] +
+                   in2[6] * in[2] +
+                   in2[0] * in[8] +
+                   in2[8] * in[0];
+  output[9] =      in2[4] * in[5] +
+                   in2[5] * in[4] +
+                   in2[3] * in[6] +
+                   in2[6] * in[3] +
+                   in2[2] * in[7] +
+                   in2[7] * in[2] +
+                   in2[1] * in[8] +
+                   in2[8] * in[1] +
+                   in2[0] * in[9] +
+                   in2[9] * in[0];
+  output[10] = 2 * (in2[5] * in[5] +
+                    in2[3] * in[7] +
+                    in2[7] * in[3] +
+                    in2[1] * in[9] +
+                    in2[9] * in[1]) +
+                   in2[4] * in[6] +
+                   in2[6] * in[4] +
+                   in2[2] * in[8] +
+                   in2[8] * in[2];
+  output[11] =     in2[5] * in[6] +
+                   in2[6] * in[5] +
+                   in2[4] * in[7] +
+                   in2[7] * in[4] +
+                   in2[3] * in[8] +
+                   in2[8] * in[3] +
+                   in2[2] * in[9] +
+                   in2[9] * in[2];
+  output[12] =     in2[6] * in[6] +
+               2 * (in2[5] * in[7] +
+                    in2[7] * in[5] +
+                    in2[3] * in[9] +
+                    in2[9] * in[3]) +
+                   in2[4] * in[8] +
+                   in2[8] * in[4];
+  output[13] =     in2[6] * in[7] +
+                   in2[7] * in[6] +
+                   in2[5] * in[8] +
+                   in2[8] * in[5] +
+                   in2[4] * in[9] +
+                   in2[9] * in[4];
+  output[14] = 2 * (in2[7] * in[7] +
+                    in2[5] * in[9] +
+                    in2[9] * in[5]) +
+                   in2[6] * in[8] +
+                   in2[8] * in[6];
+  output[15] =     in2[7] * in[8] +
+                   in2[8] * in[7] +
+                   in2[6] * in[9] +
+                   in2[9] * in[6];
+  output[16] =     in2[8] * in[8] +
+               2 * (in2[7] * in[9] +
+                    in2[9] * in[7]);
+  output[17] =     in2[8] * in[9] +
+                   in2[9] * in[8];
+  output[18] = 2 * in2[9] * in[9];
+}
+
+/* Reduce a long form to a short form by taking the input mod 2^255 - 19. */
+static void freduce_degree(felem *output) {
+  output[8] += 19 * output[18];
+  output[7] += 19 * output[17];
+  output[6] += 19 * output[16];
+  output[5] += 19 * output[15];
+  output[4] += 19 * output[14];
+  output[3] += 19 * output[13];
+  output[2] += 19 * output[12];
+  output[1] += 19 * output[11];
+  output[0] += 19 * output[10];
+}
+
+/* Reduce all coefficients of the short form input to be -2**25 <= x <= 2**25
+ */
+static void freduce_coefficients(felem *output) {
+  unsigned i;
+  do {
+    output[10] = 0;
+
+    for (i = 0; i < 10; i += 2) {
+      felem over = output[i] / 0x2000000l;
+      felem over2 = (over + ((over >> 63) * 2) + 1) / 2;
+      output[i+1] += over2;
+      output[i] -= over2 * 0x4000000l;
+
+      over = output[i+1] / 0x2000000;
+      output[i+2] += over;
+      output[i+1] -= over * 0x2000000;
+    }
+    output[0] += 19 * output[10];
+  } while (output[10]);
+}
+
+/* A helpful wrapper around fproduct: output = in * in2.
+ *
+ * output must be distinct to both inputs. The output is reduced degree and
+ * reduced coefficient.
+ */
+static void
+fmul(felem *output, felem *in, felem *in2) {
+  felem t[19];
+  fproduct(t, in, in2);
+  freduce_degree(t);
+  freduce_coefficients(t);
+  memcpy(output, t, sizeof(felem) * 10);
+}
+
+static void fsquare_inner(felem *output, felem *in) {
+  felem tmp;
+  output[0] =      in[0] * in[0];
+  output[1] =  2 * in[0] * in[1];
+  output[2] =  2 * (in[1] * in[1] +
+                    in[0] * in[2]);
+  output[3] =  2 * (in[1] * in[2] +
+                    in[0] * in[3]);
+  output[4] =      in[2] * in[2] +
+               4 * in[1] * in[3] +
+               2 * in[0] * in[4];
+  output[5] =  2 * (in[2] * in[3] +
+                    in[1] * in[4] +
+                    in[0] * in[5]);
+  output[6] =  2 * (in[3] * in[3] +
+                    in[2] * in[4] +
+                    in[0] * in[6] +
+                2 * in[1] * in[5]);
+  output[7] =  2 * (in[3] * in[4] +
+                    in[2] * in[5] +
+                    in[1] * in[6] +
+                    in[0] * in[7]);
+  tmp = in[1] * in[7] + in[3] * in[5];
+  output[8] =      in[4] * in[4] +
+               2 * (in[2] * in[6] +
+                    in[0] * in[8] +
+                        2 * tmp);
+  output[9] =  2 * (in[4] * in[5] +
+                    in[3] * in[6] +
+                    in[2] * in[7] +
+                    in[1] * in[8] +
+                    in[0] * in[9]);
+  tmp = in[3] * in[7] + in[1] * in[9];
+  output[10] = 2 * (in[5] * in[5] +
+                   in[4] * in[6] +
+                   in[2] * in[8] +
+                       2 * tmp);
+  output[11] = 2 * (in[5] * in[6] +
+                    in[4] * in[7] +
+                    in[3] * in[8] +
+                    in[2] * in[9]);
+  output[12] =     in[6] * in[6] +
+               2 * (in[4] * in[8] +
+                2 * (in[5] * in[7] +
+                     in[3] * in[9]));
+  output[13] = 2 * (in[6] * in[7] +
+                    in[5] * in[8] +
+                    in[4] * in[9]);
+  output[14] = 2 * (in[7] * in[7] +
+                    in[6] * in[8] +
+                2 * in[5] * in[9]);
+  output[15] = 2 * (in[7] * in[8] +
+                    in[6] * in[9]);
+  output[16] =     in[8] * in[8] +
+               4 * in[7] * in[9];
+  output[17] = 2 * in[8] * in[9];
+  output[18] = 2 * in[9] * in[9];
+}
+
+static void
+fsquare(felem *output, felem *in) {
+  felem t[19];
+  fsquare_inner(t, in);
+  freduce_degree(t);
+  freduce_coefficients(t);
+  memcpy(output, t, sizeof(felem) * 10);
+}
+
+/* Take a little-endian, 32-byte number and expand it into polynomial form */
+static void
+fexpand(felem *output, uchar *input) {
+#define F(n,start,shift,mask) \
+  output[n] = ((((felem) input[start + 0]) | \
+                ((felem) input[start + 1]) << 8 | \
+                ((felem) input[start + 2]) << 16 | \
+                ((felem) input[start + 3]) << 24) >> shift) & mask;
+  F(0, 0, 0, 0x3ffffff);
+  F(1, 3, 2, 0x1ffffff);
+  F(2, 6, 3, 0x3ffffff);
+  F(3, 9, 5, 0x1ffffff);
+  F(4, 12, 6, 0x3ffffff);
+  F(5, 16, 0, 0x1ffffff);
+  F(6, 19, 1, 0x3ffffff);
+  F(7, 22, 3, 0x1ffffff);
+  F(8, 25, 4, 0x3ffffff);
+  F(9, 28, 6, 0x1ffffff);
+#undef F
+}
+
+/* Take a fully reduced polynomial form number and contract it into a
+ * little-endian, 32-byte array
+ */
+static void
+fcontract(uchar *output, felem *input) {
+  int i;
+
+  do {
+    for (i = 0; i < 9; ++i) {
+      if ((i & 1) == 1) {
+        while (input[i] < 0) {
+          input[i] += 0x2000000;
+          input[i + 1]--;
+        }
+      } else {
+        while (input[i] < 0) {
+          input[i] += 0x4000000;
+          input[i + 1]--;
+        }
+      }
+    }
+    while (input[9] < 0) {
+      input[9] += 0x2000000;
+      input[0] -= 19;
+    }
+  } while (input[0] < 0);
+
+  input[1] <<= 2;
+  input[2] <<= 3;
+  input[3] <<= 5;
+  input[4] <<= 6;
+  input[6] <<= 1;
+  input[7] <<= 3;
+  input[8] <<= 4;
+  input[9] <<= 6;
+#define F(i, s) \
+  output[s+0] |=  input[i] & 0xff; \
+  output[s+1]  = (input[i] >> 8) & 0xff; \
+  output[s+2]  = (input[i] >> 16) & 0xff; \
+  output[s+3]  = (input[i] >> 24) & 0xff;
+  output[0] = 0;
+  output[16] = 0;
+  F(0,0);
+  F(1,3);
+  F(2,6);
+  F(3,9);
+  F(4,12);
+  F(5,16);
+  F(6,19);
+  F(7,22);
+  F(8,25);
+  F(9,28);
+#undef F
+}
+
+/* Input: Q, Q', Q-Q'
+ * Output: 2Q, Q+Q'
+ *
+ *   x2 z3: long form
+ *   x3 z3: long form
+ *   x z: short form, destroyed
+ *   xprime zprime: short form, destroyed
+ *   qmqp: short form, preserved
+ */
+static void fmonty(felem *x2, felem *z2,  /* output 2Q */
+                   felem *x3, felem *z3,  /* output Q + Q' */
+                   felem *x, felem *z,    /* input Q */
+                   felem *xprime, felem *zprime,  /* input Q' */
+                   felem *qmqp /* input Q - Q' */) {
+  felem origx[10], origxprime[10], zzz[19], xx[19], zz[19], xxprime[19],
+        zzprime[19], zzzprime[19], xxxprime[19];
+
+  memcpy(origx, x, 10 * sizeof(felem));
+  fsum(x, z);
+  fdifference(z, origx);  // does x - z
+
+  memcpy(origxprime, xprime, sizeof(felem) * 10);
+  fsum(xprime, zprime);
+  fdifference(zprime, origxprime);
+  fproduct(xxprime, xprime, z);
+  fproduct(zzprime, x, zprime);
+  freduce_degree(xxprime);
+  freduce_coefficients(xxprime);
+  freduce_degree(zzprime);
+  freduce_coefficients(zzprime);
+  memcpy(origxprime, xxprime, sizeof(felem) * 10);
+  fsum(xxprime, zzprime);
+  fdifference(zzprime, origxprime);
+  fsquare(xxxprime, xxprime);
+  fsquare(zzzprime, zzprime);
+  fproduct(zzprime, zzzprime, qmqp);
+  freduce_degree(zzprime);
+  freduce_coefficients(zzprime);
+  memcpy(x3, xxxprime, sizeof(felem) * 10);
+  memcpy(z3, zzprime, sizeof(felem) * 10);
+
+  fsquare(xx, x);
+  fsquare(zz, z);
+  fproduct(x2, xx, zz);
+  freduce_degree(x2);
+  freduce_coefficients(x2);
+  fdifference(zz, xx);  // does zz = xx - zz
+  memset(zzz + 10, 0, sizeof(felem) * 9);
+  fscalar_product(zzz, zz, 121665);
+  freduce_degree(zzz);
+  freduce_coefficients(zzz);
+  fsum(zzz, xx);
+  fproduct(z2, zz, zzz);
+  freduce_degree(z2);
+  freduce_coefficients(z2);
+}
+
+/* Calculates nQ where Q is the x-coordinate of a point on the curve
+ *
+ *   resultx/resultz: the x coordinate of the resulting curve point (short form)
+ *   n: a little endian, 32-byte number
+ *   q: a point of the curve (short form)
+ */
+static void
+cmult(felem *resultx, felem *resultz, uchar *n, felem *q) {
+  felem a[19] = {0}, b[19] = {1}, c[19] = {1}, d[19] = {0};
+  felem *nqpqx = a, *nqpqz = b, *nqx = c, *nqz = d, *t;
+  felem e[19] = {0}, f[19] = {1}, g[19] = {0}, h[19] = {1};
+  felem *nqpqx2 = e, *nqpqz2 = f, *nqx2 = g, *nqz2 = h;
+
+  unsigned i, j;
+
+  memcpy(nqpqx, q, sizeof(felem) * 10);
+
+  for (i = 0; i < 32; ++i) {
+    uchar byte = n[31 - i];
+    for (j = 0; j < 8; ++j) {
+      if (byte & 0x80) {
+        fmonty(nqpqx2, nqpqz2,
+               nqx2, nqz2,
+               nqpqx, nqpqz,
+               nqx, nqz,
+               q);
+      } else {
+        fmonty(nqx2, nqz2,
+               nqpqx2, nqpqz2,
+               nqx, nqz,
+               nqpqx, nqpqz,
+               q);
+      }
+
+      t = nqx;
+      nqx = nqx2;
+      nqx2 = t;
+      t = nqz;
+      nqz = nqz2;
+      nqz2 = t;
+      t = nqpqx;
+      nqpqx = nqpqx2;
+      nqpqx2 = t;
+      t = nqpqz;
+      nqpqz = nqpqz2;
+      nqpqz2 = t;
+
+      byte <<= 1;
+    }
+  }
+
+  memcpy(resultx, nqx, sizeof(felem) * 10);
+  memcpy(resultz, nqz, sizeof(felem) * 10);
+}
+
+// -----------------------------------------------------------------------------
+// Shamelessly copied from djb's code
+// -----------------------------------------------------------------------------
+static void
+crecip(felem *out, felem *z) {
+  felem z2[10];
+  felem z9[10];
+  felem z11[10];
+  felem z2_5_0[10];
+  felem z2_10_0[10];
+  felem z2_20_0[10];
+  felem z2_50_0[10];
+  felem z2_100_0[10];
+  felem t0[10];
+  felem t1[10];
+  int i;
+
+  /* 2 */ fsquare(z2,z);
+  /* 4 */ fsquare(t1,z2);
+  /* 8 */ fsquare(t0,t1);
+  /* 9 */ fmul(z9,t0,z);
+  /* 11 */ fmul(z11,z9,z2);
+  /* 22 */ fsquare(t0,z11);
+  /* 2^5 - 2^0 = 31 */ fmul(z2_5_0,t0,z9);
+
+  /* 2^6 - 2^1 */ fsquare(t0,z2_5_0);
+  /* 2^7 - 2^2 */ fsquare(t1,t0);
+  /* 2^8 - 2^3 */ fsquare(t0,t1);
+  /* 2^9 - 2^4 */ fsquare(t1,t0);
+  /* 2^10 - 2^5 */ fsquare(t0,t1);
+  /* 2^10 - 2^0 */ fmul(z2_10_0,t0,z2_5_0);
+
+  /* 2^11 - 2^1 */ fsquare(t0,z2_10_0);
+  /* 2^12 - 2^2 */ fsquare(t1,t0);
+  /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^20 - 2^0 */ fmul(z2_20_0,t1,z2_10_0);
+
+  /* 2^21 - 2^1 */ fsquare(t0,z2_20_0);
+  /* 2^22 - 2^2 */ fsquare(t1,t0);
+  /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^40 - 2^0 */ fmul(t0,t1,z2_20_0);
+
+  /* 2^41 - 2^1 */ fsquare(t1,t0);
+  /* 2^42 - 2^2 */ fsquare(t0,t1);
+  /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
+  /* 2^50 - 2^0 */ fmul(z2_50_0,t0,z2_10_0);
+
+  /* 2^51 - 2^1 */ fsquare(t0,z2_50_0);
+  /* 2^52 - 2^2 */ fsquare(t1,t0);
+  /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^100 - 2^0 */ fmul(z2_100_0,t1,z2_50_0);
+
+  /* 2^101 - 2^1 */ fsquare(t1,z2_100_0);
+  /* 2^102 - 2^2 */ fsquare(t0,t1);
+  /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
+  /* 2^200 - 2^0 */ fmul(t1,t0,z2_100_0);
+
+  /* 2^201 - 2^1 */ fsquare(t0,t1);
+  /* 2^202 - 2^2 */ fsquare(t1,t0);
+  /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^250 - 2^0 */ fmul(t0,t1,z2_50_0);
+
+  /* 2^251 - 2^1 */ fsquare(t1,t0);
+  /* 2^252 - 2^2 */ fsquare(t0,t1);
+  /* 2^253 - 2^3 */ fsquare(t1,t0);
+  /* 2^254 - 2^4 */ fsquare(t0,t1);
+  /* 2^255 - 2^5 */ fsquare(t1,t0);
+  /* 2^255 - 21 */ fmul(out,t1,z11);
+}
+
+void
+curve25519(uchar mypublic[32], uchar secret[32], uchar basepoint[32]) {
+  felem bp[10], x[10], z[10], zmone[10];
+  fexpand(bp, basepoint);
+  cmult(x, z, secret, bp);
+  crecip(zmone, z);
+  fmul(z, x, zmone);
+  fcontract(mypublic, z);
+}
--- /dev/null
+++ b/libsec/curve25519_dh.c
@@ -1,0 +1,37 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+static uchar nine[32] = {9};
+static uchar zero[32] = {0};
+
+void
+curve25519_dh_new(uchar x[32], uchar y[32])
+{
+	uchar b;
+
+	/* new public/private key pair */
+	genrandom(x, 32);
+	b = x[31];
+	x[0] &= ~7;			/* clear bit 0,1,2 */
+	x[31] = 0x40 | (b & 0x7f);	/* set bit 254, clear bit 255 */
+	curve25519(y, x, nine);
+
+	/* bit 255 is always 0, so make it random */
+	y[31] |= b & 0x80;
+}
+
+int
+curve25519_dh_finish(uchar x[32], uchar y[32], uchar z[32])
+{
+	/* remove the random bit */
+	y[31] &= 0x7f;
+
+	/* calculate dhx key */
+	curve25519(z, x, y);
+
+	memset(x, 0, 32);
+	memset(y, 0, 32);
+
+	return tsmemcmp(z, zero, 32) != 0;
+}
--- /dev/null
+++ b/libsec/decodepem.c
@@ -1,0 +1,89 @@
+#include "os.h"
+#include <libsec.h>
+
+#define STRLEN(s)	(sizeof(s)-1)
+
+uchar*
+decodePEM(char *s, char *type, int *len, char **new_s)
+{
+	uchar *d;
+	char *t, *e, *tt;
+	int n;
+
+	*len = 0;
+
+	/*
+	 * 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], "-----", STRLEN("-----")) == 0
+		&& strchr("\r\n", tt[STRLEN("-----BEGIN ")+n+STRLEN("-----")]) != nil)
+			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], "-----", STRLEN("-----")) == 0
+		&& strchr("\r\n", tt[STRLEN("-----END ")+n+STRLEN("-----")]) != nil)
+			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;
+	}
+
+	if(new_s)
+		*new_s = tt+1;
+	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;
+}
+
+PEMChain*
+decodepemchain(char *s, char *type)
+{
+	PEMChain *first = nil, *last = nil, *chp;
+	uchar *d;
+	char *e;
+	int n;
+
+	e = strchr(s, '\0');
+	while (s < e) {
+		d = decodePEM(s, type, &n, &s);
+		if(d == nil)
+			break;
+		chp = malloc(sizeof(PEMChain));
+		chp->next = nil;
+		chp->pem = d;
+		chp->pemlen = n;
+		if (first == nil)
+			first = chp;
+		else
+			last->next = chp;
+		last = chp;
+	}
+	return first;
+}
--- /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[32])
+{
+	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[32])
+{
+	uchar k64[8];
+
+	des56to64(key, k64);
+	des_key_setup(k64, ek);	
+}
--- /dev/null
+++ b/libsec/des3CBC.c
@@ -1,0 +1,58 @@
+#include "os.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/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/dh.c
@@ -1,0 +1,74 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+
+mpint*
+dh_new(DHstate *dh, mpint *p, mpint *q, mpint *g)
+{
+	mpint *pm1;
+	int n;
+
+	memset(dh, 0, sizeof(*dh));
+	if(mpcmp(g, mpone) <= 0)
+		return nil;
+
+	n = mpsignif(p);
+	pm1 = mpnew(n);
+	mpsub(p, mpone, pm1);
+	dh->p = mpcopy(p);
+	dh->g = mpcopy(g);
+	dh->q = mpcopy(q != nil ? q : pm1);
+	dh->x = mpnew(mpsignif(dh->q));
+	dh->y = mpnew(n);
+	for(;;){
+		mpnrand(dh->q, genrandom, dh->x);
+		mpexp(dh->g, dh->x, dh->p, dh->y);
+		if(mpcmp(dh->y, mpone) > 0 && mpcmp(dh->y, pm1) < 0)
+			break;
+	}
+	mpfree(pm1);
+
+	return dh->y;
+}
+
+mpint*
+dh_finish(DHstate *dh, mpint *y)
+{
+	mpint *k = nil;
+
+	if(y == nil || dh->x == nil || dh->p == nil || dh->q == nil)
+		goto Out;
+
+	/* y > 1 */
+	if(mpcmp(y, mpone) <= 0)
+		goto Out;
+
+	k = mpnew(mpsignif(dh->p));
+
+	/* y < p-1 */
+	mpsub(dh->p, mpone, k);
+	if(mpcmp(y, k) >= 0){
+Bad:
+		mpfree(k);
+		k = nil;
+		goto Out;
+	}
+
+	/* y**q % p == 1 if q < p-1 */
+	if(mpcmp(dh->q, k) < 0){
+		mpexp(y, dh->q, dh->p, k);
+		if(mpcmp(k, mpone) != 0)
+			goto Bad;
+	}
+
+	mpexp(y, dh->x, dh->p, k);
+
+Out:
+	mpfree(dh->p);
+	mpfree(dh->q);
+	mpfree(dh->g);
+	mpfree(dh->x);
+	mpfree(dh->y);
+	memset(dh, 0, sizeof(*dh));
+	return k;
+}
--- /dev/null
+++ b/libsec/ecc.c
@@ -1,0 +1,612 @@
+#include "os.h"
+#include <mp.h>
+#include <libsec.h>
+#include <ctype.h>
+
+extern void jacobian_affine(mpint *p,
+	mpint *X, mpint *Y, mpint *Z);
+extern void jacobian_dbl(mpint *p, mpint *a,
+	mpint *X1, mpint *Y1, mpint *Z1,
+	mpint *X3, mpint *Y3, mpint *Z3);
+extern void jacobian_add(mpint *p, mpint *a,
+	mpint *X1, mpint *Y1, mpint *Z1,
+	mpint *X2, mpint *Y2, mpint *Z2,
+	mpint *X3, mpint *Y3, mpint *Z3);
+
+void
+ecassign(ECdomain *dom, ECpoint *a, ECpoint *b)
+{
+	if((b->inf = a->inf) != 0)
+		return;
+	mpassign(a->x, b->x);
+	mpassign(a->y, b->y);
+	if(b->z != nil){
+		mpassign(a->z != nil ? a->z : mpone, b->z);
+		return;
+	}
+	if(a->z != nil){
+		b->z = mpcopy(a->z);
+		jacobian_affine(dom->p, b->x, b->y, b->z);
+		mpfree(b->z);
+		b->z = nil;
+	}
+}
+
+void
+ecadd(ECdomain *dom, ECpoint *a, ECpoint *b, ECpoint *s)
+{
+	if(a->inf && b->inf){
+		s->inf = 1;
+		return;
+	}
+	if(a->inf){
+		ecassign(dom, b, s);
+		return;
+	}
+	if(b->inf){
+		ecassign(dom, a, s);
+		return;
+	}
+
+	if(s->z == nil){
+		s->z = mpcopy(mpone);
+		ecadd(dom, a, b, s);
+		if(!s->inf)
+			jacobian_affine(dom->p, s->x, s->y, s->z);
+		mpfree(s->z);
+		s->z = nil;
+		return;
+	}
+
+	if(a == b)
+		jacobian_dbl(dom->p, dom->a,
+			a->x, a->y, a->z != nil ? a->z : mpone,
+			s->x, s->y, s->z);
+	else
+		jacobian_add(dom->p, dom->a,
+			a->x, a->y, a->z != nil ? a->z : mpone,
+			b->x, b->y, b->z != nil ? b->z : mpone,
+			s->x, s->y, s->z);
+	s->inf = mpcmp(s->z, mpzero) == 0;
+}
+
+void
+ecmul(ECdomain *dom, ECpoint *a, mpint *k, ECpoint *s)
+{
+	ECpoint ns, na;
+	mpint *l;
+
+	if(a->inf || mpcmp(k, mpzero) == 0){
+		s->inf = 1;
+		return;
+	}
+	ns.inf = 1;
+	ns.x = mpnew(0);
+	ns.y = mpnew(0);
+	ns.z = mpnew(0);
+	na.x = mpnew(0);
+	na.y = mpnew(0);
+	na.z = mpnew(0);
+	ecassign(dom, a, &na);
+	l = mpcopy(k);
+	l->sign = 1;
+	while(mpcmp(l, mpzero) != 0){
+		if(l->p[0] & 1)
+			ecadd(dom, &na, &ns, &ns);
+		ecadd(dom, &na, &na, &na);
+		mpright(l, 1, l);
+	}
+	if(k->sign < 0 && !ns.inf){
+		ns.y->sign = -1;
+		mpmod(ns.y, dom->p, ns.y);
+	}
+	ecassign(dom, &ns, s);
+	mpfree(ns.x);
+	mpfree(ns.y);
+	mpfree(ns.z);
+	mpfree(na.x);
+	mpfree(na.y);
+	mpfree(na.z);
+	mpfree(l);
+}
+
+int
+ecverify(ECdomain *dom, ECpoint *a)
+{
+	mpint *p, *q;
+	int r;
+
+	if(a->inf)
+		return 1;
+
+	assert(a->z == nil);	/* need affine coordinates */
+	p = mpnew(0);
+	q = mpnew(0);
+	mpmodmul(a->y, a->y, dom->p, p);
+	mpmodmul(a->x, a->x, dom->p, q);
+	mpmodadd(q, dom->a, dom->p, q);
+	mpmodmul(q, a->x, dom->p, q);
+	mpmodadd(q, dom->b, dom->p, q);
+	r = mpcmp(p, q);
+	mpfree(p);
+	mpfree(q);
+	return r == 0;
+}
+
+int
+ecpubverify(ECdomain *dom, ECpub *a)
+{
+	ECpoint p;
+	int r;
+
+	if(a->inf)
+		return 0;
+	if(!ecverify(dom, a))
+		return 0;
+	p.x = mpnew(0);
+	p.y = mpnew(0);
+	p.z = mpnew(0);
+	ecmul(dom, a, dom->n, &p);
+	r = p.inf;
+	mpfree(p.x);
+	mpfree(p.y);
+	mpfree(p.z);
+	return r;
+}
+
+static void
+fixnibble(uchar *a)
+{
+	if(*a >= 'a')
+		*a -= 'a'-10;
+	else if(*a >= 'A')
+		*a -= 'A'-10;
+	else
+		*a -= '0';
+}
+
+static int
+octet(char **s)
+{
+	uchar c, d;
+	
+	c = *(*s)++;
+	if(!isxdigit(c))
+		return -1;
+	d = *(*s)++;
+	if(!isxdigit(d))
+		return -1;
+	fixnibble(&c);
+	fixnibble(&d);
+	return (c << 4) | d;
+}
+
+static mpint*
+halfpt(ECdomain *dom, char *s, char **rptr, mpint *out)
+{
+	char *buf, *r;
+	int n;
+	mpint *ret;
+	
+	n = ((mpsignif(dom->p)+7)/8)*2;
+	if(strlen(s) < n)
+		return 0;
+	buf = malloc(n+1);
+	buf[n] = 0;
+	memcpy(buf, s, n);
+	ret = strtomp(buf, &r, 16, out);
+	*rptr = s + (r - buf);
+	free(buf);
+	return ret;
+}
+
+static int
+mpleg(mpint *a, mpint *b)
+{
+	int r, k;
+	mpint *m, *n, *t;
+	
+	r = 1;
+	m = mpcopy(a);
+	n = mpcopy(b);
+	for(;;){
+		if(mpcmp(m, n) > 0)
+			mpmod(m, n, m);
+		if(mpcmp(m, mpzero) == 0){
+			r = 0;
+			break;
+		}
+		if(mpcmp(m, mpone) == 0)
+			break;
+		k = mplowbits0(m);
+		if(k > 0){
+			if(k & 1)
+				switch(n->p[0] & 15){
+				case 3: case 5: case 11: case 13:
+					r = -r;
+				}
+			mpright(m, k, m);
+		}
+		if((n->p[0] & 3) == 3 && (m->p[0] & 3) == 3)
+			r = -r;
+		t = m;
+		m = n;
+		n = t;
+	}
+	mpfree(m);
+	mpfree(n);
+	return r;
+}
+
+static int
+mpsqrt(mpint *n, mpint *p, mpint *r)
+{
+	mpint *a, *t, *s, *xp, *xq, *yp, *yq, *zp, *zq, *N;
+
+	if(mpleg(n, p) == -1)
+		return 0;
+	a = mpnew(0);
+	t = mpnew(0);
+	s = mpnew(0);
+	N = mpnew(0);
+	xp = mpnew(0);
+	xq = mpnew(0);
+	yp = mpnew(0);
+	yq = mpnew(0);
+	zp = mpnew(0);
+	zq = mpnew(0);
+	for(;;){
+		for(;;){
+			mpnrand(p, genrandom, a);
+			if(mpcmp(a, mpzero) > 0)
+				break;
+		}
+		mpmul(a, a, t);
+		mpsub(t, n, t);
+		mpmod(t, p, t);
+		if(mpleg(t, p) == -1)
+			break;
+	}
+	mpadd(p, mpone, N);
+	mpright(N, 1, N);
+	mpmul(a, a, t);
+	mpsub(t, n, t);
+	mpassign(a, xp);
+	uitomp(1, xq);
+	uitomp(1, yp);
+	uitomp(0, yq);
+	while(mpcmp(N, mpzero) != 0){
+		if(N->p[0] & 1){
+			mpmul(xp, yp, zp);
+			mpmul(xq, yq, zq);
+			mpmul(zq, t, zq);
+			mpadd(zp, zq, zp);
+			mpmod(zp, p, zp);
+			mpmul(xp, yq, zq);
+			mpmul(xq, yp, s);
+			mpadd(zq, s, zq);
+			mpmod(zq, p, yq);
+			mpassign(zp, yp);
+		}
+		mpmul(xp, xp, zp);
+		mpmul(xq, xq, zq);
+		mpmul(zq, t, zq);
+		mpadd(zp, zq, zp);
+		mpmod(zp, p, zp);
+		mpmul(xp, xq, zq);
+		mpadd(zq, zq, zq);
+		mpmod(zq, p, xq);
+		mpassign(zp, xp);
+		mpright(N, 1, N);
+	}
+	if(mpcmp(yq, mpzero) != 0)
+		abort();
+	mpassign(yp, r);
+	mpfree(a);
+	mpfree(t);
+	mpfree(s);
+	mpfree(N);
+	mpfree(xp);
+	mpfree(xq);
+	mpfree(yp);
+	mpfree(yq);
+	mpfree(zp);
+	mpfree(zq);
+	return 1;
+}
+
+ECpoint*
+strtoec(ECdomain *dom, char *s, char **rptr, ECpoint *ret)
+{
+	int allocd, o;
+	mpint *r;
+
+	allocd = 0;
+	if(ret == nil){
+		allocd = 1;
+		ret = mallocz(sizeof(*ret), 1);
+		if(ret == nil)
+			return nil;
+		ret->x = mpnew(0);
+		ret->y = mpnew(0);
+	}
+	ret->inf = 0;
+	o = 0;
+	switch(octet(&s)){
+	case 0:
+		ret->inf = 1;
+		break;
+	case 3:
+		o = 1;
+	case 2:
+		if(halfpt(dom, s, &s, ret->x) == nil)
+			goto err;
+		r = mpnew(0);
+		mpmul(ret->x, ret->x, r);
+		mpadd(r, dom->a, r);
+		mpmul(r, ret->x, r);
+		mpadd(r, dom->b, r);
+		if(!mpsqrt(r, dom->p, r)){
+			mpfree(r);
+			goto err;
+		}
+		if((r->p[0] & 1) != o)
+			mpsub(dom->p, r, r);
+		mpassign(r, ret->y);
+		mpfree(r);
+		if(!ecverify(dom, ret))
+			goto err;
+		break;
+	case 4:
+		if(halfpt(dom, s, &s, ret->x) == nil)
+			goto err;
+		if(halfpt(dom, s, &s, ret->y) == nil)
+			goto err;
+		if(!ecverify(dom, ret))
+			goto err;
+		break;
+	}
+	if(ret->z != nil && !ret->inf)
+		mpassign(mpone, ret->z);
+	return ret;
+
+err:
+	if(rptr)
+		*rptr = s;
+	if(allocd){
+		mpfree(ret->x);
+		mpfree(ret->y);
+		free(ret);
+	}
+	return nil;
+}
+
+ECpriv*
+ecgen(ECdomain *dom, ECpriv *p)
+{
+	if(p == nil){
+		p = mallocz(sizeof(*p), 1);
+		if(p == nil)
+			return nil;
+		p->a.x = mpnew(0);
+		p->a.y = mpnew(0);
+		p->d = mpnew(0);
+	}
+	for(;;){
+		mpnrand(dom->n, genrandom, p->d);
+		if(mpcmp(p->d, mpzero) > 0)
+			break;
+	}
+	ecmul(dom, &dom->G, p->d, &p->a);
+	return p;
+}
+
+void
+ecdsasign(ECdomain *dom, ECpriv *priv, uchar *dig, int len, mpint *r, mpint *s)
+{
+	ECpriv tmp;
+	mpint *E, *t;
+
+	tmp.a.x = mpnew(0);
+	tmp.a.y = mpnew(0);
+	tmp.a.z = nil;
+	tmp.d = mpnew(0);
+	E = betomp(dig, len, nil);
+	t = mpnew(0);
+	if(mpsignif(dom->n) < 8*len)
+		mpright(E, 8*len - mpsignif(dom->n), E);
+	for(;;){
+		ecgen(dom, &tmp);
+		mpmod(tmp.a.x, dom->n, r);
+		if(mpcmp(r, mpzero) == 0)
+			continue;
+		mpmul(r, priv->d, s);
+		mpadd(E, s, s);
+		mpinvert(tmp.d, dom->n, t);
+		mpmodmul(s, t, dom->n, s);
+		if(mpcmp(s, mpzero) != 0)
+			break;
+	}
+	mpfree(t);
+	mpfree(E);
+	mpfree(tmp.a.x);
+	mpfree(tmp.a.y);
+	mpfree(tmp.d);
+}
+
+int
+ecdsaverify(ECdomain *dom, ECpub *pub, uchar *dig, int len, mpint *r, mpint *s)
+{
+	mpint *E, *t, *u1, *u2;
+	ECpoint R, S;
+	int ret;
+
+	if(mpcmp(r, mpone) < 0 || mpcmp(s, mpone) < 0 || mpcmp(r, dom->n) >= 0 || mpcmp(r, dom->n) >= 0)
+		return 0;
+	E = betomp(dig, len, nil);
+	if(mpsignif(dom->n) < 8*len)
+		mpright(E, 8*len - mpsignif(dom->n), E);
+	t = mpnew(0);
+	u1 = mpnew(0);
+	u2 = mpnew(0);
+	R.x = mpnew(0);
+	R.y = mpnew(0);
+	R.z = mpnew(0);
+	S.x = mpnew(0);
+	S.y = mpnew(0);
+	S.z = mpnew(0);
+	mpinvert(s, dom->n, t);
+	mpmodmul(E, t, dom->n, u1);
+	mpmodmul(r, t, dom->n, u2);
+	ecmul(dom, &dom->G, u1, &R);
+	ecmul(dom, pub, u2, &S);
+	ecadd(dom, &R, &S, &R);
+	ret = 0;
+	if(!R.inf){
+		jacobian_affine(dom->p, R.x, R.y, R.z);
+		mpmod(R.x, dom->n, t);
+		ret = mpcmp(r, t) == 0;
+	}
+	mpfree(E);
+	mpfree(t);
+	mpfree(u1);
+	mpfree(u2);
+	mpfree(R.x);
+	mpfree(R.y);
+	mpfree(R.z);
+	mpfree(S.x);
+	mpfree(S.y);
+	mpfree(S.z);
+	return ret;
+}
+
+static char *code = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+
+void
+base58enc(uchar *src, char *dst, int len)
+{
+	mpint *n, *r, *b;
+	char *sdst, t;
+	
+	sdst = dst;
+	n = betomp(src, len, nil);
+	b = uitomp(58, nil);
+	r = mpnew(0);
+	while(mpcmp(n, mpzero) != 0){
+		mpdiv(n, b, n, r);
+		*dst++ = code[mptoui(r)];
+	}
+	for(; *src == 0; src++)
+		*dst++ = code[0];
+	dst--;
+	while(dst > sdst){
+		t = *sdst;
+		*sdst++ = *dst;
+		*dst-- = t;
+	}
+}
+
+int
+base58dec(char *src, uchar *dst, int len)
+{
+	mpint *n, *b, *r;
+	char *t;
+	
+	n = mpnew(0);
+	r = mpnew(0);
+	b = uitomp(58, nil);
+	for(; *src; src++){
+		t = strchr(code, *src);
+		if(t == nil){
+			mpfree(n);
+			mpfree(r);
+			mpfree(b);
+			werrstr("invalid base58 char");
+			return -1;
+		}
+		uitomp(t - code, r);
+		mpmul(n, b, n);
+		mpadd(n, r, n);
+	}
+	mptober(n, dst, len);
+	mpfree(n);
+	mpfree(r);
+	mpfree(b);
+	return 0;
+}
+
+void
+ecdominit(ECdomain *dom, void (*init)(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h))
+{
+	memset(dom, 0, sizeof(*dom));
+	dom->p = mpnew(0);
+	dom->a = mpnew(0);
+	dom->b = mpnew(0);
+	dom->G.x = mpnew(0);
+	dom->G.y = mpnew(0);
+	dom->n = mpnew(0);
+	dom->h = mpnew(0);
+	if(init){
+		(*init)(dom->p, dom->a, dom->b, dom->G.x, dom->G.y, dom->n, dom->h);
+		dom->p = mpfield(dom->p);
+	}
+}
+
+void
+ecdomfree(ECdomain *dom)
+{
+	mpfree(dom->p);
+	mpfree(dom->a);
+	mpfree(dom->b);
+	mpfree(dom->G.x);
+	mpfree(dom->G.y);
+	mpfree(dom->n);
+	mpfree(dom->h);
+	memset(dom, 0, sizeof(*dom));
+}
+
+int
+ecencodepub(ECdomain *dom, ECpub *pub, uchar *data, int len)
+{
+	int n;
+
+	n = (mpsignif(dom->p)+7)/8;
+	if(len < 1 + 2*n)
+		return 0;
+	len = 1 + 2*n;
+	data[0] = 0x04;
+	mptober(pub->x, data+1, n);
+	mptober(pub->y, data+1+n, n);
+	return len;
+}
+
+ECpub*
+ecdecodepub(ECdomain *dom, uchar *data, int len)
+{
+	ECpub *pub;
+	int n;
+
+	n = (mpsignif(dom->p)+7)/8;
+	if(len != 1 + 2*n || data[0] != 0x04)
+		return nil;
+	pub = mallocz(sizeof(*pub), 1);
+	if(pub == nil)
+		return nil;
+	pub->x = betomp(data+1, n, nil);
+	pub->y = betomp(data+1+n, n, nil);
+	if(!ecpubverify(dom, pub)){
+		ecpubfree(pub);
+		pub = nil;
+	}
+	return pub;
+}
+
+void
+ecpubfree(ECpub *p)
+{
+	if(p == nil)
+		return;
+	mpfree(p->x);
+	mpfree(p->y);
+	free(p);
+}
--- /dev/null
+++ b/libsec/fastrand.c
@@ -1,0 +1,15 @@
+#include "os.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/genrandom.c
@@ -1,0 +1,44 @@
+#include "os.h"
+#include <libsec.h>
+
+static void
+init(Chachastate *cs)
+{
+	ulong seed[11];
+	int i;
+
+	for(i=0; i<nelem(seed); i++)
+		seed[i] = truerand();
+
+	setupChachastate(cs, (uchar*)&seed[0], 32, (uchar*)&seed[8], 12, 20);
+	memset(seed, 0, sizeof(seed));
+}
+
+static void
+fill(Chachastate *cs, uchar *p, int n)
+{
+	Chachastate c;
+
+	c = *cs;
+	chacha_encrypt((uchar*)&cs->input[4], 32, &c);
+	if(++cs->input[13] == 0)
+		if(++cs->input[14] == 0)
+			++cs->input[15];
+
+	chacha_encrypt(p, n, &c);
+	memset(&c, 0, sizeof(c));
+}
+
+void
+genrandom(uchar *p, int n)
+{
+	static QLock lk;
+	static Chachastate cs;
+
+	qlock(&lk);
+	if(cs.rounds == 0)
+		init(&cs);
+	cs.input[4] ^= getpid();	/* fork protection */
+	fill(&cs, p, n);
+	qunlock(&lk);
+}
--- /dev/null
+++ b/libsec/hkdf.c
@@ -1,0 +1,36 @@
+#include "os.h"
+#include <libsec.h>
+
+/* rfc5869 */
+void
+hkdf_x(uchar *salt, ulong nsalt, uchar *info, ulong ninfo,
+	uchar *key, ulong nkey, uchar *d, ulong dlen,
+	DigestState* (*x)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*), int xlen)
+{
+	uchar prk[256], tmp[256], cnt;
+	DigestState *ds;
+
+	assert(xlen <= sizeof(tmp));
+
+	memset(tmp, 0, xlen);
+	if(nsalt == 0){
+		salt = tmp;
+		nsalt = xlen;
+	}
+	/* note that salt and key are swapped in this case */
+	(*x)(key, nkey, salt, nsalt, prk, nil);
+	ds = nil;
+	for(cnt=1;; cnt++) {
+		if(ninfo > 0)
+			ds = (*x)(info, ninfo, prk, xlen, nil, ds);
+		(*x)(&cnt, 1, prk, xlen, tmp, ds);
+		if(dlen <= xlen){
+			memmove(d, tmp, dlen);
+			break;
+		}
+		memmove(d, tmp, xlen);
+		dlen -= xlen;
+		d += xlen;
+		ds = (*x)(tmp, xlen, prk, xlen, nil, nil);
+	}
+}
--- /dev/null
+++ b/libsec/hmac.c
@@ -1,0 +1,46 @@
+#include "os.h"
+#include <libsec.h>
+
+/* rfc2104 */
+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[Hmacblksz+1], innerdigest[256];
+
+	if(xlen > sizeof(innerdigest))
+		return nil;
+	if(klen > Hmacblksz){
+		if(xlen > Hmacblksz)
+			return nil;
+		(*x)(key, klen, innerdigest, nil);
+		key = innerdigest;
+		klen = xlen;
+	}
+
+	/* first time through */
+	if(s == nil || s->seeded == 0){
+		memset(pad, 0x36, Hmacblksz);
+		pad[Hmacblksz] = 0;
+		for(i = 0; i < klen; i++)
+			pad[i] ^= key[i];
+		s = (*x)(pad, Hmacblksz, nil, s);
+		if(s == nil)
+			return nil;
+	}
+
+	s = (*x)(p, len, nil, s);
+	if(digest == nil)
+		return s;
+
+	/* last time through */
+	memset(pad, 0x5c, Hmacblksz);
+	pad[Hmacblksz] = 0;
+	for(i = 0; i < klen; i++)
+		pad[i] ^= key[i];
+	(*x)(nil, 0, innerdigest, s);
+	s = (*x)(pad, Hmacblksz, nil, nil);
+	(*x)(innerdigest, xlen, digest, s);
+	return nil;
+}
--- /dev/null
+++ b/libsec/jacobian.c
@@ -1,0 +1,166 @@
+#include "os.h"
+#include <mp.h>
+void jacobian_new(mpint *x, mpint *y, mpint *z, mpint *X, mpint *Y, mpint *Z){
+	mpassign(x, X);
+	mpassign(y, Y);
+	mpassign(z, Z);
+	}
+void jacobian_inf(mpint *X, mpint *Y, mpint *Z){
+	jacobian_new(mpzero, mpone, mpzero, X, Y, Z);
+	}
+void jacobian_affine(mpint *p, mpint *X, mpint *Y, mpint *Z){
+	mpint *ZZZ = mpnew(0);
+	mpint *ZZ = mpnew(0);
+	if(mpcmp(Z, mpzero) != 0){
+		mpmodmul(Z, Z, p, ZZ);
+		mpmodmul(ZZ, Z, p, ZZZ);
+		mpint *tmp1 = mpnew(0);
+		mpinvert(ZZ, p, tmp1);
+		mpmodmul(X, tmp1, p, X);
+		mpfree(tmp1);
+		tmp1 = mpnew(0);
+		mpinvert(ZZZ, p, tmp1);
+		mpmodmul(Y, tmp1, p, Y);
+		mpfree(tmp1);
+		mpassign(mpone, Z);
+		}
+	mpfree(ZZZ);
+	mpfree(ZZ);
+	}
+void jacobian_dbl(mpint *p, mpint *a, mpint *X1, mpint *Y1, mpint *Z1, mpint *X3, mpint *Y3, mpint *Z3){
+	mpint *M = mpnew(0);
+	mpint *S = mpnew(0);
+	mpint *ZZ = mpnew(0);
+	mpint *YYYY = mpnew(0);
+	mpint *YY = mpnew(0);
+	mpint *XX = mpnew(0);
+	if(mpcmp(Y1, mpzero) == 0){
+		jacobian_inf(X3, Y3, Z3);
+		}else{
+		mpmodmul(X1, X1, p, XX);
+		mpmodmul(Y1, Y1, p, YY);
+		mpmodmul(YY, YY, p, YYYY);
+		mpmodmul(Z1, Z1, p, ZZ);
+		mpint *tmp1 = mpnew(0);
+		mpmodadd(X1, YY, p, tmp1);
+		mpmodmul(tmp1, tmp1, p, tmp1);
+		mpmodsub(tmp1, XX, p, tmp1);
+		mpmodsub(tmp1, YYYY, p, tmp1);
+		mpmodadd(tmp1, tmp1, p, S); // 2*tmp1
+		mpfree(tmp1);
+		tmp1 = mpnew(0);
+		uitomp(3UL, tmp1);
+		mpmodmul(tmp1, XX, p, M);
+		mpfree(tmp1);
+		tmp1 = mpnew(0);
+		mpint *tmp2 = mpnew(0);
+		mpmodmul(ZZ, ZZ, p, tmp2);
+		mpmodmul(a, tmp2, p, tmp1);
+		mpfree(tmp2);
+		mpmodadd(M, tmp1, p, M);
+		mpfree(tmp1);
+		mpmodadd(Y1, Z1, p, Z3);
+		mpmodmul(Z3, Z3, p, Z3);
+		mpmodsub(Z3, YY, p, Z3);
+		mpmodsub(Z3, ZZ, p, Z3);
+		mpmodmul(M, M, p, X3);
+		tmp1 = mpnew(0);
+		mpmodadd(S, S, p, tmp1); // 2*S
+		mpmodsub(X3, tmp1, p, X3);
+		mpfree(tmp1);
+		tmp1 = mpnew(0);
+		mpmodsub(S, X3, p, tmp1);
+		mpmodmul(M, tmp1, p, Y3);
+		mpfree(tmp1);
+		tmp1 = mpnew(0);
+		tmp2 = mpnew(0);
+		uitomp(8UL, tmp2);
+		mpmodmul(tmp2, YYYY, p, tmp1);
+		mpfree(tmp2);
+		mpmodsub(Y3, tmp1, p, Y3);
+		mpfree(tmp1);
+		}
+	mpfree(M);
+	mpfree(S);
+	mpfree(ZZ);
+	mpfree(YYYY);
+	mpfree(YY);
+	mpfree(XX);
+	}
+void jacobian_add(mpint *p, mpint *a, mpint *X1, mpint *Y1, mpint *Z1, mpint *X2, mpint *Y2, mpint *Z2, mpint *X3, mpint *Y3, mpint *Z3){
+	mpint *V = mpnew(0);
+	mpint *r = mpnew(0);
+	mpint *J = mpnew(0);
+	mpint *I = mpnew(0);
+	mpint *H = mpnew(0);
+	mpint *S2 = mpnew(0);
+	mpint *S1 = mpnew(0);
+	mpint *U2 = mpnew(0);
+	mpint *U1 = mpnew(0);
+	mpint *Z2Z2 = mpnew(0);
+	mpint *Z1Z1 = mpnew(0);
+	mpmodmul(Z1, Z1, p, Z1Z1);
+	mpmodmul(Z2, Z2, p, Z2Z2);
+	mpmodmul(X1, Z2Z2, p, U1);
+	mpmodmul(X2, Z1Z1, p, U2);
+	mpint *tmp1 = mpnew(0);
+	mpmodmul(Y1, Z2, p, tmp1);
+	mpmodmul(tmp1, Z2Z2, p, S1);
+	mpfree(tmp1);
+	tmp1 = mpnew(0);
+	mpmodmul(Y2, Z1, p, tmp1);
+	mpmodmul(tmp1, Z1Z1, p, S2);
+	mpfree(tmp1);
+	if(mpcmp(U1, U2) == 0){
+		if(mpcmp(S1, S2) != 0){
+			jacobian_inf(X3, Y3, Z3);
+			}else{
+			jacobian_dbl(p, a, X1, Y1, Z1, X3, Y3, Z3);
+			}
+		}else{
+		mpmodsub(U2, U1, p, H);
+		mpmodadd(H, H, p, I); // 2*H
+		mpmodmul(I, I, p, I);
+		mpmodmul(H, I, p, J);
+		mpint *tmp2 = mpnew(0);
+		mpmodsub(S2, S1, p, tmp2);
+		mpmodadd(tmp2, tmp2, p, r); // 2*tmp2
+		mpfree(tmp2);
+		mpmodmul(U1, I, p, V);
+		mpmodmul(r, r, p, X3);
+		mpmodsub(X3, J, p, X3);
+		tmp2 = mpnew(0);
+		mpmodadd(V, V, p, tmp2); // 2*V
+		mpmodsub(X3, tmp2, p, X3);
+		mpfree(tmp2);
+		tmp2 = mpnew(0);
+		mpmodsub(V, X3, p, tmp2);
+		mpmodmul(r, tmp2, p, Y3);
+		mpfree(tmp2);
+		tmp2 = mpnew(0);
+		mpint *tmp3 = mpnew(0);
+		mpmodadd(S1, S1, p, tmp3); // 2*S1
+		mpmodmul(tmp3, J, p, tmp2);
+		mpfree(tmp3);
+		mpmodsub(Y3, tmp2, p, Y3);
+		mpfree(tmp2);
+		tmp2 = mpnew(0);
+		mpmodadd(Z1, Z2, p, tmp2);
+		mpmodmul(tmp2, tmp2, p, tmp2);
+		mpmodsub(tmp2, Z1Z1, p, tmp2);
+		mpmodsub(tmp2, Z2Z2, p, tmp2);
+		mpmodmul(tmp2, H, p, Z3);
+		mpfree(tmp2);
+		}
+	mpfree(V);
+	mpfree(r);
+	mpfree(J);
+	mpfree(I);
+	mpfree(H);
+	mpfree(S2);
+	mpfree(S1);
+	mpfree(U2);
+	mpfree(U1);
+	mpfree(Z2Z2);
+	mpfree(Z1Z1);
+	}
--- /dev/null
+++ b/libsec/md5.c
@@ -1,0 +1,154 @@
+#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);
+
+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;
+	}
+}
+
+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/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/nfastrand.c
@@ -1,0 +1,22 @@
+#include "os.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)
+		sysfatal("nfastrand: n too large");
+
+	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/pbkdf2.c
@@ -1,0 +1,32 @@
+#include "os.h"
+#include <libsec.h>
+
+/* rfc2898 */
+void
+pbkdf2_x(uchar *p, ulong plen, uchar *s, ulong slen,
+	ulong rounds, uchar *d, ulong dlen,
+	DigestState* (*x)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*), int xlen)
+{
+	uchar block[256], tmp[256];
+	ulong i, j, k, n;
+	DigestState *ds;
+
+	assert(xlen <= sizeof(tmp));
+
+	for(i = 1; dlen > 0; i++, d += n, dlen -= n){
+		tmp[3] = i;
+		tmp[2] = i >> 8;
+		tmp[1] = i >> 16;
+		tmp[0] = i >> 24;
+		ds = (*x)(s, slen, p, plen, nil, nil);
+		(*x)(tmp, 4, p, plen, block, ds);
+		memmove(tmp, block, xlen);
+		for(j = 1; j < rounds; j++){
+			(*x)(tmp, xlen, p, plen, tmp, nil);
+			for(k=0; k<xlen; k++)
+				block[k] ^= tmp[k];
+		}
+		n = dlen > xlen ? xlen : dlen;
+		memmove(d, block, n); 
+	}
+}
--- /dev/null
+++ b/libsec/poly1305.c
@@ -1,0 +1,195 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+	poly1305 implementation using 32 bit * 32 bit = 64 bit multiplication and 64 bit addition
+
+	derived from http://github.com/floodberry/poly1305-donna
+*/
+
+#define U8TO32(p)	((u32int)(p)[0] | (u32int)(p)[1]<<8 | (u32int)(p)[2]<<16 | (u32int)(p)[3]<<24)
+#define U32TO8(p, v)	(p)[0]=(v), (p)[1]=(v)>>8, (p)[2]=(v)>>16, (p)[3]=(v)>>24
+
+/* (r,s) = (key[0:15],key[16:31]), the one time key */
+DigestState*
+poly1305(uchar *m, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+	u32int r0,r1,r2,r3,r4, s1,s2,s3,s4, h0,h1,h2,h3,h4, g0,g1,g2,g3,g4;
+	u64int d0,d1,d2,d3,d4, f;
+	u32int hibit, mask, c;
+
+	if(s == nil){
+		s = malloc(sizeof(*s));
+		if(s == nil)
+			return nil;
+		memset(s, 0, sizeof(*s));
+		s->malloced = 1;
+	}
+
+	if(s->seeded == 0){
+		assert(klen == 32);
+
+		/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
+		s->state[0] = (U8TO32(&key[ 0])     ) & 0x3ffffff;
+		s->state[1] = (U8TO32(&key[ 3]) >> 2) & 0x3ffff03;
+		s->state[2] = (U8TO32(&key[ 6]) >> 4) & 0x3ffc0ff;
+		s->state[3] = (U8TO32(&key[ 9]) >> 6) & 0x3f03fff;
+		s->state[4] = (U8TO32(&key[12]) >> 8) & 0x00fffff;
+
+		/* h = 0 */
+		s->state[5] = 0;
+		s->state[6] = 0;
+		s->state[7] = 0;
+		s->state[8] = 0;
+		s->state[9] = 0;
+
+		/* save pad for later */
+		s->state[10] = U8TO32(&key[16]);
+		s->state[11] = U8TO32(&key[20]);
+		s->state[12] = U8TO32(&key[24]);
+		s->state[13] = U8TO32(&key[28]);
+
+		s->seeded = 1;
+	}
+
+	if(s->blen){
+		c = 16 - s->blen;
+		if(c > len)
+			c = len;
+		memmove(s->buf + s->blen, m, c);
+		len -= c, m += c;
+		s->blen += c;
+		if(s->blen == 16){
+			s->blen = 0;
+			poly1305(s->buf, 16, key, klen, nil, s);
+		} else if(len == 0){
+			m = s->buf;
+			len = s->blen;
+			s->blen = 0;
+		}
+	}
+
+	r0 = s->state[0];
+	r1 = s->state[1];
+	r2 = s->state[2];
+	r3 = s->state[3];
+	r4 = s->state[4];
+
+	h0 = s->state[5];
+	h1 = s->state[6];
+	h2 = s->state[7];
+	h3 = s->state[8];
+	h4 = s->state[9];
+
+	s1 = r1 * 5;
+	s2 = r2 * 5;
+	s3 = r3 * 5;
+	s4 = r4 * 5;
+
+	hibit = 1<<24;	/* 1<<128 */
+
+	while(len >= 16){
+Block:
+		/* h += m[i] */
+		h0 += (U8TO32(&m[0])     ) & 0x3ffffff;
+		h1 += (U8TO32(&m[3]) >> 2) & 0x3ffffff;
+		h2 += (U8TO32(&m[6]) >> 4) & 0x3ffffff;
+		h3 += (U8TO32(&m[9]) >> 6) & 0x3ffffff;
+		h4 += (U8TO32(&m[12])>> 8) | hibit;
+
+		/* h *= r */
+		d0 = ((u64int)h0 * r0) + ((u64int)h1 * s4) + ((u64int)h2 * s3) + ((u64int)h3 * s2) + ((u64int)h4 * s1);
+		d1 = ((u64int)h0 * r1) + ((u64int)h1 * r0) + ((u64int)h2 * s4) + ((u64int)h3 * s3) + ((u64int)h4 * s2);
+		d2 = ((u64int)h0 * r2) + ((u64int)h1 * r1) + ((u64int)h2 * r0) + ((u64int)h3 * s4) + ((u64int)h4 * s3);
+		d3 = ((u64int)h0 * r3) + ((u64int)h1 * r2) + ((u64int)h2 * r1) + ((u64int)h3 * r0) + ((u64int)h4 * s4);
+		d4 = ((u64int)h0 * r4) + ((u64int)h1 * r3) + ((u64int)h2 * r2) + ((u64int)h3 * r1) + ((u64int)h4 * r0);
+
+		/* (partial) h %= p */
+		              c = (u32int)(d0 >> 26); h0 = (u32int)d0 & 0x3ffffff;
+		d1 += c;      c = (u32int)(d1 >> 26); h1 = (u32int)d1 & 0x3ffffff;
+		d2 += c;      c = (u32int)(d2 >> 26); h2 = (u32int)d2 & 0x3ffffff;
+		d3 += c;      c = (u32int)(d3 >> 26); h3 = (u32int)d3 & 0x3ffffff;
+		d4 += c;      c = (u32int)(d4 >> 26); h4 = (u32int)d4 & 0x3ffffff;
+		h0 += c * 5;  c = (h0 >> 26); h0 = h0 & 0x3ffffff;
+		h1 += c;
+
+		len -= 16, m += 16;
+	}
+
+	if(len){
+		s->blen = len;
+		memmove(s->buf, m, len);
+	}
+
+	if(digest == nil){
+		s->state[5] = h0;
+		s->state[6] = h1;
+		s->state[7] = h2;
+		s->state[8] = h3;
+		s->state[9] = h4;
+		return s;
+	}
+
+	if(len){
+		m = s->buf;
+		m[len++] = 1;
+		while(len < 16)
+			m[len++] = 0;
+		hibit = 0;
+		goto Block;
+	}
+
+	             c = h1 >> 26; h1 = h1 & 0x3ffffff;
+	h2 +=     c; c = h2 >> 26; h2 = h2 & 0x3ffffff;
+	h3 +=     c; c = h3 >> 26; h3 = h3 & 0x3ffffff;
+	h4 +=     c; c = h4 >> 26; h4 = h4 & 0x3ffffff;
+	h0 += c * 5; c = h0 >> 26; h0 = h0 & 0x3ffffff;
+	h1 +=     c;
+
+	/* compute h + -p */
+	g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff;
+	g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff;
+	g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff;
+	g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff;
+	g4 = h4 + c - (1 << 26);
+
+	/* select h if h < p, or h + -p if h >= p */
+	mask = (g4 >> 31) - 1;
+	g0 &= mask;
+	g1 &= mask;
+	g2 &= mask;
+	g3 &= mask;
+	g4 &= mask;
+	mask = ~mask;
+	h0 = (h0 & mask) | g0;
+	h1 = (h1 & mask) | g1;
+	h2 = (h2 & mask) | g2;
+	h3 = (h3 & mask) | g3;
+	h4 = (h4 & mask) | g4;
+
+	/* h = h % (2^128) */
+	h0 = (h0      ) | (h1 << 26);
+	h1 = (h1 >>  6) | (h2 << 20);
+	h2 = (h2 >> 12) | (h3 << 14);
+	h3 = (h3 >> 18) | (h4 <<  8);
+	
+	/* digest = (h + pad) % (2^128) */
+	f = (u64int)h0 + s->state[10]            ; h0 = (u32int)f;
+	f = (u64int)h1 + s->state[11] + (f >> 32); h1 = (u32int)f;
+	f = (u64int)h2 + s->state[12] + (f >> 32); h2 = (u32int)f;
+	f = (u64int)h3 + s->state[13] + (f >> 32); h3 = (u32int)f;
+
+	U32TO8(&digest[0], h0);
+	U32TO8(&digest[4], h1);
+	U32TO8(&digest[8], h2);
+	U32TO8(&digest[12], h3);
+
+	if(s->malloced){
+		memset(s, 0, sizeof(*s));
+		free(s);
+		return nil;
+	}
+
+	memset(s, 0, sizeof(*s));
+	return nil;
+}
--- /dev/null
+++ b/libsec/prng.c
@@ -1,0 +1,14 @@
+#include "os.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/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,63 @@
+#include "os.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) {
+		close(fd);
+		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: %r", filename);
+		return nil;
+	}
+	binary = decodePEM(pem, "CERTIFICATE", pcertlen, nil);
+	free(pem);
+	if(binary == nil){
+		werrstr("can't parse %s", filename);
+		return nil;
+	}
+	return binary;
+}
+
+PEMChain *
+readcertchain(char *filename)
+{
+	char *chfile;
+
+	chfile = readfile(filename);
+	if (chfile == nil) {
+		werrstr("can't read %s: %r", filename);
+		return nil;
+	}
+	return decodepemchain(chfile, "CERTIFICATE");
+}
+
--- /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/secp256k1.c
@@ -1,0 +1,11 @@
+#include "os.h"
+#include <mp.h>
+void secp256k1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h){
+	strtomp("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", nil, 16, p);
+	mpassign(mpzero, a);
+	uitomp(7UL, b);
+	strtomp("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", nil, 16, x);
+	strtomp("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", nil, 16, y);
+	strtomp("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", nil, 16, n);
+	mpassign(mpone, h);
+	}
--- /dev/null
+++ b/libsec/secp256r1.c
@@ -1,0 +1,12 @@
+#include "os.h"
+#include <mp.h>
+void secp256r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h){
+	strtomp("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", nil, 16, p);
+	uitomp(3UL, a);
+	mpsub(p, a, a);
+	strtomp("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", nil, 16, b);
+	strtomp("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", nil, 16, x);
+	strtomp("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", nil, 16, y);
+	strtomp("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", nil, 16, n);
+	mpassign(mpone, h);
+	}
--- /dev/null
+++ b/libsec/secp384r1.c
@@ -1,0 +1,12 @@
+#include "os.h"
+#include <mp.h>
+void secp384r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h){
+	strtomp("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", nil, 16, p);
+	uitomp(3UL, a);
+	mpsub(p, a, a);
+	strtomp("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", nil, 16, b);
+	strtomp("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", nil, 16, x);
+	strtomp("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", nil, 16, y);
+	strtomp("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", nil, 16, n);
+	mpassign(mpone, h);
+	}
--- /dev/null
+++ b/libsec/sha1.c
@@ -1,0 +1,134 @@
+#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;
+	}
+}
+
+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);
+}
--- /dev/null
+++ b/libsec/sha1block.c
@@ -1,0 +1,137 @@
+#include "os.h"
+
+#define ROTL(x,n)	(((x)<<n)|((x)>>(32-n)))
+
+#define F0(x,y,z)	(0x5a827999 + ((z) ^ ((x) & ((y) ^ (z)))))
+#define F1(x,y,z)	(0x6ed9eba1 + ((x) ^ (y) ^ (z)))
+#define F2(x,y,z)	(0x8f1bbcdc + (((x) & (y)) | (((x) | (y)) & (z))))
+#define F3(x,y,z)	(0xca62c1d6 + ((x) ^ (y) ^ (z)))
+
+void
+_sha1block(uchar *p, ulong len, u32int *s)
+{
+	u32int w[16], a, b, c, d, e;
+	uchar *end;
+
+	/* 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];
+
+#define STEP(a,b,c,d,e,f,i) \
+	if(i < 16) {\
+		w[i] = p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3]; \
+		p += 4; \
+	} else { \
+		u32int x = w[(i-3)&15] ^ w[(i-8)&15] ^ w[(i-14)&15] ^ w[(i-16)&15]; \
+		w[i&15] = ROTL(x, 1); \
+	} \
+	e += ROTL(a, 5) + w[i&15] + f(b,c,d); \
+	b = ROTL(b, 30);
+
+		STEP(a,b,c,d,e,F0,0);
+		STEP(e,a,b,c,d,F0,1);
+		STEP(d,e,a,b,c,F0,2);
+		STEP(c,d,e,a,b,F0,3);
+		STEP(b,c,d,e,a,F0,4);
+	
+		STEP(a,b,c,d,e,F0,5);
+		STEP(e,a,b,c,d,F0,6);
+		STEP(d,e,a,b,c,F0,7);
+		STEP(c,d,e,a,b,F0,8);
+		STEP(b,c,d,e,a,F0,9);
+	
+		STEP(a,b,c,d,e,F0,10);
+		STEP(e,a,b,c,d,F0,11);
+		STEP(d,e,a,b,c,F0,12);
+		STEP(c,d,e,a,b,F0,13);
+		STEP(b,c,d,e,a,F0,14);
+	
+		STEP(a,b,c,d,e,F0,15);
+		STEP(e,a,b,c,d,F0,16);
+		STEP(d,e,a,b,c,F0,17);
+		STEP(c,d,e,a,b,F0,18);
+		STEP(b,c,d,e,a,F0,19);
+	
+		STEP(a,b,c,d,e,F1,20);
+		STEP(e,a,b,c,d,F1,21);
+		STEP(d,e,a,b,c,F1,22);
+		STEP(c,d,e,a,b,F1,23);
+		STEP(b,c,d,e,a,F1,24);
+	
+		STEP(a,b,c,d,e,F1,25);
+		STEP(e,a,b,c,d,F1,26);
+		STEP(d,e,a,b,c,F1,27);
+		STEP(c,d,e,a,b,F1,28);
+		STEP(b,c,d,e,a,F1,29);
+	
+		STEP(a,b,c,d,e,F1,30);
+		STEP(e,a,b,c,d,F1,31);
+		STEP(d,e,a,b,c,F1,32);
+		STEP(c,d,e,a,b,F1,33);
+		STEP(b,c,d,e,a,F1,34);
+	
+		STEP(a,b,c,d,e,F1,35);
+		STEP(e,a,b,c,d,F1,36);
+		STEP(d,e,a,b,c,F1,37);
+		STEP(c,d,e,a,b,F1,38);
+		STEP(b,c,d,e,a,F1,39);
+	
+		STEP(a,b,c,d,e,F2,40);
+		STEP(e,a,b,c,d,F2,41);
+		STEP(d,e,a,b,c,F2,42);
+		STEP(c,d,e,a,b,F2,43);
+		STEP(b,c,d,e,a,F2,44);
+	
+		STEP(a,b,c,d,e,F2,45);
+		STEP(e,a,b,c,d,F2,46);
+		STEP(d,e,a,b,c,F2,47);
+		STEP(c,d,e,a,b,F2,48);
+		STEP(b,c,d,e,a,F2,49);
+	
+		STEP(a,b,c,d,e,F2,50);
+		STEP(e,a,b,c,d,F2,51);
+		STEP(d,e,a,b,c,F2,52);
+		STEP(c,d,e,a,b,F2,53);
+		STEP(b,c,d,e,a,F2,54);
+	
+		STEP(a,b,c,d,e,F2,55);
+		STEP(e,a,b,c,d,F2,56);
+		STEP(d,e,a,b,c,F2,57);
+		STEP(c,d,e,a,b,F2,58);
+		STEP(b,c,d,e,a,F2,59);
+	
+		STEP(a,b,c,d,e,F3,60);
+		STEP(e,a,b,c,d,F3,61);
+		STEP(d,e,a,b,c,F3,62);
+		STEP(c,d,e,a,b,F3,63);
+		STEP(b,c,d,e,a,F3,64);
+	
+		STEP(a,b,c,d,e,F3,65);
+		STEP(e,a,b,c,d,F3,66);
+		STEP(d,e,a,b,c,F3,67);
+		STEP(c,d,e,a,b,F3,68);
+		STEP(b,c,d,e,a,F3,69);
+	
+		STEP(a,b,c,d,e,F3,70);
+		STEP(e,a,b,c,d,F3,71);
+		STEP(d,e,a,b,c,F3,72);
+		STEP(c,d,e,a,b,F3,73);
+		STEP(b,c,d,e,a,F3,74);
+	
+		STEP(a,b,c,d,e,F3,75);
+		STEP(e,a,b,c,d,F3,76);
+		STEP(d,e,a,b,c,F3,77);
+		STEP(c,d,e,a,b,F3,78);
+		STEP(b,c,d,e,a,F3,79);
+
+		s[0] += a;
+		s[1] += b;
+		s[2] += c;
+		s[3] += d;
+		s[4] += e;
+	}
+}
--- /dev/null
+++ b/libsec/sha2_128.c
@@ -1,0 +1,191 @@
+/*
+ * sha2 128-bit
+ */
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+
+static void encode64(uchar*, u64int*, ulong);
+static DigestState* sha2_128(uchar *, ulong, uchar *, SHA2_256state *, int);
+
+extern void _sha2block128(uchar*, ulong, u64int*);
+
+/*
+ *  for sha2_384 and sha2_512, len must be multiple of 128 for all but
+ *  the last call.  There must be room in the input buffer to pad.
+ *
+ *  Note: sha2_384 calls sha2_512block as sha2_384; it just uses a different
+ *  initial seed to produce a truncated 384b hash result.  otherwise
+ *  it's the same as sha2_512.
+ */
+SHA2_384state*
+sha2_384(uchar *p, ulong len, uchar *digest, SHA2_384state *s)
+{
+	if(s == nil) {
+		s = mallocz(sizeof(*s), 1);
+		if(s == nil)
+			return nil;
+		s->malloced = 1;
+	}
+	if(s->seeded == 0){
+		/*
+		 * seed the state with the first 64 bits of the fractional
+		 * parts of the square roots of the 9th thru 16th primes.
+		 */
+ 		s->bstate[0] = 0xcbbb9d5dc1059ed8LL;
+		s->bstate[1] = 0x629a292a367cd507LL;
+		s->bstate[2] = 0x9159015a3070dd17LL;
+		s->bstate[3] = 0x152fecd8f70e5939LL;
+		s->bstate[4] = 0x67332667ffc00b31LL;
+		s->bstate[5] = 0x8eb44a8768581511LL;
+		s->bstate[6] = 0xdb0c2e0d64f98fa7LL;
+		s->bstate[7] = 0x47b5481dbefa4fa4LL;
+		s->seeded = 1;
+	}
+	return sha2_128(p, len, digest, s, SHA2_384dlen);
+}
+
+SHA2_512state*
+sha2_512(uchar *p, ulong len, uchar *digest, SHA2_512state *s)
+{
+
+	if(s == nil) {
+		s = mallocz(sizeof(*s), 1);
+		if(s == nil)
+			return nil;
+		s->malloced = 1;
+	}
+	if(s->seeded == 0){
+		/*
+		 * seed the state with the first 64 bits of the fractional
+		 * parts of the square roots of the first 8 primes 2..19).
+		 */
+ 		s->bstate[0] = 0x6a09e667f3bcc908LL;
+		s->bstate[1] = 0xbb67ae8584caa73bLL;
+		s->bstate[2] = 0x3c6ef372fe94f82bLL;
+		s->bstate[3] = 0xa54ff53a5f1d36f1LL;
+		s->bstate[4] = 0x510e527fade682d1LL;
+		s->bstate[5] = 0x9b05688c2b3e6c1fLL;
+		s->bstate[6] = 0x1f83d9abfb41bd6bLL;
+		s->bstate[7] = 0x5be0cd19137e2179LL;
+		s->seeded = 1;
+	}
+	return sha2_128(p, len, digest, s, SHA2_512dlen);
+}
+
+/* common 128 byte block padding and count code for SHA2_384 and SHA2_512 */
+static DigestState*
+sha2_128(uchar *p, ulong len, uchar *digest, SHA2_512state *s, int dlen)
+{
+	int i;
+	u64int x[16];
+	uchar buf[256];
+	uchar *e;
+
+	/* fill out the partial 128 byte block from previous calls */
+	if(s->blen){
+		i = 128 - s->blen;
+		if(len < i)
+			i = len;
+		memmove(s->buf + s->blen, p, i);
+		len -= i;
+		s->blen += i;
+		p += i;
+		if(s->blen == 128){
+			_sha2block128(s->buf, s->blen, s->bstate);
+			s->len += s->blen;
+			s->blen = 0;
+		}
+	}
+
+	/* do 128 byte blocks */
+	i = len & ~(128-1);
+	if(i){
+		_sha2block128(p, i, s->bstate);
+		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 128 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 < 112)
+		i = 112 - len;
+	else
+		i = 240 - len;
+	memset(e, 0, i);
+	*e = 0x80;
+	len += i;
+
+	/* append the count */
+	x[0] = 0;			/* assume 32b length, i.e. < 4GB */
+	x[1] = s->len<<3;
+	encode64(p+len, x, 16);
+
+	/* digest the last part */
+	_sha2block128(p, len+16, s->bstate);
+	s->len += len+16;
+
+	/* return result and free state */
+	encode64(digest, s->bstate, dlen);
+	if(s->malloced == 1)
+		free(s);
+	return nil;
+}
+
+/*
+ * Encodes input (ulong long) into output (uchar).
+ * Assumes len is a multiple of 8.
+ */
+static void
+encode64(uchar *output, u64int *input, ulong len)
+{
+	u64int x;
+	uchar *e;
+
+	for(e = output + len; output < e;) {
+		x = *input++;
+		*output++ = x >> 56;
+		*output++ = x >> 48;
+		*output++ = x >> 40;
+		*output++ = x >> 32;
+		*output++ = x >> 24;
+		*output++ = x >> 16;
+		*output++ = x >> 8;
+		*output++ = x;
+	}
+}
+
+DigestState*
+hmac_sha2_384(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest,
+	DigestState *s)
+{
+	return hmac_x(p, len, key, klen, digest, s, sha2_384, SHA2_384dlen);
+}
+
+DigestState*
+hmac_sha2_512(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest,
+	DigestState *s)
+{
+	return hmac_x(p, len, key, klen, digest, s, sha2_512, SHA2_512dlen);
+}
--- /dev/null
+++ b/libsec/sha2_64.c
@@ -1,0 +1,187 @@
+/*
+ * sha2 64-bit
+ */
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+
+static void encode32(uchar*, u32int*, ulong);
+static DigestState* sha2_64(uchar *, ulong, uchar *, SHA2_256state *, int);
+
+extern void _sha2block64(uchar*, ulong, u32int*);
+
+/*
+ *  for sha2_224 and sha2_256, len must be multiple of 64 for all but
+ *  the last call.  There must be room in the input buffer to pad.
+ *
+ *  Note: sha2_224 calls sha2_256block as sha2_224, just uses different
+ *  initial seed and produces a 224b hash result.  otherwise it's
+ *  the same as sha2_256.
+ */
+
+SHA2_224state*
+sha2_224(uchar *p, ulong len, uchar *digest, SHA2_224state *s)
+{
+	if(s == nil) {
+		s = mallocz(sizeof(*s), 1);
+		if(s == nil)
+			return nil;
+		s->malloced = 1;
+	}
+	if(s->seeded == 0){
+		/*
+		 * seed the state with the first 32 bits of the fractional
+		 * parts of the square roots of the first 8 primes 2..19).
+		 */
+		s->state[0] = 0xc1059ed8;
+		s->state[1] = 0x367cd507;
+		s->state[2] = 0x3070dd17;
+		s->state[3] = 0xf70e5939;
+		s->state[4] = 0xffc00b31;
+		s->state[5] = 0x68581511;
+		s->state[6] = 0x64f98fa7;
+		s->state[7] = 0xbefa4fa4;
+		s->seeded = 1;
+	}
+	return sha2_64(p, len, digest, s, SHA2_224dlen);
+}
+
+SHA2_256state*
+sha2_256(uchar *p, ulong len, uchar *digest, SHA2_256state *s)
+{
+	if(s == nil) {
+		s = mallocz(sizeof(*s), 1);
+		if(s == nil)
+			return nil;
+		s->malloced = 1;
+	}
+	if(s->seeded == 0){
+		/*
+		 * seed the state with the first 32 bits of the fractional
+		 * parts of the square roots of the first 8 primes 2..19).
+		 */
+		s->state[0] = 0x6a09e667;
+		s->state[1] = 0xbb67ae85;
+		s->state[2] = 0x3c6ef372;
+		s->state[3] = 0xa54ff53a;
+		s->state[4] = 0x510e527f;
+		s->state[5] = 0x9b05688c;
+		s->state[6] = 0x1f83d9ab;
+		s->state[7] = 0x5be0cd19;
+		s->seeded = 1;
+	}
+	return sha2_64(p, len, digest, s, SHA2_256dlen);
+}
+
+/* common 64 byte block padding and count code for SHA2_224 and SHA2_256 */
+static DigestState*
+sha2_64(uchar *p, ulong len, uchar *digest, SHA2_256state *s, int dlen)
+{
+	int i;
+	u32int x[16];
+	uchar buf[128];
+	uchar *e;
+
+	/* 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){
+			_sha2block64(s->buf, s->blen, s->state);
+			s->len += s->blen;
+			s->blen = 0;
+		}
+	}
+
+	/* do 64 byte blocks */
+	i = len & ~(64-1);
+	if(i){
+		_sha2block64(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;
+	encode32(p+len, x, 8);
+
+	/* digest the last part */
+	_sha2block64(p, len+8, s->state);
+	s->len += len+8;
+
+	/* return result and free state */
+	encode32(digest, s->state, dlen);
+	if(s->malloced == 1)
+		free(s);
+	return nil;
+}
+
+/*
+ * Encodes input (ulong) into output (uchar).
+ * Assumes len is a multiple of 4.
+ */
+static void
+encode32(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;
+	}
+}
+
+DigestState*
+hmac_sha2_224(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest,
+	DigestState *s)
+{
+	return hmac_x(p, len, key, klen, digest, s, sha2_224, SHA2_224dlen);
+}
+
+DigestState*
+hmac_sha2_256(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest,
+	DigestState *s)
+{
+	return hmac_x(p, len, key, klen, digest, s, sha2_256, SHA2_256dlen);
+}
--- /dev/null
+++ b/libsec/sha2block128.c
@@ -1,0 +1,176 @@
+/*
+ * sha2_512 block cipher - unrolled version
+ *
+ *   note: the following upper and lower case macro names are distinct
+ *	   and reflect the functions defined in FIPS pub. 180-2.
+ */
+
+#include "os.h"
+
+#define ROTR(x,n)	(((x) >> (n)) | ((x) << (64-(n))))
+#define sigma0(x)	(ROTR((x),1) ^ ROTR((x),8) ^ ((x) >> 7))
+#define sigma1(x)	(ROTR((x),19) ^ ROTR((x),61) ^ ((x) >> 6))
+#define SIGMA0(x)	(ROTR((x),28) ^ ROTR((x),34) ^ ROTR((x),39))
+#define SIGMA1(x)	(ROTR((x),14) ^ ROTR((x),18) ^ ROTR((x),41))
+#define Ch(x,y,z)	((z) ^ ((x) & ((y) ^ (z))))
+#define Maj(x,y,z)	(((x) | (y)) & ((z) | ((x) & (y))))
+
+/*
+ * first 64 bits of the fractional parts of cube roots of
+ * first 80 primes (2..311).
+ */
+static u64int K512[80] = {
+	0x428a2f98d728ae22LL, 0x7137449123ef65cdLL, 0xb5c0fbcfec4d3b2fLL, 0xe9b5dba58189dbbcLL,
+	0x3956c25bf348b538LL, 0x59f111f1b605d019LL, 0x923f82a4af194f9bLL, 0xab1c5ed5da6d8118LL,
+	0xd807aa98a3030242LL, 0x12835b0145706fbeLL, 0x243185be4ee4b28cLL, 0x550c7dc3d5ffb4e2LL,
+	0x72be5d74f27b896fLL, 0x80deb1fe3b1696b1LL, 0x9bdc06a725c71235LL, 0xc19bf174cf692694LL,
+	0xe49b69c19ef14ad2LL, 0xefbe4786384f25e3LL, 0x0fc19dc68b8cd5b5LL, 0x240ca1cc77ac9c65LL,
+	0x2de92c6f592b0275LL, 0x4a7484aa6ea6e483LL, 0x5cb0a9dcbd41fbd4LL, 0x76f988da831153b5LL,
+	0x983e5152ee66dfabLL, 0xa831c66d2db43210LL, 0xb00327c898fb213fLL, 0xbf597fc7beef0ee4LL,
+	0xc6e00bf33da88fc2LL, 0xd5a79147930aa725LL, 0x06ca6351e003826fLL, 0x142929670a0e6e70LL,
+	0x27b70a8546d22ffcLL, 0x2e1b21385c26c926LL, 0x4d2c6dfc5ac42aedLL, 0x53380d139d95b3dfLL,
+	0x650a73548baf63deLL, 0x766a0abb3c77b2a8LL, 0x81c2c92e47edaee6LL, 0x92722c851482353bLL,
+	0xa2bfe8a14cf10364LL, 0xa81a664bbc423001LL, 0xc24b8b70d0f89791LL, 0xc76c51a30654be30LL,
+	0xd192e819d6ef5218LL, 0xd69906245565a910LL, 0xf40e35855771202aLL, 0x106aa07032bbd1b8LL,
+	0x19a4c116b8d2d0c8LL, 0x1e376c085141ab53LL, 0x2748774cdf8eeb99LL, 0x34b0bcb5e19b48a8LL,
+	0x391c0cb3c5c95a63LL, 0x4ed8aa4ae3418acbLL, 0x5b9cca4f7763e373LL, 0x682e6ff3d6b2b8a3LL,
+	0x748f82ee5defb2fcLL, 0x78a5636f43172f60LL, 0x84c87814a1f0ab72LL, 0x8cc702081a6439ecLL,
+	0x90befffa23631e28LL, 0xa4506cebde82bde9LL, 0xbef9a3f7b2c67915LL, 0xc67178f2e372532bLL,
+	0xca273eceea26619cLL, 0xd186b8c721c0c207LL, 0xeada7dd6cde0eb1eLL, 0xf57d4f7fee6ed178LL,
+	0x06f067aa72176fbaLL, 0x0a637dc5a2c898a6LL, 0x113f9804bef90daeLL, 0x1b710b35131c471bLL,
+	0x28db77f523047d84LL, 0x32caab7b40c72493LL, 0x3c9ebe0a15c9bebcLL, 0x431d67c49c100d4cLL,
+	0x4cc5d4becb3e42b6LL, 0x597f299cfc657e2aLL, 0x5fcb6fab3ad6faecLL, 0x6c44198c4a475817LL
+};
+
+void
+_sha2block128(uchar *p, ulong len, u64int *s)
+{
+	u64int w[16], a, b, c, d, e, f, g, h;
+	uchar *end;
+
+	/* 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];
+		f = s[5];
+		g = s[6];
+		h = s[7];
+
+#define STEP(a,b,c,d,e,f,g,h,i) \
+	if(i < 16) { \
+		w[i] = 	(u64int)(p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3])<<32 | \
+			(p[4]<<24 | p[5]<<16 | p[6]<<8 | p[7]); \
+		p += 8; \
+	} else { \
+		u64int s0, s1; \
+		s1 = sigma1(w[(i-2)&15]); \
+		s0 = sigma0(w[(i-15)&15]); \
+		w[i&15] += s1 + w[(i-7)&15] + s0; \
+	} \
+	h += SIGMA1(e) + Ch(e,f,g) + K512[i] + w[i&15]; \
+	d += h; \
+	h += SIGMA0(a) + Maj(a,b,c);
+
+		STEP(a,b,c,d,e,f,g,h,0);
+		STEP(h,a,b,c,d,e,f,g,1);
+		STEP(g,h,a,b,c,d,e,f,2);
+		STEP(f,g,h,a,b,c,d,e,3);
+		STEP(e,f,g,h,a,b,c,d,4);
+		STEP(d,e,f,g,h,a,b,c,5);
+		STEP(c,d,e,f,g,h,a,b,6);
+		STEP(b,c,d,e,f,g,h,a,7);
+
+		STEP(a,b,c,d,e,f,g,h,8);
+		STEP(h,a,b,c,d,e,f,g,9);
+		STEP(g,h,a,b,c,d,e,f,10);
+		STEP(f,g,h,a,b,c,d,e,11);
+		STEP(e,f,g,h,a,b,c,d,12);
+		STEP(d,e,f,g,h,a,b,c,13);
+		STEP(c,d,e,f,g,h,a,b,14);
+		STEP(b,c,d,e,f,g,h,a,15);
+
+		STEP(a,b,c,d,e,f,g,h,16);
+		STEP(h,a,b,c,d,e,f,g,17);
+		STEP(g,h,a,b,c,d,e,f,18);
+		STEP(f,g,h,a,b,c,d,e,19);
+		STEP(e,f,g,h,a,b,c,d,20);
+		STEP(d,e,f,g,h,a,b,c,21);
+		STEP(c,d,e,f,g,h,a,b,22);
+		STEP(b,c,d,e,f,g,h,a,23);
+
+		STEP(a,b,c,d,e,f,g,h,24);
+		STEP(h,a,b,c,d,e,f,g,25);
+		STEP(g,h,a,b,c,d,e,f,26);
+		STEP(f,g,h,a,b,c,d,e,27);
+		STEP(e,f,g,h,a,b,c,d,28);
+		STEP(d,e,f,g,h,a,b,c,29);
+		STEP(c,d,e,f,g,h,a,b,30);
+		STEP(b,c,d,e,f,g,h,a,31);
+
+		STEP(a,b,c,d,e,f,g,h,32);
+		STEP(h,a,b,c,d,e,f,g,33);
+		STEP(g,h,a,b,c,d,e,f,34);
+		STEP(f,g,h,a,b,c,d,e,35);
+		STEP(e,f,g,h,a,b,c,d,36);
+		STEP(d,e,f,g,h,a,b,c,37);
+		STEP(c,d,e,f,g,h,a,b,38);
+		STEP(b,c,d,e,f,g,h,a,39);
+
+		STEP(a,b,c,d,e,f,g,h,40);
+		STEP(h,a,b,c,d,e,f,g,41);
+		STEP(g,h,a,b,c,d,e,f,42);
+		STEP(f,g,h,a,b,c,d,e,43);
+		STEP(e,f,g,h,a,b,c,d,44);
+		STEP(d,e,f,g,h,a,b,c,45);
+		STEP(c,d,e,f,g,h,a,b,46);
+		STEP(b,c,d,e,f,g,h,a,47);
+
+		STEP(a,b,c,d,e,f,g,h,48);
+		STEP(h,a,b,c,d,e,f,g,49);
+		STEP(g,h,a,b,c,d,e,f,50);
+		STEP(f,g,h,a,b,c,d,e,51);
+		STEP(e,f,g,h,a,b,c,d,52);
+		STEP(d,e,f,g,h,a,b,c,53);
+		STEP(c,d,e,f,g,h,a,b,54);
+		STEP(b,c,d,e,f,g,h,a,55);
+
+		STEP(a,b,c,d,e,f,g,h,56);
+		STEP(h,a,b,c,d,e,f,g,57);
+		STEP(g,h,a,b,c,d,e,f,58);
+		STEP(f,g,h,a,b,c,d,e,59);
+		STEP(e,f,g,h,a,b,c,d,60);
+		STEP(d,e,f,g,h,a,b,c,61);
+		STEP(c,d,e,f,g,h,a,b,62);
+		STEP(b,c,d,e,f,g,h,a,63);
+
+		STEP(a,b,c,d,e,f,g,h,64);
+		STEP(h,a,b,c,d,e,f,g,65);
+		STEP(g,h,a,b,c,d,e,f,66);
+		STEP(f,g,h,a,b,c,d,e,67);
+		STEP(e,f,g,h,a,b,c,d,68);
+		STEP(d,e,f,g,h,a,b,c,69);
+		STEP(c,d,e,f,g,h,a,b,70);
+		STEP(b,c,d,e,f,g,h,a,71);
+
+		STEP(a,b,c,d,e,f,g,h,72);
+		STEP(h,a,b,c,d,e,f,g,73);
+		STEP(g,h,a,b,c,d,e,f,74);
+		STEP(f,g,h,a,b,c,d,e,75);
+		STEP(e,f,g,h,a,b,c,d,76);
+		STEP(d,e,f,g,h,a,b,c,77);
+		STEP(c,d,e,f,g,h,a,b,78);
+		STEP(b,c,d,e,f,g,h,a,79);
+
+		s[0] += a;
+		s[1] += b;
+		s[2] += c;
+		s[3] += d;
+		s[4] += e;
+		s[5] += f;
+		s[6] += g;
+		s[7] += h;
+	}
+}
--- /dev/null
+++ b/libsec/sha2block64.c
@@ -1,0 +1,150 @@
+/*
+ * sha2_256 block cipher - unrolled version
+ *
+ *   note: the following upper and lower case macro names are distinct
+ *	   and reflect the functions defined in FIPS pub. 180-2.
+ */
+
+#include "os.h"
+
+#define ROTR(x,n)	(((x) >> (n)) | ((x) << (32-(n))))
+#define sigma0(x)	(ROTR((x),7) ^ ROTR((x),18) ^ ((x) >> 3))
+#define sigma1(x)	(ROTR((x),17) ^ ROTR((x),19) ^ ((x) >> 10))
+#define SIGMA0(x)	(ROTR((x),2) ^ ROTR((x),13) ^ ROTR((x),22))
+#define SIGMA1(x)	(ROTR((x),6) ^ ROTR((x),11) ^ ROTR((x),25))
+#define Ch(x,y,z)	((z) ^ ((x) & ((y) ^ (z))))
+#define Maj(x,y,z)	(((x) | (y)) & ((z) | ((x) & (y))))
+
+/*
+ * first 32 bits of the fractional parts of cube roots of
+ * first 64 primes (2..311).
+ */
+static u32int K256[64] = {
+	0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,
+	0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+	0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,
+	0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+	0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,
+	0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+	0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,
+	0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+	0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,
+	0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+	0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,
+	0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+	0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,
+	0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+	0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,
+	0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2,
+};
+
+void
+_sha2block64(uchar *p, ulong len, u32int *s)
+{
+	u32int w[16], a, b, c, d, e, f, g, h;
+	uchar *end;
+
+	/* 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];
+		f = s[5];
+		g = s[6];
+		h = s[7];
+
+#define STEP(a,b,c,d,e,f,g,h,i) \
+	if(i < 16) {\
+		w[i] = p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3]; \
+		p += 4; \
+	} else { \
+		w[i&15] += sigma1(w[(i-2)&15]) + w[(i-7)&15] + sigma0(w[(i-15)&15]); \
+	} \
+	h += SIGMA1(e) + Ch(e,f,g) + K256[i] + w[i&15]; \
+	d += h; \
+	h += SIGMA0(a) + Maj(a,b,c);
+
+		STEP(a,b,c,d,e,f,g,h,0);
+		STEP(h,a,b,c,d,e,f,g,1);
+		STEP(g,h,a,b,c,d,e,f,2);
+		STEP(f,g,h,a,b,c,d,e,3);
+		STEP(e,f,g,h,a,b,c,d,4);
+		STEP(d,e,f,g,h,a,b,c,5);
+		STEP(c,d,e,f,g,h,a,b,6);
+		STEP(b,c,d,e,f,g,h,a,7);
+
+		STEP(a,b,c,d,e,f,g,h,8);
+		STEP(h,a,b,c,d,e,f,g,9);
+		STEP(g,h,a,b,c,d,e,f,10);
+		STEP(f,g,h,a,b,c,d,e,11);
+		STEP(e,f,g,h,a,b,c,d,12);
+		STEP(d,e,f,g,h,a,b,c,13);
+		STEP(c,d,e,f,g,h,a,b,14);
+		STEP(b,c,d,e,f,g,h,a,15);
+
+		STEP(a,b,c,d,e,f,g,h,16);
+		STEP(h,a,b,c,d,e,f,g,17);
+		STEP(g,h,a,b,c,d,e,f,18);
+		STEP(f,g,h,a,b,c,d,e,19);
+		STEP(e,f,g,h,a,b,c,d,20);
+		STEP(d,e,f,g,h,a,b,c,21);
+		STEP(c,d,e,f,g,h,a,b,22);
+		STEP(b,c,d,e,f,g,h,a,23);
+
+		STEP(a,b,c,d,e,f,g,h,24);
+		STEP(h,a,b,c,d,e,f,g,25);
+		STEP(g,h,a,b,c,d,e,f,26);
+		STEP(f,g,h,a,b,c,d,e,27);
+		STEP(e,f,g,h,a,b,c,d,28);
+		STEP(d,e,f,g,h,a,b,c,29);
+		STEP(c,d,e,f,g,h,a,b,30);
+		STEP(b,c,d,e,f,g,h,a,31);
+
+		STEP(a,b,c,d,e,f,g,h,32);
+		STEP(h,a,b,c,d,e,f,g,33);
+		STEP(g,h,a,b,c,d,e,f,34);
+		STEP(f,g,h,a,b,c,d,e,35);
+		STEP(e,f,g,h,a,b,c,d,36);
+		STEP(d,e,f,g,h,a,b,c,37);
+		STEP(c,d,e,f,g,h,a,b,38);
+		STEP(b,c,d,e,f,g,h,a,39);
+
+		STEP(a,b,c,d,e,f,g,h,40);
+		STEP(h,a,b,c,d,e,f,g,41);
+		STEP(g,h,a,b,c,d,e,f,42);
+		STEP(f,g,h,a,b,c,d,e,43);
+		STEP(e,f,g,h,a,b,c,d,44);
+		STEP(d,e,f,g,h,a,b,c,45);
+		STEP(c,d,e,f,g,h,a,b,46);
+		STEP(b,c,d,e,f,g,h,a,47);
+
+		STEP(a,b,c,d,e,f,g,h,48);
+		STEP(h,a,b,c,d,e,f,g,49);
+		STEP(g,h,a,b,c,d,e,f,50);
+		STEP(f,g,h,a,b,c,d,e,51);
+		STEP(e,f,g,h,a,b,c,d,52);
+		STEP(d,e,f,g,h,a,b,c,53);
+		STEP(c,d,e,f,g,h,a,b,54);
+		STEP(b,c,d,e,f,g,h,a,55);
+
+		STEP(a,b,c,d,e,f,g,h,56);
+		STEP(h,a,b,c,d,e,f,g,57);
+		STEP(g,h,a,b,c,d,e,f,58);
+		STEP(f,g,h,a,b,c,d,e,59);
+		STEP(e,f,g,h,a,b,c,d,60);
+		STEP(d,e,f,g,h,a,b,c,61);
+		STEP(c,d,e,f,g,h,a,b,62);
+		STEP(b,c,d,e,f,g,h,a,63);
+
+		s[0] += a;
+		s[1] += b;
+		s[2] += c;
+		s[3] += d;
+		s[4] += e;
+		s[5] += f;
+		s[6] += g;
+		s[7] += h;
+	}
+}
--- /dev/null
+++ b/libsec/thumb.c
@@ -1,0 +1,170 @@
+#include "os.h"
+#include <bio.h>
+#include <libsec.h>
+
+enum{ ThumbTab = 1<<10 };
+
+static Thumbprint*
+tablehead(uchar *hash, Thumbprint *table)
+{
+	return &table[((hash[0]<<8) + hash[1]) & (ThumbTab-1)];
+}
+
+void
+freeThumbprints(Thumbprint *table)
+{
+	Thumbprint *hd, *p, *q;
+
+	if(table == nil)
+		return;
+	for(hd = table; hd < table+ThumbTab; hd++){
+		for(p = hd->next; p && p != hd; p = q){
+			q = p->next;
+			free(p);
+		}
+	}
+	free(table);
+}
+
+int
+okThumbprint(uchar *hash, int len, Thumbprint *table)
+{
+	Thumbprint *hd, *p;
+
+	if(table == nil)
+		return 0;
+	hd = tablehead(hash, table);
+	for(p = hd->next; p; p = p->next){
+		if(p->len == len && memcmp(hash, p->hash, len) == 0)
+			return 1;
+		if(p == hd)
+			break;
+	}
+	return 0;
+}
+
+int
+okCertificate(uchar *cert, int len, Thumbprint *table)
+{
+	uchar hash[SHA2_256dlen];
+	char thumb[2*SHA2_256dlen+1];
+
+	if(table == nil){
+		werrstr("no thumbprints provided");
+		return 0;
+	}
+	if(cert == nil || len <= 0){
+		werrstr("no certificate provided");
+		return 0;
+	}
+
+	sha1(cert, len, hash, nil);
+	if(okThumbprint(hash, SHA1dlen, table))
+		return 1;
+
+	sha2_256(cert, len, hash, nil);
+	if(okThumbprint(hash, SHA2_256dlen, table))
+		return 1;
+
+	if(X509digestSPKI(cert, len, sha2_256, hash) < 0)
+		return 0;
+	if(okThumbprint(hash, SHA2_256dlen, table))
+		return 1;
+
+	len = enc64(thumb, sizeof(thumb), hash, SHA2_256dlen);
+	while(len > 0 && thumb[len-1] == '=')
+		len--;
+	thumb[len] = '\0';
+	werrstr("sha256=%s", thumb);
+
+	return 0;
+}
+
+static int
+loadThumbprints(char *file, char *tag, Thumbprint *table, Thumbprint *crltab, int depth)
+{
+	Thumbprint *hd, *entry;
+	char *line, *field[50];
+	uchar hash[SHA2_256dlen];
+	Biobuf *bin;
+	int len, n;
+
+	if(depth > 8){
+		werrstr("too many includes, last file %s", file);
+		return -1;
+	}
+	if(access(file, AEXIST) < 0)
+		return 0;	/* not an error */
+	if((bin = Bopen(file, OREAD)) == nil)
+		return -1;
+	for(; (line = Brdstr(bin, '\n', 1)) != nil; free(line)){
+		if(tokenize(line, field, nelem(field)) < 2)
+			continue;
+		if(strcmp(field[0], "#include") == 0){
+			if(loadThumbprints(field[1], tag, table, crltab, depth+1) < 0)
+				goto err;
+			continue;
+		}
+		if(strcmp(field[0], tag) != 0)
+			continue;
+		if(strncmp(field[1], "sha1=", 5) == 0){
+			field[1] += 5;
+			len = SHA1dlen;
+		} else if(strncmp(field[1], "sha256=", 7) == 0){
+			field[1] += 7;
+			len = SHA2_256dlen;
+		} else {
+			continue;
+		}
+		n = strlen(field[1]);
+		if((n != len*2 || dec16(hash, len, field[1], n) != len)
+		&& dec64(hash, len, field[1], n) != len){
+			werrstr("malformed %s entry in %s: %s", tag, file, field[1]);
+			goto err;
+		}
+		if(crltab && okThumbprint(hash, len, crltab))
+			continue;
+		hd = tablehead(hash, table);
+		if(hd->next == nil)
+			entry = hd;
+		else {
+			if((entry = malloc(sizeof(*entry))) == nil)
+				goto err;
+			entry->next = hd->next;
+		}
+		hd->next = entry;
+		entry->len = len;
+		memcpy(entry->hash, hash, len);
+	}
+	Bterm(bin);
+	return 0;
+err:
+	free(line);
+	Bterm(bin);
+	return -1;
+}
+
+Thumbprint *
+initThumbprints(char *ok, char *crl, char *tag)
+{
+	Thumbprint *table, *crltab;
+
+	table = crltab = nil;
+	if(crl){
+		if((crltab = malloc(ThumbTab * sizeof(*crltab))) == nil)
+			goto err;
+		memset(crltab, 0, ThumbTab * sizeof(*crltab));
+		if(loadThumbprints(crl, tag, crltab, nil, 0) < 0)
+			goto err;
+	}
+	if((table = malloc(ThumbTab * sizeof(*table))) == nil)
+		goto err;
+	memset(table, 0, ThumbTab * sizeof(*table));
+	if(loadThumbprints(ok, tag, table, crltab, 0) < 0){
+		freeThumbprints(table);
+		table = nil;
+	}
+err:
+	freeThumbprints(crltab);
+	return table;
+}
--- /dev/null
+++ b/libsec/tlshand.c
@@ -1,0 +1,3024 @@
+#include <u.h>
+#include <libc.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 = 160,	// amount of secret we may need
+	MAXdlen = SHA2_512dlen,
+	RandomSize = 32,
+	MasterSecretSize = 48,
+	AQueue = 0,
+	AFlush = 1,
+};
+
+typedef struct Bytes{
+	int len;
+	uchar data[];
+} Bytes;
+
+typedef struct Ints{
+	int len;
+	int data[];
+} Ints;
+
+typedef struct Algs{
+	char *enc;
+	char *digest;
+	int nsecret;
+	int tlsid;
+	int ok;
+} Algs;
+
+typedef struct Namedcurve{
+	int tlsid;
+	void (*init)(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h);
+} Namedcurve;
+
+typedef struct Finished{
+	uchar verify[SSL3FinishedLen];
+	int n;
+} Finished;
+
+typedef struct HandshakeHash {
+	MD5state	md5;
+	SHAstate	sha1;
+	SHA2_256state	sha2_256;
+} HandshakeHash;
+
+typedef struct TlsSec TlsSec;
+struct TlsSec {
+	RSApub *rsapub;
+	AuthRpc *rpc;	// factotum for rsa private key
+	uchar *psk;	// pre-shared key
+	int psklen;
+	int clientVers;			// version in ClientHello
+	uchar sec[MasterSecretSize];	// master secret
+	uchar srandom[RandomSize];	// server random
+	uchar crandom[RandomSize];	// client random
+
+	Namedcurve *nc; // selected curve for ECDHE
+	// diffie hellman state
+	DHstate dh;
+	struct {
+		ECdomain dom;
+		ECpriv Q;
+	} ec;
+	uchar X[32];
+
+	// byte generation and handshake checksum
+	void (*prf)(uchar*, int, uchar*, int, char*, uchar*, int);
+	void (*setFinished)(TlsSec*, HandshakeHash, uchar*, int);
+	int nfin;
+};
+
+typedef struct TlsConnection{
+	TlsSec sec[1];	// 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
+	Bytes *cert;	// server certificate; only last - no chain
+
+	int cipher;
+	int nsecret;	// amount of secret data to init keys
+	char *digest;	// name of digest algorithm to use
+	char *enc;	// name of encryption algorithm to use
+
+	// for finished messages
+	HandshakeHash	handhash;
+	Finished	finished;
+
+	uchar *sendp;
+	uchar buf[1<<16];
+} TlsConnection;
+
+typedef struct Msg{
+	int tag;
+	union {
+		struct {
+			int	version;
+			uchar 	random[RandomSize];
+			Bytes*	sid;
+			Ints*	ciphers;
+			Bytes*	compressors;
+			Bytes*	extensions;
+		} clientHello;
+		struct {
+			int	version;
+			uchar	random[RandomSize];
+			Bytes*	sid;
+			int	cipher;
+			int	compressor;
+			Bytes*	extensions;
+		} serverHello;
+		struct {
+			int ncert;
+			Bytes **certs;
+		} certificate;
+		struct {
+			Bytes *types;
+			Ints *sigalgs;
+			int nca;
+			Bytes **cas;
+		} certificateRequest;
+		struct {
+			Bytes *pskid;
+			Bytes *key;
+		} clientKeyExchange;
+		struct {
+			Bytes *pskid;
+			Bytes *dh_p;
+			Bytes *dh_g;
+			Bytes *dh_Ys;
+			Bytes *dh_parameters;
+			Bytes *dh_signature;
+			int sigalg;
+			int curve;
+		} serverKeyExchange;
+		struct {
+			int sigalg;
+			Bytes *signature;
+		} certificateVerify;		
+		Finished finished;
+	} u;
+} Msg;
+
+
+enum {
+	SSL3Version	= 0x0300,
+	TLS10Version	= 0x0301,
+	TLS11Version	= 0x0302,
+	TLS12Version	= 0x0303,
+	ProtocolVersion	= TLS12Version,	// 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,
+	EInappropriateFallback = 86,
+	EUserCanceled = 90,
+	ENoRenegotiation = 100,
+	EUnknownPSKidentity = 115,
+	EMax = 256
+};
+
+// cipher suites
+enum {
+	TLS_RSA_WITH_3DES_EDE_CBC_SHA		= 0X000A,
+	TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA	= 0X0016,
+
+	TLS_RSA_WITH_AES_128_CBC_SHA		= 0X002F,
+	TLS_DHE_RSA_WITH_AES_128_CBC_SHA	= 0X0033,
+	TLS_RSA_WITH_AES_256_CBC_SHA		= 0X0035,
+	TLS_DHE_RSA_WITH_AES_256_CBC_SHA	= 0X0039,
+	TLS_RSA_WITH_AES_128_CBC_SHA256		= 0X003C,
+	TLS_RSA_WITH_AES_256_CBC_SHA256		= 0X003D,
+	TLS_DHE_RSA_WITH_AES_128_CBC_SHA256	= 0X0067,
+
+	TLS_RSA_WITH_AES_128_GCM_SHA256		= 0x009C,
+	TLS_DHE_RSA_WITH_AES_128_GCM_SHA256	= 0x009E,
+
+	TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA	= 0xC013,
+	TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA	= 0xC014,
+	TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256	= 0xC023,
+	TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256	= 0xC027,
+
+	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B,
+	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256	= 0xC02F,
+
+	GOOGLE_ECDHE_RSA_WITH_CHACHA20_POLY1305		= 0xCC13,
+	GOOGLE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305	= 0xCC14,
+	GOOGLE_DHE_RSA_WITH_CHACHA20_POLY1305		= 0xCC15,
+
+	TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305	= 0xCCA8,
+	TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305	= 0xCCA9,
+	TLS_DHE_RSA_WITH_CHACHA20_POLY1305	= 0xCCAA,
+
+	TLS_PSK_WITH_CHACHA20_POLY1305		= 0xCCAB,
+	TLS_PSK_WITH_AES_128_CBC_SHA256		= 0x00AE,
+	TLS_PSK_WITH_AES_128_CBC_SHA		= 0x008C,
+
+	TLS_FALLBACK_SCSV = 0x5600,
+};
+
+// compression methods
+enum {
+	CompressionNull = 0,
+	CompressionMax
+};
+
+
+// curves
+enum {
+	X25519 = 0x001d,
+};
+
+// extensions
+enum {
+	Extsni = 0x0000,
+	Extec = 0x000a,
+	Extecp = 0x000b,
+	Extsigalgs = 0x000d,
+};
+
+static Algs cipherAlgs[] = {
+	// ECDHE-ECDSA
+	{"ccpoly96_aead", "clear", 2*(32+12), TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305},
+	{"ccpoly64_aead", "clear", 2*32, GOOGLE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305},
+	{"aes_128_gcm_aead", "clear", 2*(16+4), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+	{"aes_128_cbc", "sha256", 2*(16+16+SHA2_256dlen), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256},
+
+	// ECDHE-RSA
+	{"ccpoly96_aead", "clear", 2*(32+12), TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
+	{"ccpoly64_aead", "clear", 2*32, GOOGLE_ECDHE_RSA_WITH_CHACHA20_POLY1305},
+	{"aes_128_gcm_aead", "clear", 2*(16+4), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+	{"aes_128_cbc", "sha256", 2*(16+16+SHA2_256dlen), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
+	{"aes_128_cbc", "sha1", 2*(16+16+SHA1dlen), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+	{"aes_256_cbc", "sha1", 2*(32+16+SHA1dlen), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
+
+	// DHE-RSA
+	{"ccpoly96_aead", "clear", 2*(32+12), TLS_DHE_RSA_WITH_CHACHA20_POLY1305},
+	{"ccpoly64_aead", "clear", 2*32, GOOGLE_DHE_RSA_WITH_CHACHA20_POLY1305},
+	{"aes_128_gcm_aead", "clear", 2*(16+4), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+	{"aes_128_cbc", "sha256", 2*(16+16+SHA2_256dlen), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+	{"aes_128_cbc", "sha1", 2*(16+16+SHA1dlen), TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+	{"aes_256_cbc", "sha1", 2*(32+16+SHA1dlen), TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+	{"3des_ede_cbc","sha1",	2*(4*8+SHA1dlen), TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	// RSA
+	{"aes_128_gcm_aead", "clear", 2*(16+4), TLS_RSA_WITH_AES_128_GCM_SHA256},
+	{"aes_128_cbc", "sha256", 2*(16+16+SHA2_256dlen), TLS_RSA_WITH_AES_128_CBC_SHA256},
+	{"aes_256_cbc", "sha256", 2*(32+16+SHA2_256dlen), TLS_RSA_WITH_AES_256_CBC_SHA256},
+	{"aes_128_cbc", "sha1", 2*(16+16+SHA1dlen), TLS_RSA_WITH_AES_128_CBC_SHA},
+	{"aes_256_cbc", "sha1", 2*(32+16+SHA1dlen), TLS_RSA_WITH_AES_256_CBC_SHA},
+	{"3des_ede_cbc","sha1",	2*(4*8+SHA1dlen), TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+	// PSK
+	{"ccpoly96_aead", "clear", 2*(32+12), TLS_PSK_WITH_CHACHA20_POLY1305},
+	{"aes_128_cbc", "sha256", 2*(16+16+SHA2_256dlen), TLS_PSK_WITH_AES_128_CBC_SHA256},
+	{"aes_128_cbc", "sha1", 2*(16+16+SHA1dlen), TLS_PSK_WITH_AES_128_CBC_SHA},
+};
+
+static uchar compressors[] = {
+	CompressionNull,
+};
+
+static Namedcurve namedcurves[] = {
+	X25519, nil,
+	0x0017, secp256r1,
+	0x0018, secp384r1,
+};
+
+static uchar pointformats[] = {
+	CompressionNull /* support of uncompressed point format is mandatory */
+};
+
+static struct {
+	DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*);
+	int len;
+} hashfun[] = {
+/*	[0x00]  is reserved for MD5+SHA1 for < TLS1.2 */
+	[0x01]	{md5,		MD5dlen},
+	[0x02]	{sha1,		SHA1dlen},
+	[0x03]	{sha2_224,	SHA2_224dlen},
+	[0x04]	{sha2_256,	SHA2_256dlen},
+	[0x05]	{sha2_384,	SHA2_384dlen},
+	[0x06]	{sha2_512,	SHA2_512dlen},
+};
+
+// signature algorithms (only RSA and ECDSA at the moment)
+static int sigalgs[] = {
+	0x0603,		/* SHA512 ECDSA */
+	0x0503,		/* SHA384 ECDSA */
+	0x0403,		/* SHA256 ECDSA */
+	0x0203,		/* SHA1 ECDSA */
+
+	0x0601,		/* SHA512 RSA */
+	0x0501,		/* SHA384 RSA */
+	0x0401,		/* SHA256 RSA */
+	0x0201,		/* SHA1 RSA */
+};
+
+static TlsConnection *tlsServer2(int ctl, int hand,
+	uchar *cert, int certlen,
+	char *pskid, uchar *psk, int psklen,
+	int (*trace)(char*fmt, ...), PEMChain *chain);
+static TlsConnection *tlsClient2(int ctl, int hand,
+	uchar *cert, int certlen,
+	char *pskid, uchar *psk, int psklen,
+	uchar *ext, int extlen, 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, ...);
+static int setVersion(TlsConnection *c, int version);
+static int setSecrets(TlsConnection *c, int isclient);
+static int finishedMatch(TlsConnection *c, Finished *f);
+static void tlsConnectionFree(TlsConnection *c);
+
+static int isDHE(int tlsid);
+static int isECDHE(int tlsid);
+static int isPSK(int tlsid);
+static int isECDSA(int tlsid);
+
+static int setAlgs(TlsConnection *c, int a);
+static int okCipher(Ints *cv, int ispsk, int canec);
+static int okCompression(Bytes *cv);
+static int initCiphers(void);
+static Ints* makeciphers(int ispsk);
+
+static AuthRpc* factotum_rsa_open(RSApub *rsapub);
+static mpint* factotum_rsa_decrypt(AuthRpc *rpc, mpint *cipher);
+static void factotum_rsa_close(AuthRpc *rpc);
+
+static void	tlsSecInits(TlsSec *sec, int cvers, uchar *crandom);
+static int	tlsSecRSAs(TlsSec *sec, Bytes *epm);
+static Bytes*	tlsSecECDHEs1(TlsSec *sec);
+static int	tlsSecECDHEs2(TlsSec *sec, Bytes *Yc);
+static void	tlsSecInitc(TlsSec *sec, int cvers);
+static Bytes*	tlsSecRSAc(TlsSec *sec, uchar *cert, int ncert);
+static Bytes*	tlsSecDHEc(TlsSec *sec, Bytes *p, Bytes *g, Bytes *Ys);
+static Bytes*	tlsSecECDHEc(TlsSec *sec, int curve, Bytes *Ys);
+static void	tlsSecVers(TlsSec *sec, int v);
+static int	tlsSecFinished(TlsSec *sec, HandshakeHash hsh, uchar *fin, int nfin, int isclient);
+static void	setMasterSecret(TlsSec *sec, Bytes *pm);
+static int	digestDHparams(TlsSec *sec, Bytes *par, uchar digest[MAXdlen], int sigalg);
+static char*	verifyDHparams(TlsSec *sec, Bytes *par, Bytes *cert, Bytes *sig, int sigalg);
+
+static Bytes*	pkcs1_encrypt(Bytes* data, RSApub* key);
+static Bytes*	pkcs1_decrypt(TlsSec *sec, Bytes *data);
+static Bytes*	pkcs1_sign(TlsSec *sec, uchar *digest, int digestlen, int sigalg);
+
+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 int get24(uchar *p);
+static int get16(uchar *p);
+static Bytes* newbytes(int len);
+static Bytes* makebytes(uchar* buf, int len);
+static Bytes* mptobytes(mpint* big, int len);
+static mpint* bytestomp(Bytes* bytes);
+static void freebytes(Bytes* b);
+static Ints* newints(int len);
+static void freeints(Ints* b);
+static int lookupid(Ints* b, int id);
+
+/* x509.c */
+extern mpint*	pkcs1padbuf(uchar *buf, int len, mpint *modulus, int blocktype);
+extern int	pkcs1unpadbuf(uchar *buf, int len, mpint *modulus, int blocktype);
+extern int	asn1encodedigest(DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest, uchar *buf, int len);
+
+//================= 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];
+	uchar seed[2*RandomSize];
+	int n, data, ctl, hand;
+	TlsConnection *tls;
+
+	if(conn == nil)
+		return -1;
+	ctl = open("#a/tls/clone", ORDWR|OCEXEC);
+	if(ctl < 0)
+		return -1;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0){
+		close(ctl);
+		return -1;
+	}
+	buf[n] = 0;
+	snprint(conn->dir, sizeof(conn->dir), "#a/tls/%s", buf);
+	snprint(dname, sizeof(dname), "#a/tls/%s/hand", buf);
+	hand = open(dname, ORDWR|OCEXEC);
+	if(hand < 0){
+		close(ctl);
+		return -1;
+	}
+	data = -1;
+	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
+	tls = tlsServer2(ctl, hand,
+		conn->cert, conn->certlen,
+		conn->pskID, conn->psk, conn->psklen,
+		conn->trace, conn->chain);
+	if(tls != nil){
+		snprint(dname, sizeof(dname), "#a/tls/%s/data", buf);
+		data = open(dname, ORDWR);
+	}
+	close(hand);
+	close(ctl);
+	if(data < 0){
+		tlsConnectionFree(tls);
+		return -1;
+	}
+	free(conn->cert);
+	conn->cert = nil;  // client certificates are not yet implemented
+	conn->certlen = 0;
+	conn->sessionIDlen = 0;
+	conn->sessionID = nil;
+	if(conn->sessionKey != nil
+	&& conn->sessionType != nil
+	&& strcmp(conn->sessionType, "ttls") == 0){
+		memmove(seed, tls->sec->crandom, RandomSize);
+		memmove(seed+RandomSize, tls->sec->srandom, RandomSize);
+		tls->sec->prf(
+			conn->sessionKey, conn->sessionKeylen,
+			tls->sec->sec, MasterSecretSize,
+			conn->sessionConst, 
+			seed, sizeof(seed));
+	}
+	tlsConnectionFree(tls);
+	close(fd);
+	return data;
+}
+
+static uchar*
+tlsClientExtensions(TLSconn *conn, int *plen)
+{
+	uchar *b, *p;
+	int i, n, m;
+
+	p = b = nil;
+
+	// RFC6066 - Server Name Identification
+	if(conn->serverName != nil && (n = strlen(conn->serverName)) > 0){
+		m = p - b;
+		b = erealloc(b, m + 2+2+2+1+2+n);
+		p = b + m;
+
+		put16(p, Extsni), p += 2;	/* Type: server_name */
+		put16(p, 2+1+2+n), p += 2;	/* Length */
+		put16(p, 1+2+n), p += 2;	/* Server Name list length */
+		*p++ = 0;			/* Server Name Type: host_name */
+		put16(p, n), p += 2;		/* Server Name length */
+		memmove(p, conn->serverName, n);
+		p += n;
+	}
+
+	// Elliptic Curves (also called Supported Groups)
+	if(ProtocolVersion >= TLS10Version){
+		m = p - b;
+		b = erealloc(b, m + 2+2+2+nelem(namedcurves)*2 + 2+2+1+nelem(pointformats));
+		p = b + m;
+
+		n = nelem(namedcurves);
+		put16(p, Extec), p += 2;	/* Type: elliptic_curves / supported_groups */
+		put16(p, (n+1)*2), p += 2;	/* Length */
+		put16(p, n*2), p += 2;		/* Elliptic Curves Length */
+		for(i=0; i < n; i++){		/* Elliptic Curves */
+			put16(p, namedcurves[i].tlsid);
+			p += 2;
+		}
+
+		n = nelem(pointformats);
+		put16(p, Extecp), p += 2;	/* Type: ec_point_formats */
+		put16(p, n+1), p += 2;		/* Length */
+		*p++ = n;			/* EC point formats Length */
+		for(i=0; i < n; i++)		/* EC point formats */
+			*p++ = pointformats[i];
+	}
+
+	// signature algorithms
+	if(ProtocolVersion >= TLS12Version){
+		n = nelem(sigalgs);
+
+		m = p - b;
+		b = erealloc(b, m + 2+2+2+n*2);
+		p = b + m;
+
+		put16(p, Extsigalgs), p += 2;
+		put16(p, n*2 + 2), p += 2;
+		put16(p, n*2), p += 2;
+		for(i=0; i < n; i++){
+			put16(p, sigalgs[i]);
+			p += 2;
+		}
+	}
+	
+	*plen = p - b;
+	return b;
+}
+
+//	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];
+	uchar seed[2*RandomSize];
+	int n, data, ctl, hand;
+	TlsConnection *tls;
+	uchar *ext;
+
+	if(conn == nil)
+		return -1;
+	ctl = open("#a/tls/clone", ORDWR|OCEXEC);
+	if(ctl < 0)
+		return -1;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0){
+		close(ctl);
+		return -1;
+	}
+	buf[n] = 0;
+	snprint(conn->dir, sizeof(conn->dir), "#a/tls/%s", buf);
+	snprint(dname, sizeof(dname), "#a/tls/%s/hand", buf);
+	hand = open(dname, ORDWR|OCEXEC);
+	if(hand < 0){
+		close(ctl);
+		return -1;
+	}
+	snprint(dname, sizeof(dname), "#a/tls/%s/data", buf);
+	data = open(dname, ORDWR);
+	if(data < 0){
+		close(hand);
+		close(ctl);
+		return -1;
+	}
+	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
+	ext = tlsClientExtensions(conn, &n);
+	tls = tlsClient2(ctl, hand,
+		conn->cert, conn->certlen, 
+		conn->pskID, conn->psk, conn->psklen,
+		ext, n, conn->trace);
+	free(ext);
+	close(hand);
+	close(ctl);
+	if(tls == nil){
+		close(data);
+		return -1;
+	}
+	free(conn->cert);
+	if(tls->cert != nil){
+		conn->certlen = tls->cert->len;
+		conn->cert = emalloc(conn->certlen);
+		memcpy(conn->cert, tls->cert->data, conn->certlen);
+	} else {
+		conn->certlen = 0;
+		conn->cert = nil;
+	}
+	conn->sessionIDlen = 0;
+	conn->sessionID = nil;
+	if(conn->sessionKey != nil
+	&& conn->sessionType != nil
+	&& strcmp(conn->sessionType, "ttls") == 0){
+		memmove(seed, tls->sec->crandom, RandomSize);
+		memmove(seed+RandomSize, tls->sec->srandom, RandomSize);
+		tls->sec->prf(
+			conn->sessionKey, conn->sessionKeylen,
+			tls->sec->sec, MasterSecretSize,
+			conn->sessionConst, 
+			seed, sizeof(seed));
+	}
+	tlsConnectionFree(tls);
+	close(fd);
+	return data;
+}
+
+static int
+countchain(PEMChain *p)
+{
+	int i = 0;
+
+	while (p) {
+		i++;
+		p = p->next;
+	}
+	return i;
+}
+
+static int
+checkClientExtensions(TlsConnection *c, Bytes *ext)
+{
+	uchar *p, *e;
+	int i, j, n;
+
+	if(ext == nil)
+		return 0;
+
+	for(p = ext->data, e = p+ext->len; p < e; p += n){
+		if(e-p < 4)
+			goto Short;
+		p += 4;
+		if(e-p < (n = get16(p-2)))
+			goto Short;
+		switch(get16(p-4)){
+		case Extec:
+			if(n < 4 || n % 2 || get16(p) != (n -= 2))
+				goto Short;
+			p += 2;
+			for(i = 0; i < nelem(namedcurves) && c->sec->nc == nil; i++)
+				for(j = 0; j < n; j += 2)
+					if(namedcurves[i].tlsid == get16(p+j)){
+						c->sec->nc = &namedcurves[i];
+						break;
+					}
+			break;
+		}
+	}
+
+	return 0;
+Short:
+	tlsError(c, EDecodeError, "clienthello extensions has invalid length");
+	return -1; 
+} 
+
+static TlsConnection *
+tlsServer2(int ctl, int hand,
+	uchar *cert, int certlen,
+	char *pskid, uchar *psk, int psklen,
+	int (*trace)(char*fmt, ...), PEMChain *chp)
+{
+	int cipher, compressor, numcerts, i;
+	TlsConnection *c;
+	Msg m;
+
+	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;
+	c->sendp = c->buf;
+
+	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;
+	}
+	if(trace)
+		trace("ClientHello version %x\n", m.u.clientHello.version);
+	if(setVersion(c, m.u.clientHello.version) < 0) {
+		tlsError(c, EIllegalParameter, "incompatible version");
+		goto Err;
+	}
+	if(c->version < ProtocolVersion
+	&& lookupid(m.u.clientHello.ciphers, TLS_FALLBACK_SCSV) >= 0){
+		tlsError(c, EInappropriateFallback, "inappropriate fallback");
+		goto Err;
+	}
+	tlsSecInits(c->sec, m.u.clientHello.version, m.u.clientHello.random);
+	tlsSecVers(c->sec, c->version);
+	if(psklen > 0){
+		c->sec->psk = psk;
+		c->sec->psklen = psklen;
+	}
+	if(certlen > 0){
+		/* server certificate */
+		c->sec->rsapub = X509toRSApub(cert, certlen, nil, 0);
+		if(c->sec->rsapub == nil){
+			tlsError(c, EHandshakeFailure, "invalid X509/rsa certificate");
+			goto Err;
+		}
+		c->sec->rpc = factotum_rsa_open(c->sec->rsapub);
+		if(c->sec->rpc == nil){
+			tlsError(c, EHandshakeFailure, "factotum_rsa_open: %r");
+			goto Err;
+		}
+	}
+	if(checkClientExtensions(c, m.u.clientHello.extensions) < 0)
+		goto Err;
+	cipher = okCipher(m.u.clientHello.ciphers, psklen > 0, c->sec->nc != nil);
+	if(cipher < 0 || !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;
+	}
+	if(trace)
+		trace("  cipher %x, compressor %x\n", cipher, compressor);
+	msgClear(&m);
+
+	m.tag = HServerHello;
+	m.u.serverHello.version = c->version;
+	memmove(m.u.serverHello.random, c->sec->srandom, RandomSize);
+	m.u.serverHello.cipher = cipher;
+	m.u.serverHello.compressor = compressor;
+	m.u.serverHello.sid = makebytes(nil, 0);
+	if(!msgSend(c, &m, AQueue))
+		goto Err;
+
+	if(certlen > 0){
+		m.tag = HCertificate;
+		numcerts = countchain(chp);
+		m.u.certificate.ncert = 1 + numcerts;
+		m.u.certificate.certs = emalloc(m.u.certificate.ncert * sizeof(Bytes*));
+		m.u.certificate.certs[0] = makebytes(cert, certlen);
+		for (i = 0; i < numcerts && chp; i++, chp = chp->next)
+			m.u.certificate.certs[i+1] = makebytes(chp->pem, chp->pemlen);
+		if(!msgSend(c, &m, AQueue))
+			goto Err;
+	}
+
+	if(isECDHE(cipher)){
+		m.tag = HServerKeyExchange;
+		m.u.serverKeyExchange.curve = c->sec->nc->tlsid;
+		m.u.serverKeyExchange.dh_parameters = tlsSecECDHEs1(c->sec);
+		if(m.u.serverKeyExchange.dh_parameters == nil){
+			tlsError(c, EInternalError, "can't set DH parameters");
+			goto Err;
+		}
+
+		/* sign the DH parameters */
+		if(certlen > 0){
+			uchar digest[MAXdlen];
+			int digestlen;
+
+			if(c->version >= TLS12Version)
+				m.u.serverKeyExchange.sigalg = 0x0401;	/* RSA SHA256 */
+			digestlen = digestDHparams(c->sec, m.u.serverKeyExchange.dh_parameters,
+				digest, m.u.serverKeyExchange.sigalg);
+			if((m.u.serverKeyExchange.dh_signature = pkcs1_sign(c->sec, digest, digestlen,
+				m.u.serverKeyExchange.sigalg)) == nil){
+				tlsError(c, EHandshakeFailure, "pkcs1_sign: %r");
+				goto Err;
+			}
+		}
+		if(!msgSend(c, &m, AQueue))
+			goto Err;
+	}
+
+	m.tag = HServerHelloDone;
+	if(!msgSend(c, &m, AFlush))
+		goto Err;
+
+	if(!msgRecv(c, &m))
+		goto Err;
+	if(m.tag != HClientKeyExchange) {
+		tlsError(c, EUnexpectedMessage, "expected a client key exchange");
+		goto Err;
+	}
+	if(pskid != nil){
+		if(m.u.clientKeyExchange.pskid == nil
+		|| m.u.clientKeyExchange.pskid->len != strlen(pskid)
+		|| memcmp(pskid, m.u.clientKeyExchange.pskid->data, m.u.clientKeyExchange.pskid->len) != 0){
+			tlsError(c, EUnknownPSKidentity, "unknown or missing pskid");
+			goto Err;
+		}
+	}
+	if(isECDHE(cipher)){
+		if(tlsSecECDHEs2(c->sec, m.u.clientKeyExchange.key) < 0){
+			tlsError(c, EHandshakeFailure, "couldn't set keys: %r");
+			goto Err;
+		}
+	} else if(certlen > 0){
+		if(tlsSecRSAs(c->sec, m.u.clientKeyExchange.key) < 0){
+			tlsError(c, EHandshakeFailure, "couldn't set keys: %r");
+			goto Err;
+		}
+	} else if(psklen > 0){
+		setMasterSecret(c->sec, newbytes(psklen));
+	} else {
+		tlsError(c, EInternalError, "no psk or certificate");
+		goto Err;
+	}
+
+	if(trace)
+		trace("tls secrets\n");
+	if(setSecrets(c, 0) < 0){
+		tlsError(c, EHandshakeFailure, "can't set secrets: %r");
+		goto Err;
+	}
+
+	/* no CertificateVerify; skip to Finished */
+	if(tlsSecFinished(c->sec, c->handhash, 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->handhash, 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;
+	if(trace)
+		trace("tls finished\n");
+
+	if(fprint(c->ctl, "opened") < 0)
+		goto Err;
+	return c;
+
+Err:
+	msgClear(&m);
+	tlsConnectionFree(c);
+	return nil;
+}
+
+static Bytes*
+tlsSecDHEc(TlsSec *sec, Bytes *p, Bytes *g, Bytes *Ys)
+{
+	DHstate *dh = &sec->dh;
+	mpint *G, *P, *Y, *K;
+	Bytes *Yc;
+	int n;
+
+	if(p == nil || g == nil || Ys == nil)
+		return nil;
+	// reject dh primes that is susceptible to logjam
+	if(p->len <= 1024/8)
+		return nil;
+	Yc = nil;
+	P = bytestomp(p);
+	G = bytestomp(g);
+	Y = bytestomp(Ys);
+	K = nil;
+
+	if(dh_new(dh, P, nil, G) == nil)
+		goto Out;
+	n = (mpsignif(P)+7)/8;
+	Yc = mptobytes(dh->y, n);
+	K = dh_finish(dh, Y);	/* zeros dh */
+	if(K == nil){
+		freebytes(Yc);
+		Yc = nil;
+		goto Out;
+	}
+	setMasterSecret(sec, mptobytes(K, n));
+
+Out:
+	mpfree(K);
+	mpfree(Y);
+	mpfree(G);
+	mpfree(P);
+
+	return Yc;
+}
+
+static Bytes*
+tlsSecECDHEc(TlsSec *sec, int curve, Bytes *Ys)
+{
+	ECdomain *dom = &sec->ec.dom;
+	ECpriv *Q = &sec->ec.Q;
+	ECpub *pub;
+	ECpoint K;
+	Namedcurve *nc;
+	Bytes *Yc;
+	Bytes *Z;
+	int n;
+
+	if(Ys == nil)
+		return nil;
+
+	if(curve == X25519){
+		if(Ys->len != 32)
+			return nil;
+		Yc = newbytes(32);
+		curve25519_dh_new(sec->X, Yc->data);
+		Z = newbytes(32);
+		if(!curve25519_dh_finish(sec->X, Ys->data, Z->data)){
+			freebytes(Yc);
+			freebytes(Z);
+			return nil;
+		}
+		setMasterSecret(sec, Z);
+	}else{
+		for(nc = namedcurves; nc->tlsid != curve; nc++)
+			if(nc == &namedcurves[nelem(namedcurves)])
+				return nil;
+		ecdominit(dom, nc->init);
+		pub = ecdecodepub(dom, Ys->data, Ys->len);
+		if(pub == nil)
+			return nil;
+
+		memset(Q, 0, sizeof(*Q));
+		Q->a.x = mpnew(0);
+		Q->a.y = mpnew(0);
+		Q->d = mpnew(0);
+
+		memset(&K, 0, sizeof(K));
+		K.x = mpnew(0);
+		K.y = mpnew(0);
+
+		ecgen(dom, Q);
+		ecmul(dom, pub, Q->d, &K);
+
+		n = (mpsignif(dom->p)+7)/8;
+		setMasterSecret(sec, mptobytes(K.x, n));
+		Yc = newbytes(1 + 2*n);
+		Yc->len = ecencodepub(dom, &Q->a, Yc->data, Yc->len);
+
+		mpfree(K.x);
+		mpfree(K.y);
+
+		ecpubfree(pub);
+	}
+	return Yc;
+}
+
+static TlsConnection *
+tlsClient2(int ctl, int hand,
+	uchar *cert, int certlen,
+	char *pskid, uchar *psk, int psklen,
+	uchar *ext, int extlen,
+	int (*trace)(char*fmt, ...))
+{
+	int creq, dhx, cipher;
+	TlsConnection *c;
+	Bytes *epm;
+	Msg m;
+
+	if(!initCiphers())
+		return nil;
+
+	epm = nil;
+	memset(&m, 0, sizeof(m));
+	c = emalloc(sizeof(TlsConnection));
+
+	c->ctl = ctl;
+	c->hand = hand;
+	c->trace = trace;
+	c->cert = nil;
+	c->sendp = c->buf;
+
+	c->version = ProtocolVersion;
+	tlsSecInitc(c->sec, c->version);
+	if(psklen > 0){
+		c->sec->psk = psk;
+		c->sec->psklen = psklen;
+	}
+	if(certlen > 0){
+		/* client certificate */
+		c->sec->rsapub = X509toRSApub(cert, certlen, nil, 0);
+		if(c->sec->rsapub == nil){
+			tlsError(c, EInternalError, "invalid X509/rsa certificate");
+			goto Err;
+		}
+		c->sec->rpc = factotum_rsa_open(c->sec->rsapub);
+		if(c->sec->rpc == nil){
+			tlsError(c, EInternalError, "factotum_rsa_open: %r");
+			goto Err;
+		}
+	}
+
+	/* client hello */
+	m.tag = HClientHello;
+	m.u.clientHello.version = c->version;
+	memmove(m.u.clientHello.random, c->sec->crandom, RandomSize);
+	m.u.clientHello.sid = makebytes(nil, 0);
+	m.u.clientHello.ciphers = makeciphers(psklen > 0);
+	m.u.clientHello.compressors = makebytes(compressors,sizeof(compressors));
+	m.u.clientHello.extensions = makebytes(ext, extlen);
+	if(!msgSend(c, &m, AFlush))
+		goto Err;
+
+	/* 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;
+	}
+	tlsSecVers(c->sec, c->version);
+	memmove(c->sec->srandom, m.u.serverHello.random, RandomSize);
+
+	cipher = m.u.serverHello.cipher;
+	if((psklen > 0) != isPSK(cipher) || !setAlgs(c, cipher)) {
+		tlsError(c, EIllegalParameter, "invalid cipher suite");
+		goto Err;
+	}
+	if(m.u.serverHello.compressor != CompressionNull) {
+		tlsError(c, EIllegalParameter, "invalid compression");
+		goto Err;
+	}
+	dhx = isDHE(cipher) || isECDHE(cipher);
+	if(!msgRecv(c, &m))
+		goto Err;
+	if(m.tag == HCertificate){
+		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);
+		if(!msgRecv(c, &m))
+			goto Err;
+	} else if(psklen == 0) {
+		tlsError(c, EUnexpectedMessage, "expected a certificate");
+		goto Err;
+	}
+	if(m.tag == HServerKeyExchange) {
+		if(dhx){
+			char *err = verifyDHparams(c->sec,
+				m.u.serverKeyExchange.dh_parameters,
+				c->cert,
+				m.u.serverKeyExchange.dh_signature,
+				c->version<TLS12Version ? 0x01 : m.u.serverKeyExchange.sigalg);
+			if(err != nil){
+				tlsError(c, EBadCertificate, "can't verify DH parameters: %s", err);
+				goto Err;
+			}
+			if(isECDHE(cipher))
+				epm = tlsSecECDHEc(c->sec,
+					m.u.serverKeyExchange.curve,
+					m.u.serverKeyExchange.dh_Ys);
+			else
+				epm = tlsSecDHEc(c->sec,
+					m.u.serverKeyExchange.dh_p, 
+					m.u.serverKeyExchange.dh_g,
+					m.u.serverKeyExchange.dh_Ys);
+			if(epm == nil){
+				tlsError(c, EHandshakeFailure, "bad DH parameters");
+				goto Err;
+			}
+		} else if(psklen == 0){
+			tlsError(c, EUnexpectedMessage, "got an server key exchange");
+			goto Err;
+		}
+		if(!msgRecv(c, &m))
+			goto Err;
+	} else if(dhx){
+		tlsError(c, EUnexpectedMessage, "expected server key exchange");
+		goto Err;
+	}
+
+	/* certificate request (optional) */
+	creq = 0;
+	if(m.tag == HCertificateRequest) {
+		creq = 1;
+		if(!msgRecv(c, &m))
+			goto Err;
+	}
+
+	if(m.tag != HServerHelloDone) {
+		tlsError(c, EUnexpectedMessage, "expected a server hello done");
+		goto Err;
+	}
+	msgClear(&m);
+
+	if(!dhx){
+		if(c->cert != nil){
+			epm = tlsSecRSAc(c->sec, c->cert->data, c->cert->len);
+			if(epm == nil){
+				tlsError(c, EBadCertificate, "bad certificate: %r");
+				goto Err;
+			}
+		} else if(psklen > 0){
+			setMasterSecret(c->sec, newbytes(psklen));
+		} else {
+			tlsError(c, EInternalError, "no psk or certificate");
+			goto Err;
+		}
+	}
+
+	if(trace)
+		trace("tls secrets\n");
+	if(setSecrets(c, 1) < 0){
+		tlsError(c, EHandshakeFailure, "can't set secrets: %r");
+		goto Err;
+	}
+
+	if(creq) {
+		m.tag = HCertificate;
+		if(certlen > 0){
+			m.u.certificate.ncert = 1;
+			m.u.certificate.certs = emalloc(m.u.certificate.ncert * sizeof(Bytes*));
+			m.u.certificate.certs[0] = makebytes(cert, certlen);
+		}		
+		if(!msgSend(c, &m, AFlush))
+			goto Err;
+	}
+
+	/* client key exchange */
+	m.tag = HClientKeyExchange;
+	if(psklen > 0){
+		if(pskid == nil)
+			pskid = "";
+		m.u.clientKeyExchange.pskid = makebytes((uchar*)pskid, strlen(pskid));
+	}
+	m.u.clientKeyExchange.key = epm;
+	epm = nil;
+	 
+	if(!msgSend(c, &m, AFlush))
+		goto Err;
+
+	/* certificate verify */
+	if(creq && certlen > 0) {
+		HandshakeHash hsave;
+		uchar digest[MAXdlen];
+		int digestlen;
+
+		/* save the state for the Finish message */
+		hsave = c->handhash;
+		if(c->version < TLS12Version){
+			md5(nil, 0, digest, &c->handhash.md5);
+			sha1(nil, 0, digest+MD5dlen, &c->handhash.sha1);
+			digestlen = MD5dlen+SHA1dlen;
+		} else {
+			m.u.certificateVerify.sigalg = 0x0401;	/* RSA SHA256 */
+			sha2_256(nil, 0, digest, &c->handhash.sha2_256);
+			digestlen = SHA2_256dlen;
+		}
+		c->handhash = hsave;
+
+		if((m.u.certificateVerify.signature = pkcs1_sign(c->sec, digest, digestlen,
+			m.u.certificateVerify.sigalg)) == nil){
+			tlsError(c, EHandshakeFailure, "pkcs1_sign: %r");
+			goto Err;
+		}
+
+		m.tag = HCertificateVerify;
+		if(!msgSend(c, &m, AFlush))
+			goto Err;
+	} 
+
+	/* 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->handhash, 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)) {
+		tlsError(c, EInternalError, "can't flush after client Finished: %r");
+		goto Err;
+	}
+
+	if(tlsSecFinished(c->sec, c->handhash, c->finished.verify, c->finished.n, 0) < 0){
+		tlsError(c, EInternalError, "can't set finished 0: %r");
+		goto Err;
+	}
+	if(!msgRecv(c, &m)) {
+		tlsError(c, EInternalError, "can't read server Finished: %r");
+		goto Err;
+	}
+	if(m.tag != HFinished) {
+		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;
+	}
+	return c;
+
+Err:
+	free(epm);
+	msgClear(&m);
+	tlsConnectionFree(c);
+	return nil;
+}
+
+
+//================= message functions ========================
+
+static void
+msgHash(TlsConnection *c, uchar *p, int n)
+{
+	md5(p, n, 0, &c->handhash.md5);
+	sha1(p, n, 0, &c->handhash.sha1);
+	if(c->version >= TLS12Version)
+		sha2_256(p, n, 0, &c->handhash.sha2_256);
+}
+
+static int
+msgSend(TlsConnection *c, Msg *m, int act)
+{
+	uchar *p, *e; // sendp = start of new message;  p = write pointer; e = end pointer
+	int n, i;
+
+	p = c->sendp;
+	e = &c->buf[sizeof(c->buf)];
+	if(c->trace)
+		c->trace("send %s", msgPrint((char*)p, e - p, 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:
+		if(p+2+RandomSize > e)
+			goto Overflow;
+		put16(p, m->u.clientHello.version), p += 2;
+		memmove(p, m->u.clientHello.random, RandomSize);
+		p += RandomSize;
+
+		if(p+1+(n = m->u.clientHello.sid->len) > e)
+			goto Overflow;
+		*p++ = n;
+		memmove(p, m->u.clientHello.sid->data, n);
+		p += n;
+
+		if(p+2+(n = m->u.clientHello.ciphers->len) > e)
+			goto Overflow;
+		put16(p, n*2), p += 2;
+		for(i=0; i<n; i++)
+			put16(p, m->u.clientHello.ciphers->data[i]), p += 2;
+
+		if(p+1+(n = m->u.clientHello.compressors->len) > e)
+			goto Overflow;
+		*p++ = n;
+		memmove(p, m->u.clientHello.compressors->data, n);
+		p += n;
+
+		if(m->u.clientHello.extensions == nil
+		|| (n = m->u.clientHello.extensions->len) == 0)
+			break;
+		if(p+2+n > e)
+			goto Overflow;
+		put16(p, n), p += 2;
+		memmove(p, m->u.clientHello.extensions->data, n);
+		p += n;
+		break;
+	case HServerHello:
+		if(p+2+RandomSize > e)
+			goto Overflow;
+		put16(p, m->u.serverHello.version), p += 2;
+		memmove(p, m->u.serverHello.random, RandomSize);
+		p += RandomSize;
+
+		if(p+1+(n = m->u.serverHello.sid->len) > e)
+			goto Overflow;
+		*p++ = n;
+		memmove(p, m->u.serverHello.sid->data, n);
+		p += n;
+
+		if(p+2+1 > e)
+			goto Overflow;
+		put16(p, m->u.serverHello.cipher), p += 2;
+		*p++ = m->u.serverHello.compressor;
+
+		if(m->u.serverHello.extensions == nil
+		|| (n = m->u.serverHello.extensions->len) == 0)
+			break;
+		if(p+2+n > e)
+			goto Overflow;
+		put16(p, n), p += 2;
+		memmove(p, m->u.serverHello.extensions->data, n);
+		p += n;
+		break;
+	case HServerHelloDone:
+		break;
+	case HCertificate:
+		n = 0;
+		for(i = 0; i < m->u.certificate.ncert; i++)
+			n += 3 + m->u.certificate.certs[i]->len;
+		if(p+3+n > e)
+			goto Overflow;
+		put24(p, n), p += 3;
+		for(i = 0; i < m->u.certificate.ncert; i++){
+			n = m->u.certificate.certs[i]->len;
+			put24(p, n), p += 3;
+			memmove(p, m->u.certificate.certs[i]->data, n);
+			p += n;
+		}
+		break;
+	case HCertificateVerify:
+		if(p+2+2+(n = m->u.certificateVerify.signature->len) > e)
+			goto Overflow;
+		if(m->u.certificateVerify.sigalg != 0)
+			put16(p, m->u.certificateVerify.sigalg), p += 2;
+		put16(p, n), p += 2;
+		memmove(p, m->u.certificateVerify.signature->data, n);
+		p += n;
+		break;
+	case HServerKeyExchange:
+		if(m->u.serverKeyExchange.pskid != nil){
+			if(p+2+(n = m->u.serverKeyExchange.pskid->len) > e)
+				goto Overflow;
+			put16(p, n), p += 2;
+			memmove(p, m->u.serverKeyExchange.pskid->data, n);
+			p += n;
+		}
+		if(m->u.serverKeyExchange.dh_parameters == nil)
+			break;
+		if(p+(n = m->u.serverKeyExchange.dh_parameters->len) > e)
+			goto Overflow;
+		memmove(p, m->u.serverKeyExchange.dh_parameters->data, n);
+		p += n;
+		if(m->u.serverKeyExchange.dh_signature == nil)
+			break;
+		if(p+2+2+(n = m->u.serverKeyExchange.dh_signature->len) > e)
+			goto Overflow;
+		if(c->version >= TLS12Version)
+			put16(p, m->u.serverKeyExchange.sigalg), p += 2;
+		put16(p, n), p += 2;
+		memmove(p, m->u.serverKeyExchange.dh_signature->data, n);
+		p += n;
+		break;
+	case HClientKeyExchange:
+		if(m->u.clientKeyExchange.pskid != nil){
+			if(p+2+(n = m->u.clientKeyExchange.pskid->len) > e)
+				goto Overflow;
+			put16(p, n), p += 2;
+			memmove(p, m->u.clientKeyExchange.pskid->data, n);
+			p += n;
+		}
+		if(m->u.clientKeyExchange.key == nil)
+			break;
+		if(p+2+(n = m->u.clientKeyExchange.key->len) > e)
+			goto Overflow;
+		if(isECDHE(c->cipher))
+			*p++ = n;
+		else if(isDHE(c->cipher) || c->version != SSL3Version)
+			put16(p, n), p += 2;
+		memmove(p, m->u.clientKeyExchange.key->data, n);
+		p += n;
+		break;
+	case HFinished:
+		if(p+m->u.finished.n > e)
+			goto Overflow;
+		memmove(p, m->u.finished.verify, m->u.finished.n);
+		p += m->u.finished.n;
+		break;
+	}
+
+	// go back and fill in size
+	n = p - c->sendp;
+	put24(c->sendp+1, n-4);
+
+	// remember hash of Handshake messages
+	if(m->tag != HHelloRequest)
+		msgHash(c, c->sendp, n);
+
+	c->sendp = p;
+	if(act == AFlush){
+		c->sendp = c->buf;
+		if(write(c->hand, c->buf, p - c->buf) < 0){
+			fprint(2, "write error: %r\n");
+			goto Err;
+		}
+	}
+	msgClear(m);
+	return 1;
+Overflow:
+	tlsError(c, EInternalError, "not enougth send buffer for message (%d)", m->tag);
+Err:
+	msgClear(m);
+	return 0;
+}
+
+static uchar*
+tlsReadN(TlsConnection *c, int n)
+{
+	uchar *p, *w, *e;
+
+	e = &c->buf[sizeof(c->buf)];
+	p = e - n;
+	if(n > sizeof(c->buf) || p < c->sendp){
+		tlsError(c, EDecodeError, "handshake message too long %d", n);
+		return nil;
+	}
+	for(w = p; w < e; w += n)
+		if((n = read(c->hand, w, e - w)) <= 0)
+			return nil;
+	return p;
+}
+
+static int
+msgRecv(TlsConnection *c, Msg *m)
+{
+	uchar *p, *s;
+	int type, n, nn, i;
+
+	msgClear(m);
+	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(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. */
+		int nsid, nrandom, nciph;
+
+		p = tlsReadN(c, n);
+		if(p == nil)
+			return 0;
+		msgHash(c, p, n);
+		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 || n - nrandom < nn)
+			goto Err;
+		/* 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;
+	}
+	msgHash(c, p, 4);
+
+	p = tlsReadN(c, n);
+	if(p == nil)
+		return 0;
+
+	msgHash(c, p, n);
+
+	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 % 2 || 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 = makebytes(p+1, nn);
+		p += nn + 1, n -= nn + 1;
+
+		if(n < 2)
+			break;
+		nn = get16(p);
+		if(nn > n-2)
+			goto Short;
+		m->u.clientHello.extensions = makebytes(p+2, nn);
+		n -= nn + 2;
+		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];
+		p += 3, n -= 3;
+
+		if(n < 2)
+			break;
+		nn = get16(p);
+		if(nn > n-2)
+			goto Short;
+		m->u.serverHello.extensions = makebytes(p+2, nn);
+		n -= nn + 2;
+		break;
+	case HCertificate:
+		if(n < 3)
+			goto Short;
+		nn = get24(p);
+		p += 3, n -= 3;
+		if(nn == 0 && n > 0)
+			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 < 1)
+			goto Short;
+		nn = p[0];
+		p++, n--;
+		if(nn > n)
+			goto Short;
+		m->u.certificateRequest.types = makebytes(p, nn);
+		p += nn, n -= nn;
+		if(c->version >= TLS12Version){
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn % 2)
+				goto Short;
+			m->u.certificateRequest.sigalgs = newints(nn>>1);
+			for(i = 0; i < nn; i += 2)
+				m->u.certificateRequest.sigalgs->data[i >> 1] = get16(&p[i]);
+			p += nn, n -= nn;
+
+		}
+		if(n < 2)
+			goto Short;
+		nn = get16(p);
+		p += 2, n -= 2;
+		/* nn == 0 can happen; yahoo's servers do it */
+		if(nn != n)
+			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 HServerKeyExchange:
+		if(isPSK(c->cipher)){
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn > n)
+				goto Short;
+			m->u.serverKeyExchange.pskid = makebytes(p, nn);
+			p += nn, n -= nn;
+			if(n == 0)
+				break;
+		}
+		if(n < 2)
+			goto Short;
+		s = p;
+		if(isECDHE(c->cipher)){
+			nn = *p;
+			p++, n--;
+			if(nn != 3 || nn > n) /* not a named curve */
+				goto Short;
+			nn = get16(p);
+			p += 2, n -= 2;
+			m->u.serverKeyExchange.curve = nn;
+
+			nn = *p++, n--;
+			if(nn < 1 || nn > n)
+				goto Short;
+			m->u.serverKeyExchange.dh_Ys = makebytes(p, nn);
+			p += nn, n -= nn;
+		}else if(isDHE(c->cipher)){
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn < 1 || nn > n)
+				goto Short;
+			m->u.serverKeyExchange.dh_p = makebytes(p, nn);
+			p += nn, n -= nn;
+	
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn < 1 || nn > n)
+				goto Short;
+			m->u.serverKeyExchange.dh_g = makebytes(p, nn);
+			p += nn, n -= nn;
+	
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn < 1 || nn > n)
+				goto Short;
+			m->u.serverKeyExchange.dh_Ys = makebytes(p, nn);
+			p += nn, n -= nn;
+		} else {
+			/* should not happen */
+			goto Short;
+		}
+		m->u.serverKeyExchange.dh_parameters = makebytes(s, p - s);
+		if(n >= 2){
+			m->u.serverKeyExchange.sigalg = 0;
+			if(c->version >= TLS12Version){
+				m->u.serverKeyExchange.sigalg = get16(p);
+				p += 2, n -= 2;
+				if(n < 2)
+					goto Short;
+			}
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn > 0 && nn <= n){
+				m->u.serverKeyExchange.dh_signature = makebytes(p, nn);
+				n -= nn;
+			}
+		}
+		break;		
+	case HClientKeyExchange:
+		if(isPSK(c->cipher)){
+			if(n < 2)
+				goto Short;
+			nn = get16(p);
+			p += 2, n -= 2;
+			if(nn > n)
+				goto Short;
+			m->u.clientKeyExchange.pskid = makebytes(p, nn);
+			p += nn, n -= nn;
+			if(n == 0)
+				break;
+		}
+		if(n < 2)
+			goto Short;
+		if(isECDHE(c->cipher))
+			nn = *p++, n--;
+		else if(isDHE(c->cipher) || c->version != SSL3Version)
+			nn = get16(p), p += 2, n -= 2;
+		else
+			nn = n;
+		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(n != 0 && type != HClientHello && type != HServerHello)
+		goto Short;
+Ok:
+	if(c->trace)
+		c->trace("recv %s", msgPrint((char*)c->sendp, &c->buf[sizeof(c->buf)] - c->sendp, m));
+	return 1;
+Short:
+	tlsError(c, EDecodeError, "handshake message (%d) has invalid length", type);
+Err:
+	msgClear(m);
+	return 0;
+}
+
+static void
+msgClear(Msg *m)
+{
+	int i;
+
+	switch(m->tag) {
+	case HHelloRequest:
+		break;
+	case HClientHello:
+		freebytes(m->u.clientHello.sid);
+		freeints(m->u.clientHello.ciphers);
+		freebytes(m->u.clientHello.compressors);
+		freebytes(m->u.clientHello.extensions);
+		break;
+	case HServerHello:
+		freebytes(m->u.serverHello.sid);
+		freebytes(m->u.serverHello.extensions);
+		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);
+		freeints(m->u.certificateRequest.sigalgs);
+		for(i=0; i<m->u.certificateRequest.nca; i++)
+			freebytes(m->u.certificateRequest.cas[i]);
+		free(m->u.certificateRequest.cas);
+		break;
+	case HCertificateVerify:
+		freebytes(m->u.certificateVerify.signature);
+		break;
+	case HServerHelloDone:
+		break;
+	case HServerKeyExchange:
+		freebytes(m->u.serverKeyExchange.pskid);
+		freebytes(m->u.serverKeyExchange.dh_p);
+		freebytes(m->u.serverKeyExchange.dh_g);
+		freebytes(m->u.serverKeyExchange.dh_Ys);
+		freebytes(m->u.serverKeyExchange.dh_parameters);
+		freebytes(m->u.serverKeyExchange.dh_signature);
+		break;
+	case HClientKeyExchange:
+		freebytes(m->u.clientKeyExchange.pskid);
+		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);
+	if(b == nil)
+		bs = seprint(bs, be, "nil");
+	else {
+		bs = seprint(bs, be, "<%d> [ ", b->len);
+		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);
+	if(b == nil)
+		bs = seprint(bs, be, "nil");
+	else {
+		bs = seprint(bs, be, "[ ");
+		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");
+		if(m->u.clientHello.extensions != nil)
+			bs = bytesPrint(bs, be, "\textensions: ", m->u.clientHello.extensions, "\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);
+		if(m->u.serverHello.extensions != nil)
+			bs = bytesPrint(bs, be, "\textensions: ", m->u.serverHello.extensions, "\n");
+		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");
+		if(m->u.certificateRequest.sigalgs != nil)
+			bs = intsPrint(bs, be, "\tsigalgs: ", m->u.certificateRequest.sigalgs, "\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 HCertificateVerify:
+		bs = seprint(bs, be, "HCertificateVerify\n");
+		if(m->u.certificateVerify.sigalg != 0)
+			bs = seprint(bs, be, "\tsigalg: %.4x\n", m->u.certificateVerify.sigalg);
+		bs = bytesPrint(bs, be, "\tsignature: ", m->u.certificateVerify.signature,"\n");
+		break;	
+	case HServerHelloDone:
+		bs = seprint(bs, be, "ServerHelloDone\n");
+		break;
+	case HServerKeyExchange:
+		bs = seprint(bs, be, "HServerKeyExchange\n");
+		if(m->u.serverKeyExchange.pskid != nil)
+			bs = bytesPrint(bs, be, "\tpskid: ", m->u.serverKeyExchange.pskid, "\n");
+		if(m->u.serverKeyExchange.dh_parameters == nil)
+			break;
+		if(m->u.serverKeyExchange.curve != 0){
+			bs = seprint(bs, be, "\tcurve: %.4x\n", m->u.serverKeyExchange.curve);
+		} else {
+			bs = bytesPrint(bs, be, "\tdh_p: ", m->u.serverKeyExchange.dh_p, "\n");
+			bs = bytesPrint(bs, be, "\tdh_g: ", m->u.serverKeyExchange.dh_g, "\n");
+		}
+		bs = bytesPrint(bs, be, "\tdh_Ys: ", m->u.serverKeyExchange.dh_Ys, "\n");
+		if(m->u.serverKeyExchange.sigalg != 0)
+			bs = seprint(bs, be, "\tsigalg: %.4x\n", m->u.serverKeyExchange.sigalg);
+		bs = bytesPrint(bs, be, "\tdh_parameters: ", m->u.serverKeyExchange.dh_parameters, "\n");
+		bs = bytesPrint(bs, be, "\tdh_signature: ", m->u.serverKeyExchange.dh_signature, "\n");
+		break;
+	case HClientKeyExchange:
+		bs = seprint(bs, be, "HClientKeyExchange\n");
+		if(m->u.clientKeyExchange.pskid != nil)
+			bs = bytesPrint(bs, be, "\tpskid: ", m->u.clientKeyExchange.pskid, "\n");
+		if(m->u.clientKeyExchange.key != nil)
+			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);
+	if(c->erred)
+		fprint(2, "double error: %r, %s", msg);
+	else
+		errstr(msg, sizeof(msg));
+	c->erred = 1;
+	fprint(c->ctl, "alert %d", err);
+}
+
+// commit to specific version number
+static int
+setVersion(TlsConnection *c, int version)
+{
+	if(version > MaxProtoVersion || version < MinProtoVersion)
+		return -1;
+	if(version > c->version)
+		version = c->version;
+	if(version == SSL3Version) {
+		c->version = version;
+		c->finished.n = SSL3FinishedLen;
+	}else {
+		c->version = version;
+		c->finished.n = TLSFinishedLen;
+	}
+	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 tsmemcmp(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)
+{
+	if(c == nil)
+		return;
+
+	dh_finish(&c->sec->dh, nil);
+
+	mpfree(c->sec->ec.Q.a.x);
+	mpfree(c->sec->ec.Q.a.y);
+	mpfree(c->sec->ec.Q.d);
+	ecdomfree(&c->sec->ec.dom);
+
+	factotum_rsa_close(c->sec->rpc);
+	rsapubfree(c->sec->rsapub);
+	freebytes(c->cert);
+
+	memset(c, 0, sizeof(*c));
+	free(c);
+}
+
+
+//================= cipher choices ========================
+
+static int
+isDHE(int tlsid)
+{
+	switch(tlsid){
+	case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+	case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ 	case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ 	case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ 	case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+	case TLS_DHE_RSA_WITH_CHACHA20_POLY1305:
+	case GOOGLE_DHE_RSA_WITH_CHACHA20_POLY1305:
+		return 1;
+	}
+	return 0;
+}
+
+static int
+isECDHE(int tlsid)
+{
+	switch(tlsid){
+	case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
+	case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305:
+
+	case GOOGLE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
+	case GOOGLE_ECDHE_RSA_WITH_CHACHA20_POLY1305:
+
+	case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+	case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+
+	case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+	case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+	case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+	case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+		return 1;
+	}
+	return 0;
+}
+
+static int
+isPSK(int tlsid)
+{
+	switch(tlsid){
+	case TLS_PSK_WITH_CHACHA20_POLY1305:
+	case TLS_PSK_WITH_AES_128_CBC_SHA256:
+	case TLS_PSK_WITH_AES_128_CBC_SHA:
+		return 1;
+	}
+	return 0;
+}
+
+static int
+isECDSA(int tlsid)
+{
+	switch(tlsid){
+	case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
+	case GOOGLE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
+	case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+	case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+		return 1;
+	}
+	return 0;
+}
+
+static int
+setAlgs(TlsConnection *c, int a)
+{
+	int i;
+
+	for(i = 0; i < nelem(cipherAlgs); i++){
+		if(cipherAlgs[i].tlsid == a){
+			c->cipher = 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 ispsk, int canec)
+{
+	int i, c;
+
+	for(i = 0; i < nelem(cipherAlgs); i++) {
+		c = cipherAlgs[i].tlsid;
+		if(!cipherAlgs[i].ok || isECDSA(c) || isDHE(c))
+			continue;
+		if(isPSK(c) != ispsk)
+			continue;
+		if(isECDHE(c) && !canec)
+			continue;
+		if(lookupid(cv, c) >= 0)
+			return c;
+	}
+	return -1;
+}
+
+static int
+okCompression(Bytes *cv)
+{
+	int i, c;
+
+	for(i = 0; i < nelem(compressors); i++) {
+		c = compressors[i];
+		if(memchr(cv->data, c, cv->len) != nil)
+			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|OCEXEC);
+	if(j < 0){
+		werrstr("can't open #a/tls/encalgs: %r");
+		goto out;
+	}
+	n = read(j, s, MaxAlgF-1);
+	close(j);
+	if(n <= 0){
+		werrstr("nothing in #a/tls/encalgs: %r");
+		goto out;
+	}
+	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|OCEXEC);
+	if(j < 0){
+		werrstr("can't open #a/tls/hashalgs: %r");
+		goto out;
+	}
+	n = read(j, s, MaxAlgF-1);
+	close(j);
+	if(n <= 0){
+		werrstr("nothing in #a/tls/hashalgs: %r");
+		goto out;
+	}
+	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++;
+	}
+out:
+	unlock(&ciphLock);
+	return nciphers;
+}
+
+static Ints*
+makeciphers(int ispsk)
+{
+	Ints *is;
+	int i, j;
+
+	is = newints(nciphers);
+	j = 0;
+	for(i = 0; i < nelem(cipherAlgs); i++)
+		if(cipherAlgs[i].ok && isPSK(cipherAlgs[i].tlsid) == ispsk)
+			is->data[j++] = cipherAlgs[i].tlsid;
+	is->len = j;
+	return is;
+}
+
+
+//================= security functions ========================
+
+// given a public key, set up connection to factotum
+// for using corresponding private key
+static AuthRpc*
+factotum_rsa_open(RSApub *rsapub)
+{
+	int afd;
+	char *s;
+	mpint *n;
+	AuthRpc *rpc;
+
+	// start talking to factotum
+	if((afd = open("/mnt/factotum/rpc", ORDWR|OCEXEC)) < 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){
+		// roll factotum keyring around to match public key
+		n = mpnew(0);
+		while(auth_rpc(rpc, "read", nil, 0) == ARok){
+			if(strtomp(rpc->arg, nil, 16, n) != nil
+			&& mpcmp(n, rsapub->n) == 0){
+				mpfree(n);
+				return rpc;
+			}
+		}
+		mpfree(n);
+	}
+	factotum_rsa_close(rpc);
+	return nil;
+}
+
+static mpint*
+factotum_rsa_decrypt(AuthRpc *rpc, mpint *cipher)
+{
+	char *p;
+	int rv;
+
+	if(cipher == nil)
+		return nil;
+	p = mptoa(cipher, 16, nil, 0);
+	mpfree(cipher);
+	if(p == 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;
+	return strtomp(rpc->arg, nil, 16, nil);
+}
+
+static void
+factotum_rsa_close(AuthRpc *rpc)
+{
+	if(rpc == nil)
+		return;
+	close(rpc->afd);
+	auth_freerpc(rpc);
+}
+
+// buf ^= prf
+static void
+tlsP(uchar *buf, int nbuf, uchar *key, int nkey, uchar *label, int nlabel, uchar *seed, int nseed,
+	DigestState* (*x)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*), int xlen)
+{
+	uchar ai[SHA2_256dlen], tmp[SHA2_256dlen];
+	DigestState *s;
+	int n, i;
+
+	assert(xlen <= sizeof(ai) && xlen <= sizeof(tmp));
+	// generate a1
+	s = x(label, nlabel, key, nkey, nil, nil);
+	x(seed, nseed, key, nkey, ai, s);
+
+	while(nbuf > 0) {
+		s = x(ai, xlen, key, nkey, nil, nil);
+		s = x(label, nlabel, key, nkey, nil, s);
+		x(seed, nseed, key, nkey, tmp, s);
+		n = xlen;
+		if(n > nbuf)
+			n = nbuf;
+		for(i = 0; i < n; i++)
+			buf[i] ^= tmp[i];
+		buf += n;
+		nbuf -= n;
+		x(ai, xlen, key, nkey, tmp, nil);
+		memmove(ai, tmp, xlen);
+	}
+}
+
+
+// fill buf with md5(args)^sha1(args)
+static void
+tls10PRF(uchar *buf, int nbuf, uchar *key, int nkey, char *label, uchar *seed, int nseed)
+{
+	int nlabel = strlen(label);
+	int n = (nkey + 1) >> 1;
+
+	memset(buf, 0, nbuf);
+	tlsP(buf, nbuf, key, n, (uchar*)label, nlabel, seed, nseed,
+		hmac_md5, MD5dlen);
+	tlsP(buf, nbuf, key+nkey-n, n, (uchar*)label, nlabel, seed, nseed,
+		hmac_sha1, SHA1dlen);
+}
+
+static void
+tls12PRF(uchar *buf, int nbuf, uchar *key, int nkey, char *label, uchar *seed, int nseed)
+{
+	memset(buf, 0, nbuf);
+	tlsP(buf, nbuf, key, nkey, (uchar*)label, strlen(label), seed, nseed,
+		hmac_sha2_256, SHA2_256dlen);
+}
+
+static void
+sslPRF(uchar *buf, int nbuf, uchar *key, int nkey, char *label, uchar *seed, int nseed)
+{
+	uchar sha1dig[SHA1dlen], md5dig[MD5dlen], tmp[26];
+	DigestState *s;
+	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);
+		sha1(seed, nseed, 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 void
+sslSetFinished(TlsSec *sec, HandshakeHash hsh, 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, &hsh.md5);
+	md5(sec->sec, MasterSecretSize, nil, &hsh.md5);
+	memset(pad, 0x36, 48);
+	md5(pad, 48, nil, &hsh.md5);
+	md5(nil, 0, h0, &hsh.md5);
+	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, &hsh.sha1);
+	sha1(sec->sec, MasterSecretSize, nil, &hsh.sha1);
+	memset(pad, 0x36, 40);
+	sha1(pad, 40, nil, &hsh.sha1);
+	sha1(nil, 0, h1, &hsh.sha1);
+	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
+tls10SetFinished(TlsSec *sec, HandshakeHash hsh, uchar *finished, int isclient)
+{
+	uchar h[MD5dlen+SHA1dlen];
+	char *label;
+
+	// get current hash value, but allow further messages to be hashed in
+	md5(nil, 0, h, &hsh.md5);
+	sha1(nil, 0, h+MD5dlen, &hsh.sha1);
+
+	if(isclient)
+		label = "client finished";
+	else
+		label = "server finished";
+	tls10PRF(finished, TLSFinishedLen, sec->sec, MasterSecretSize, label, h, sizeof(h));
+}
+
+static void
+tls12SetFinished(TlsSec *sec, HandshakeHash hsh, uchar *finished, int isclient)
+{
+	uchar seed[SHA2_256dlen];
+	char *label;
+
+	// get current hash value, but allow further messages to be hashed in
+	sha2_256(nil, 0, seed, &hsh.sha2_256);
+
+	if(isclient)
+		label = "client finished";
+	else
+		label = "server finished";
+	tls12PRF(finished, TLSFinishedLen, sec->sec, MasterSecretSize, label, seed, SHA2_256dlen);
+}
+
+static void
+tlsSecInits(TlsSec *sec, int cvers, uchar *crandom)
+{
+	memset(sec, 0, sizeof(*sec));
+	sec->clientVers = cvers;
+	memmove(sec->crandom, crandom, RandomSize);
+
+	// putting time()'s output to the first 4 bytes is no
+	// longer recommended and is not useful
+	genrandom(sec->srandom, RandomSize);
+}
+
+static int
+tlsSecRSAs(TlsSec *sec, Bytes *epm)
+{
+	Bytes *pm;
+
+	if(epm == nil){
+		werrstr("no encrypted premaster secret");
+		return -1;
+	}
+	// if the client messed up, just continue as if everything is ok,
+	// to prevent attacks to check for correctly formatted messages.
+	pm = pkcs1_decrypt(sec, epm);
+	if(pm == nil || pm->len != MasterSecretSize || get16(pm->data) != sec->clientVers){
+		freebytes(pm);
+		pm = newbytes(MasterSecretSize);
+		genrandom(pm->data, pm->len);
+	}
+	setMasterSecret(sec, pm);
+	return 0;
+}
+
+static Bytes*
+tlsSecECDHEs1(TlsSec *sec)
+{
+	ECdomain *dom = &sec->ec.dom;
+	ECpriv *Q = &sec->ec.Q;
+	Bytes *par;
+	int n;
+
+	if(sec->nc == nil)
+		return nil;
+	if(sec->nc->tlsid == X25519){
+		par = newbytes(1+2+1+32);
+		par->data[0] = 3;
+		put16(par->data+1, X25519);
+		par->data[3] = 32;
+		curve25519_dh_new(sec->X, par->data+4);
+	}else{
+		ecdominit(dom, sec->nc->init);
+		memset(Q, 0, sizeof(*Q));
+		Q->a.x = mpnew(0);
+		Q->a.y = mpnew(0);
+		Q->d = mpnew(0);
+		ecgen(dom, Q);
+		n = 1 + 2*((mpsignif(dom->p)+7)/8);
+		par = newbytes(1+2+1+n);
+		par->data[0] = 3;
+		put16(par->data+1, sec->nc->tlsid);
+		n = ecencodepub(dom, &Q->a, par->data+4, par->len-4);
+		par->data[3] = n;
+		par->len = 1+2+1+n;
+	}
+	return par;
+}
+
+static int
+tlsSecECDHEs2(TlsSec *sec, Bytes *Yc)
+{
+	ECdomain *dom = &sec->ec.dom;
+	ECpriv *Q = &sec->ec.Q;
+	ECpoint K;
+	ECpub *Y;
+	Bytes *Z;
+
+	if(Yc == nil){
+		werrstr("no public key");
+		return -1;
+	}
+
+	if(sec->nc->tlsid == X25519){
+		if(Yc->len != 32){
+			werrstr("bad public key");
+			return -1;
+		}
+		Z = newbytes(32);
+		if(!curve25519_dh_finish(sec->X, Yc->data, Z->data)){
+			werrstr("unlucky shared key");
+			freebytes(Z);
+			return -1;
+		}
+		setMasterSecret(sec, Z);
+	}else{
+		if((Y = ecdecodepub(dom, Yc->data, Yc->len)) == nil){
+			werrstr("bad public key");
+			return -1;
+		}
+
+		memset(&K, 0, sizeof(K));
+		K.x = mpnew(0);
+		K.y = mpnew(0);
+
+		ecmul(dom, Y, Q->d, &K);
+
+		setMasterSecret(sec, mptobytes(K.x, (mpsignif(dom->p)+7)/8));
+
+		mpfree(K.x);
+		mpfree(K.y);
+
+		ecpubfree(Y);
+	}
+	return 0;
+}
+
+static void
+tlsSecInitc(TlsSec *sec, int cvers)
+{
+	memset(sec, 0, sizeof(*sec));
+	sec->clientVers = cvers;
+	// see the comment on tlsSecInits
+	genrandom(sec->crandom, RandomSize);
+}
+
+static Bytes*
+tlsSecRSAc(TlsSec *sec, uchar *cert, int ncert)
+{
+	RSApub *pub;
+	Bytes *pm, *epm;
+
+	pub = X509toRSApub(cert, ncert, nil, 0);
+	if(pub == nil){
+		werrstr("invalid x509/rsa certificate");
+		return nil;
+	}
+	pm = newbytes(MasterSecretSize);
+	put16(pm->data, sec->clientVers);
+	genrandom(pm->data+2, MasterSecretSize - 2);
+	epm = pkcs1_encrypt(pm, pub);
+	setMasterSecret(sec, pm);
+	rsapubfree(pub);
+	return epm;
+}
+
+static int
+tlsSecFinished(TlsSec *sec, HandshakeHash hsh, uchar *fin, int nfin, int isclient)
+{
+	if(sec->nfin != nfin){
+		werrstr("invalid finished exchange");
+		return -1;
+	}
+	hsh.md5.malloced = 0;
+	hsh.sha1.malloced = 0;
+	hsh.sha2_256.malloced = 0;
+	(*sec->setFinished)(sec, hsh, fin, isclient);
+	return 0;
+}
+
+static void
+tlsSecVers(TlsSec *sec, int v)
+{
+	if(v == SSL3Version){
+		sec->setFinished = sslSetFinished;
+		sec->nfin = SSL3FinishedLen;
+		sec->prf = sslPRF;
+	}else if(v < TLS12Version) {
+		sec->setFinished = tls10SetFinished;
+		sec->nfin = TLSFinishedLen;
+		sec->prf = tls10PRF;
+	}else {
+		sec->setFinished = tls12SetFinished;
+		sec->nfin = TLSFinishedLen;
+		sec->prf = tls12PRF;
+	}
+}
+
+static int
+setSecrets(TlsConnection *c, int isclient)
+{
+	uchar kd[MaxKeyData], seed[2*RandomSize];
+	char *secrets;
+	int rv;
+
+	assert(c->nsecret <= sizeof(kd));
+	secrets = emalloc(2*c->nsecret);
+
+	memmove(seed, c->sec->srandom, RandomSize);
+	memmove(seed+RandomSize, c->sec->crandom, RandomSize);
+	/*
+	 * generate secret keys from the master secret.
+	 *
+	 * different cipher selections will require different amounts
+	 * of key expansion and use of key expansion data,
+	 * but it's all generated using the same function.
+	 */
+	(*c->sec->prf)(kd, c->nsecret, c->sec->sec, MasterSecretSize, "key expansion",
+			seed, sizeof(seed));
+
+	enc64(secrets, 2*c->nsecret, kd, c->nsecret);
+	memset(kd, 0, c->nsecret);
+
+	rv = fprint(c->ctl, "secret %s %s %d %s", c->digest, c->enc, isclient, secrets);
+	memset(secrets, 0, 2*c->nsecret);
+	free(secrets);
+
+	return rv;
+}
+
+/*
+ * set the master secret from the pre-master secret,
+ * destroys premaster.
+ */
+static void
+setMasterSecret(TlsSec *sec, Bytes *pm)
+{
+	uchar seed[2*RandomSize];
+
+	if(sec->psklen > 0){
+		Bytes *opm = pm;
+		uchar *p;
+
+		/* concatenate psk to pre-master secret */
+		pm = newbytes(4 + opm->len + sec->psklen);
+		p = pm->data;
+		put16(p, opm->len), p += 2;
+		memmove(p, opm->data, opm->len), p += opm->len;
+		put16(p, sec->psklen), p += 2;
+		memmove(p, sec->psk, sec->psklen);
+
+		memset(opm->data, 0, opm->len);
+		freebytes(opm);
+	}
+
+	memmove(seed, sec->crandom, RandomSize);
+	memmove(seed+RandomSize, sec->srandom, RandomSize);
+	(*sec->prf)(sec->sec, MasterSecretSize, pm->data, pm->len, "master secret",
+			seed, sizeof(seed));
+
+	memset(pm->data, 0, pm->len);	
+	freebytes(pm);
+}
+
+static int
+digestDHparams(TlsSec *sec, Bytes *par, uchar digest[MAXdlen], int sigalg)
+{
+	int hashalg = (sigalg>>8) & 0xFF;
+	int digestlen;
+	Bytes *blob;
+
+	blob = newbytes(2*RandomSize + par->len);
+	memmove(blob->data+0*RandomSize, sec->crandom, RandomSize);
+	memmove(blob->data+1*RandomSize, sec->srandom, RandomSize);
+	memmove(blob->data+2*RandomSize, par->data, par->len);
+	if(hashalg == 0){
+		digestlen = MD5dlen+SHA1dlen;
+		md5(blob->data, blob->len, digest, nil);
+		sha1(blob->data, blob->len, digest+MD5dlen, nil);
+	} else {
+		digestlen = -1;
+		if(hashalg < nelem(hashfun) && hashfun[hashalg].fun != nil){
+			digestlen = hashfun[hashalg].len;
+			(*hashfun[hashalg].fun)(blob->data, blob->len, digest, nil);
+		}
+	}
+	freebytes(blob);
+	return digestlen;
+}
+
+static char*
+verifyDHparams(TlsSec *sec, Bytes *par, Bytes *cert, Bytes *sig, int sigalg)
+{
+	uchar digest[MAXdlen];
+	int digestlen;
+	ECdomain dom;
+	ECpub *ecpk;
+	RSApub *rsapk;
+	char *err;
+
+	if(par == nil || par->len <= 0)
+		return "no DH parameters";
+
+	if(sig == nil || sig->len <= 0){
+		if(sec->psklen > 0)
+			return nil;
+		return "no signature";
+	}
+
+	if(cert == nil)
+		return "no certificate";
+
+	digestlen = digestDHparams(sec, par, digest, sigalg);
+	if(digestlen <= 0)
+		return "unknown signature digest algorithm";
+	
+	switch(sigalg & 0xFF){
+	case 0x01:
+		rsapk = X509toRSApub(cert->data, cert->len, nil, 0);
+		if(rsapk == nil)
+			return "bad certificate";
+		err = X509rsaverifydigest(sig->data, sig->len, digest, digestlen, rsapk);
+		rsapubfree(rsapk);
+		break;
+	case 0x03:
+		ecpk = X509toECpub(cert->data, cert->len, nil, 0, &dom);
+		if(ecpk == nil)
+			return "bad certificate";
+		err = X509ecdsaverifydigest(sig->data, sig->len, digest, digestlen, &dom, ecpk);
+		ecdomfree(&dom);
+		ecpubfree(ecpk);
+		break;
+	default:
+		err = "signaure algorithm not RSA or ECDSA";
+	}
+
+	return err;
+}
+
+// encrypt data according to PKCS#1, /lib/rfc/rfc2437 9.1.2.1
+static Bytes*
+pkcs1_encrypt(Bytes* data, RSApub* key)
+{
+	mpint *x, *y;
+
+	x = pkcs1padbuf(data->data, data->len, key->n, 2);
+	if(x == nil)
+		return nil;
+	y = rsaencrypt(key, x, nil);
+	mpfree(x);
+	data = newbytes((mpsignif(key->n)+7)/8);
+	mptober(y, data->data, data->len);
+	mpfree(y);
+	return data;
+}
+
+// decrypt data according to PKCS#1, with given key.
+static Bytes*
+pkcs1_decrypt(TlsSec *sec, Bytes *data)
+{
+	mpint *y;
+
+	if(data->len != (mpsignif(sec->rsapub->n)+7)/8)
+		return nil;
+	y = factotum_rsa_decrypt(sec->rpc, bytestomp(data));
+	if(y == nil)
+		return nil;
+	data = mptobytes(y, (mpsignif(y)+7)/8);
+	mpfree(y);
+	if((data->len = pkcs1unpadbuf(data->data, data->len, sec->rsapub->n, 2)) < 0){
+		freebytes(data);
+		return nil;
+	}
+	return data;
+}
+
+static Bytes*
+pkcs1_sign(TlsSec *sec, uchar *digest, int digestlen, int sigalg)
+{
+	int hashalg = (sigalg>>8)&0xFF;
+	mpint *signedMP;
+	Bytes *signature;
+	uchar buf[128];
+
+	if(hashalg > 0 && hashalg < nelem(hashfun) && hashfun[hashalg].len == digestlen)
+		digestlen = asn1encodedigest(hashfun[hashalg].fun, digest, buf, sizeof(buf));
+	else if(digestlen == MD5dlen+SHA1dlen)
+		memmove(buf, digest, digestlen);
+	else
+		digestlen = -1;
+	if(digestlen <= 0){
+		werrstr("bad digest algorithm");
+		return nil;
+	}
+
+	signedMP = factotum_rsa_decrypt(sec->rpc, pkcs1padbuf(buf, digestlen, sec->rsapub->n, 1));
+	if(signedMP == nil)
+		return nil;
+	signature = mptobytes(signedMP, (mpsignif(sec->rsapub->n)+7)/8);
+	mpfree(signedMP);
+	return signature;
+}
+
+
+//================= general utility functions ========================
+
+static void *
+emalloc(int n)
+{
+	void *p;
+	if(n==0)
+		n=1;
+	p = malloc(n);
+	if(p == nil)
+		sysfatal("out of memory");
+	memset(p, 0, n);
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+static void *
+erealloc(void *ReallocP, int ReallocN)
+{
+	if(ReallocN == 0)
+		ReallocN = 1;
+	if(ReallocP == nil)
+		ReallocP = emalloc(ReallocN);
+	else if((ReallocP = realloc(ReallocP, ReallocN)) == nil)
+		sysfatal("out of memory");
+	setrealloctag(ReallocP, getcallerpc(&ReallocP));
+	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 int
+get24(uchar *p)
+{
+	return (p[0]<<16)|(p[1]<<8)|p[2];
+}
+
+static int
+get16(uchar *p)
+{
+	return (p[0]<<8)|p[1];
+}
+
+static Bytes*
+newbytes(int len)
+{
+	Bytes* ans;
+
+	if(len < 0)
+		abort();
+	ans = emalloc(sizeof(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)
+{
+	free(b);
+}
+
+static mpint*
+bytestomp(Bytes* bytes)
+{
+	return betomp(bytes->data, bytes->len, nil);
+}
+
+/*
+ * Convert mpint* to Bytes, putting high order byte first.
+ */
+static Bytes*
+mptobytes(mpint *big, int len)
+{
+	Bytes* ans;
+
+	if(len == 0) len++;
+	ans = newbytes(len);
+	mptober(big, ans->data, ans->len);
+	return ans;
+}
+
+/* len is number of ints */
+static Ints*
+newints(int len)
+{
+	Ints* ans;
+
+	if(len < 0 || len > ((uint)-1>>1)/sizeof(int))
+		abort();
+	ans = emalloc(sizeof(Ints) + len*sizeof(int));
+	ans->len = len;
+	return ans;
+}
+
+static void
+freeints(Ints* b)
+{
+	free(b);
+}
+
+static int
+lookupid(Ints* b, int id)
+{
+	int i;
+
+	for(i=0; i<b->len; i++)
+		if(b->data[i] == id)
+			return i;
+	return -1;
+}
--- /dev/null
+++ b/libsec/tsmemcmp.c
@@ -1,0 +1,25 @@
+#include "os.h"
+#include <libsec.h>
+
+/*
+ * timing safe memcmp()
+ */
+int
+tsmemcmp(void *a1, void *a2, ulong n)
+{
+	int lt, gt, c1, c2, r, m;
+	uchar *s1, *s2;
+
+	r = m = 0;
+	s1 = a1;
+	s2 = a2;
+	while(n--){
+		c1 = *s1++;
+		c2 = *s2++;
+		lt = (c1 - c2) >> 8;
+		gt = (c2 - c1) >> 8;
+		r |= (lt - gt) & ~m;
+		m |= lt | gt;
+	}
+	return r;
+}
--- /dev/null
+++ b/libsec/x509.c
@@ -1,0 +1,3033 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+
+/*=============================================================*/
+/*  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 UTF8String 12
+#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[];
+};
+
+struct Ints {
+	int	len;
+	int	data[];
+};
+
+struct Bits {
+	int	len;		/* number of bytes */
+	int	unusedbits;	/* unused bits in last byte */
+	uchar	data[];		/* 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	encode(Elem e, Bytes** pbytes);
+static int	oid_lookup(Ints* o, Ints** tab);
+static void	freevalfields(Value* v);
+static mpint	*asn1mpint(Elem *e);
+static void	edump(Elem);
+
+#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)
+		sysfatal("out of memory");
+	memset(p, 0, n);
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+static char*
+estrdup(char *s)
+{
+	char *d;
+	int n;
+
+	n = strlen(s)+1;
+	d = emalloc(n);
+	memmove(d, s, n);
+	return d;
+}
+
+
+/*
+ * 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;
+	int err;
+
+	err = ber_decode(&p, &a[alen], pelem);
+	if(err == ASN_OK && p != &a[alen])
+		err = ASN_EVALLEN;
+	return err;
+}
+
+/*
+ * 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;
+
+	memset(pelem, 0, sizeof(*pelem));
+	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
+			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 UTF8String:
+	case NumericString:
+	case PrintableString:
+	case TeletexString:
+	case VideotexString:
+	case IA5String:
+	case UTCTime:
+	case GeneralizedTime:
+	case GraphicString:
+	case VisibleString:
+	case GeneralString:
+	case UniversalString:
+	case BMPString:
+		err = octet_decode(&p, pend, length, isconstr, &va);
+		if(err == ASN_OK) {
+			uchar *s;
+			char *d;
+			Rune r;
+			int n;
+
+			switch(kind){
+			case UniversalString:
+				n = va->len / 4;
+				d = emalloc(n*UTFmax+1);
+				pval->u.stringval = d;
+				s = va->data;
+				while(n > 0){
+					r = s[0]<<24 | s[1]<<16 | s[2]<<8 | s[3];
+					if(r == 0)
+						break;
+					n--;
+					s += 4;
+					d += runetochar(d, &r);
+				}
+				*d = 0;
+				break;
+			case BMPString:
+				n = va->len / 2;
+				d = emalloc(n*UTFmax+1);
+				pval->u.stringval = d;
+				s = va->data;
+				while(n > 0){
+					r = s[0]<<8 | s[1];
+					if(r == 0)
+						break;
+					n--;
+					s += 2;
+					d += runetochar(d, &r);
+				}
+				*d = 0;
+				break;
+			default:
+				n = va->len;
+				d = emalloc(n+1);
+				pval->u.stringval = d;
+				s = va->data;
+				while(n > 0){
+					if((*d = *s) == 0)
+						break;
+					n--;
+					s++;
+					d++;
+				}
+				*d = 0;
+				break;
+			}
+			if(n != 0){
+				err = ASN_EINVAL;
+				free(pval->u.stringval);
+			} else 
+				pval->tag = VString;
+			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);
+				freevalfields(&elem.val);
+				freebytes(ans);
+				ans = newans;
+				break;
+
+			case VEOC:
+				if(length == -1)
+					goto cloop_done;
+				/* no break */
+			default:
+				freevalfields(&elem.val);
+				p = pold;
+				err = ASN_EINVAL;
+				goto cloop_done;
+			}
+		}
+cloop_done:
+		if(err != ASN_OK){
+			freebytes(ans);
+			ans = nil;
+		}
+	}
+	*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)
+			freeelist(lve);
+		else {
+			/* 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 UTF8String:
+	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)
+{
+	if(pe->tag.class == Universal && pe->tag.num == INTEGER && pe->val.tag == VBigInt) {
+		*pbigint = pe->val.u.bigintval;
+		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 UTF8String:
+		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;
+
+	if(len < 0)
+		abort();
+	ans = emalloc(sizeof(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)
+{
+	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;
+
+	if(len < 0 || len > ((uint)-1>>1)/sizeof(int))
+		abort();
+	ans = emalloc(sizeof(Ints) + len*sizeof(int));
+	ans->len = len;
+	return ans;
+}
+
+static Ints*
+makeints(int* buf, int len)
+{
+	Ints* ans;
+
+	ans = newints(len);
+	memmove(ans->data, buf, len*sizeof(int));
+	return ans;
+}
+
+static void
+freeints(Ints* b)
+{
+	free(b);
+}
+
+/* len is number of bytes */
+static Bits*
+newbits(int len)
+{
+	Bits* ans;
+
+	if(len < 0)
+		abort();
+	ans = emalloc(sizeof(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)
+{
+	free(b);
+}
+
+static Elist*
+mkel(Elem e, Elist* tail)
+{
+	Elist* el;
+
+	el = (Elist*)emalloc(sizeof(Elist));
+	setmalloctag(el, getcallerpc(&e));
+	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:
+		free(v->u.stringval);
+		break;
+	case VSeq:
+		el = v->u.seqval;
+		for(l = el; l != nil; l = l->tl)
+			freevalfields(&l->hd.val);
+		freeelist(el);
+		break;
+	case VSet:
+		el = v->u.setval;
+		for(l = el; l != nil; l = l->tl)
+			freevalfields(&l->hd.val);
+		freeelist(el);
+		break;
+	}
+	memset(v, 0, sizeof(*v));
+}
+
+static mpint*
+asn1mpint(Elem *e)
+{
+	Bytes *b;
+	int v;
+
+	if(is_int(e, &v))
+		return itomp(v, nil);
+	if(is_bigint(e, &b))
+		return betomp(b->data, b->len, nil);
+	return nil;
+}
+
+/* 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;
+	Bits*	publickey;
+	int	signature_alg;
+	Bits*	signature;
+	int	curve;
+} CertX509;
+
+/* Algorithm object-ids */
+enum {
+	ALG_rsaEncryption,
+	ALG_md2WithRSAEncryption,
+	ALG_md4WithRSAEncryption,
+	ALG_md5WithRSAEncryption,
+
+	ALG_sha1WithRSAEncryption,
+	ALG_sha1WithRSAEncryptionOiw,
+
+	ALG_sha256WithRSAEncryption,
+	ALG_sha384WithRSAEncryption,
+	ALG_sha512WithRSAEncryption,
+	ALG_sha224WithRSAEncryption,
+
+	ALG_ecPublicKey,
+	ALG_sha1WithECDSA,
+	ALG_sha256WithECDSA,
+	ALG_sha384WithECDSA,
+	ALG_sha512WithECDSA,
+
+	ALG_md5,
+	ALG_sha1,
+	ALG_sha256,
+	ALG_sha384,
+	ALG_sha512,
+	ALG_sha224,
+
+	NUMALGS
+};
+
+typedef struct Ints15 {
+	int		len;
+	int		data[15];
+} Ints15;
+
+typedef struct DigestAlg {
+	int		alg;
+	DigestState*	(*fun)(uchar*,ulong,uchar*,DigestState*);
+	int		len;
+} DigestAlg;
+
+static DigestAlg alg_md5 = { ALG_md5, md5, MD5dlen};
+static DigestAlg alg_sha1 = { ALG_sha1, sha1, SHA1dlen };
+static DigestAlg alg_sha256 = { ALG_sha256, sha2_256, SHA2_256dlen };
+static DigestAlg alg_sha384 = { ALG_sha384, sha2_384, SHA2_384dlen };
+static DigestAlg alg_sha512 = { ALG_sha512, sha2_512, SHA2_512dlen };
+static DigestAlg alg_sha224 = { ALG_sha224, sha2_224, SHA2_224dlen };
+
+/* maximum length of digest output of the digest algs above */
+enum {
+	MAXdlen = SHA2_512dlen,
+};
+
+static Ints15 oid_rsaEncryption = {7, 1, 2, 840, 113549, 1, 1, 1 };
+
+static Ints15 oid_md2WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 2 };
+static Ints15 oid_md4WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 3 };
+static Ints15 oid_md5WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 4 };
+static Ints15 oid_sha1WithRSAEncryption ={7, 1, 2, 840, 113549, 1, 1, 5 };
+static Ints15 oid_sha1WithRSAEncryptionOiw ={6, 1, 3, 14, 3, 2, 29 };
+static Ints15 oid_sha256WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 11 };
+static Ints15 oid_sha384WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 12 };
+static Ints15 oid_sha512WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 13 };
+static Ints15 oid_sha224WithRSAEncryption = {7, 1, 2, 840, 113549, 1, 1, 14 };
+
+static Ints15 oid_ecPublicKey = {6, 1, 2, 840, 10045, 2, 1 };
+static Ints15 oid_sha1WithECDSA = {6, 1, 2, 840, 10045, 4, 1 };
+static Ints15 oid_sha256WithECDSA = {7, 1, 2, 840, 10045, 4, 3, 2 };
+static Ints15 oid_sha384WithECDSA = {7, 1, 2, 840, 10045, 4, 3, 3 };
+static Ints15 oid_sha512WithECDSA = {7, 1, 2, 840, 10045, 4, 3, 4 };
+
+static Ints15 oid_md5 = {6, 1, 2, 840, 113549, 2, 5 };
+static Ints15 oid_sha1 = {6, 1, 3, 14, 3, 2, 26 };
+static Ints15 oid_sha256= {9, 2, 16, 840, 1, 101, 3, 4, 2, 1 };
+static Ints15 oid_sha384= {9, 2, 16, 840, 1, 101, 3, 4, 2, 2 };
+static Ints15 oid_sha512= {9, 2, 16, 840, 1, 101, 3, 4, 2, 3 };
+static Ints15 oid_sha224= {9, 2, 16, 840, 1, 101, 3, 4, 2, 4 };
+
+static Ints *alg_oid_tab[NUMALGS+1] = {
+	(Ints*)&oid_rsaEncryption,
+	(Ints*)&oid_md2WithRSAEncryption,
+	(Ints*)&oid_md4WithRSAEncryption,
+	(Ints*)&oid_md5WithRSAEncryption,
+
+	(Ints*)&oid_sha1WithRSAEncryption,
+	(Ints*)&oid_sha1WithRSAEncryptionOiw,
+
+	(Ints*)&oid_sha256WithRSAEncryption,
+	(Ints*)&oid_sha384WithRSAEncryption,
+	(Ints*)&oid_sha512WithRSAEncryption,
+	(Ints*)&oid_sha224WithRSAEncryption,
+
+	(Ints*)&oid_ecPublicKey,
+	(Ints*)&oid_sha1WithECDSA,
+	(Ints*)&oid_sha256WithECDSA,
+	(Ints*)&oid_sha384WithECDSA,
+	(Ints*)&oid_sha512WithECDSA,
+
+	(Ints*)&oid_md5,
+	(Ints*)&oid_sha1,
+	(Ints*)&oid_sha256,
+	(Ints*)&oid_sha384,
+	(Ints*)&oid_sha512,
+	(Ints*)&oid_sha224,
+	nil
+};
+
+static DigestAlg *digestalg[NUMALGS+1] = {
+	&alg_md5, &alg_md5, &alg_md5, &alg_md5,
+	&alg_sha1, &alg_sha1,
+	&alg_sha256, &alg_sha384, &alg_sha512, &alg_sha224,
+	&alg_sha256, &alg_sha1, &alg_sha256, &alg_sha384, &alg_sha512,
+	&alg_md5, &alg_sha1, &alg_sha256, &alg_sha384, &alg_sha512, &alg_sha224,
+	nil
+};
+
+static Bytes* encode_digest(DigestAlg *da, uchar *digest);
+
+static Ints15 oid_secp256r1 = {7, 1, 2, 840, 10045, 3, 1, 7};
+static Ints15 oid_secp384r1 = {5, 1, 3, 132, 0, 34};
+
+static Ints *namedcurves_oid_tab[] = {
+	(Ints*)&oid_secp256r1,
+	(Ints*)&oid_secp384r1,
+	nil,
+};
+static void (*namedcurves[])(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h) = {
+	secp256r1,
+	secp384r1,
+	nil,
+};
+
+static void
+freecert(CertX509* c)
+{
+	if(c == nil)
+		return;
+	free(c->issuer);
+	free(c->validity_start);
+	free(c->validity_end);
+	free(c->subject);
+	freebits(c->publickey);
+	freebits(c->signature);
+	free(c);
+}
+
+/*
+ * 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 int
+parse_curve(Elem* e)
+{
+	Elist* el;
+	Ints* oid;
+
+	if(!is_seq(e, &el) || elistlen(el)<2 || !is_oid(&el->tl->hd, &oid))
+		return -1;
+	return oid_lookup(oid, namedcurves_oid_tab);
+}
+
+static CertX509*
+decode_cert(uchar *buf, int len)
+{
+	int ok = 0;
+	int n;
+	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;
+	CertX509* c = nil;
+
+	if(decode(buf, 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;
+	c->curve = -1;
+	if(c->publickey_alg == ALG_ecPublicKey){
+		c->curve = parse_curve(&elpubkey->hd);
+		if(c->curve < 0)
+			goto errret;
+	}
+	elpubkey = elpubkey->tl;
+	if(!is_bitstring(&elpubkey->hd, &bits))
+		goto errret;
+	elpubkey->hd.val.u.bitstringval = nil;	/* transfer ownership */
+	c->publickey = bits;
+
+	/*resume Certificate */
+	if(c->signature_alg < 0)
+		goto errret;
+	if(!is_bitstring(esig, &bits))
+		goto errret;
+	esig->val.u.bitstringval = nil;	/* transfer ownership */
+	c->signature = bits;
+	ok = 1;
+
+errret:
+	freevalfields(&ecert.val);	/* recurses through lists, too */
+	if(!ok){
+		freecert(c);
+		c = nil;
+	}
+	return c;
+}
+
+/*
+ *	RSAPublickKey ::= SEQUENCE {
+ *		modulus INTEGER,
+ *		publicExponent INTEGER
+ *	}
+ */
+RSApub*
+asn1toRSApub(uchar *buf, int len)
+{
+	Elem e;
+	Elist *el;
+	RSApub* key;
+
+	key = nil;
+	if(decode(buf, len, &e) != ASN_OK)
+		goto errret;
+	if(!is_seq(&e, &el) || elistlen(el) != 2)
+		goto errret;
+
+	key = rsapuballoc();
+	if((key->n = asn1mpint(&el->hd)) == nil)
+		goto errret;
+	el = el->tl;
+	if((key->ek = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	freevalfields(&e.val);
+	return key;
+errret:
+	freevalfields(&e.val);
+	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 }
+ */
+RSApriv*
+asn1toRSApriv(uchar *buf, int len)
+{
+	int version;
+	Elem e;
+	Elist *el;
+	Bytes *b;
+	RSApriv* key = nil;
+
+	if(decode(buf, len, &e) != ASN_OK)
+		goto errret;
+	if(!is_seq(&e, &el))
+		goto errret;
+
+	if(!is_int(&el->hd, &version) || version != 0)
+		goto errret;
+
+	if(elistlen(el) != 9){
+		if(elistlen(el) == 3
+		&& parse_alg(&el->tl->hd) == ALG_rsaEncryption
+		&& is_octetstring(&el->tl->tl->hd, &b)){
+			key = asn1toRSApriv(b->data, b->len);
+			if(key != nil)
+				goto done;
+		}
+		goto errret;
+	}
+
+	key = rsaprivalloc();
+	el = el->tl;
+	if((key->pub.n = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	el = el->tl;
+	if((key->pub.ek = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	el = el->tl;
+	if((key->dk = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	el = el->tl;
+	if((key->q = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	el = el->tl;
+	if((key->p = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	el = el->tl;
+	if((key->kq = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	el = el->tl;
+	if((key->kp = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+	el = el->tl;
+	if((key->c2 = asn1mpint(&el->hd)) == nil)
+		goto errret;
+
+done:
+	freevalfields(&e.val);
+	return key;
+errret:
+	freevalfields(&e.val);
+	rsaprivfree(key);
+	return nil;
+}
+
+/*
+ * 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 int
+digest_certinfo(uchar *cert, int ncert, DigestAlg *da, uchar *digest)
+{
+	uchar *info, *p, *pend;
+	int isconstr, length;
+	Tag tag;
+	Elem elem;
+
+	p = cert;
+	pend = cert + ncert;
+	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 ||
+	   p+length < p)
+		return -1;
+	info = p;
+	if(ber_decode(&p, pend, &elem) != ASN_OK)
+		return -1;
+	freevalfields(&elem.val);
+	if(elem.tag.num != SEQUENCE)
+		return -1;
+	(*da->fun)(info, p - info, digest, nil);
+	return da->len;
+}
+
+mpint*
+pkcs1padbuf(uchar *buf, int len, mpint *modulus, int blocktype)
+{
+	int i, n = (mpsignif(modulus)-1)/8;
+	int pad = n - 2 - len;
+	uchar *p;
+	mpint *mp;
+
+	if(pad < 8){
+		werrstr("rsa modulus too small");
+		return nil;
+	}
+	if((p = malloc(n)) == nil)
+		return nil;
+	p[0] = blocktype;
+	switch(blocktype){
+	default:
+	case 1:
+		memset(p+1, 0xFF, pad);
+		break;
+	case 2:
+		for(i=1; i <= pad; i++)
+			p[i] = 1 + nfastrand(255);
+		break;
+	}
+	p[1+pad] = 0;
+	memmove(p+2+pad, buf, len);
+	mp = betomp(p, n, nil);
+	free(p);
+	return mp;
+}
+
+int
+pkcs1unpadbuf(uchar *buf, int len, mpint *modulus, int blocktype)
+{
+	uchar *p = buf + 1, *e = buf + len;
+
+	if(len < 1 || len != (mpsignif(modulus)-1)/8 || buf[0] != blocktype)
+		return -1;
+	switch(blocktype){
+	default:
+	case 1:
+		while(p < e && *p == 0xFF)
+			p++;
+		break;
+	case 2:
+		while(p < e && *p != 0x00)
+			p++;
+		break;
+	}
+	if(p - buf <= 8 || p >= e || *p++ != 0x00)
+		return -1;
+	memmove(buf, p, len = e - p);
+	return len;
+}
+
+static char Ebadsig[] = "bad signature";
+
+char*
+X509rsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, RSApub *pk)
+{
+	mpint *x, *y;
+	DigestAlg **dp;
+	Bytes *digest;
+	uchar *buf;
+	int len;
+	char *err;
+
+	x = betomp(sig, siglen, nil);
+	y = rsaencrypt(pk, x, nil);
+	mpfree(x);
+	len = mptobe(y, nil, 0, &buf);
+	mpfree(y);	
+
+	err = Ebadsig;
+	len = pkcs1unpadbuf(buf, len, pk->n, 1);
+	if(len == edigestlen && tsmemcmp(buf, edigest, edigestlen) == 0)
+		err = nil;
+	for(dp = digestalg; err != nil && *dp != nil; dp++){
+		if((*dp)->len != edigestlen)
+			continue;
+		digest = encode_digest(*dp, edigest);
+		if(digest->len == len && tsmemcmp(digest->data, buf, len) == 0)
+			err = nil;
+		freebytes(digest);
+	}
+	free(buf);
+	return err;
+}
+
+char*
+X509ecdsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, ECdomain *dom, ECpub *pub)
+{
+	Elem e;
+	Elist *el;
+	mpint *r, *s;
+	char *err;
+
+	r = s = nil;
+	err = Ebadsig;
+	if(decode(sig, siglen, &e) != ASN_OK)
+		goto end;
+	if(!is_seq(&e, &el) || elistlen(el) != 2)
+		goto end;
+	r = asn1mpint(&el->hd);
+	if(r == nil)
+		goto end;
+	el = el->tl;
+	s = asn1mpint(&el->hd);
+	if(s == nil)
+		goto end;
+	if(ecdsaverify(dom, pub, edigest, edigestlen, r, s))
+		err = nil;
+end:
+	freevalfields(&e.val);
+	mpfree(s);
+	mpfree(r);
+	return err;
+}
+
+static void
+copysubject(char *name, int nname, char *subject)
+{
+	char *e;
+
+	if(name == nil)
+		return;
+	memset(name, 0, nname);
+	if(subject == nil)
+		return;
+	strncpy(name, subject, nname-1);
+	e = strchr(name, ',');
+	if(e != nil)
+		*e = 0;	/* take just CN part of Distinguished Name */
+}
+
+ECpub*
+X509toECpub(uchar *cert, int ncert, char *name, int nname, ECdomain *dom)
+{
+	CertX509 *c;
+	ECpub *pub;
+
+	c = decode_cert(cert, ncert);
+	if(c == nil)
+		return nil;
+	copysubject(name, nname, c->subject);
+	pub = nil;
+	if(c->publickey_alg == ALG_ecPublicKey){
+		ecdominit(dom, namedcurves[c->curve]);
+		pub = ecdecodepub(dom, c->publickey->data, c->publickey->len);
+		if(pub == nil)
+			ecdomfree(dom);
+	}
+	freecert(c);
+	return pub;
+}
+
+char*
+X509ecdsaverify(uchar *cert, int ncert, ECdomain *dom, ECpub *pk)
+{
+	char *e;
+	CertX509 *c;
+	int digestlen;
+	uchar digest[MAXdlen];
+
+	c = decode_cert(cert, ncert);
+	if(c == nil)
+		return "cannot decode cert";
+	digestlen = digest_certinfo(cert, ncert, digestalg[c->signature_alg], digest);
+	if(digestlen <= 0){
+		freecert(c);
+		return "cannot decode certinfo";
+	}
+	e = X509ecdsaverifydigest(c->signature->data, c->signature->len, digest, digestlen, dom, pk);
+	freecert(c);
+	return e;
+}
+
+RSApub*
+X509toRSApub(uchar *cert, int ncert, char *name, int nname)
+{
+	CertX509 *c;
+	RSApub *pub;
+
+	c = decode_cert(cert, ncert);
+	if(c == nil)
+		return nil;
+	copysubject(name, nname, c->subject);
+	pub = nil;
+	if(c->publickey_alg == ALG_rsaEncryption)
+		pub = asn1toRSApub(c->publickey->data, c->publickey->len);
+	freecert(c);
+	return pub;
+}
+
+char*
+X509rsaverify(uchar *cert, int ncert, RSApub *pk)
+{
+	char *e;
+	CertX509 *c;
+	int digestlen;
+	uchar digest[MAXdlen];
+
+	c = decode_cert(cert, ncert);
+	if(c == nil)
+		return "cannot decode cert";
+	digestlen = digest_certinfo(cert, ncert, digestalg[c->signature_alg], digest);
+	if(digestlen <= 0){
+		freecert(c);
+		return "cannot decode certinfo";
+	}
+	e = X509rsaverifydigest(c->signature->data, c->signature->len, digest, digestlen, pk);
+	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;
+
+	e.tag.class = Universal;
+	e.tag.num = INTEGER;
+	e.val.tag = VBigInt;
+	e.val.u.bigintval = newbytes((mpsignif(p)+8)/8);
+	if(p->sign < 0){
+		mpint *s = mpnew(e.val.u.bigintval->len*8+1);
+		mpleft(mpone, e.val.u.bigintval->len*8, s);
+		mpadd(p, s, s);
+		mptober(s, e.val.u.bigintval->data, e.val.u.bigintval->len);
+		mpfree(s);
+	} else {
+		mptober(p, e.val.u.bigintval->data, e.val.u.bigintval->len);
+	}
+	return e;
+}
+
+static int
+printable(char *s)
+{
+	int c;
+
+	while((c = (uchar)*s++) != 0){
+		if((c >= 'a' && c <= 'z')
+		|| (c >= 'A' && c <= 'Z')
+		|| (c >= '0' && c <= '9')
+		|| strchr("'=()+,-./:? ", c) != nil)
+			continue;
+		return 0;
+	}
+	return 1;
+}
+
+#define DirectoryString 0
+
+static Elem
+mkstring(char *s, int t)
+{
+	Elem e;
+
+	if(t == DirectoryString)
+		t = printable(s) ? PrintableString : UTF8String;
+	e.tag.class = Universal;
+	e.tag.num = t;
+	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, sizeof(utc), "%.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];
+	int	stype;
+} Ints7pref;
+Ints7pref DN_oid[] = {
+	{4, 2, 5, 4, 6, 0, 0, 0,        "C=", PrintableString},
+	{4, 2, 5, 4, 8, 0, 0, 0,        "ST=",DirectoryString},
+	{4, 2, 5, 4, 7, 0, 0, 0,        "L=", DirectoryString},
+	{4, 2, 5, 4, 10, 0, 0, 0,       "O=", DirectoryString},
+	{4, 2, 5, 4, 11, 0, 0, 0,       "OU=",DirectoryString},
+	{4, 2, 5, 4, 3, 0, 0, 0,        "CN=",DirectoryString},
+	{7, 1,2,840,113549,1,9,1,       "E=", IA5String},
+	{7, 0,9,2342,19200300,100,1,25,	"DC=",IA5String},
+};
+
+static Elem
+mkname(Ints7pref *oid, char *subj)
+{
+	return mkset(mkel(mkseq(mkel(mkoid((Ints*)oid), mkel(mkstring(subj, oid->stype), 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);
+}
+
+/*
+ * DigestInfo ::= SEQUENCE {
+ *	digestAlgorithm AlgorithmIdentifier,
+ *	digest OCTET STRING }
+ */
+static Bytes*
+encode_digest(DigestAlg *da, uchar *digest)
+{
+	Bytes *b = nil;
+	Elem e = mkseq(
+		mkel(mkalg(da->alg),
+		mkel(mkoctet(digest, da->len),
+		nil)));
+	encode(e, &b);
+	freevalfields(&e.val);
+	return b;
+}
+
+int
+asn1encodedigest(DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest, uchar *buf, int len)
+{
+	Bytes *bytes;
+	DigestAlg **dp;
+
+	for(dp = digestalg; *dp != nil; dp++){
+		if((*dp)->fun != fun)
+			continue;
+		bytes = encode_digest(*dp, digest);
+		if(bytes == nil)
+			break;
+		if(bytes->len > len){
+			freebytes(bytes);
+			break;
+		}
+		len = bytes->len;
+		memmove(buf, bytes->data, len);
+		freebytes(bytes);
+		return len;
+	}
+	return -1;
+}
+
+static Elem
+mkcont(Elem e, int num)
+{
+	e = mkseq(mkel(e, nil));
+	e.tag.class = Context;
+	e.tag.num = num;
+	return e;
+}
+
+static Elem
+mkaltname(char *s)
+{
+	Elem e;
+	int i;
+
+	for(i=0; i<nelem(DN_oid); i++){
+		if(strstr(s, DN_oid[i].prefix) != nil)
+			return mkcont(mkDN(s), 4); /* DN */
+	}
+	e = mkstring(s, IA5String);
+	e.tag.class = Context;
+	e.tag.num = strchr(s, '@') != nil ? 1 : 2; /* email : DNS */
+	return e;
+}
+
+static Elist*
+mkaltnames(char *alts)
+{
+	Elist *el;
+	char *s, *p;
+
+	if(alts == nil)
+		return nil;
+
+	el = nil;
+	alts = estrdup(alts);
+	for(s = alts; s != nil; s = p){
+		while(*s == ' ')
+			s++;
+		if(*s == '\0')
+			break;
+		if((p = strchr(s, ',')) != nil)
+			*p++ = 0;
+		el = mkel(mkaltname(s), el);
+	}
+	free(alts);
+	return el;
+}
+
+static Elist*
+mkextel(Elem e, Ints *oid, Elist *el)
+{
+	Bytes *b = nil;
+
+	if(encode(e, &b) == ASN_OK){
+		el = mkel(mkseq(
+			mkel(mkoid(oid),
+			mkel(mkoctet(b->data, b->len),
+			nil))), el);
+		freebytes(b);
+	}
+	freevalfields(&e.val);
+	return el;
+}
+
+static Ints15 oid_subjectAltName = {4, 2, 5, 29, 17 };
+static Ints15 oid_extensionRequest = { 7, 1, 2, 840, 113549, 1, 9, 14};
+
+static Elist*
+mkextensions(char *alts, int req)
+{
+	Elist *sl, *xl;
+
+	xl = nil;
+	if((sl = mkaltnames(alts)) != nil)
+		xl = mkextel(mkseq(sl), (Ints*)&oid_subjectAltName, xl);
+	if(xl != nil){
+		if(req) return mkel(mkcont(mkseq(
+			mkel(mkoid((Ints*)&oid_extensionRequest),
+			mkel(mkset(mkel(mkseq(xl), nil)), nil))), 0), nil);
+		return mkel(mkcont(mkseq(xl), 3), nil);
+	}
+	return nil;
+}
+
+static char*
+splitalts(char *s)
+{
+	int q;
+
+	for(q = 0; *s != '\0'; s++){
+		if(*s == '\'')
+			q ^= 1;
+		else if(q == 0 && *s == ','){
+			*s++ = 0;
+			return s;
+		}
+	}
+	return nil;
+}
+
+static Bytes*
+encode_rsapubkey(RSApub *pk)
+{
+	Bytes *b = nil;
+	Elem e = mkseq(
+		mkel(mkbigint(pk->n),
+		mkel(mpsignif(pk->ek)<32 ? mkint(mptoi(pk->ek)) : mkbigint(pk->ek),
+		nil)));
+	encode(e, &b);
+	freevalfields(&e.val);
+	return b;
+}
+
+int
+asn1encodeRSApub(RSApub *pk, uchar *buf, int len)
+{
+	Bytes *b = encode_rsapubkey(pk);
+	if(b == nil)
+		return -1;
+	if(b->len > len){
+		freebytes(b);
+		werrstr("buffer too small");
+		return -1;
+	}
+	memmove(buf, b->data, len = b->len);
+	freebytes(b);
+	return len;
+}
+
+uchar*
+X509rsagen(RSApriv *priv, char *subj, ulong valid[2], int *certlen)
+{
+	int serial = 0, sigalg = ALG_sha256WithRSAEncryption;
+	uchar *cert = nil;
+	Bytes *certbytes, *pkbytes, *certinfobytes, *sigbytes;
+	Elem e, certinfo;
+	DigestAlg *da;
+	uchar digest[MAXdlen], *buf;
+	int buflen;
+	mpint *pkcs1;
+	char *alts;
+
+	if((pkbytes = encode_rsapubkey(&priv->pub)) == nil)
+		return nil;
+
+	subj = estrdup(subj);
+	alts = splitalts(subj);
+
+	e = mkseq(
+		mkel(mkcont(mkint(2), 0),
+		mkel(mkint(serial),
+		mkel(mkalg(sigalg),
+		mkel(mkDN(subj),
+		mkel(mkseq(
+			mkel(mkutc(valid[0]),
+			mkel(mkutc(valid[1]),
+			nil))),
+		mkel(mkDN(subj),
+		mkel(mkseq(
+			mkel(mkalg(ALG_rsaEncryption),
+			mkel(mkbits(pkbytes->data, pkbytes->len),
+			nil))),
+		mkextensions(alts, 0)))))))));
+	freebytes(pkbytes);
+	if(encode(e, &certinfobytes) != ASN_OK)
+		goto errret;
+
+	da = digestalg[sigalg];
+	(*da->fun)(certinfobytes->data, certinfobytes->len, digest, 0);
+	freebytes(certinfobytes);
+	certinfo = e;
+
+	sigbytes = encode_digest(da, digest);
+	if(sigbytes == nil)
+		goto errret;
+	pkcs1 = pkcs1padbuf(sigbytes->data, sigbytes->len, priv->pub.n, 1);
+	freebytes(sigbytes);
+	if(pkcs1 == nil)
+		goto errret;
+
+	rsadecrypt(priv, pkcs1, pkcs1);
+	buflen = mptobe(pkcs1, nil, 0, &buf);
+	mpfree(pkcs1);
+	e = mkseq(
+		mkel(certinfo,
+		mkel(mkalg(sigalg),
+		mkel(mkbits(buf, buflen),
+		nil))));
+	free(buf);
+	if(encode(e, &certbytes) != ASN_OK)
+		goto errret;
+	if(certlen != nil)
+		*certlen = certbytes->len;
+	cert = (uchar*)certbytes;
+	memmove(cert, certbytes->data, certbytes->len);
+errret:
+	freevalfields(&e.val);
+	free(subj);
+	return cert;
+}
+
+uchar*
+X509rsareq(RSApriv *priv, char *subj, int *certlen)
+{
+	/* RFC 2314, PKCS #10 Certification Request Syntax */
+	int version = 0, sigalg = ALG_sha256WithRSAEncryption;
+	uchar *cert = nil;
+	Bytes *certbytes, *pkbytes, *certinfobytes, *sigbytes;
+	Elem e, certinfo;
+	DigestAlg *da;
+	uchar digest[MAXdlen], *buf;
+	int buflen;
+	mpint *pkcs1;
+	char *alts;
+
+	if((pkbytes = encode_rsapubkey(&priv->pub)) == nil)
+		return nil;
+
+	subj = estrdup(subj);
+	alts = splitalts(subj);
+
+	e = mkseq(
+		mkel(mkint(version),
+		mkel(mkDN(subj),
+		mkel(mkseq(
+			mkel(mkalg(ALG_rsaEncryption),
+			mkel(mkbits(pkbytes->data, pkbytes->len),
+			nil))),
+		mkextensions(alts, 1)))));
+	freebytes(pkbytes);
+	if(encode(e, &certinfobytes) != ASN_OK)
+		goto errret;
+	da = digestalg[sigalg];
+	(*da->fun)(certinfobytes->data, certinfobytes->len, digest, 0);
+	freebytes(certinfobytes);
+	certinfo = e;
+
+	sigbytes = encode_digest(da, digest);
+	if(sigbytes == nil)
+		goto errret;
+	pkcs1 = pkcs1padbuf(sigbytes->data, sigbytes->len, priv->pub.n, 1);
+	freebytes(sigbytes);
+	if(pkcs1 == nil)
+		goto errret;
+
+	rsadecrypt(priv, pkcs1, pkcs1);
+	buflen = mptobe(pkcs1, nil, 0, &buf);
+	mpfree(pkcs1);
+	e = mkseq(
+		mkel(certinfo,
+		mkel(mkalg(sigalg),
+		mkel(mkbits(buf, buflen),
+		nil))));
+	free(buf);
+	if(encode(e, &certbytes) != ASN_OK)
+		goto errret;
+	if(certlen != nil)
+		*certlen = certbytes->len;
+	cert = (uchar*)certbytes;
+	memmove(cert, certbytes->data, certbytes->len);
+errret:
+	freevalfields(&e.val);
+	free(subj);
+	return cert;
+}
+
+static void
+digestSPKI(int alg, uchar *pubkey, int npubkey, DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest)
+{
+	Bytes *b = nil;
+	Elem e = mkseq(mkel(mkalg(alg), mkel(mkbits(pubkey, npubkey), nil)));
+	encode(e, &b);
+	freevalfields(&e.val);
+	(*fun)(b->data, b->len, digest, nil);
+	freebytes(b);
+}
+
+int
+X509digestSPKI(uchar *cert, int ncert, DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest)
+{
+	CertX509 *c;
+
+	c = decode_cert(cert, ncert);
+	if(c == nil){
+		werrstr("cannot decode cert");
+		return -1;
+	}
+	digestSPKI(c->publickey_alg, c->publickey->data, c->publickey->len, fun, digest);
+	freecert(c);
+	return 0;
+}
+
+static char*
+tagdump(Tag tag)
+{
+	static char buf[32];
+
+	if(tag.class != Universal){
+		snprint(buf, sizeof(buf), "class%d,num%d", tag.class, tag.num);
+		return buf;
+	}
+	switch(tag.num){
+	case BOOLEAN: return "BOOLEAN";
+	case INTEGER: return "INTEGER";
+	case BIT_STRING: return "BIT STRING";
+	case OCTET_STRING: return "OCTET STRING";
+	case NULLTAG: return "NULLTAG";
+	case OBJECT_ID: return "OID";
+	case ObjectDescriptor: return "OBJECT_DES";
+	case EXTERNAL: return "EXTERNAL";
+	case REAL: return "REAL";
+	case ENUMERATED: return "ENUMERATED";
+	case EMBEDDED_PDV: return "EMBEDDED PDV";
+	case SEQUENCE: return "SEQUENCE";
+	case SETOF: return "SETOF";
+	case UTF8String: return "UTF8String";
+	case NumericString: return "NumericString";
+	case PrintableString: return "PrintableString";
+	case TeletexString: return "TeletexString";
+	case VideotexString: return "VideotexString";
+	case IA5String: return "IA5String";
+	case UTCTime: return "UTCTime";
+	case GeneralizedTime: return "GeneralizedTime";
+	case GraphicString: return "GraphicString";
+	case VisibleString: return "VisibleString";
+	case GeneralString: return "GeneralString";
+	case UniversalString: return "UniversalString";
+	case BMPString: return "BMPString";
+	default:
+		snprint(buf, sizeof(buf), "Universal,num%d", tag.num);
+		return buf;
+	}
+}
+
+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[%d]...", v.u.bitstringval->len*8 - v.u.bitstringval->unusedbits); 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;
+	CertX509 *c;
+	RSApub *rsapub;
+	ECpub *ecpub;
+	ECdomain ecdom;
+	int digestlen;
+	uchar digest[MAXdlen];
+
+	print("begin X509dump\n");
+	c = decode_cert(cert, ncert);
+	if(c == nil){
+		print("cannot decode cert\n");
+		return;
+	}
+
+	digestlen = digest_certinfo(cert, ncert, digestalg[c->signature_alg], digest);
+	if(digestlen <= 0){
+		freecert(c);
+		print("cannot decode certinfo\n");
+		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);
+	print("sigalg=%d digest=%.*H\n", c->signature_alg, digestlen, digest);
+	print("publickey_alg=%d pubkey[%d] %.*H\n", c->publickey_alg, c->publickey->len,
+		c->publickey->len, c->publickey->data);
+
+	switch(c->publickey_alg){
+	case ALG_rsaEncryption:
+		rsapub = asn1toRSApub(c->publickey->data, c->publickey->len);
+		if(rsapub != nil){
+			print("rsa pubkey e=%B n(%d)=%B\n", rsapub->ek, mpsignif(rsapub->n), rsapub->n);
+			e = X509rsaverifydigest(c->signature->data, c->signature->len,
+				digest, digestlen, rsapub);
+			if(e==nil)
+				e = "nil (meaning ok)";
+			print("self-signed X509rsaverifydigest returns: %s\n", e);
+			rsapubfree(rsapub);
+		}
+		break;
+	case ALG_ecPublicKey:
+		ecdominit(&ecdom, namedcurves[c->curve]);
+		ecpub = ecdecodepub(&ecdom, c->publickey->data, c->publickey->len);
+		if(ecpub != nil){
+			e = X509ecdsaverifydigest(c->signature->data, c->signature->len,
+				digest, digestlen, &ecdom, ecpub);
+			if(e==nil)
+				e = "nil (meaning ok)";
+			print("self-signed X509ecdsaverifydigest returns: %s\n", e);
+			ecpubfree(ecpub);
+		}
+		ecdomfree(&ecdom);
+		break;
+	}
+
+	digestSPKI(c->publickey_alg, c->publickey->data, c->publickey->len, sha2_256, digest);
+	print("publickey_thumbprint sha256=%.*[\n", SHA2_256dlen, digest);
+
+	sha2_256(cert, ncert, digest, nil);
+	print("cert_thumbprint sha256=%.*[\n", SHA2_256dlen, digest);
+
+	sha1(cert, ncert, digest, nil);
+	print("cert_thumbprint sha1=%.*H\n", SHA1dlen, digest);
+
+	freecert(c);
+	print("end X509dump\n");
+}
--- /dev/null
+++ b/main.c
@@ -1,0 +1,92 @@
+#include "u.h"
+#include "lib.h"
+#include "kern/dat.h"
+#include "kern/fns.h"
+#include "user.h"
+#include "drawcpu.h"
+#include "ip.h"
+#include "authsrv.h"
+
+char *argv0;
+
+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);
+}
+
+// TODO: remove libgui, or at least revamp as cpubody goes away
+void cpubody(void) {}
+
+char*
+estrdup(char *s)
+{
+	s = strdup(s);
+	if(s == nil)
+		sysfatal("out of memory");
+    return s;
+}
+
+int
+main(int argc, char **argv)
+{
+	int fd;
+	char *authdom;
+	extern ulong kerndate;
+
+	kerndate = seconds();
+	eve = getuser();
+	if(eve == nil)
+		eve = "drawcpu";
+
+	sizebug();
+
+	/* TODO: Flag/env in password + authdom or nvram */
+	authdom = "9front";
+    char *pass = "cinnamon";
+
+	osinit();
+	procinit0();
+	printinit();
+
+	chandevreset();
+	chandevinit();
+	quotefmtinstall();
+
+	if(bind("#c", "/dev", MBEFORE) < 0)
+		panic("bind #c: %r");
+	if(bind("#e", "/env", MREPL|MCREATE) < 0)
+		panic("bind #e: %r");
+	if(bind("#I", "/net", MBEFORE) < 0)
+		panic("bind #I: %r");
+	if(bind("#U", "/root", MREPL) < 0)
+		panic("bind #U: %r");
+	bind("#A", "/dev", MAFTER);
+	bind("#N", "/dev", MAFTER);
+	bind("#C", "/", MAFTER);
+
+	dbg = open("/dev/null", ORDWR);
+	fprint(dbg, "Starting debug\n");
+	if((fd = p9authsrv(authdom, pass)) < 0){
+		fprint(dbg, "unable to authenticate client: %r\n");
+		goto Exit;
+	}
+	if(session(fd) < 0)
+		fprint(dbg, "session failed: %r\n");
+Exit:
+	close(fd);
+
+	_exit(0);
+}
--- /dev/null
+++ b/posix-386/Makefile
@@ -1,0 +1,18 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
--- /dev/null
+++ b/posix-386/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((uintptr*)a)[-1];
+}
--- /dev/null
+++ b/posix-386/tas.c
@@ -1,0 +1,23 @@
+#include "u.h"
+#include "libc.h"
+
+int
+tas(int *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-amd64/Makefile
@@ -1,0 +1,15 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
--- /dev/null
+++ b/posix-amd64/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((uintptr*)a)[-1];
+}
--- /dev/null
+++ b/posix-amd64/tas.c
@@ -1,0 +1,23 @@
+#include "u.h"
+#include "libc.h"
+
+int
+tas(int *x)
+{
+	int     v;
+
+	__asm__(	"movl   $1, %%eax\n\t"
+				"xchgl  %%eax,(%%rcx)"
+				: "=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-arm/Makefile
@@ -1,0 +1,23 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
+
+%.s: %.spp
+	cpp $*.spp >$*.s
+
+
--- /dev/null
+++ b/posix-arm/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((uintptr*)a)[-1];
+}
--- /dev/null
+++ b/posix-arm/tas.c
@@ -1,0 +1,35 @@
+#include "u.h"
+#include "libc.h"
+
+int
+tas(int *x)
+{
+	int     v, t, i = 1;
+
+#if ARMv5
+	__asm__(
+		"swp  %0, %1, [%2]"
+		: "=&r" (v)
+		: "r" (1), "r" (x)
+		: "memory"
+	);
+#else
+	__asm__ (
+		"1:	ldrex	%0, [%2]\n"
+		"	strex	%1, %3, [%2]\n"
+		"	teq	%1, #0\n"
+		"	bne	1b"
+		: "=&r" (v), "=&r" (t)
+		: "r" (x), "r" (i)
+		: "cc");
+#endif
+	switch(v) {
+	case 0:
+	case 1:
+		return v;
+	default:
+		print("canlock: corrupted 0x%lux\n", v);
+		return 1;
+	}
+}
+
--- /dev/null
+++ b/posix-arm64/Makefile
@@ -1,0 +1,22 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
+
+%.s: %.spp
+	cpp $*.spp >$*.s
+
--- /dev/null
+++ b/posix-arm64/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((uintptr*)a)[-1];
+}
--- /dev/null
+++ b/posix-arm64/tas.c
@@ -1,0 +1,34 @@
+#include "u.h"
+#include "libc.h"
+
+#ifndef __has_builtin
+#define __has_builtin(x) 0
+#endif
+
+int
+tas(int *x)
+{
+#if __has_builtin(__atomic_test_and_set) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7)))
+	return __atomic_test_and_set(x, __ATOMIC_ACQ_REL);
+#else
+	int     v,t, i = 1;
+
+	__asm__ (
+		"1:	ldxr	%0, [%2]\n"
+		"	stxr	%w1, %3, [%2]\n"
+		"	cmp	%1, #0\n"
+		"	bne	1b"
+		: "=&r" (v), "=&r" (t)
+		: "r" (x), "r" (i)
+		: "cc");
+
+	switch(v) {
+	case 0:
+	case 1:
+		return v;
+	default:
+		print("canlock: corrupted 0x%lux\n", v);
+		return 1;
+	}
+#endif
+}
--- /dev/null
+++ b/posix-factotum.c
@@ -1,0 +1,110 @@
+#include <u.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <authsrv.h>
+#include <libsec.h>
+#include "drawcpu.h"
+
+#undef socket
+#undef connect
+#undef getenv
+#undef access
+
+char*
+getuser(void)
+{
+	static char user[64];
+	struct passwd *pw;
+
+	pw = getpwuid(getuid());
+	if(pw == nil)
+		return "none";
+	strecpy(user, user+sizeof user, pw->pw_name);
+	return user;
+}
+/*
+ * Absent other hints, it works reasonably well to use
+ * the X11 display name as the name space identifier.
+ * This is how sam's B has worked since the early days.
+ * Since most programs using name spaces are also using X,
+ * this still seems reasonable.  Terminal-only sessions
+ * can set $NAMESPACE.
+ */
+static char*
+nsfromdisplay(void)
+{
+	char *disp, *p;
+
+	if((disp = getenv("DISPLAY")) == nil){
+		werrstr("$DISPLAY not set");
+		return nil;
+	}
+
+	/* canonicalize: xxx:0.0 => xxx:0 */
+	p = strrchr(disp, ':');
+	if(p){
+		p++;
+		while(isdigit((uchar)*p))
+			p++;
+		if(strcmp(p, ".0") == 0)
+			*p = 0;
+	}
+
+	return smprint("/tmp/ns.%s.%s", getuser(), disp);
+}
+
+char*
+getns(void)
+{
+	char *ns;
+
+	ns = getenv("NAMESPACE");
+	if(ns == nil)
+		ns = nsfromdisplay();
+	if(ns == nil){
+		werrstr("$NAMESPACE not set, %r");
+		return nil;
+	}
+	return ns;
+}
+
+int
+dialfactotum(void)
+{
+	int fd;
+	struct sockaddr_un su;
+	char *name;
+	
+	name = smprint("%s/factotum", getns());
+
+	if(name == nil || access(name, 0) < 0)
+		goto err;
+	memset(&su, 0, sizeof su);
+	su.sun_family = AF_UNIX;
+	if(strlen(name)+1 > sizeof su.sun_path){
+		werrstr("socket name too long");
+		goto err;
+	}
+	strcpy(su.sun_path, name);
+	if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){
+		werrstr("socket: %r");
+		goto err;
+	}
+	if(connect(fd, (struct sockaddr*)&su, sizeof su) < 0){
+		werrstr("connect %s: %r", name);
+		close(fd);
+		goto err;
+	}
+
+	free(name);
+	return lfdfd(fd);
+err:
+	free(name);
+	return -1;
+}
+
--- /dev/null
+++ b/posix-mips/Makefile
@@ -1,0 +1,16 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+tas.$O: tas.s
+	ln -sf tas.s tas.S
+	$(CC) -c -o tas.$O -mips3 tas.S
--- /dev/null
+++ b/posix-mips/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((ulong*)a)[-1];
+}
--- /dev/null
+++ b/posix-mips/tas.s
@@ -1,0 +1,20 @@
+#include <machine/regdef.h>
+
+.globl tas
+.ent tas 2
+
+tas:
+.set noreorder
+1:
+	ori	t1, zero, 12345	/* t1 = 12345 */
+	ll	t0, (a0)		/* t0 = *a0 */
+	sc	t1, (a0)		/* *a0 = t1 if *a0 hasn't changed; t1=success */
+	beq	t1, zero, 1b		/* repeat if *a0 did change */
+	nop
+
+	j $31				/* return */
+	or	v0, t0, zero		/* set return value on way out */
+
+.set reorder
+.end tas
+
--- /dev/null
+++ b/posix-port/Makefile
@@ -1,0 +1,22 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
+
+%.s: %.spp
+	cpp $*.spp >$*.s
+
+
--- /dev/null
+++ b/posix-port/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return 0;
+}
--- /dev/null
+++ b/posix-power/Makefile
@@ -1,0 +1,25 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+CFLAGS+= -Wa,-mregnames
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
+
+%.s: %.spp
+	cpp $*.spp >$*.s
+
+
--- /dev/null
+++ b/posix-power/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((uintptr*)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(int *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/posix-riscv64/Makefile
@@ -1,0 +1,18 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
--- /dev/null
+++ b/posix-riscv64/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((uintptr*)a)[-1];
+}
--- /dev/null
+++ b/posix-riscv64/tas.c
@@ -1,0 +1,28 @@
+#include "u.h"
+#include "libc.h"
+
+int
+tas(int *x)
+{
+	int v, i = 1;
+
+	__asm__(
+		"1:	lr.w t0, (%1)\n"
+		"	sc.w t1, %2, (%1)\n"
+		"	bnez t1, 1b\n"
+		"       mv %0, t0"
+		: "=r" (v)
+		: "r" (x), "r" (i)
+		: "t1", "t0"
+	);
+
+	switch(v) {
+	case 0:
+	case 1:
+		return v;
+	default:
+		print("canlock: corrupted 0x%lux\n", v);
+		return 1;
+	}
+}
+
--- /dev/null
+++ b/posix-sun4u/Makefile
@@ -1,0 +1,24 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
+
+%.s: %.spp
+	cpp $*.spp >$*.s
+
+
+
--- /dev/null
+++ b/posix-sun4u/getcallerpc.c
@@ -1,0 +1,9 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((ulong*)a)[-1];
+}
+
--- /dev/null
+++ b/posix-sun4u/tas.s
@@ -1,0 +1,5 @@
+.globl tas
+tas:
+	retl
+	ldstub [%o0], %o0
+
--- /dev/null
+++ b/resource.h
@@ -1,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by drawcpu.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,668 @@
+/*
+ * 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 <authsrv.h>
+#include "drawcpu.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(authserver == nil)
+			return -1;
+		strecpy(buf, buf+sizeof buf, authserver);
+		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", "secstore"), 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 tsmemcmp(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;
+	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)
+				sysfatal("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 = estrdup(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 = estrdup(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/session.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <authsrv.h>
+#include <libsec.h>
+#include "drawcpu.h"
+
+// TODO: aanserver
+static void
+sessionexit(void)
+{
+    char *s = getenv("rstatus");
+    if(s != nil)
+        exit(*s);
+}
+
+int
+session(int fd)
+{
+    int n;
+    char buf[1024];
+    //n = read(fd, buf, sizeof buf);
+    //if (n > 0) {
+    //return rcmain(buf, fd);
+    //}
+    close(fd);
+    // write(dbg, buf, n);
+    // sed out any line that says 'service=cpu' and put 'service=unix'
+    return -1;
+}
--- /dev/null
+++ b/tmp.sh
@@ -1,0 +1,3 @@
+#!/bin/sh
+
+./drawcpu 2>tmpfile
--- /dev/null
+++ b/win32-386/Makefile
@@ -1,0 +1,23 @@
+ROOT=..
+include ../Make.config
+LIB=../libmachdep.a
+
+OFILES=\
+	getcallerpc.$O\
+	tas.$O
+
+default: $(LIB)
+$(LIB): $(OFILES)
+	$(AR) r $(LIB) $(OFILES)
+	$(RANLIB) $(LIB)
+
+%.$O: %.c
+	$(CC) $(CFLAGS) $*.c
+
+%.$O: %.s
+	$(AS) -o $*.$O $*.s
+
+%.s: %.spp
+	cpp $*.spp >$*.s
+
+
--- /dev/null
+++ b/win32-386/getcallerpc.c
@@ -1,0 +1,8 @@
+#include "u.h"
+#include "libc.h"
+
+uintptr
+getcallerpc(void *a)
+{
+	return ((uintptr*)a)[-1];
+}
--- /dev/null
+++ b/win32-386/tas.c
@@ -1,0 +1,23 @@
+#include "u.h"
+#include "libc.h"
+
+int
+tas(int *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/win32-factotum.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <authsrv.h>
+#include <libsec.h>
+#include "drawcpu.h"
+
+#undef getenv
+
+char*
+getuser(void)
+{
+	return getenv("USER");
+}
+
+int
+dialfactotum(void)
+{
+	return -1;
+}
+
--- /dev/null
+++ b/win32-ip.c
@@ -1,0 +1,1 @@
+//TODO: I don't want to do this, if anyone wants into windows we need the pm_announce, pm_listen and pm_accept filled out
\ No newline at end of file