tInitial revision - plan9port - [fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port
Log
Files
Refs
README
LICENSE
---
commit 76193d7cb0457807b2f0b95f909ab5de19480cd7
parent ed7c8e8d02c02bdbff1e88a6d8d1419f39af48ad
Author: rsc 
Date:   Tue, 30 Sep 2003 17:47:42 +0000

Initial revision

Diffstat:
  A man/man7/regexp9.7                  |     150 +++++++++++++++++++++++++++++++
  A man/man7/utf.7                      |      91 +++++++++++++++++++++++++++++++
  A src/cmd/mk/LICENSE                  |     258 +++++++++++++++++++++++++++++++
  A src/cmd/mk/Make.FreeBSD-386         |       7 +++++++
  A src/cmd/mk/Make.HP-UX-9000          |       6 ++++++
  A src/cmd/mk/Make.Linux-386           |       7 +++++++
  A src/cmd/mk/Make.OSF1-alpha          |       6 ++++++
  A src/cmd/mk/Make.SunOS-sun4u         |       2 ++
  A src/cmd/mk/Make.SunOS-sun4u-cc      |       6 ++++++
  A src/cmd/mk/Make.SunOS-sun4u-gcc     |       6 ++++++
  A src/cmd/mk/Makefile                 |     117 +++++++++++++++++++++++++++++++
  A src/cmd/mk/Makefile.MID             |      45 +++++++++++++++++++++++++++++++
  A src/cmd/mk/arc.c                    |      52 +++++++++++++++++++++++++++++++
  A src/cmd/mk/archive.c                |     180 +++++++++++++++++++++++++++++++
  A src/cmd/mk/bundle.ports             |      46 +++++++++++++++++++++++++++++++
  A src/cmd/mk/env.c                    |     149 +++++++++++++++++++++++++++++++
  A src/cmd/mk/file.c                   |      90 +++++++++++++++++++++++++++++++
  A src/cmd/mk/fns.h                    |      84 +++++++++++++++++++++++++++++++
  A src/cmd/mk/graph.c                  |     279 +++++++++++++++++++++++++++++++
  A src/cmd/mk/lex.c                    |     147 +++++++++++++++++++++++++++++++
  A src/cmd/mk/main.c                   |     285 +++++++++++++++++++++++++++++++
  A src/cmd/mk/match.c                  |      49 +++++++++++++++++++++++++++++++
  A src/cmd/mk/mk.1                     |     665 +++++++++++++++++++++++++++++++
  A src/cmd/mk/mk.c                     |     226 +++++++++++++++++++++++++++++++
  A src/cmd/mk/mk.h                     |     203 +++++++++++++++++++++++++++++++
  A src/cmd/mk/mkfile                   |       9 +++++++++
  A src/cmd/mk/mkfile.test              |       5 +++++
  A src/cmd/mk/parse.c                  |     307 +++++++++++++++++++++++++++++++
  A src/cmd/mk/rc.c                     |     175 +++++++++++++++++++++++++++++++
  A src/cmd/mk/recipe.c                 |     117 +++++++++++++++++++++++++++++++
  A src/cmd/mk/rpm.spec                 |      29 +++++++++++++++++++++++++++++
  A src/cmd/mk/rule.c                   |     107 +++++++++++++++++++++++++++++++
  A src/cmd/mk/run.c                    |     296 +++++++++++++++++++++++++++++++
  A src/cmd/mk/sh.c                     |     189 +++++++++++++++++++++++++++++++
  A src/cmd/mk/shprint.c                |     123 +++++++++++++++++++++++++++++++
  A src/cmd/mk/symtab.c                 |      95 ++++++++++++++++++++++++++++++
  A src/cmd/mk/unix.c                   |     306 +++++++++++++++++++++++++++++++
  A src/cmd/mk/var.c                    |      41 +++++++++++++++++++++++++++++++
  A src/cmd/mk/varsub.c                 |     256 +++++++++++++++++++++++++++++++
  A src/cmd/mk/word.c                   |     180 +++++++++++++++++++++++++++++++
  A src/cmd/sam/LICENSE                 |     258 +++++++++++++++++++++++++++++++
  A src/cmd/sam/Makefile                |      18 ++++++++++++++++++
  A src/cmd/sam/address.c               |     240 +++++++++++++++++++++++++++++++
  A src/cmd/sam/buff.c                  |     302 +++++++++++++++++++++++++++++++
  A src/cmd/sam/cmd.c                   |     594 +++++++++++++++++++++++++++++++
  A src/cmd/sam/disk.c                  |     122 +++++++++++++++++++++++++++++++
  A src/cmd/sam/error.c                 |     144 +++++++++++++++++++++++++++++++
  A src/cmd/sam/errors.h                |      65 +++++++++++++++++++++++++++++++
  A src/cmd/sam/file.c                  |     631 +++++++++++++++++++++++++++++++
  A src/cmd/sam/io.c                    |     262 +++++++++++++++++++++++++++++++
  A src/cmd/sam/list.c                  |      47 +++++++++++++++++++++++++++++++
  A src/cmd/sam/mesg.c                  |     821 ++++++++++++++++++++++++++++++
  A src/cmd/sam/mesg.h                  |     131 +++++++++++++++++++++++++++++++
  A src/cmd/sam/mkfile                  |      40 +++++++++++++++++++++++++++++++
  A src/cmd/sam/moveto.c                |     173 +++++++++++++++++++++++++++++++
  A src/cmd/sam/multi.c                 |     123 +++++++++++++++++++++++++++++++
  A src/cmd/sam/parse.h                 |      68 +++++++++++++++++++++++++++++++
  A src/cmd/sam/plan9.c                 |     185 ++++++++++++++++++++++++++++++
  A src/cmd/sam/plumb.c                 |       9 +++++++++
  A src/cmd/sam/rasp.c                  |     325 +++++++++++++++++++++++++++++++
  A src/cmd/sam/regexp.c                |     801 ++++++++++++++++++++++++++++++
  A src/cmd/sam/sam                     |       0 
  A src/cmd/sam/sam.c                   |     739 ++++++++++++++++++++++++++++++
  A src/cmd/sam/sam.h                   |     407 +++++++++++++++++++++++++++++++
  A src/cmd/sam/shell.c                 |     152 +++++++++++++++++++++++++++++++
  A src/cmd/sam/unix.c                  |     272 +++++++++++++++++++++++++++++++
  A src/cmd/sam/xec.c                   |     508 +++++++++++++++++++++++++++++++
  A src/libdraw/BOT                     |       0 
  A src/libdraw/Make.Darwin-PowerMacin… |       6 ++++++
  A src/libdraw/Make.FreeBSD-386        |       7 +++++++
  A src/libdraw/Make.HP-UX-9000         |       6 ++++++
  A src/libdraw/Make.Linux-386          |       7 +++++++
  A src/libdraw/Make.NetBSD-386         |       7 +++++++
  A src/libdraw/Make.OSF1-alpha         |       6 ++++++
  A src/libdraw/Make.SunOS-sun4u        |       2 ++
  A src/libdraw/Make.SunOS-sun4u-cc     |       6 ++++++
  A src/libdraw/Make.SunOS-sun4u-gcc    |       6 ++++++
  A src/libdraw/Makefile                |     194 ++++++++++++++++++++++++++++++
  A src/libdraw/Makefile.MID            |     123 +++++++++++++++++++++++++++++++
  A src/libdraw/alloc.c                 |     237 +++++++++++++++++++++++++++++++
  A src/libdraw/arith.c                 |     206 +++++++++++++++++++++++++++++++
  A src/libdraw/buildfont.c             |     141 +++++++++++++++++++++++++++++++
  A src/libdraw/bytesperline.c          |      34 +++++++++++++++++++++++++++++++
  A src/libdraw/chan.c                  |      77 +++++++++++++++++++++++++++++++
  A src/libdraw/creadimage.c            |     113 +++++++++++++++++++++++++++++++
  A src/libdraw/cursor.h                |       7 +++++++
  A src/libdraw/devdraw.c               |    1587 +++++++++++++++++++++++++++++++
  A src/libdraw/draw.h                  |     520 +++++++++++++++++++++++++++++++
  A src/libdraw/ellipse.c               |      82 +++++++++++++++++++++++++++++++
  A src/libdraw/emenuhit.c              |     271 +++++++++++++++++++++++++++++++
  A src/libdraw/event.c                 |     486 +++++++++++++++++++++++++++++++
  A src/libdraw/event.h                 |      63 +++++++++++++++++++++++++++++++
  A src/libdraw/font.c                  |     401 +++++++++++++++++++++++++++++++
  A src/libdraw/getsubfont.c            |      36 +++++++++++++++++++++++++++++++
  A src/libdraw/init.c                  |     203 +++++++++++++++++++++++++++++++
  A src/libdraw/keyboard.c              |     102 +++++++++++++++++++++++++++++++
  A src/libdraw/keyboard.h              |      36 +++++++++++++++++++++++++++++++
  A src/libdraw/libdraw.x               |       1 +
  A src/libdraw/md-alloc.c              |     200 +++++++++++++++++++++++++++++++
  A src/libdraw/md-arc.c                |     116 ++++++++++++++++++++++++++++++
  A src/libdraw/md-arctest.c            |      61 +++++++++++++++++++++++++++++++
  A src/libdraw/md-cload.c              |      68 +++++++++++++++++++++++++++++++
  A src/libdraw/md-cmap.c               |     320 +++++++++++++++++++++++++++++++
  A src/libdraw/md-cread.c              |      96 +++++++++++++++++++++++++++++++
  A src/libdraw/md-defont.c             |      68 +++++++++++++++++++++++++++++++
  A src/libdraw/md-draw.c               |    2487 +++++++++++++++++++++++++++++++
  A src/libdraw/md-drawtest.c           |    1004 +++++++++++++++++++++++++++++++
  A src/libdraw/md-ellipse.c            |     247 +++++++++++++++++++++++++++++++
  A src/libdraw/md-fillpoly.c           |     524 +++++++++++++++++++++++++++++++
  A src/libdraw/md-hwdraw.c             |      12 ++++++++++++
  A src/libdraw/md-iprint.c             |      12 ++++++++++++
  A src/libdraw/md-line.c               |     484 +++++++++++++++++++++++++++++++
  A src/libdraw/md-load.c               |      72 +++++++++++++++++++++++++++++++
  A src/libdraw/md-mkcmap.c             |      79 +++++++++++++++++++++++++++++++
  A src/libdraw/md-openmemsubfont.c     |      53 ++++++++++++++++++++++++++++++
  A src/libdraw/md-poly.c               |      23 +++++++++++++++++++++++
  A src/libdraw/md-read.c               |     111 ++++++++++++++++++++++++++++++
  A src/libdraw/md-string.c             |      66 +++++++++++++++++++++++++++++++
  A src/libdraw/md-subfont.c            |      34 +++++++++++++++++++++++++++++++
  A src/libdraw/md-unload.c             |      25 +++++++++++++++++++++++++
  A src/libdraw/md-write.c              |     183 +++++++++++++++++++++++++++++++
  A src/libdraw/memdraw.h               |     209 +++++++++++++++++++++++++++++++
  A src/libdraw/memlayer.h              |      48 +++++++++++++++++++++++++++++++
  A src/libdraw/menuhit.c               |     277 +++++++++++++++++++++++++++++++
  A src/libdraw/mkfile                  |       1 +
  A src/libdraw/ml-draw.c               |     192 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-lalloc.c             |      79 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-layerop.c            |     112 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-ldelete.c            |      67 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-lhide.c              |      67 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-line.c               |     122 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-load.c               |      55 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-lorigin.c            |     107 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-lsetrefresh.c        |      35 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-ltofront.c           |      80 +++++++++++++++++++++++++++++++
  A src/libdraw/ml-ltorear.c            |      69 ++++++++++++++++++++++++++++++
  A src/libdraw/ml-unload.c             |      52 +++++++++++++++++++++++++++++++
  A src/libdraw/mouse.c                 |     139 ++++++++++++++++++++++++++++++
  A src/libdraw/mouse.h                 |      44 +++++++++++++++++++++++++++++++
  A src/libdraw/openfont.c              |      32 +++++++++++++++++++++++++++++++
  A src/libdraw/readcolmap.c            |      49 +++++++++++++++++++++++++++++++
  A src/libdraw/readimage.c             |     118 +++++++++++++++++++++++++++++++
  A src/libdraw/readsubfont.c           |      58 ++++++++++++++++++++++++++++++
  A src/libdraw/string.c                |     137 +++++++++++++++++++++++++++++++
  A src/libdraw/stringwidth.c           |      97 ++++++++++++++++++++++++++++++
  A src/libdraw/subfont.c               |      28 ++++++++++++++++++++++++++++
  A src/libdraw/subfontcache.c          |      39 +++++++++++++++++++++++++++++++
  A src/libdraw/subfontname.c           |      44 +++++++++++++++++++++++++++++++
  A src/libdraw/unix.c                  |      16 ++++++++++++++++
  A src/libdraw/unloadimage.c           |      53 ++++++++++++++++++++++++++++++
  A src/libdraw/writecolmap.c           |      35 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-alloc.c             |     120 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-cload.c             |      19 +++++++++++++++++++
  A src/libdraw/x11-draw.c              |     143 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-event.c             |     136 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-fill.c              |      57 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-get.c               |     110 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-inc.h               |      31 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-init.c              |     584 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-itrans.c            |     258 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-keyboard.c          |      71 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-load.c              |      19 +++++++++++++++++++
  A src/libdraw/x11-memdraw.h           |      93 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-mouse.c             |     107 +++++++++++++++++++++++++++++++
  A src/libdraw/x11-pixelbits.c         |      17 +++++++++++++++++
  A src/libdraw/x11-unload.c            |      16 ++++++++++++++++
  A src/libthread/386.c                 |      21 +++++++++++++++++++++
  A src/libthread/FreeBSD-386.s         |      18 ++++++++++++++++++
  A src/libthread/LICENSE               |     258 +++++++++++++++++++++++++++++++
  A src/libthread/Make.Darwin-PowerMac… |       6 ++++++
  A src/libthread/Make.FreeBSD-386      |       7 +++++++
  A src/libthread/Make.HP-UX-9000       |       6 ++++++
  A src/libthread/Make.Linux-386        |       7 +++++++
  A src/libthread/Make.NetBSD-386       |       7 +++++++
  A src/libthread/Make.OSF1-alpha       |       6 ++++++
  A src/libthread/Make.SunOS-sun4u      |       2 ++
  A src/libthread/Make.SunOS-sun4u-cc   |       6 ++++++
  A src/libthread/Make.SunOS-sun4u-gcc  |       6 ++++++
  A src/libthread/Makefile              |     125 +++++++++++++++++++++++++++++++
  A src/libthread/Makefile.MID          |      54 +++++++++++++++++++++++++++++++
  A src/libthread/NOTICE                |      19 +++++++++++++++++++
  A src/libthread/README                |      19 +++++++++++++++++++
  A src/libthread/asm-FreeBSD-386.s     |      49 +++++++++++++++++++++++++++++++
  A src/libthread/asm-Linux-386.s       |       1 +
  A src/libthread/bundle.ports          |      42 +++++++++++++++++++++++++++++++
  A src/libthread/channel.c             |     485 +++++++++++++++++++++++++++++++
  A src/libthread/chanprint.c           |      18 ++++++++++++++++++
  A src/libthread/create.c              |     182 +++++++++++++++++++++++++++++++
  A src/libthread/debug.c               |      48 +++++++++++++++++++++++++++++++
  A src/libthread/exec-unix.c           |     124 +++++++++++++++++++++++++++++++
  A src/libthread/exec.c                |      77 +++++++++++++++++++++++++++++++
  A src/libthread/exit.c                |      63 +++++++++++++++++++++++++++++++
  A src/libthread/getpid.c              |       8 ++++++++
  A src/libthread/id.c                  |     135 +++++++++++++++++++++++++++++++
  A src/libthread/iocall.c              |      49 +++++++++++++++++++++++++++++++
  A src/libthread/ioclose.c             |      16 ++++++++++++++++
  A src/libthread/iodial.c              |      21 +++++++++++++++++++++
  A src/libthread/ioopen.c              |      20 ++++++++++++++++++++
  A src/libthread/ioproc.3              |     179 +++++++++++++++++++++++++++++++
  A src/libthread/ioproc.c              |      74 +++++++++++++++++++++++++++++++
  A src/libthread/ioread.c              |      20 ++++++++++++++++++++
  A src/libthread/ioreadn.c             |      21 +++++++++++++++++++++
  A src/libthread/iosleep.c             |      16 ++++++++++++++++
  A src/libthread/iowrite.c             |      21 +++++++++++++++++++++
  A src/libthread/kill.c                |      89 +++++++++++++++++++++++++++++++
  A src/libthread/label.h               |      24 ++++++++++++++++++++++++
  A src/libthread/lib.c                 |      35 +++++++++++++++++++++++++++++++
  A src/libthread/main.c                |     124 +++++++++++++++++++++++++++++++
  A src/libthread/memset.c              |       8 ++++++++
  A src/libthread/memsetd.c             |       8 ++++++++
  A src/libthread/mkfile                |       2 ++
  A src/libthread/note.c                |     143 +++++++++++++++++++++++++++++++
  A src/libthread/proctab.c             |      64 +++++++++++++++++++++++++++++++
  A src/libthread/ref.c                 |      13 +++++++++++++
  A src/libthread/rendez.c              |     104 +++++++++++++++++++++++++++++++
  A src/libthread/rpm.spec              |      26 ++++++++++++++++++++++++++
  A src/libthread/sched.c               |     192 +++++++++++++++++++++++++++++++
  A src/libthread/texec.c               |      34 +++++++++++++++++++++++++++++++
  A src/libthread/thread.3              |     576 +++++++++++++++++++++++++++++++
  A src/libthread/thread.h              |     132 +++++++++++++++++++++++++++++++
  A src/libthread/threadimpl.h          |     219 +++++++++++++++++++++++++++++++
  A src/libthread/tprimes               |       0 
  A src/libthread/tprimes.c             |      62 +++++++++++++++++++++++++++++++

223 files changed, 32479 insertions(+), 0 deletions(-)
---
diff --git a/man/man7/regexp9.7 b/man/man7/regexp9.7
t@@ -0,0 +1,150 @@
+.TH REGEXP9 7
+.de EX
+.nf
+.ft B
+..
+.de EE
+.fi
+.ft R
+..
+.de LR
+.if t .BR \\$1 \\$2
+.if n .RB ` \\$1 '\\$2
+..
+.de L
+.nh
+.if t .B \\$1
+.if n .RB ` \\$1 '
+..
+.SH NAME
+regexp9 \- Plan 9 regular expression notation
+.SH DESCRIPTION
+This manual page describes the regular expression
+syntax used by the Plan 9 regular expression library
+.IR regexp9 (3).
+It is the form used by
+.IR egrep (1)
+before
+.I egrep
+got complicated.
+.PP
+A 
+.I "regular expression"
+specifies
+a set of strings of characters.
+A member of this set of strings is said to be
+.I matched
+by the regular expression.  In many applications
+a delimiter character, commonly
+.LR / ,
+bounds a regular expression.
+In the following specification for regular expressions
+the word `character' means any character (rune) but newline.
+.PP
+The syntax for a regular expression
+.B e0
+is
+.IP
+.EX
+e3:  literal | charclass | '.' | '^' | '$' | '(' e0 ')'
+
+e2:  e3
+  |  e2 REP
+
+REP: '*' | '+' | '?'
+
+e1:  e2
+  |  e1 e2
+
+e0:  e1
+  |  e0 '|' e1
+.EE
+.PP
+A
+.B literal
+is any non-metacharacter, or a metacharacter
+(one of
+.BR .*+?[]()|\e^$ ),
+or the delimiter
+preceded by 
+.LR \e .
+.PP
+A
+.B charclass
+is a nonempty string
+.I s
+bracketed
+.BI [ \|s\| ]
+(or
+.BI [^ s\| ]\fR);
+it matches any character in (or not in)
+.IR s .
+A negated character class never
+matches newline.
+A substring 
+.IB a - b\f1,
+with
+.I a
+and
+.I b
+in ascending
+order, stands for the inclusive
+range of
+characters between
+.I a
+and
+.IR b .
+In 
+.IR s ,
+the metacharacters
+.LR - ,
+.LR ] ,
+an initial
+.LR ^ ,
+and the regular expression delimiter
+must be preceded by a
+.LR \e ;
+other metacharacters 
+have no special meaning and
+may appear unescaped.
+.PP
+A 
+.L .
+matches any character.
+.PP
+A
+.L ^
+matches the beginning of a line;
+.L $
+matches the end of the line.
+.PP
+The 
+.B REP
+operators match zero or more
+.RB ( * ),
+one or more
+.RB ( + ),
+zero or one
+.RB ( ? ),
+instances respectively of the preceding regular expression 
+.BR e2 .
+.PP
+A concatenated regular expression,
+.BR "e1\|e2" ,
+matches a match to 
+.B e1
+followed by a match to
+.BR e2 .
+.PP
+An alternative regular expression,
+.BR "e0\||\|e1" ,
+matches either a match to
+.B e0
+or a match to
+.BR e1 .
+.PP
+A match to any part of a regular expression
+extends as far as possible without preventing
+a match to the remainder of the regular expression.
+.SH "SEE ALSO"
+.IR regexp9 (3)
diff --git a/man/man7/utf.7 b/man/man7/utf.7
t@@ -0,0 +1,91 @@
+.TH UTF 7
+.SH NAME
+UTF, Unicode, ASCII, rune \- character set and format
+.SH DESCRIPTION
+The Plan 9 character set and representation are
+based on the Unicode Standard and on the ISO multibyte
+.SM UTF-8
+encoding (Universal Character
+Set Transformation Format, 8 bits wide).
+The Unicode Standard represents its characters in 16
+bits;
+.SM UTF-8
+represents such
+values in an 8-bit byte stream.
+Throughout this manual,
+.SM UTF-8
+is shortened to
+.SM UTF.
+.PP
+In Plan 9, a
+.I rune
+is a 16-bit quantity representing a Unicode character.
+Internally, programs may store characters as runes.
+However, any external manifestation of textual information,
+in files or at the interface between programs, uses a
+machine-independent, byte-stream encoding called
+.SM UTF.
+.PP
+.SM UTF
+is designed so the 7-bit
+.SM ASCII
+set (values hexadecimal 00 to 7F),
+appear only as themselves
+in the encoding.
+Runes with values above 7F appear as sequences of two or more
+bytes with values only from 80 to FF.
+.PP
+The
+.SM UTF
+encoding of the Unicode Standard is backward compatible with
+.SM ASCII\c
+:
+programs presented only with
+.SM ASCII
+work on Plan 9
+even if not written to deal with
+.SM UTF,
+as do
+programs that deal with uninterpreted byte streams.
+However, programs that perform semantic processing on
+.SM ASCII
+graphic
+characters must convert from
+.SM UTF
+to runes
+in order to work properly with non-\c
+.SM ASCII
+input.
+See
+.IR rune (2).
+.PP
+Letting numbers be binary,
+a rune x is converted to a multibyte
+.SM UTF
+sequence
+as follows:
+.PP
+01.   x in [00000000.0bbbbbbb] → 0bbbbbbb
+.br
+10.   x in [00000bbb.bbbbbbbb] → 110bbbbb, 10bbbbbb
+.br
+11.   x in [bbbbbbbb.bbbbbbbb] → 1110bbbb, 10bbbbbb, 10bbbbbb
+.br
+.PP
+Conversion 01 provides a one-byte sequence that spans the
+.SM ASCII
+character set in a compatible way.
+Conversions 10 and 11 represent higher-valued characters
+as sequences of two or three bytes with the high bit set.
+Plan 9 does not support the 4, 5, and 6 byte sequences proposed by X-Open.
+When there are multiple ways to encode a value, for example rune 0,
+the shortest encoding is used.
+.PP
+In the inverse mapping,
+any sequence except those described above
+is incorrect and is converted to rune hexadecimal 0080.
+.SH "SEE ALSO"
+.IR ascii (1),
+.IR tcs (1),
+.IR rune (3),
+.IR "The Unicode Standard" .
diff --git a/src/cmd/mk/LICENSE b/src/cmd/mk/LICENSE
t@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+   to redistribute (other than with the Plan 9 Operating System)
+   the screen imprinter fonts identified in subdirectory
+   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+   covered by the Aladdin Free Public License, reproduced in the file
+   /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+     Program, and
+  b. in the case of each Contributor,
+
+     i. changes to the Program, and
+    ii. additions to the Program;
+
+    where such changes and/or additions to the Program were added to the
+    Program by such Contributor itself or anyone acting on such
+    Contributor's behalf, and the Contributor explicitly consents, in
+    accordance with Section 3C, to characterization of the changes and/or
+    additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free copyright
+    license to reproduce, prepare derivative works of, publicly display,
+    publicly perform, distribute and sublicense the Contribution of such
+    Contributor, if any, and such derivative works, in source code and
+    object code form.
+    
+ b. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free patent
+    license under Licensed Patents to make, use, sell, offer to sell,
+    import and otherwise transfer the Contribution of such Contributor, if
+    any, in source code and object code form. The patent license granted
+    by a Contributor shall also apply to the combination of the
+    Contribution of that Contributor and the Program if, at the time the
+    Contribution is added by the Contributor, such addition of the
+    Contribution causes such combination to be covered by the Licensed
+    Patents. The patent license granted by a Contributor shall not apply
+    to (i) any other combinations which include the Contribution, nor to
+    (ii) Contributions of other Contributors. No hardware per se is
+    licensed hereunder.
+    
+ c. Recipient understands that although each Contributor grants the
+    licenses to its Contributions set forth herein, no assurances are
+    provided by any Contributor that the Program does not infringe the
+    patent or other intellectual property rights of any other entity. Each
+    Contributor disclaims any liability to Recipient for claims brought by
+    any other entity based on infringement of intellectual property rights
+    or otherwise. As a condition to exercising the rights and licenses
+    granted hereunder, each Recipient hereby assumes sole responsibility
+    to secure any other intellectual property rights needed, if any. For
+    example, if a third party patent license is required to allow
+    Recipient to distribute the Program, it is Recipient's responsibility
+    to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+    copyright rights in its Contribution, if any, to grant the copyright
+    license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+    form, a copy of this Agreement or Distributor's own license agreement
+    is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+    license agreement:
+
+      i. effectively disclaims on behalf of all Contributors all warranties
+         and conditions, express and implied, including warranties or
+         conditions of title and non-infringement, and implied warranties or
+         conditions of merchantability and fitness for a particular purpose;
+     ii. effectively excludes on behalf of all Contributors all liability
+         for damages, including direct, indirect, special, incidental and
+         consequential damages, such as lost profits; and
+    iii. states that any provisions which differ from this Agreement are
+         offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+   location in the Program:
+
+   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+   Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), 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 OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/cmd/mk/Make.FreeBSD-386 b/src/cmd/mk/Make.FreeBSD-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.HP-UX-9000 b/src/cmd/mk/Make.HP-UX-9000
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.Linux-386 b/src/cmd/mk/Make.Linux-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.OSF1-alpha b/src/cmd/mk/Make.OSF1-alpha
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u b/src/cmd/mk/Make.SunOS-sun4u
t@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u-cc b/src/cmd/mk/Make.SunOS-sun4u-cc
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u-gcc b/src/cmd/mk/Make.SunOS-sun4u-gcc
t@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Makefile b/src/cmd/mk/Makefile
t@@ -0,0 +1,117 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+TARG=mk
+VERSION=2.0
+PORTPLACE=devel/mk
+NAME=mk
+
+OFILES=\
+        arc.$O\
+        archive.$O\
+        bufblock.$O\
+        env.$O\
+        file.$O\
+        graph.$O\
+        job.$O\
+        lex.$O\
+        main.$O\
+        match.$O\
+        mk.$O\
+        parse.$O\
+        recipe.$O\
+        rule.$O\
+        run.$O\
+        sh.$O\
+        shprint.$O\
+        symtab.$O\
+        var.$O\
+        varsub.$O\
+        word.$O\
+        unix.$O\
+
+HFILES=\
+        mk.h\
+        fns.h\
+
+all: $(TARG)
+
+TGZFILES+=mk.pdf
+
+install: $(LIB)
+        test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
+        test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
+        install -m 0755 mk $(PREFIX)/bin/mk
+        cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
+        install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
+        install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
+
+
+$(TARG): $(OFILES)
+        $(CC) -o $(TARG) $(OFILES) -L$(PREFIX)/lib -lregexp9 -lbio -lfmt -lutf
+
+
+.c.$O:
+        $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+        $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+        rm -rf $(NAME)-$(VERSION)
+        mkdir $(NAME)-$(VERSION)
+        cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+        tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+        rm -rf $(NAME)-$(VERSION)
+
+clean:
+        rm -f $(OFILES) $(LIB)
+
+nuke:
+        rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+        make tgz
+        cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+        rpm -ba rpm.spec
+        cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+        cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+        scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+        make tgz
+        rm -rf $(PORTDIR)
+        mkdir $(PORTDIR)
+        cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+        cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+        (cd $(PORTDIR); make makesum)
+        (cd $(PORTDIR); make)
+        (cd $(PORTDIR); /usr/local/bin/portlint)
+        rm -rf $(PORTDIR)/work
+        shar `find $(PORTDIR)` > ports.shar
+        (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+        scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/cmd/mk/Makefile.MID b/src/cmd/mk/Makefile.MID
t@@ -0,0 +1,45 @@
+TARG=mk
+VERSION=2.0
+PORTPLACE=devel/mk
+NAME=mk
+
+OFILES=\
+        arc.$O\
+        archive.$O\
+        bufblock.$O\
+        env.$O\
+        file.$O\
+        graph.$O\
+        job.$O\
+        lex.$O\
+        main.$O\
+        match.$O\
+        mk.$O\
+        parse.$O\
+        recipe.$O\
+        rule.$O\
+        run.$O\
+        sh.$O\
+        shprint.$O\
+        symtab.$O\
+        var.$O\
+        varsub.$O\
+        word.$O\
+        unix.$O\
+
+HFILES=\
+        mk.h\
+        fns.h\
+
+all: $(TARG)
+
+TGZFILES+=mk.pdf
+
+install: $(LIB)
+        test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
+        test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
+        install -m 0755 mk $(PREFIX)/bin/mk
+        cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
+        install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
+        install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
+
diff --git a/src/cmd/mk/arc.c b/src/cmd/mk/arc.c
t@@ -0,0 +1,52 @@
+#include        "mk.h"
+
+Arc *
+newarc(Node *n, Rule *r, char *stem, Resub *match)
+{
+        Arc *a;
+
+        a = (Arc *)Malloc(sizeof(Arc));
+        a->n = n;
+        a->r = r;
+        a->stem = strdup(stem);
+        rcopy(a->match, match, NREGEXP);
+        a->next = 0;
+        a->flag = 0;
+        a->prog = r->prog;
+        return(a);
+}
+
+void
+dumpa(char *s, Arc *a)
+{
+        char buf[1024];
+
+        Bprint(&bout, "%sArc@%p: n=%p r=%p flag=0x%x stem='%s'",
+                s, a, a->n, a->r, a->flag, a->stem);
+        if(a->prog)
+                Bprint(&bout, " prog='%s'", a->prog);
+        Bprint(&bout, "\n");
+
+        if(a->n){
+                snprint(buf, sizeof(buf), "%s    ", (*s == ' ')? s:"");
+                dumpn(buf, a->n);
+        }
+}
+
+void
+nrep(void)
+{
+        Symtab *sym;
+        Word *w;
+
+        sym = symlook("NREP", S_VAR, 0);
+        if(sym){
+                w = (Word *) sym->value;
+                if (w && w->s && *w->s)
+                        nreps = atoi(w->s);
+        }
+        if(nreps < 1)
+                nreps = 1;
+        if(DEBUG(D_GRAPH))
+                Bprint(&bout, "nreps = %d\n", nreps);
+}
diff --git a/src/cmd/mk/archive.c b/src/cmd/mk/archive.c
t@@ -0,0 +1,180 @@
+#include        "mk.h"
+#define        ARMAG        "!\n"
+#define        SARMAG        8
+
+#define        ARFMAG        "`\n"
+#define SARNAME        16
+
+struct        ar_hdr
+{
+        char        name[SARNAME];
+        char        date[12];
+        char        uid[6];
+        char        gid[6];
+        char        mode[8];
+        char        size[10];
+        char        fmag[2];
+};
+#define        SAR_HDR        (SARNAME+44)
+
+static int dolong;
+
+static void atimes(char *);
+static char *split(char*, char**);
+
+long
+atimeof(int force, char *name)
+{
+        Symtab *sym;
+        long t;
+        char *archive, *member, buf[512];
+
+        archive = split(name, &member);
+        if(archive == 0)
+                Exit();
+
+        t = mtime(archive);
+        sym = symlook(archive, S_AGG, 0);
+        if(sym){
+                if(force || (t > (long)sym->value)){
+                        atimes(archive);
+                        sym->value = (void *)t;
+                }
+        }
+        else{
+                atimes(archive);
+                /* mark the aggegate as having been done */
+                symlook(strdup(archive), S_AGG, "")->value = (void *)t;
+        }
+                /* truncate long member name to sizeof of name field in archive header */
+        if(dolong)
+                snprint(buf, sizeof(buf), "%s(%s)", archive, member);
+        else
+                snprint(buf, sizeof(buf), "%s(%.*s)", archive, SARNAME, member);
+        sym = symlook(buf, S_TIME, 0);
+        if (sym)
+                return (long)sym->value;        /* uggh */
+        return 0;
+}
+
+void
+atouch(char *name)
+{
+        char *archive, *member;
+        int fd, i;
+        struct ar_hdr h;
+        long t;
+
+        archive = split(name, &member);
+        if(archive == 0)
+                Exit();
+
+        fd = open(archive, ORDWR);
+        if(fd < 0){
+                fd = create(archive, OWRITE, 0666);
+                if(fd < 0){
+                        fprint(2, "create %s: %r\n", archive);
+                        Exit();
+                }
+                write(fd, ARMAG, SARMAG);
+        }
+        if(symlook(name, S_TIME, 0)){
+                /* hoon off and change it in situ */
+                LSEEK(fd, SARMAG, 0);
+                while(read(fd, (char *)&h, sizeof(h)) == sizeof(h)){
+                        for(i = SARNAME-1; i > 0 && h.name[i] == ' '; i--)
+                                        ;
+                        h.name[i+1]=0;
+                        if(strcmp(member, h.name) == 0){
+                                t = SARNAME-sizeof(h);        /* ughgghh */
+                                LSEEK(fd, t, 1);
+                                fprint(fd, "%-12ld", time(0));
+                                break;
+                        }
+                        t = atol(h.size);
+                        if(t&01) t++;
+                        LSEEK(fd, t, 1);
+                }
+        }
+        close(fd);
+}
+
+static void
+atimes(char *ar)
+{
+        struct ar_hdr h;
+        long t;
+        int fd, i;
+        char buf[BIGBLOCK];
+        char name[sizeof(h.name)+1];
+
+        fd = open(ar, OREAD);
+        if(fd < 0)
+                return;
+
+        if(read(fd, buf, SARMAG) != SARMAG){
+                close(fd);
+                return;
+        }
+        while(read(fd, (char *)&h, sizeof(h)) == sizeof(h)){
+                t = atol(h.date);
+                if(t == 0)        /* as it sometimes happens; thanks ken */
+                        t = 1;
+                strncpy(name, h.name, sizeof(h.name));
+                for(i = sizeof(h.name)-1; i > 0 && name[i] == ' '; i--)
+                                ;
+                if(name[i] == '/')                /* system V bug */
+                        i--;
+                name[i+1]=0;
+                sprint(buf, "%s(%s)", ar, h.size);
+                symlook(strdup(buf), S_TIME, (void *)t)->value = (void *)t;
+                t = atol(h.size);
+                if(t&01) t++;
+                LSEEK(fd, t, 1);
+        }
+        close(fd);
+}
+
+static int
+type(char *file)
+{
+        int fd;
+        char buf[SARMAG];
+
+        fd = open(file, OREAD);
+        if(fd < 0){
+                if(symlook(file, S_BITCH, 0) == 0){
+                        Bprint(&bout, "%s doesn't exist: assuming it will be an archive\n", file);
+                        symlook(file, S_BITCH, (void *)file);
+                }
+                return 1;
+        }
+        if(read(fd, buf, SARMAG) != SARMAG){
+                close(fd);
+                return 0;
+        }
+        close(fd);
+        return !strncmp(ARMAG, buf, SARMAG);
+}
+
+static char*
+split(char *name, char **member)
+{
+        char *p, *q;
+
+        p = strdup(name);
+        q = utfrune(p, '(');
+        if(q){
+                *q++ = 0;
+                if(member)
+                        *member = q;
+                q = utfrune(q, ')');
+                if (q)
+                        *q = 0;
+                if(type(p))
+                        return p;
+                free(p);
+                fprint(2, "mk: '%s' is not an archive\n", name);
+        }
+        return 0;
+}
diff --git a/src/cmd/mk/bundle.ports b/src/cmd/mk/bundle.ports
t@@ -0,0 +1,46 @@
+--- Makefile ---
+# New ports collection makefile for: mk
+# Date Created:                11 Feb 2003
+# Whom:                        rsc
+#
+# THIS LINE NEEDS REPLACING.  IT'S HERE TO GET BY PORTLINT
+# $FreeBSD: ports/devel/mk/Makefile,v 1.1 2003/02/12 00:51:22 rsc Exp $
+
+PORTNAME=        mk
+PORTVERSION=        2.0
+CATEGORIES=        devel
+MASTER_SITES=        http://pdos.lcs.mit.edu/~rsc/software/
+EXTRACT_SUFX=        .tgz
+
+MAINTAINER=        rsc@post.harvard.edu
+
+DEPENDS=        ${PORTSDIR}/devel/libutf \
+        ${PORTSDIR}/devel/libfmt \
+        ${PORTSDIR}/devel/libbio \
+        ${PORTSDIR}/devel/libregexp9
+
+MAN1=                mk.1
+USE_REINPLACE=        yes
+
+.include 
+
+post-patch:
+        ${REINPLACE_CMD} -e 's,$$(PREFIX),${PREFIX},g' ${WRKSRC}/Makefile
+
+.include 
+--- pkg-comment ---
+Streamlined replacement for make
+--- pkg-descr ---
+Mk is a streamlined replacement for make, written for 
+Tenth Edition Research Unix by Andrew Hume.
+
+WWW: http://pdos.lcs.mit.edu/~rsc/software/#mk
+
+Russ Cox
+rsc@post.harvard.edu
+--- pkg-plist ---
+bin/mk
+doc/mk.pdf
+--- /dev/null ---
+This is just a way to make sure blank lines don't
+creep into pkg-plist.
diff --git a/src/cmd/mk/env.c b/src/cmd/mk/env.c
t@@ -0,0 +1,149 @@
+#include        "mk.h"
+
+enum {
+        ENVQUANTA=10
+};
+
+Envy        *envy;
+static int nextv;
+
+static char        *myenv[] =
+{
+        "target",
+        "stem",
+        "prereq",
+        "pid",
+        "nproc",
+        "newprereq",
+        "alltarget",
+        "newmember",
+        "stem0",                /* must be in order from here */
+        "stem1",
+        "stem2",
+        "stem3",
+        "stem4",
+        "stem5",
+        "stem6",
+        "stem7",
+        "stem8",
+        "stem9",
+        0,
+};
+
+void
+initenv(void)
+{
+        char **p;
+
+        for(p = myenv; *p; p++)
+                symlook(*p, S_INTERNAL, (void *)"");
+        readenv();                                /* o.s. dependent */
+}
+
+static void
+envinsert(char *name, Word *value)
+{
+        static int envsize;
+
+        if (nextv >= envsize) {
+                envsize += ENVQUANTA;
+                envy = (Envy *) Realloc((char *) envy, envsize*sizeof(Envy));
+        }
+        envy[nextv].name = name;
+        envy[nextv++].values = value;
+}
+
+static void
+envupd(char *name, Word *value)
+{
+        Envy *e;
+
+        for(e = envy; e->name; e++)
+                if(strcmp(name, e->name) == 0){
+                        delword(e->values);
+                        e->values = value;
+                        return;
+                }
+        e->name = name;
+        e->values = value;
+        envinsert(0,0);
+}
+
+static void
+ecopy(Symtab *s)
+{
+        char **p;
+
+        if(symlook(s->name, S_NOEXPORT, 0))
+                return;
+        for(p = myenv; *p; p++)
+                if(strcmp(*p, s->name) == 0)
+                        return;
+        envinsert(s->name, (Word *) s->value);
+}
+
+void
+execinit(void)
+{
+        char **p;
+
+        nextv = 0;
+        for(p = myenv; *p; p++)
+                envinsert(*p, stow(""));
+
+        symtraverse(S_VAR, ecopy);
+        envinsert(0, 0);
+}
+
+Envy*
+buildenv(Job *j, int slot)
+{
+        char **p, *cp, *qp;
+        Word *w, *v, **l;
+        int i;
+        char buf[256];
+
+        envupd("target", wdup(j->t));
+        if(j->r->attr®EXP)
+                envupd("stem",newword(""));
+        else
+                envupd("stem", newword(j->stem));
+        envupd("prereq", wdup(j->p));
+        sprint(buf, "%d", getpid());
+        envupd("pid", newword(buf));
+        sprint(buf, "%d", slot);
+        envupd("nproc", newword(buf));
+        envupd("newprereq", wdup(j->np));
+        envupd("alltarget", wdup(j->at));
+        l = &v;
+        v = w = wdup(j->np);
+        while(w){
+                cp = strchr(w->s, '(');
+                if(cp){
+                        qp = strchr(cp+1, ')');
+                        if(qp){
+                                *qp = 0;
+                                strcpy(w->s, cp+1);
+                                l = &w->next;
+                                w = w->next;
+                                continue;
+                        }
+                }
+                *l = w->next;
+                free(w->s);
+                free(w);
+                w = *l;
+        }
+        envupd("newmember", v);
+                /* update stem0 -> stem9 */
+        for(p = myenv; *p; p++)
+                if(strcmp(*p, "stem0") == 0)
+                        break;
+        for(i = 0; *p; i++, p++){
+                if((j->r->attr®EXP) && j->match[i])
+                        envupd(*p, newword(j->match[i]));
+                else 
+                        envupd(*p, newword(""));
+        }
+        return envy;
+}
diff --git a/src/cmd/mk/file.c b/src/cmd/mk/file.c
t@@ -0,0 +1,90 @@
+#include        "mk.h"
+
+/* table-driven version in bootes dump of 12/31/96 */
+
+long
+mtime(char *name)
+{
+        return mkmtime(name);
+}
+
+long
+timeof(char *name, int force)
+{
+        Symtab *sym;
+        long t;
+
+        if(utfrune(name, '('))
+                return atimeof(force, name);        /* archive */
+
+        if(force)
+                return mtime(name);
+
+
+        sym = symlook(name, S_TIME, 0);
+        if (sym)
+                return (long) sym->value;                /* uggh */
+
+        t = mtime(name);
+        if(t == 0)
+                return 0;
+
+        symlook(name, S_TIME, (void*)t);                /* install time in cache */
+        return t;
+}
+
+void
+touch(char *name)
+{
+        Bprint(&bout, "touch(%s)\n", name);
+        if(nflag)
+                return;
+
+        if(utfrune(name, '('))
+                atouch(name);                /* archive */
+        else if(chgtime(name) < 0) {
+                fprint(2, "%s: %r\n", name);
+                Exit();
+        }
+}
+
+void
+delete(char *name)
+{
+        if(utfrune(name, '(') == 0) {                /* file */
+                if(remove(name) < 0)
+                        fprint(2, "remove %s: %r\n", name);
+        } else
+                fprint(2, "hoon off; mk can'tdelete archive members\n");
+}
+
+void
+timeinit(char *s)
+{
+        long t;
+        char *cp;
+        Rune r;
+        int c, n;
+
+        t = time(0);
+        while (*s) {
+                cp = s;
+                do{
+                        n = chartorune(&r, s);
+                        if (r == ' ' || r == ',' || r == '\n')
+                                break;
+                        s += n;
+                } while(*s);
+                c = *s;
+                *s = 0;
+                symlook(strdup(cp), S_TIME, (void *)t)->value = (void *)t;
+                if (c)
+                        *s++ = c;
+                while(*s){
+                        n = chartorune(&r, s);
+                        if(r != ' ' && r != ',' && r != '\n')
+                                break;
+                        s += n;
+                }
+        }
+}
diff --git a/src/cmd/mk/fns.h b/src/cmd/mk/fns.h
t@@ -0,0 +1,84 @@
+void        addrule(char*, Word*, char*, Word*, int, int, char*);
+void        addrules(Word*, Word*, char*, int, int, char*);
+void        addw(Word*, char*);
+void        assert(char*, int);
+int        assline(Biobuf *, Bufblock *);
+long        atimeof(int,char*);
+void        atouch(char*);
+void        bufcpy(Bufblock *, char *, int);
+Envy        *buildenv(Job*, int);
+void        catchnotes(void);
+char         *charin(char *, char *);
+int        chgtime(char*);
+void        clrmade(Node*);
+char        *copyq(char*, Rune, Bufblock*);
+void        delete(char*);
+void        delword(Word*);
+int        dorecipe(Node*);
+void        dumpa(char*, Arc*);
+void        dumpj(char*, Job*, int);
+void        dumpn(char*, Node*);
+void        dumpr(char*, Rule*);
+void        dumpv(char*);
+void        dumpw(char*, Word*);
+int        escapetoken(Biobuf*, Bufblock*, int, int);
+void        execinit(void);
+int        execsh(char*, char*, Bufblock*, Envy*);
+void        Exit(void);
+char        *expandquote(char*, Rune, Bufblock*);
+void        expunge(int, char*);
+void        freebuf(Bufblock*);
+void        front(char*);
+Node        *graph(char*);
+void        growbuf(Bufblock *);
+void        initenv(void);
+void        insert(Bufblock *, int);
+void        ipop(void);
+void        ipush(void);
+void        killchildren(char*);
+void        *Malloc(int);
+char        *maketmp(int*);
+int        match(char*, char*, char*);
+char *membername(char*, int, char*);
+void        mk(char*);
+ulong        mkmtime(char*);
+long        mtime(char*);
+Arc        *newarc(Node*, Rule*, char*, Resub*);
+Bufblock *newbuf(void);
+Job        *newjob(Rule*, Node*, char*, char**, Word*, Word*, Word*, Word*);
+Word        *newword(char*);
+int        nextrune(Biobuf*, int);
+int        nextslot(void);
+void        nproc(void);
+void        nrep(void);
+int        outofdate(Node*, Arc*, int);
+void        parse(char*, int, int);
+int        pipecmd(char*, Envy*, int*);
+void        prusage(void);
+void        rcopy(char**, Resub*, int);
+void        readenv(void);
+void        *Realloc(void*, int);
+void        rinsert(Bufblock *, Rune);
+char        *rulecnt(void);
+void        run(Job*);
+void        setvar(char*, void*);
+char        *shname(char*);
+void        shprint(char*, Envy*, Bufblock*);
+Word        *stow(char*);
+void        subst(char*, char*, char*);
+void        symdel(char*, int);
+void        syminit(void);
+Symtab        *symlook(char*, int, void*);
+void        symstat(void);
+void        symtraverse(int, void(*)(Symtab*));
+void        timeinit(char*);
+long        timeof(char*, int);
+void        touch(char*);
+void        update(int, Node*);
+void        usage(void);
+Word        *varsub(char**);
+int        waitfor(char*);
+int        waitup(int, int*);
+Word        *wdup(Word*);
+int        work(Node*, Node*, Arc*);
+char        *wtos(Word*, int);
diff --git a/src/cmd/mk/graph.c b/src/cmd/mk/graph.c
t@@ -0,0 +1,279 @@
+#include        "mk.h"
+
+static Node *applyrules(char *, char *);
+static void togo(Node *);
+static int vacuous(Node *);
+static Node *newnode(char *);
+static void trace(char *, Arc *);
+static void cyclechk(Node *);
+static void ambiguous(Node *);
+static void attribute(Node *);
+
+Node *
+graph(char *target)
+{
+        Node *node;
+        char *cnt;
+
+        cnt = rulecnt();
+        node = applyrules(target, cnt);
+        free(cnt);
+        cyclechk(node);
+        node->flags |= PROBABLE;        /* make sure it doesn't get deleted */
+        vacuous(node);
+        ambiguous(node);
+        attribute(node);
+        return(node);
+}
+
+static Node *
+applyrules(char *target, char *cnt)
+{
+        Symtab *sym;
+        Node *node;
+        Rule *r;
+        Arc head, *a = &head;
+        Word *w;
+        char stem[NAMEBLOCK], buf[NAMEBLOCK];
+        Resub rmatch[NREGEXP];
+
+/*        print("applyrules(%lux='%s')\n", target, target);*//**/
+        sym = symlook(target, S_NODE, 0);
+        if(sym)
+                return (Node *)(sym->value);
+        target = strdup(target);
+        node = newnode(target);
+        head.n = 0;
+        head.next = 0;
+        sym = symlook(target, S_TARGET, 0);
+        memset((char*)rmatch, 0, sizeof(rmatch));
+        for(r = sym? (Rule *)(sym->value):0; r; r = r->chain){
+                if(r->attr&META) continue;
+                if(strcmp(target, r->target)) continue;
+                if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue;        /* no effect; ignore */
+                if(cnt[r->rule] >= nreps) continue;
+                cnt[r->rule]++;
+                node->flags |= PROBABLE;
+
+/*                if(r->attr&VIR)
+ *                        node->flags |= VIRTUAL;
+ *                if(r->attr&NOREC)
+ *                        node->flags |= NORECIPE;
+ *                if(r->attr&DEL)
+ *                        node->flags |= DELETE;
+ */
+                if(!r->tail || !r->tail->s || !*r->tail->s) {
+                        a->next = newarc((Node *)0, r, "", rmatch);
+                        a = a->next;
+                } else
+                        for(w = r->tail; w; w = w->next){
+                                a->next = newarc(applyrules(w->s, cnt), r, "", rmatch);
+                                a = a->next;
+                }
+                cnt[r->rule]--;
+                head.n = node;
+        }
+        for(r = metarules; r; r = r->next){
+                if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue;        /* no effect; ignore */
+                if ((r->attr&NOVIRT) && a != &head && (a->r->attr&VIR))
+                        continue;
+                if(r->attr®EXP){
+                        stem[0] = 0;
+                        patrule = r;
+                        memset((char*)rmatch, 0, sizeof(rmatch));
+                        if(regexec(r->pat, node->name, rmatch, NREGEXP) == 0)
+                                continue;
+                } else {
+                        if(!match(node->name, r->target, stem)) continue;
+                }
+                if(cnt[r->rule] >= nreps) continue;
+                cnt[r->rule]++;
+
+/*                if(r->attr&VIR)
+ *                        node->flags |= VIRTUAL;
+ *                if(r->attr&NOREC)
+ *                        node->flags |= NORECIPE;
+ *                if(r->attr&DEL)
+ *                        node->flags |= DELETE;
+ */
+
+                if(!r->tail || !r->tail->s || !*r->tail->s) {
+                        a->next = newarc((Node *)0, r, stem, rmatch);
+                        a = a->next;
+                } else
+                        for(w = r->tail; w; w = w->next){
+                                if(r->attr®EXP)
+                                        regsub(w->s, buf, sizeof buf, rmatch, NREGEXP);
+                                else
+                                        subst(stem, w->s, buf);
+                                a->next = newarc(applyrules(buf, cnt), r, stem, rmatch);
+                                a = a->next;
+                        }
+                cnt[r->rule]--;
+        }
+        a->next = node->prereqs;
+        node->prereqs = head.next;
+        return(node);
+}
+
+static void
+togo(Node *node)
+{
+        Arc *la, *a;
+
+        /* delete them now */
+        la = 0;
+        for(a = node->prereqs; a; la = a, a = a->next)
+                if(a->flag&TOGO){
+                        if(a == node->prereqs)
+                                node->prereqs = a->next;
+                        else
+                                la->next = a->next, a = la;
+                }
+}
+
+static int
+vacuous(Node *node)
+{
+        Arc *la, *a;
+        int vac = !(node->flags&PROBABLE);
+
+        if(node->flags&READY)
+                return(node->flags&VACUOUS);
+        node->flags |= READY;
+        for(a = node->prereqs; a; a = a->next)
+                if(a->n && vacuous(a->n) && (a->r->attr&META))
+                        a->flag |= TOGO;
+                else
+                        vac = 0;
+        /* if a rule generated arcs that DON'T go; no others from that rule go */
+        for(a = node->prereqs; a; a = a->next)
+                if((a->flag&TOGO) == 0)
+                        for(la = node->prereqs; la; la = la->next)
+                                if((la->flag&TOGO) && (la->r == a->r)){
+                                        la->flag &= ~TOGO;
+                                }
+        togo(node);
+        if(vac)
+                node->flags |= VACUOUS;
+        return(vac);
+}
+
+static Node *
+newnode(char *name)
+{
+        register Node *node;
+
+        node = (Node *)Malloc(sizeof(Node));
+        symlook(name, S_NODE, (void *)node);
+        node->name = name;
+        node->time = timeof(name, 0);
+        node->prereqs = 0;
+        node->flags = node->time? PROBABLE : 0;
+        node->next = 0;
+        return(node);
+}
+
+void
+dumpn(char *s, Node *n)
+{
+        char buf[1024];
+        Arc *a;
+
+        sprint(buf, "%s   ", (*s == ' ')? s:"");
+        Bprint(&bout, "%s%s@%ld: time=%ld flags=0x%x next=%ld\n",
+                s, n->name, n, n->time, n->flags, n->next);
+        for(a = n->prereqs; a; a = a->next)
+                dumpa(buf, a);
+}
+
+static void
+trace(char *s, Arc *a)
+{
+        fprint(2, "\t%s", s);
+        while(a){
+                fprint(2, " <-(%s:%d)- %s", a->r->file, a->r->line,
+                        a->n? a->n->name:"");
+                if(a->n){
+                        for(a = a->n->prereqs; a; a = a->next)
+                                if(*a->r->recipe) break;
+                } else
+                        a = 0;
+        }
+        fprint(2, "\n");
+}
+
+static void
+cyclechk(Node *n)
+{
+        Arc *a;
+
+        if((n->flags&CYCLE) && n->prereqs){
+                fprint(2, "mk: cycle in graph detected at target %s\n", n->name);
+                Exit();
+        }
+        n->flags |= CYCLE;
+        for(a = n->prereqs; a; a = a->next)
+                if(a->n)
+                        cyclechk(a->n);
+        n->flags &= ~CYCLE;
+}
+
+static void
+ambiguous(Node *n)
+{
+        Arc *a;
+        Rule *r = 0;
+        Arc *la;
+        int bad = 0;
+
+        la = 0;
+        for(a = n->prereqs; a; a = a->next){
+                if(a->n)
+                        ambiguous(a->n);
+                if(*a->r->recipe == 0) continue;
+                if(r == 0)
+                        r = a->r, la = a;
+                else{
+                        if(r->recipe != a->r->recipe){
+                                if((r->attr&META) && !(a->r->attr&META)){
+                                        la->flag |= TOGO;
+                                        r = a->r, la = a;
+                                } else if(!(r->attr&META) && (a->r->attr&META)){
+                                        a->flag |= TOGO;
+                                        continue;
+                                }
+                        }
+                        if(r->recipe != a->r->recipe){
+                                if(bad == 0){
+                                        fprint(2, "mk: ambiguous recipes for %s:\n", n->name);
+                                        bad = 1;
+                                        trace(n->name, la);
+                                }
+                                trace(n->name, a);
+                        }
+                }
+        }
+        if(bad)
+                Exit();
+        togo(n);
+}
+
+static void
+attribute(Node *n)
+{
+        register Arc *a;
+
+        for(a = n->prereqs; a; a = a->next){
+                if(a->r->attr&VIR)
+                        n->flags |= VIRTUAL;
+                if(a->r->attr&NOREC)
+                        n->flags |= NORECIPE;
+                if(a->r->attr&DEL)
+                        n->flags |= DELETE;
+                if(a->n)
+                        attribute(a->n);
+        }
+        if(n->flags&VIRTUAL)
+                n->time = 0;
+}
diff --git a/src/cmd/mk/lex.c b/src/cmd/mk/lex.c
t@@ -0,0 +1,147 @@
+#include        "mk.h"
+
+static        int        bquote(Biobuf*, Bufblock*);
+
+/*
+ *        Assemble a line skipping blank lines, comments, and eliding
+ *        escaped newlines
+ */
+int
+assline(Biobuf *bp, Bufblock *buf)
+{
+        int c;
+        int lastc;
+
+        buf->current=buf->start;
+        while ((c = nextrune(bp, 1)) >= 0){
+                switch(c)
+                {
+                case '\r':                /* consumes CRs for Win95 */
+                        continue;
+                case '\n':
+                        if (buf->current != buf->start) {
+                                insert(buf, 0);
+                                return 1;
+                        }
+                        break;                /* skip empty lines */
+                case '\\':
+                case '\'':
+                case '"':
+                        rinsert(buf, c);
+                        if (escapetoken(bp, buf, 1, c) == 0)
+                                Exit();
+                        break;
+                case '`':
+                        if (bquote(bp, buf) == 0)
+                                Exit();
+                        break;
+                case '#':
+                        lastc = '#';
+                        while ((c = Bgetc(bp)) != '\n') {
+                                if (c < 0)
+                                        goto eof;
+                                if(c != '\r')
+                                        lastc = c;
+                        }
+                        mkinline++;
+                        if (lastc == '\\')
+                                break;                /* propagate escaped newlines??*/
+                        if (buf->current != buf->start) {
+                                insert(buf, 0);
+                                return 1;
+                        }
+                        break;
+                default:
+                        rinsert(buf, c);
+                        break;
+                }
+        }
+eof:
+        insert(buf, 0);
+        return *buf->start != 0;
+}
+
+/*
+ *        assemble a back-quoted shell command into a buffer
+ */
+static int
+bquote(Biobuf *bp, Bufblock *buf)
+{
+        int c, line, term;
+        int start;
+
+        line = mkinline;
+        while((c = Bgetrune(bp)) == ' ' || c == '\t')
+                        ;
+        if(c == '{'){
+                term = '}';                /* rc style */
+                while((c = Bgetrune(bp)) == ' ' || c == '\t')
+                        ;
+        } else
+                term = '`';                /* sh style */
+
+        start = buf->current-buf->start;
+        for(;c > 0; c = nextrune(bp, 0)){
+                if(c == term){
+                        insert(buf, '\n');
+                        insert(buf,0);
+                        buf->current = buf->start+start;
+                        execinit();
+                        execsh(0, buf->current, buf, envy);
+                        return 1;
+                }
+                if(c == '\n')
+                        break;
+                if(c == '\'' || c == '"' || c == '\\'){
+                        insert(buf, c);
+                        if(!escapetoken(bp, buf, 1, c))
+                                return 0;
+                        continue;
+                }
+                rinsert(buf, c);
+        }
+        SYNERR(line);
+        fprint(2, "missing closing %c after `\n", term);
+        return 0;
+}
+
+/*
+ *        get next character stripping escaped newlines
+ *        the flag specifies whether escaped newlines are to be elided or
+ *        replaced with a blank.
+ */
+int
+nextrune(Biobuf *bp, int elide)
+{
+        int c, c2;
+        static int savec;
+
+        if(savec){
+                c = savec;
+                savec = 0;
+                return c;
+        }
+
+        for (;;) {
+                c = Bgetrune(bp);
+                if (c == '\\') {
+                        c2 = Bgetrune(bp);
+                        if(c2 == '\r'){
+                                savec = c2;
+                                c2 = Bgetrune(bp);
+                        }
+                        if (c2 == '\n') {
+                                savec = 0;
+                                mkinline++;
+                                if (elide)
+                                        continue;
+                                return ' ';
+                        }
+                        Bungetrune(bp);
+                }
+                if (c == '\n')
+                        mkinline++;
+                return c;
+        }
+        return 0;
+}
diff --git a/src/cmd/mk/main.c b/src/cmd/mk/main.c
t@@ -0,0 +1,285 @@
+#include        "mk.h"
+
+#define                MKFILE                "mkfile"
+
+int debug;
+Rule *rules, *metarules;
+int nflag = 0;
+int tflag = 0;
+int iflag = 0;
+int kflag = 0;
+int aflag = 0;
+int uflag = 0;
+char *explain = 0;
+Word *target1;
+int nreps = 1;
+Job *jobs;
+Biobuf bout;
+Rule *patrule;
+void badusage(void);
+#ifdef        PROF
+short buf[10000];
+#endif
+
+int
+main(int argc, char **argv)
+{
+        Word *w;
+        char *s, *temp;
+        char *files[256], **f = files, **ff;
+        int sflag = 0;
+        int i;
+        int tfd = -1;
+        Biobuf tb;
+        Bufblock *buf;
+        Bufblock *whatif;
+
+        /*
+         *  start with a copy of the current environment variables
+         *  instead of sharing them
+         */
+
+        Binit(&bout, 1, OWRITE);
+        buf = newbuf();
+        whatif = 0;
+        USED(argc);
+        for(argv++; *argv && (**argv == '-'); argv++)
+        {
+                bufcpy(buf, argv[0], strlen(argv[0]));
+                insert(buf, ' ');
+                switch(argv[0][1])
+                {
+                case 'a':
+                        aflag = 1;
+                        break;
+                case 'd':
+                        if(*(s = &argv[0][2]))
+                                while(*s) switch(*s++)
+                                {
+                                case 'p':        debug |= D_PARSE; break;
+                                case 'g':        debug |= D_GRAPH; break;
+                                case 'e':        debug |= D_EXEC; break;
+                                }
+                        else
+                                debug = 0xFFFF;
+                        break;
+                case 'e':
+                        explain = &argv[0][2];
+                        break;
+                case 'f':
+                        if(*++argv == 0)
+                                badusage();
+                        *f++ = *argv;
+                        bufcpy(buf, argv[0], strlen(argv[0]));
+                        insert(buf, ' ');
+                        break;
+                case 'i':
+                        iflag = 1;
+                        break;
+                case 'k':
+                        kflag = 1;
+                        break;
+                case 'n':
+                        nflag = 1;
+                        break;
+                case 's':
+                        sflag = 1;
+                        break;
+                case 't':
+                        tflag = 1;
+                        break;
+                case 'u':
+                        uflag = 1;
+                        break;
+                case 'w':
+                        if(whatif == 0)
+                                whatif = newbuf();
+                        else
+                                insert(whatif, ' ');
+                        if(argv[0][2])
+                                bufcpy(whatif, &argv[0][2], strlen(&argv[0][2]));
+                        else {
+                                if(*++argv == 0)
+                                        badusage();
+                                bufcpy(whatif, &argv[0][0], strlen(&argv[0][0]));
+                        }
+                        break;
+                default:
+                        badusage();
+                }
+        }
+#ifdef        PROF
+        {
+                extern etext();
+                monitor(main, etext, buf, sizeof buf, 300);
+        }
+#endif
+
+        if(aflag)
+                iflag = 1;
+        usage();
+        syminit();
+        initenv();
+        usage();
+
+        /*
+                assignment args become null strings
+        */
+        temp = 0;
+        for(i = 0; argv[i]; i++) if(utfrune(argv[i], '=')){
+                bufcpy(buf, argv[i], strlen(argv[i]));
+                insert(buf, ' ');
+                if(tfd < 0){
+                        temp = maketmp(&tfd);
+                        if(temp == 0) {
+                                fprint(2, "temp file: %r\n");
+                                Exit();
+                        }
+                        Binit(&tb, tfd, OWRITE);
+                }
+                Bprint(&tb, "%s\n", argv[i]);
+                *argv[i] = 0;
+        }
+        if(tfd >= 0){
+                Bflush(&tb);
+                LSEEK(tfd, 0L, 0);
+                parse("command line args", tfd, 1);
+                remove(temp);
+        }
+
+        if (buf->current != buf->start) {
+                buf->current--;
+                insert(buf, 0);
+        }
+        symlook("MKFLAGS", S_VAR, (void *) stow(buf->start));
+        buf->current = buf->start;
+        for(i = 0; argv[i]; i++){
+                if(*argv[i] == 0) continue;
+                if(i)
+                        insert(buf, ' ');
+                bufcpy(buf, argv[i], strlen(argv[i]));
+        }
+        insert(buf, 0);
+        symlook("MKARGS", S_VAR, (void *) stow(buf->start));
+        freebuf(buf);
+
+        if(f == files){
+                if(access(MKFILE, 4) == 0)
+                        parse(MKFILE, open(MKFILE, 0), 0);
+        } else
+                for(ff = files; ff < f; ff++)
+                        parse(*ff, open(*ff, 0), 0);
+        if(DEBUG(D_PARSE)){
+                dumpw("default targets", target1);
+                dumpr("rules", rules);
+                dumpr("metarules", metarules);
+                dumpv("variables");
+        }
+        if(whatif){
+                insert(whatif, 0);
+                timeinit(whatif->start);
+                freebuf(whatif);
+        }
+        execinit();
+        /* skip assignment args */
+        while(*argv && (**argv == 0))
+                argv++;
+
+        catchnotes();
+        if(*argv == 0){
+                if(target1)
+                        for(w = target1; w; w = w->next)
+                                mk(w->s);
+                else {
+                        fprint(2, "mk: nothing to mk\n");
+                        Exit();
+                }
+        } else {
+                if(sflag){
+                        for(; *argv; argv++)
+                                if(**argv)
+                                        mk(*argv);
+                } else {
+                        Word *head, *tail, *t;
+
+                        /* fake a new rule with all the args as prereqs */
+                        tail = 0;
+                        t = 0;
+                        for(; *argv; argv++)
+                                if(**argv){
+                                        if(tail == 0)
+                                                tail = t = newword(*argv);
+                                        else {
+                                                t->next = newword(*argv);
+                                                t = t->next;
+                                        }
+                                }
+                        if(tail->next == 0)
+                                mk(tail->s);
+                        else {
+                                head = newword("command line arguments");
+                                addrules(head, tail, strdup(""), VIR, mkinline, 0);
+                                mk(head->s);
+                        }
+                }
+        }
+        if(uflag)
+                prusage();
+        exits(0);
+}
+
+void
+badusage(void)
+{
+
+        fprint(2, "Usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n");
+        Exit();
+}
+
+void *
+Malloc(int n)
+{
+        register void *s;
+
+        s = malloc(n);
+        if(!s) {
+                fprint(2, "mk: cannot alloc %d bytes\n", n);
+                Exit();
+        }
+        return(s);
+}
+
+void *
+Realloc(void *s, int n)
+{
+        if(s)
+                s = realloc(s, n);
+        else
+                s = malloc(n);
+        if(!s) {
+                fprint(2, "mk: cannot alloc %d bytes\n", n);
+                Exit();
+        }
+        return(s);
+}
+
+void
+assert(char *s, int n)
+{
+        if(!n){
+                fprint(2, "mk: Assertion ``%s'' failed.\n", s);
+                Exit();
+        }
+}
+
+void
+regerror(char *s)
+{
+        if(patrule)
+                fprint(2, "mk: %s:%d: regular expression error; %s\n",
+                        patrule->file, patrule->line, s);
+        else
+                fprint(2, "mk: %s:%d: regular expression error; %s\n",
+                        infile, mkinline, s);
+        Exit();
+}
diff --git a/src/cmd/mk/match.c b/src/cmd/mk/match.c
t@@ -0,0 +1,49 @@
+#include        "mk.h"
+
+int
+match(char *name, char *template, char *stem)
+{
+        Rune r;
+        int n;
+
+        while(*name && *template){
+                n = chartorune(&r, template);
+                if (PERCENT(r))
+                        break;
+                while (n--)
+                        if(*name++ != *template++)
+                                return 0;
+        }
+        if(!PERCENT(*template))
+                return 0;
+        n = strlen(name)-strlen(template+1);
+        if (n < 0)
+                return 0;
+        if (strcmp(template+1, name+n))
+                return 0;
+        strncpy(stem, name, n);
+        stem[n] = 0;
+        if(*template == '&')
+                return !charin(stem, "./");
+        return 1;
+}
+
+void
+subst(char *stem, char *template, char *dest)
+{
+        Rune r;
+        char *s;
+        int n;
+
+        while(*template){
+                n = chartorune(&r, template);
+                if (PERCENT(r)) {
+                        template += n;
+                        for (s = stem; *s; s++)
+                                *dest++ = *s;
+                } else
+                        while (n--)
+                                *dest++ = *template++;
+        }
+        *dest = 0;
+}
diff --git a/src/cmd/mk/mk.1 b/src/cmd/mk/mk.1
t@@ -0,0 +1,665 @@
+.TH MK 1
+.de EX
+.nf
+.ft B
+..
+.de EE
+.fi
+.ft R
+..
+.de LR
+.if t .BR \\$1 \\$2
+.if n .RB ` \\$1 '\\$2
+..
+.de L
+.nh
+.if t .B \\$1
+.if n .RB ` \\$1 '
+..
+.SH NAME
+mk \- maintain (make) related files
+.SH SYNOPSIS
+.B mk
+[
+.B -f
+.I mkfile
+] ...
+[
+.I option ...
+]
+[
+.I target ...
+]
+.SH DESCRIPTION
+.I Mk
+uses the dependency rules specified in
+.I mkfile
+to control the update (usually by compilation) of
+.I targets
+(usually files)
+from the source files upon which they depend.
+The
+.I mkfile
+(default
+.LR mkfile )
+contains a
+.I rule
+for each target that identifies the files and other
+targets upon which it depends and an
+.IR sh (1)
+script, a
+.IR recipe ,
+to update the target.
+The script is run if the target does not exist
+or if it is older than any of the files it depends on.
+.I Mkfile
+may also contain
+.I meta-rules
+that define actions for updating implicit targets.
+If no
+.I target
+is specified, the target of the first rule (not meta-rule) in
+.I mkfile
+is updated.
+.PP
+The environment variable
+.B $NPROC
+determines how many targets may be updated simultaneously;
+Some operating systems, e.g., Plan 9, set
+.B $NPROC
+automatically to the number of CPUs on the current machine.
+.PP
+Options are:
+.TP \w'\fL-d[egp]\ 'u
+.B -a
+Assume all targets to be out of date.
+Thus, everything is updated.
+.PD 0
+.TP
+.BR -d [ egp ]
+Produce debugging output
+.RB ( p
+is for parsing,
+.B g
+for graph building,
+.B e
+for execution).
+.TP
+.B -e
+Explain why each target is made.
+.TP
+.B -i
+Force any missing intermediate targets to be made.
+.TP
+.B -k
+Do as much work as possible in the face of errors.
+.TP
+.B -n
+Print, but do not execute, the commands
+needed to update the targets.
+.TP
+.B -s
+Make the command line arguments sequentially rather than in parallel.
+.TP
+.B -t
+Touch (update the modified date of) file targets, without
+executing any recipes.
+.TP
+.BI -w target1 , target2,...
+Pretend the modify time for each
+.I target
+is the current time; useful in conjunction with
+.B -n
+to learn what updates would be triggered by
+modifying the
+.IR targets .
+.PD
+.SS The \fLmkfile\fP
+A
+.I mkfile
+consists of
+.I assignments
+(described under `Environment') and
+.IR rules .
+A rule contains
+.I targets
+and a
+.IR tail .
+A target is a literal string
+and is normally a file name.
+The tail contains zero or more 
+.I prerequisites
+and an optional
+.IR recipe ,
+which is an
+.B shell
+script.
+Each line of the recipe must begin with white space.
+A rule takes the form
+.IP
+.EX
+target: prereq1 prereq2
+        \f2recipe using\fP prereq1, prereq2 \f2to build\fP target
+.EE
+.PP
+When the recipe is executed,
+the first character on every line is elided.
+.PP
+After the colon on the target line, a rule may specify
+.IR attributes ,
+described below.
+.PP
+A
+.I meta-rule 
+has a target of the form
+.IB A % B
+where
+.I A
+and
+.I B
+are (possibly empty) strings.
+A meta-rule acts as a rule for any potential target whose
+name matches
+.IB A % B
+with
+.B %
+replaced by an arbitrary string, called the
+.IR stem .
+In interpreting a meta-rule,
+the stem is substituted for all occurrences of
+.B %
+in the prerequisite names.
+In the recipe of a meta-rule, the environment variable
+.B $stem
+contains the string matched by the
+.BR % .
+For example, a meta-rule to compile a C program using
+.IR cc (1)
+might be:
+.IP
+.EX
+%:    %.c
+        cc -c $stem.c
+        cc -o $stem $stem.o
+.EE
+.PP
+Meta-rules may contain an ampersand
+.B &
+rather than a percent sign
+.BR % .
+A
+.B %
+matches a maximal length string of any characters;
+an
+.B &
+matches a maximal length string of any characters except period
+or slash.
+.PP
+The text of the
+.I mkfile
+is processed as follows.
+Lines beginning with
+.B <
+followed by a file name are replaced by the contents of the named
+file.
+Lines beginning with
+.B "<|"
+followed by a file name are replaced by the output
+of the execution of the named
+file.
+Blank lines and comments, which run from unquoted
+.B #
+characters to the following newline, are deleted.
+The character sequence backslash-newline is deleted,
+so long lines in
+.I mkfile
+may be folded.
+Non-recipe lines are processed by substituting for
+.BI `{ command }
+the output of the
+.I command
+when run by
+.IR sh .
+References to variables are replaced by the variables' values.
+Special characters may be quoted using single quotes
+.BR \&''
+as in
+.IR sh (1).
+.PP
+Assignments and rules are distinguished by
+the first unquoted occurrence of
+.B :
+(rule)
+or
+.B =
+(assignment).
+.PP
+A later rule may modify or override an existing rule under the
+following conditions:
+.TP
+\-
+If the targets of the rules exactly match and one rule
+contains only a prerequisite clause and no recipe, the
+clause is added to the prerequisites of the other rule.
+If either or both targets are virtual, the recipe is
+always executed.
+.TP
+\-
+If the targets of the rules match exactly and the
+prerequisites do not match and both rules
+contain recipes,
+.I mk
+reports an ``ambiguous recipe'' error.
+.TP
+\-
+If the target and prerequisites of both rules match exactly,
+the second rule overrides the first.
+.SS Environment
+Rules may make use of
+shell
+environment variables.
+A legal reference of the form
+.B $OBJ
+or
+.B ${name}
+is expanded as in
+.IR sh (1).
+A reference of the form
+.BI ${name: A % B = C\fL%\fID\fL}\fR,
+where
+.I A, B, C, D
+are (possibly empty) strings,
+has the value formed by expanding
+.B $name
+and substituting
+.I C
+for
+.I A
+and
+.I D
+for
+.I B
+in each word in
+.B $name
+that matches pattern
+.IB A % B\f1.
+.PP
+Variables can be set by
+assignments of the form
+.I
+        var\fL=\fR[\fIattr\fL=\fR]\fIvalue\fR
+.br
+Blanks in the
+.I value
+break it into words.
+Such variables are exported
+to the environment of
+recipes as they are executed, unless
+.BR U ,
+the only legal attribute
+.IR attr ,
+is present.
+The initial value of a variable is
+taken from (in increasing order of precedence)
+the default values below,
+.I mk's
+environment, the
+.IR mkfiles ,
+and any command line assignment as an argument to
+.IR mk .
+A variable assignment argument overrides the first (but not any subsequent)
+assignment to that variable.
+The variable
+.B MKFLAGS
+contains all the option arguments (arguments starting with
+.L -
+or containing
+.LR = )
+and
+.B MKARGS
+contains all the targets in the call to
+.IR mk .
+.PP
+Dynamic information may be included in the mkfile by using a line of the form
+.IP
+\fR<|\fIcommand\fR \fIargs\fR
+.LP
+This runs the command 
+.I command
+with the given arguments
+.I args
+and pipes its standard output to
+.I mk
+to be included as part of the mkfile. For instance, the Inferno kernels
+use this technique
+to run a shell command with an awk script and a configuration
+file as arguments in order for
+the
+.I awk
+script to process the file and output a set of variables and their values.
+.SS Execution
+.PP
+During execution,
+.I mk
+determines which targets must be updated, and in what order,
+to build the
+.I names
+specified on the command line.
+It then runs the associated recipes.
+.PP
+A target is considered up to date if it has no prerequisites or
+if all its prerequisites are up to date and it is newer
+than all its prerequisites.
+Once the recipe for a target has executed, the target is
+considered up to date.
+.PP
+The date stamp
+used to determine if a target is up to date is computed
+differently for different types of targets.
+If a target is
+.I virtual
+(the target of a rule with the
+.B V
+attribute),
+its date stamp is initially zero; when the target is
+updated the date stamp is set to
+the most recent date stamp of its prerequisites.
+Otherwise, if a target does not exist as a file,
+its date stamp is set to the most recent date stamp of its prerequisites,
+or zero if it has no prerequisites.
+Otherwise, the target is the name of a file and
+the target's date stamp is always that file's modification date.
+The date stamp is computed when the target is needed in
+the execution of a rule; it is not a static value.
+.PP
+Nonexistent targets that have prerequisites
+and are themselves prerequisites are treated specially.
+Such a target
+.I t
+is given the date stamp of its most recent prerequisite
+and if this causes all the targets which have
+.I t
+as a prerequisite to be up to date,
+.I t
+is considered up to date.
+Otherwise,
+.I t
+is made in the normal fashion.
+The
+.B -i
+flag overrides this special treatment.
+.PP
+Files may be made in any order that respects
+the preceding restrictions.
+.PP
+A recipe is executed by supplying the recipe as standard input to
+the command
+.BR /bin/sh .
+(Note that unlike
+.IR make ,
+.I mk
+feeds the entire recipe to the shell rather than running each line
+of the recipe separately.)
+The environment is augmented by the following variables:
+.TP 14
+.B $alltarget
+all the targets of this rule.
+.TP
+.B $newprereq
+the prerequisites that caused this rule to execute.
+.TP
+.B $newmember
+the prerequisites that are members of an aggregate
+that caused this rule to execute.
+When the prerequisites of a rule are members of an
+aggregate,
+.B $newprereq
+contains the name of the aggregate and out of date
+members, while
+.B $newmember
+contains only the name of the members.
+.TP
+.B $nproc
+the process slot for this recipe.
+It satisfies
+.RB 0≤ $nproc < $NPROC .
+.TP
+.B $pid
+the process id for the
+.I mk
+executing the recipe.
+.TP
+.B $prereq
+all the prerequisites for this rule.
+.TP
+.B $stem
+if this is a meta-rule,
+.B $stem
+is the string that matched
+.B %
+or
+.BR & .
+Otherwise, it is empty.
+For regular expression meta-rules (see below), the variables
+.LR stem0 ", ...,"
+.L stem9
+are set to the corresponding subexpressions.
+.TP
+.B $target
+the targets for this rule that need to be remade.
+.PP
+These variables are available only during the execution of a recipe,
+not while evaluating the
+.IR mkfile .
+.PP
+Unless the rule has the
+.B Q
+attribute,
+the recipe is printed prior to execution
+with recognizable environment variables expanded.
+Commands returning error status
+cause
+.I mk
+to terminate.
+.PP
+Recipes and backquoted
+.B rc
+commands in places such as assignments
+execute in a copy of
+.I mk's
+environment; changes they make to
+environment variables are not visible from
+.IR mk .
+.PP
+Variable substitution in a rule is done when
+the rule is read; variable substitution in the recipe is done
+when the recipe is executed.  For example:
+.IP
+.EX
+bar=a.c
+foo:        $bar
+        $CC -o foo $bar
+bar=b.c
+.EE
+.PP
+will compile
+.B b.c
+into
+.BR foo ,
+if
+.B a.c
+is newer than
+.BR foo .
+.SS Aggregates
+Names of the form
+.IR a ( b )
+refer to member
+.I b
+of the aggregate
+.IR a .
+Currently, the only aggregates supported are
+.IR ar (1)
+archives.
+.SS Attributes
+The colon separating the target from the prerequisites
+may be
+immediately followed by
+.I attributes
+and another colon.
+The attributes are:
+.TP
+.B D
+If the recipe exits with a non-null status, the target is deleted.
+.TP
+.B E
+Continue execution if the recipe draws errors.
+.TP
+.B N
+If there is no recipe, the target has its time updated.
+.TP
+.B n
+The rule is a meta-rule that cannot be a target of a virtual rule.
+Only files match the pattern in the target.
+.TP
+.B P
+The characters after the
+.B P
+until the terminating
+.B :
+are taken as a program name.
+It will be invoked as
+.B "sh -c prog 'arg1' 'arg2'"
+and should return a zero exit status
+if and only if arg1 is up to date with respect to arg2.
+Date stamps are still propagated in the normal way.
+.TP
+.B Q
+The recipe is not printed prior to execution.
+.TP
+.B R
+The rule is a meta-rule using regular expressions.
+In the rule,
+.B %
+has no special meaning.
+The target is interpreted as a regular expression as defined in
+.IR regexp (6).
+The prerequisites may contain references
+to subexpressions in form
+.BI \e n\f1,
+as in the substitute command of
+.IR sed (1).
+.TP
+.B U
+The targets are considered to have been updated
+even if the recipe did not do so.
+.TP
+.B V
+The targets of this rule are marked as virtual.
+They are distinct from files of the same name.
+.PD
+.SH EXAMPLES
+A simple mkfile to compile a program:
+.IP
+.EX
+.ta 8n +8n +8n +8n +8n +8n +8n
+
diff --git a/src/cmd/mk/mk.c b/src/cmd/mk/mk.c
t@@ -0,0 +1,226 @@
+#include        "mk.h"
+
+int runerrs;
+
+void
+mk(char *target)
+{
+        Node *node;
+        int did = 0;
+
+        nproc();                /* it can be updated dynamically */
+        nrep();                        /* it can be updated dynamically */
+        runerrs = 0;
+        node = graph(target);
+        if(DEBUG(D_GRAPH)){
+                dumpn("new target\n", node);
+                Bflush(&bout);
+        }
+        clrmade(node);
+        while(node->flags&NOTMADE){
+                if(work(node, (Node *)0, (Arc *)0))
+                        did = 1;        /* found something to do */
+                else {
+                        if(waitup(1, (int *)0) > 0){
+                                if(node->flags&(NOTMADE|BEINGMADE)){
+                                        assert("must be run errors", runerrs);
+                                        break;        /* nothing more waiting */
+                                }
+                        }
+                }
+        }
+        if(node->flags&BEINGMADE)
+                waitup(-1, (int *)0);
+        while(jobs)
+                waitup(-2, (int *)0);
+        assert("target didn't get done", runerrs || (node->flags&MADE));
+        if(did == 0)
+                Bprint(&bout, "mk: '%s' is up to date\n", node->name);
+}
+
+void
+clrmade(Node *n)
+{
+        Arc *a;
+
+        n->flags &= ~(CANPRETEND|PRETENDING);
+        if(strchr(n->name, '(') ==0 || n->time)
+                n->flags |= CANPRETEND;
+        MADESET(n, NOTMADE);
+        for(a = n->prereqs; a; a = a->next)
+                if(a->n)
+                        clrmade(a->n);
+}
+
+static void
+unpretend(Node *n)
+{
+        MADESET(n, NOTMADE);
+        n->flags &= ~(CANPRETEND|PRETENDING);
+        n->time = 0;
+}
+
+int
+work(Node *node, Node *p, Arc *parc)
+{
+        Arc *a, *ra;
+        int weoutofdate;
+        int ready;
+        int did = 0;
+
+        /*print("work(%s) flags=0x%x time=%ld\n", node->name, node->flags, node->time);*//**/
+        if(node->flags&BEINGMADE)
+                return(did);
+        if((node->flags&MADE) && (node->flags&PRETENDING) && p && outofdate(p, parc, 0)){
+                if(explain)
+                        fprint(1, "unpretending %s(%ld) because %s is out of date(%ld)\n",
+                                node->name, node->time, p->name, p->time);
+                unpretend(node);
+        }
+        /*
+                have a look if we are pretending in case
+                someone has been unpretended out from underneath us
+        */
+        if(node->flags&MADE){
+                if(node->flags&PRETENDING){
+                        node->time = 0;
+                }else
+                        return(did);
+        }
+        /* consider no prerequsite case */
+        if(node->prereqs == 0){
+                if(node->time == 0){
+                        fprint(2, "mk: don't know how to make '%s'\n", node->name);
+                        if(kflag){
+                                node->flags |= BEINGMADE;
+                                runerrs++;
+                        } else
+                                Exit();
+                } else
+                        MADESET(node, MADE);
+                return(did);
+        }
+        /*
+                now see if we are out of date or what
+        */
+        ready = 1;
+        weoutofdate = aflag;
+        ra = 0;
+        for(a = node->prereqs; a; a = a->next)
+                if(a->n){
+                        did = work(a->n, node, a) || did;
+                        if(a->n->flags&(NOTMADE|BEINGMADE))
+                                ready = 0;
+                        if(outofdate(node, a, 0)){
+                                weoutofdate = 1;
+                                if((ra == 0) || (ra->n == 0)
+                                                || (ra->n->time < a->n->time))
+                                        ra = a;
+                        }
+                } else {
+                        if(node->time == 0){
+                                if(ra == 0)
+                                        ra = a;
+                                weoutofdate = 1;
+                        }
+                }
+        if(ready == 0)        /* can't do anything now */
+                return(did);
+        if(weoutofdate == 0){
+                MADESET(node, MADE);
+                return(did);
+        }
+        /*
+                can we pretend to be made?
+        */
+        if((iflag == 0) && (node->time == 0) && (node->flags&(PRETENDING|CANPRETEND))
+                        && p && ra->n && !outofdate(p, ra, 0)){
+                node->flags &= ~CANPRETEND;
+                MADESET(node, MADE);
+                if(explain && ((node->flags&PRETENDING) == 0))
+                        fprint(1, "pretending %s has time %ld\n", node->name, node->time);
+                node->flags |= PRETENDING;
+                return(did);
+        }
+        /*
+                node is out of date and we REALLY do have to do something.
+                quickly rescan for pretenders
+        */
+        for(a = node->prereqs; a; a = a->next)
+                if(a->n && (a->n->flags&PRETENDING)){
+                        if(explain)
+                                Bprint(&bout, "unpretending %s because of %s because of %s\n",
+                                a->n->name, node->name, ra->n? ra->n->name : "rule with no prerequisites");
+
+                        unpretend(a->n);
+                        did = work(a->n, node, a) || did;
+                        ready = 0;
+                }
+        if(ready == 0)        /* try later unless nothing has happened for -k's sake */
+                return(did || work(node, p, parc));
+        did = dorecipe(node) || did;
+        return(did);
+}
+
+void
+update(int fake, Node *node)
+{
+        Arc *a;
+
+        MADESET(node, fake? BEINGMADE : MADE);
+        if(((node->flags&VIRTUAL) == 0) && (access(node->name, 0) == 0)){
+                node->time = timeof(node->name, 1);
+                node->flags &= ~(CANPRETEND|PRETENDING);
+                for(a = node->prereqs; a; a = a->next)
+                        if(a->prog)
+                                outofdate(node, a, 1);
+        } else {
+                node->time = 1;
+                for(a = node->prereqs; a; a = a->next)
+                        if(a->n && outofdate(node, a, 1))
+                                node->time = a->n->time;
+        }
+/*        print("----node %s time=%ld flags=0x%x\n", node->name, node->time, node->flags);*//**/
+}
+
+static int
+pcmp(char *prog, char *p, char *q)
+{
+        char buf[3*NAMEBLOCK];
+        int pid;
+
+        Bflush(&bout);
+        sprint(buf, "%s '%s' '%s'\n", prog, p, q);
+        pid = pipecmd(buf, 0, 0);
+        while(waitup(-3, &pid) >= 0)
+                ;
+        return(pid? 2:1);
+}
+
+int
+outofdate(Node *node, Arc *arc, int eval)
+{
+        char buf[3*NAMEBLOCK], *str;
+        Symtab *sym;
+        int ret;
+
+        str = 0;
+        if(arc->prog){
+                sprint(buf, "%s%c%s", node->name, 0377, arc->n->name);
+                sym = symlook(buf, S_OUTOFDATE, 0);
+                if(sym == 0 || eval){
+                        if(sym == 0)
+                                str = strdup(buf);
+                        ret = pcmp(arc->prog, node->name, arc->n->name);
+                        if(sym)
+                                sym->value = (void *)ret;
+                        else
+                                symlook(str, S_OUTOFDATE, (void *)ret);
+                } else
+                        ret = (int)sym->value;
+                return(ret-1);
+        } else if(strchr(arc->n->name, '(') && arc->n->time == 0)  /* missing archive member */
+                return 1;
+        else
+                return node->time < arc->n->time;
+}
diff --git a/src/cmd/mk/mk.h b/src/cmd/mk/mk.h
t@@ -0,0 +1,203 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include        
+#include        
+#include 
+
+#define uchar _mkuchar
+#define ushort _mkushort
+#define uint _mkuint
+#define ulong _mkulong
+#define vlong _mkvlong
+#define uvlong _mkuvlong
+
+#define nil ((void*)0)
+
+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 OREAD O_RDONLY
+#define OWRITE O_WRONLY
+#define ORDWR O_RDWR
+#define USED(x) if(x);else
+#define remove unlink
+#define seek lseek
+#define exits(s) exit((s) && ((char*)s)[0] ? 1 : 0)
+#define create(name, mode, perm) creat(name, perm)
+#define ERRMAX 256
+
+#undef assert
+#define        assert        mkassert
+extern Biobuf bout;
+
+typedef struct Bufblock
+{
+        struct Bufblock *next;
+        char                 *start;
+        char                 *end;
+        char                 *current;
+} Bufblock;
+
+typedef struct Word
+{
+        char                 *s;
+        struct Word         *next;
+} Word;
+
+typedef struct Envy
+{
+        char                 *name;
+        Word                 *values;
+} Envy;
+
+extern Envy *envy;
+
+typedef struct Rule
+{
+        char                 *target;        /* one target */
+        Word                 *tail;                /* constituents of targets */
+        char                 *recipe;        /* do it ! */
+        short                 attr;                /* attributes */
+        short                 line;                /* source line */
+        char                 *file;                /* source file */
+        Word                 *alltargets;        /* all the targets */
+        int                 rule;                /* rule number */
+        Reprog                *pat;                /* reg exp goo */
+        char                *prog;                /* to use in out of date */
+        struct Rule        *chain;                /* hashed per target */
+        struct Rule        *next;
+} Rule;
+
+extern Rule *rules, *metarules, *patrule;
+
+/*        Rule.attr        */
+#define                META                0x0001
+#define                UNUSED                0x0002
+#define                UPD                0x0004
+#define                QUIET                0x0008
+#define                VIR                0x0010
+#define                REGEXP                0x0020
+#define                NOREC                0x0040
+#define                DEL                0x0080
+#define                NOVIRT                0x0100
+
+#define                NREGEXP                10
+
+typedef struct Arc
+{
+        short                flag;
+        struct Node        *n;
+        Rule                *r;
+        char                *stem;
+        char                *prog;
+        char                *match[NREGEXP];
+        struct Arc        *next;
+} Arc;
+
+        /* Arc.flag */
+#define                TOGO                1
+
+typedef struct Node
+{
+        char                *name;
+        long                time;
+        unsigned short        flags;
+        Arc                *prereqs;
+        struct Node        *next;                /* list for a rule */
+} Node;
+
+        /* Node.flags */
+#define                VIRTUAL                0x0001
+#define                CYCLE                0x0002
+#define                READY                0x0004
+#define                CANPRETEND        0x0008
+#define                PRETENDING        0x0010
+#define                NOTMADE                0x0020
+#define                BEINGMADE        0x0040
+#define                MADE                0x0080
+#define                MADESET(n,m)        n->flags = (n->flags&~(NOTMADE|BEINGMADE|MADE))|(m)
+#define                PROBABLE        0x0100
+#define                VACUOUS                0x0200
+#define                NORECIPE        0x0400
+#define                DELETE                0x0800
+#define                NOMINUSE        0x1000
+
+typedef struct Job
+{
+        Rule                *r;        /* master rule for job */
+        Node                *n;        /* list of node targets */
+        char                *stem;
+        char                **match;
+        Word                *p;        /* prerequistes */
+        Word                *np;        /* new prerequistes */
+        Word                *t;        /* targets */
+        Word                *at;        /* all targets */
+        int                nproc;        /* slot number */
+        struct Job        *next;
+} Job;
+extern Job *jobs;
+
+typedef struct Symtab
+{
+        short                space;
+        char                *name;
+        void                *value;
+        struct Symtab        *next;
+} Symtab;
+
+enum {
+        S_VAR,                /* variable -> value */
+        S_TARGET,        /* target -> rule */
+        S_TIME,                /* file -> time */
+        S_PID,                /* pid -> products */
+        S_NODE,                /* target name -> node */
+        S_AGG,                /* aggregate -> time */
+        S_BITCH,        /* bitched about aggregate not there */
+        S_NOEXPORT,        /* var -> noexport */
+        S_OVERRIDE,        /* can't override */
+        S_OUTOFDATE,        /* n1\377n2 -> 2(outofdate) or 1(not outofdate) */
+        S_MAKEFILE,        /* target -> node */
+        S_MAKEVAR,        /* dumpable mk variable */
+        S_EXPORTED,        /* var -> current exported value */
+        S_WESET,        /* variable; we set in the mkfile */
+        S_INTERNAL        /* an internal mk variable (e.g., stem, target) */
+};
+
+extern        int        debug;
+extern        int        nflag, tflag, iflag, kflag, aflag, mflag;
+extern        int        mkinline;
+extern        char        *infile;
+extern        int        nreps;
+extern        char        *explain;
+extern        char        *termchars;
+extern        int        IWS;
+extern        char         *shell;
+extern        char         *shellname;
+extern        char         *shflags;
+
+#define        SYNERR(l)        (fprint(2, "mk: %s:%d: syntax error; ", infile, ((l)>=0)?(l):mkinline))
+#define        RERR(r)                (fprint(2, "mk: %s:%d: rule error; ", (r)->file, (r)->line))
+#define        NAMEBLOCK        1000
+#define        BIGBLOCK        20000
+
+#define        SEP(c)                (((c)==' ')||((c)=='\t')||((c)=='\n'))
+#define WORDCHR(r)        ((r) > ' ' && !utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", (r)))
+
+#define        DEBUG(x)        (debug&(x))
+#define                D_PARSE                0x01
+#define                D_GRAPH                0x02
+#define                D_EXEC                0x04
+
+#define        LSEEK(f,o,p)        seek(f,o,p)
+
+#define        PERCENT(ch)        (((ch) == '%') || ((ch) == '&'))
+
+#include        "fns.h"
diff --git a/src/cmd/mk/mkfile b/src/cmd/mk/mkfile
t@@ -0,0 +1,9 @@
+all:V: Makefile Make.FreeBSD-386 Make.Linux-386 Make.HP-UX-9000 Make.OSF1-alpha \
+        Make.SunOS-sun4u Make.SunOS-sun4u-cc Make.SunOS-sun4u-gcc \
+        Make.NetBSD-386 Make.Darwin-PowerMacintosh
+
+Makefile:D: ../libutf/Makefile.TOP Makefile.MID ../libutf/Makefile.CMD ../libutf/Makefile.BOT
+        cat $prereq >$target
+
+Make.%: ../libutf/Make.%
+        cp $prereq $target
diff --git a/src/cmd/mk/mkfile.test b/src/cmd/mk/mkfile.test
t@@ -0,0 +1,5 @@
+a: b
+        cp b a
+
+c:V:
+        echo hello world
diff --git a/src/cmd/mk/parse.c b/src/cmd/mk/parse.c
t@@ -0,0 +1,307 @@
+#include        "mk.h"
+
+char *infile;
+int mkinline;
+static int rhead(char *, Word **, Word **, int *, char **);
+static char *rbody(Biobuf*);
+extern Word *target1;
+
+void
+parse(char *f, int fd, int varoverride)
+{
+        int hline;
+        char *body;
+        Word *head, *tail;
+        int attr, set, pid;
+        char *prog, *p;
+        int newfd;
+        Biobuf in;
+        Bufblock *buf;
+
+        if(fd < 0){
+                fprint(2, "open %s: %r\n", f);
+                Exit();
+        }
+        ipush();
+        infile = strdup(f);
+        mkinline = 1;
+        Binit(&in, fd, OREAD);
+        buf = newbuf();
+        while(assline(&in, buf)){
+                hline = mkinline;
+                switch(rhead(buf->start, &head, &tail, &attr, &prog))
+                {
+                case '<':
+                        p = wtos(tail, ' ');
+                        if(*p == 0){
+                                SYNERR(-1);
+                                fprint(2, "missing include file name\n");
+                                Exit();
+                        }
+                        newfd = open(p, OREAD);
+                        if(newfd < 0){
+                                fprint(2, "warning: skipping missing include file %s: %r\n", p);
+                        } else
+                                parse(p, newfd, 0);
+                        break;
+                case '|':
+                        p = wtos(tail, ' ');
+                        if(*p == 0){
+                                SYNERR(-1);
+                                fprint(2, "missing include program name\n");
+                                Exit();
+                        }
+                        execinit();
+                        pid=pipecmd(p, envy, &newfd);
+                        if(newfd < 0){
+                                fprint(2, "warning: skipping missing program file %s: %r\n", p);
+                        } else
+                                parse(p, newfd, 0);
+                        while(waitup(-3, &pid) >= 0)
+                                ;
+                        if(pid != 0){
+                                fprint(2, "bad include program status\n");
+                                Exit();
+                        }
+                        break;
+                case ':':
+                        body = rbody(&in);
+                        addrules(head, tail, body, attr, hline, prog);
+                        break;
+                case '=':
+                        if(head->next){
+                                SYNERR(-1);
+                                fprint(2, "multiple vars on left side of assignment\n");
+                                Exit();
+                        }
+                        if(symlook(head->s, S_OVERRIDE, 0)){
+                                set = varoverride;
+                        } else {
+                                set = 1;
+                                if(varoverride)
+                                        symlook(head->s, S_OVERRIDE, (void *)"");
+                        }
+                        if(set){
+/*
+char *cp;
+dumpw("tail", tail);
+cp = wtos(tail, ' '); print("assign %s to %s\n", head->s, cp); free(cp);
+*/
+                                setvar(head->s, (void *) tail);
+                                symlook(head->s, S_WESET, (void *)"");
+                        }
+                        if(attr)
+                                symlook(head->s, S_NOEXPORT, (void *)"");
+                        break;
+                default:
+                        SYNERR(hline);
+                        fprint(2, "expected one of :<=\n");
+                        Exit();
+                        break;
+                }
+        }
+        close(fd);
+        freebuf(buf);
+        ipop();
+}
+
+void
+addrules(Word *head, Word *tail, char *body, int attr, int hline, char *prog)
+{
+        Word *w;
+
+        assert("addrules args", head && body);
+                /* tuck away first non-meta rule as default target*/
+        if(target1 == 0 && !(attr®EXP)){
+                for(w = head; w; w = w->next)
+                        if(charin(w->s, "%&"))
+                                break;
+                if(w == 0)
+                        target1 = wdup(head);
+        }
+        for(w = head; w; w = w->next)
+                addrule(w->s, tail, body, head, attr, hline, prog);
+}
+
+static int
+rhead(char *line, Word **h, Word **t, int *attr, char **prog)
+{
+        char *p;
+        char *pp;
+        int sep;
+        Rune r;
+        int n;
+        Word *w;
+
+        p = charin(line,":=<");
+        if(p == 0)
+                return('?');
+        sep = *p;
+        *p++ = 0;
+        if(sep == '<' && *p == '|'){
+                sep = '|';
+                p++;
+        }
+        *attr = 0;
+        *prog = 0;
+        if(sep == '='){
+                pp = charin(p, termchars);        /* termchars is shell-dependent */
+                if (pp && *pp == '=') {
+                        while (p != pp) {
+                                n = chartorune(&r, p);
+                                switch(r)
+                                {
+                                default:
+                                        SYNERR(-1);
+                                        fprint(2, "unknown attribute '%c'\n",*p);
+                                        Exit();
+                                case 'U':
+                                        *attr = 1;
+                                        break;
+                                }
+                                p += n;
+                        }
+                        p++;                /* skip trailing '=' */
+                }
+        }
+        if((sep == ':') && *p && (*p != ' ') && (*p != '\t')){
+                while (*p) {
+                        n = chartorune(&r, p);
+                        if (r == ':')
+                                break;
+                        p += n;
+                        switch(r)
+                        {
+                        default:
+                                SYNERR(-1);
+                                fprint(2, "unknown attribute '%c'\n", p[-1]);
+                                Exit();
+                        case 'D':
+                                *attr |= DEL;
+                                break;
+                        case 'E':
+                                *attr |= NOMINUSE;
+                                break;
+                        case 'n':
+                                *attr |= NOVIRT;
+                                break;
+                        case 'N':
+                                *attr |= NOREC;
+                                break;
+                        case 'P':
+                                pp = utfrune(p, ':');
+                                if (pp == 0 || *pp == 0)
+                                        goto eos;
+                                *pp = 0;
+                                *prog = strdup(p);
+                                *pp = ':';
+                                p = pp;
+                                break;
+                        case 'Q':
+                                *attr |= QUIET;
+                                break;
+                        case 'R':
+                                *attr |= REGEXP;
+                                break;
+                        case 'U':
+                                *attr |= UPD;
+                                break;
+                        case 'V':
+                                *attr |= VIR;
+                                break;
+                        }
+                }
+                if (*p++ != ':') {
+        eos:
+                        SYNERR(-1);
+                        fprint(2, "missing trailing :\n");
+                        Exit();
+                }
+        }
+        *h = w = stow(line);
+        if(*w->s == 0 && sep != '<' && sep != '|') {
+                SYNERR(mkinline-1);
+                fprint(2, "no var on left side of assignment/rule\n");
+                Exit();
+        }
+        *t = stow(p);
+        return(sep);
+}
+
+static char *
+rbody(Biobuf *in)
+{
+        Bufblock *buf;
+        int r, lastr;
+        char *p;
+
+        lastr = '\n';
+        buf = newbuf();
+        for(;;){
+                r = Bgetrune(in);
+                if (r < 0)
+                        break;
+                if (lastr == '\n') {
+                        if (r == '#')
+                                rinsert(buf, r);
+                        else if (r != ' ' && r != '\t') {
+                                Bungetrune(in);
+                                break;
+                        }
+                } else
+                        rinsert(buf, r);
+                lastr = r;
+                if (r == '\n')
+                        mkinline++;
+        }
+        insert(buf, 0);
+        p = strdup(buf->start);
+        freebuf(buf);
+        return p;
+}
+
+struct input
+{
+        char *file;
+        int line;
+        struct input *next;
+};
+static struct input *inputs = 0;
+
+void
+ipush(void)
+{
+        struct input *in, *me;
+
+        me = (struct input *)Malloc(sizeof(*me));
+        me->file = infile;
+        me->line = mkinline;
+        me->next = 0;
+        if(inputs == 0)
+                inputs = me;
+        else {
+                for(in = inputs; in->next; )
+                        in = in->next;
+                in->next = me;
+        }
+}
+
+void
+ipop(void)
+{
+        struct input *in, *me;
+
+        assert("pop input list", inputs != 0);
+        if(inputs->next == 0){
+                me = inputs;
+                inputs = 0;
+        } else {
+                for(in = inputs; in->next->next; )
+                        in = in->next;
+                me = in->next;
+                in->next = 0;
+        }
+        infile = me->file;
+        mkinline = me->line;
+        free((char *)me);
+}
diff --git a/src/cmd/mk/rc.c b/src/cmd/mk/rc.c
t@@ -0,0 +1,175 @@
+#include        "mk.h"
+
+char        *termchars = "'= \t";        /*used in parse.c to isolate assignment attribute*/
+char        *shflags = "-I";        /* rc flag to force non-interactive mode */
+int        IWS = '\1';                /* inter-word separator in env - not used in plan 9 */
+
+/*
+ *        This file contains functions that depend on rc's syntax.  Most
+ *        of the routines extract strings observing rc's escape conventions
+ */
+
+
+/*
+ *        skip a token in single quotes.
+ */
+static char *
+squote(char *cp)
+{
+        Rune r;
+        int n;
+
+        while(*cp){
+                n = chartorune(&r, cp);
+                if(r == '\'') {
+                        n += chartorune(&r, cp+n);
+                        if(r != '\'')
+                                return(cp);
+                }
+                cp += n;
+        }
+        SYNERR(-1);                /* should never occur */
+        fprint(2, "missing closing '\n");
+        return 0;
+}
+
+/*
+ *        search a string for characters in a pattern set
+ *        characters in quotes and variable generators are escaped
+ */
+char *
+charin(char *cp, char *pat)
+{
+        Rune r;
+        int n, vargen;
+
+        vargen = 0;
+        while(*cp){
+                n = chartorune(&r, cp);
+                switch(r){
+                case '\'':                        /* skip quoted string */
+                        cp = squote(cp+1);        /* n must = 1 */
+                        if(!cp)
+                                return 0;
+                        break;
+                case '$':
+                        if(*(cp+1) == '{')
+                                vargen = 1;
+                        break;
+                case '}':
+                        if(vargen)
+                                vargen = 0;
+                        else if(utfrune(pat, r))
+                                return cp;
+                        break;
+                default:
+                        if(vargen == 0 && utfrune(pat, r))
+                                return cp;
+                        break;
+                }
+                cp += n;
+        }
+        if(vargen){
+                SYNERR(-1);
+                fprint(2, "missing closing } in pattern generator\n");
+        }
+        return 0;
+}
+
+/*
+ *        extract an escaped token.  Possible escape chars are single-quote,
+ *        double-quote,and backslash.  Only the first is valid for rc. the
+ *        others are just inserted into the receiving buffer.
+ */
+char*
+expandquote(char *s, Rune r, Bufblock *b)
+{
+        if (r != '\'') {
+                rinsert(b, r);
+                return s;
+        }
+
+        while(*s){
+                s += chartorune(&r, s);
+                if(r == '\'') {
+                        if(*s == '\'')
+                                s++;
+                        else
+                                return s;
+                }
+                rinsert(b, r);
+        }
+        return 0;
+}
+
+/*
+ *        Input an escaped token.  Possible escape chars are single-quote,
+ *        double-quote and backslash.  Only the first is a valid escape for
+ *        rc; the others are just inserted into the receiving buffer.
+ */
+int
+escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
+{
+        int c, line;
+
+        if(esc != '\'')
+                return 1;
+
+        line = mkinline;
+        while((c = nextrune(bp, 0)) > 0){
+                if(c == '\''){
+                        if(preserve)
+                                rinsert(buf, c);
+                        c = Bgetrune(bp);
+                        if (c < 0)
+                                break;
+                        if(c != '\''){
+                                Bungetrune(bp);
+                                return 1;
+                        }
+                }
+                rinsert(buf, c);
+        }
+        SYNERR(line); fprint(2, "missing closing %c\n", esc);
+        return 0;
+}
+
+/*
+ *        copy a single-quoted string; s points to char after opening quote
+ */
+static char *
+copysingle(char *s, Bufblock *buf)
+{
+        Rune r;
+
+        while(*s){
+                s += chartorune(&r, s);
+                rinsert(buf, r);
+                if(r == '\'')
+                        break;
+        }
+        return s;
+}
+/*
+ *        check for quoted strings.  backquotes are handled here; single quotes above.
+ *        s points to char after opening quote, q.
+ */
+char *
+copyq(char *s, Rune q, Bufblock *buf)
+{
+        if(q == '\'')                                /* copy quoted string */
+                return copysingle(s, buf);
+
+        if(q != '`')                                /* not quoted */
+                return s;
+
+        while(*s){                                /* copy backquoted string */
+                s += chartorune(&q, s);
+                rinsert(buf, q);
+                if(q == '}')
+                        break;
+                if(q == '\'')
+                        s = copysingle(s, buf);        /* copy quoted string */
+        }
+        return s;
+}
diff --git a/src/cmd/mk/recipe.c b/src/cmd/mk/recipe.c
t@@ -0,0 +1,117 @@
+#include        "mk.h"
+
+int
+dorecipe(Node *node)
+{
+        char buf[BIGBLOCK];
+        register Node *n;
+        Rule *r = 0;
+        Arc *a, *aa;
+        Word head, ahead, lp, ln, *w, *ww, *aw;
+        Symtab *s;
+        int did = 0;
+
+        aa = 0;
+        /*
+                pick up the rule
+        */
+        for(a = node->prereqs; a; a = a->next)
+                if(*a->r->recipe)
+                        r = (aa = a)->r;
+        /*
+                no recipe? go to buggery!
+        */
+        if(r == 0){
+                if(!(node->flags&VIRTUAL) && !(node->flags&NORECIPE)){
+                        fprint(2, "mk: no recipe to make '%s'\n", node->name);
+                        Exit();
+                }
+                if(strchr(node->name, '(') && node->time == 0)
+                        MADESET(node, MADE);
+                else
+                        update(0, node);
+                if(tflag){
+                        if(!(node->flags&VIRTUAL))
+                                touch(node->name);
+                        else if(explain)
+                                Bprint(&bout, "no touch of virtual '%s'\n", node->name);
+                }
+                return(did);
+        }
+        /*
+                build the node list
+        */
+        node->next = 0;
+        head.next = 0;
+        ww = &head;
+        ahead.next = 0;
+        aw = &ahead;
+        if(r->attr®EXP){
+                ww->next = newword(node->name);
+                aw->next = newword(node->name);
+        } else {
+                for(w = r->alltargets; w; w = w->next){
+                        if(r->attr&META)
+                                subst(aa->stem, w->s, buf);
+                        else
+                                strcpy(buf, w->s);
+                        aw->next = newword(buf);
+                        aw = aw->next;
+                        if((s = symlook(buf, S_NODE, 0)) == 0)
+                                continue;        /* not a node we are interested in */
+                        n = (Node *)s->value;
+                        if(aflag == 0 && n->time) {
+                                for(a = n->prereqs; a; a = a->next)
+                                        if(a->n && outofdate(n, a, 0))
+                                                break;
+                                if(a == 0)
+                                        continue;
+                        }
+                        ww->next = newword(buf);
+                        ww = ww->next;
+                        if(n == node) continue;
+                        n->next = node->next;
+                        node->next = n;
+                }
+        }
+        for(n = node; n; n = n->next)
+                if((n->flags&READY) == 0)
+                        return(did);
+        /*
+                gather the params for the job
+        */
+        lp.next = ln.next = 0;
+        for(n = node; n; n = n->next){
+                for(a = n->prereqs; a; a = a->next){
+                        if(a->n){
+                                addw(&lp, a->n->name);
+                                if(outofdate(n, a, 0)){
+                                        addw(&ln, a->n->name);
+                                        if(explain)
+                                                fprint(1, "%s(%ld) < %s(%ld)\n",
+                                                        n->name, n->time, a->n->name, a->n->time);
+                                }
+                        } else {
+                                if(explain)
+                                        fprint(1, "%s has no prerequisites\n",
+                                                        n->name);
+                        }
+                }
+                MADESET(n, BEINGMADE);
+        }
+        /*print("lt=%s ln=%s lp=%s\n",wtos(head.next, ' '),wtos(ln.next, ' '),wtos(lp.next, ' '));*//**/
+        run(newjob(r, node, aa->stem, aa->match, lp.next, ln.next, head.next, ahead.next));
+        return(1);
+}
+
+void
+addw(Word *w, char *s)
+{
+        Word *lw;
+
+        for(lw = w; w = w->next; lw = w){
+                if(strcmp(s, w->s) == 0)
+                        return;
+        }
+        lw->next = newword(s);
+}
diff --git a/src/cmd/mk/rpm.spec b/src/cmd/mk/rpm.spec
t@@ -0,0 +1,29 @@
+Summary: Streamlined replacement for make
+Name: mk
+Version: 2.0
+Release: 1
+Group: Development/Utils
+Copyright: Public Domain
+Packager: Russ Cox 
+Source: http://pdos.lcs.mit.edu/~rsc/software/mk-2.0.tgz
+URL: http://pdos.lcs.mit.edu/~rsc/software/#mk
+Requires: libfmt libbio libregexp9 libutf
+
+%description
+Mk is a streamlined replacement for make, written for 
+Tenth Edition Research Unix by Andrew Hume.
+
+http://plan9.bell-labs.com/sys/doc/mk.pdf
+%prep
+%setup
+
+%build
+make
+
+%install
+make install
+
+%files
+/usr/local/doc/mk.pdf
+/usr/local/man/man1/mk.1
+/usr/local/bin/mk
diff --git a/src/cmd/mk/rule.c b/src/cmd/mk/rule.c
t@@ -0,0 +1,107 @@
+#include        "mk.h"
+
+static Rule *lr, *lmr;
+static int rcmp(Rule *r, char *target, Word *tail);
+static int nrules = 0;
+
+void
+addrule(char *head, Word *tail, char *body, Word *ahead, int attr, int hline, char *prog)
+{
+        Rule *r;
+        Rule *rr;
+        Symtab *sym;
+        int reuse;
+
+        r = 0;
+        reuse = 0;
+        if(sym = symlook(head, S_TARGET, 0)){
+                for(r = (Rule *)sym->value; r; r = r->chain)
+                        if(rcmp(r, head, tail) == 0){
+                                reuse = 1;
+                                break;
+                        }
+        }
+        if(r == 0)
+                r = (Rule *)Malloc(sizeof(Rule));
+        r->target = head;
+        r->tail = tail;
+        r->recipe = body;
+        r->line = hline;
+        r->file = infile;
+        r->attr = attr;
+        r->alltargets = ahead;
+        r->prog = prog;
+        r->rule = nrules++;
+        if(!reuse){
+                rr = (Rule *)symlook(head, S_TARGET, (void *)r)->value;
+                if(rr != r){
+                        r->chain = rr->chain;
+                        rr->chain = r;
+                } else
+                        r->chain = 0;
+        }
+        if(!reuse)
+                r->next = 0;
+        if((attr®EXP) || charin(head, "%&")){
+                r->attr |= META;
+                if(reuse)
+                        return;
+                if(attr®EXP){
+                        patrule = r;
+                        r->pat = regcomp(head);
+                }
+                if(metarules == 0)
+                        metarules = lmr = r;
+                else {
+                        lmr->next = r;
+                        lmr = r;
+                }
+        } else {
+                if(reuse)
+                        return;
+                r->pat = 0;
+                if(rules == 0)
+                        rules = lr = r;
+                else {
+                        lr->next = r;
+                        lr = r;
+                }
+        }
+}
+
+void
+dumpr(char *s, Rule *r)
+{
+        Bprint(&bout, "%s: start=%ld\n", s, r);
+        for(; r; r = r->next){
+                Bprint(&bout, "\tRule %ld: %s[%d] attr=%x next=%ld chain=%ld alltarget='%s'",
+                        r, r->file, r->line, r->attr, r->next, r->chain, wtos(r->alltargets, ' '));
+                if(r->prog)
+                        Bprint(&bout, " prog='%s'", r->prog);
+                Bprint(&bout, "\n\ttarget=%s: %s\n", r->target, wtos(r->tail, ' '));
+                Bprint(&bout, "\trecipe@%ld='%s'\n", r->recipe, r->recipe);
+        }
+}
+
+static int
+rcmp(Rule *r, char *target, Word *tail)
+{
+        Word *w;
+
+        if(strcmp(r->target, target))
+                return 1;
+        for(w = r->tail; w && tail; w = w->next, tail = tail->next)
+                if(strcmp(w->s, tail->s))
+                        return 1;
+        return(w || tail);
+}
+
+char *
+rulecnt(void)
+{
+        char *s;
+
+        s = Malloc(nrules);
+        memset(s, 0, nrules);
+        return(s);
+}
diff --git a/src/cmd/mk/run.c b/src/cmd/mk/run.c
t@@ -0,0 +1,296 @@
+#include        "mk.h"
+
+typedef struct Event
+{
+        int pid;
+        Job *job;
+} Event;
+static Event *events;
+static int nevents, nrunning, nproclimit;
+
+typedef struct Process
+{
+        int pid;
+        int status;
+        struct Process *b, *f;
+} Process;
+static Process *phead, *pfree;
+static void sched(void);
+static void pnew(int, int), pdelete(Process *);
+
+int pidslot(int);
+
+void
+run(Job *j)
+{
+        Job *jj;
+
+        if(jobs){
+                for(jj = jobs; jj->next; jj = jj->next)
+                        ;
+                jj->next = j;
+        } else 
+                jobs = j;
+        j->next = 0;
+        /* this code also in waitup after parse redirect */
+        if(nrunning < nproclimit)
+                sched();
+}
+
+static void
+sched(void)
+{
+        char *flags;
+        Job *j;
+        Bufblock *buf;
+        int slot;
+        Node *n;
+        Envy *e;
+
+        if(jobs == 0){
+                usage();
+                return;
+        }
+        j = jobs;
+        jobs = j->next;
+        if(DEBUG(D_EXEC))
+                fprint(1, "firing up job for target %s\n", wtos(j->t, ' '));
+        slot = nextslot();
+        events[slot].job = j;
+        buf = newbuf();
+        e = buildenv(j, slot);
+        shprint(j->r->recipe, e, buf);
+        if(!tflag && (nflag || !(j->r->attr&QUIET)))
+                Bwrite(&bout, buf->start, (long)strlen(buf->start));
+        freebuf(buf);
+        if(nflag||tflag){
+                for(n = j->n; n; n = n->next){
+                        if(tflag){
+                                if(!(n->flags&VIRTUAL))
+                                        touch(n->name);
+                                else if(explain)
+                                        Bprint(&bout, "no touch of virtual '%s'\n", n->name);
+                        }
+                        n->time = time((long *)0);
+                        MADESET(n, MADE);
+                }
+        } else {
+                if(DEBUG(D_EXEC))
+                        fprint(1, "recipe='%s'", j->r->recipe);/**/
+                Bflush(&bout);
+                if(j->r->attr&NOMINUSE)
+                        flags = 0;
+                else
+                        flags = "-e";
+                events[slot].pid = execsh(flags, j->r->recipe, 0, e);
+                usage();
+                nrunning++;
+                if(DEBUG(D_EXEC))
+                        fprint(1, "pid for target %s = %d\n", wtos(j->t, ' '), events[slot].pid);
+        }
+}
+
+int
+waitup(int echildok, int *retstatus)
+{
+        Envy *e;
+        int pid;
+        int slot;
+        Symtab *s;
+        Word *w;
+        Job *j;
+        char buf[ERRMAX];
+        Bufblock *bp;
+        int uarg = 0;
+        int done;
+        Node *n;
+        Process *p;
+        extern int runerrs;
+
+        /* first check against the proces slist */
+        if(retstatus)
+                for(p = phead; p; p = p->f)
+                        if(p->pid == *retstatus){
+                                *retstatus = p->status;
+                                pdelete(p);
+                                return(-1);
+                        }
+again:                /* rogue processes */
+        pid = waitfor(buf);
+        if(pid == -1){
+                if(echildok > 0)
+                        return(1);
+                else {
+                        fprint(2, "mk: (waitup %d): %r\n", echildok);
+                        Exit();
+                }
+        }
+        if(DEBUG(D_EXEC))
+                fprint(1, "waitup got pid=%d, status='%s'\n", pid, buf);
+        if(retstatus && pid == *retstatus){
+                *retstatus = buf[0]? 1:0;
+                return(-1);
+        }
+        slot = pidslot(pid);
+        if(slot < 0){
+                if(DEBUG(D_EXEC))
+                        fprint(2, "mk: wait returned unexpected process %d\n", pid);
+                pnew(pid, buf[0]? 1:0);
+                goto again;
+        }
+        j = events[slot].job;
+        usage();
+        nrunning--;
+        events[slot].pid = -1;
+        if(buf[0]){
+                e = buildenv(j, slot);
+                bp = newbuf();
+                shprint(j->r->recipe, e, bp);
+                front(bp->start);
+                fprint(2, "mk: %s: exit status=%s", bp->start, buf);
+                freebuf(bp);
+                for(n = j->n, done = 0; n; n = n->next)
+                        if(n->flags&DELETE){
+                                if(done++ == 0)
+                                        fprint(2, ", deleting");
+                                fprint(2, " '%s'", n->name);
+                                delete(n->name);
+                        }
+                fprint(2, "\n");
+                if(kflag){
+                        runerrs++;
+                        uarg = 1;
+                } else {
+                        jobs = 0;
+                        Exit();
+                }
+        }
+        for(w = j->t; w; w = w->next){
+                if((s = symlook(w->s, S_NODE, 0)) == 0)
+                        continue;        /* not interested in this node */
+                update(uarg, (Node *)s->value);
+        }
+        if(nrunning < nproclimit)
+                sched();
+        return(0);
+}
+
+void
+nproc(void)
+{
+        Symtab *sym;
+        Word *w;
+
+        if(sym = symlook("NPROC", S_VAR, 0)) {
+                w = (Word *) sym->value;
+                if (w && w->s && w->s[0])
+                        nproclimit = atoi(w->s);
+        }
+        if(nproclimit < 1)
+                nproclimit = 1;
+        if(DEBUG(D_EXEC))
+                fprint(1, "nprocs = %d\n", nproclimit);
+        if(nproclimit > nevents){
+                if(nevents)
+                        events = (Event *)Realloc((char *)events, nproclimit*sizeof(Event));
+                else
+                        events = (Event *)Malloc(nproclimit*sizeof(Event));
+                while(nevents < nproclimit)
+                        events[nevents++].pid = 0;
+        }
+}
+
+int
+nextslot(void)
+{
+        int i;
+
+        for(i = 0; i < nproclimit; i++)
+                if(events[i].pid <= 0) return i;
+        assert("out of slots!!", 0);
+        return 0;        /* cyntax */
+}
+
+int
+pidslot(int pid)
+{
+        int i;
+
+        for(i = 0; i < nevents; i++)
+                if(events[i].pid == pid) return(i);
+        if(DEBUG(D_EXEC))
+                fprint(2, "mk: wait returned unexpected process %d\n", pid);
+        return(-1);
+}
+
+
+static void
+pnew(int pid, int status)
+{
+        Process *p;
+
+        if(pfree){
+                p = pfree;
+                pfree = p->f;
+        } else
+                p = (Process *)Malloc(sizeof(Process));
+        p->pid = pid;
+        p->status = status;
+        p->f = phead;
+        phead = p;
+        if(p->f)
+                p->f->b = p;
+        p->b = 0;
+}
+
+static void
+pdelete(Process *p)
+{
+        if(p->f)
+                p->f->b = p->b;
+        if(p->b)
+                p->b->f = p->f;
+        else
+                phead = p->f;
+        p->f = pfree;
+        pfree = p;
+}
+
+void
+killchildren(char *msg)
+{
+        Process *p;
+
+        kflag = 1;        /* to make sure waitup doesn't exit */
+        jobs = 0;        /* make sure no more get scheduled */
+        for(p = phead; p; p = p->f)
+                expunge(p->pid, msg);
+        while(waitup(1, (int *)0) == 0)
+                ;
+        Bprint(&bout, "mk: %s\n", msg);
+        Exit();
+}
+
+static long tslot[1000];
+static long tick;
+
+void
+usage(void)
+{
+        long t;
+
+        time(&t);
+        if(tick)
+                tslot[nrunning] += (t-tick);
+        tick = t;
+}
+
+void
+prusage(void)
+{
+        int i;
+
+        usage();
+        for(i = 0; i <= nevents; i++)
+                fprint(1, "%d: %ld\n", i, tslot[i]);
+}
diff --git a/src/cmd/mk/sh.c b/src/cmd/mk/sh.c
t@@ -0,0 +1,189 @@
+#include        "mk.h"
+
+char        *termchars = "\"'= \t";        /*used in parse.c to isolate assignment attribute*/
+char        *shflags = 0;
+int        IWS = ' ';                /* inter-word separator in env */
+
+/*
+ *        This file contains functions that depend on the shell's syntax.  Most
+ *        of the routines extract strings observing the shell's escape conventions.
+ */
+
+
+/*
+ *        skip a token in quotes.
+ */
+static char *
+squote(char *cp, int c)
+{
+        Rune r;
+        int n;
+
+        while(*cp){
+                n = chartorune(&r, cp);
+                if(r == c)
+                        return cp;
+                if(r == '\\')
+                        n += chartorune(&r, cp+n);
+                cp += n;
+        }
+        SYNERR(-1);                /* should never occur */
+        fprint(2, "missing closing '\n");
+        return 0;
+}
+/*
+ *        search a string for unescaped characters in a pattern set
+ */
+char *
+charin(char *cp, char *pat)
+{
+        Rune r;
+        int n, vargen;
+
+        vargen = 0;
+        while(*cp){
+                n = chartorune(&r, cp);
+                switch(r){
+                case '\\':                        /* skip escaped char */
+                        cp += n;
+                        n = chartorune(&r, cp);
+                        break;
+                case '\'':                        /* skip quoted string */
+                case '"':
+                        cp = squote(cp+1, r);        /* n must = 1 */
+                        if(!cp)
+                                return 0;
+                        break;
+                case '$':
+                        if(*(cp+1) == '{')
+                                vargen = 1;
+                        break;
+                case '}':
+                        if(vargen)
+                                vargen = 0;
+                        else if(utfrune(pat, r))
+                                return cp;
+                        break;
+                default:
+                        if(vargen == 0 && utfrune(pat, r))
+                                return cp;
+                        break;
+                }
+                cp += n;
+        }
+        if(vargen){
+                SYNERR(-1);
+                fprint(2, "missing closing } in pattern generator\n");
+        }
+        return 0;
+}
+
+/*
+ *        extract an escaped token.  Possible escape chars are single-quote,
+ *        double-quote,and backslash.
+ */
+char*
+expandquote(char *s, Rune esc, Bufblock *b)
+{
+        Rune r;
+
+        if (esc == '\\') {
+                s += chartorune(&r, s);
+                rinsert(b, r);
+                return s;
+        }
+
+        while(*s){
+                s += chartorune(&r, s);
+                if(r == esc)
+                        return s;
+                if (r == '\\') {
+                        rinsert(b, r);
+                        s += chartorune(&r, s);
+                }
+                rinsert(b, r);
+        }
+        return 0;
+}
+
+/*
+ *        Input an escaped token.  Possible escape chars are single-quote,
+ *        double-quote and backslash.
+ */
+int
+escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
+{
+        int c, line;
+
+        if(esc == '\\') {
+                c = Bgetrune(bp);
+                if(c == '\r')
+                        c = Bgetrune(bp);
+                if (c == '\n')
+                        mkinline++;
+                rinsert(buf, c);
+                return 1;
+        }
+
+        line = mkinline;
+        while((c = nextrune(bp, 0)) >= 0){
+                if(c == esc){
+                        if(preserve)
+                                rinsert(buf, c);
+                        return 1;
+                }
+                if(c == '\\') {
+                        rinsert(buf, c);
+                        c = Bgetrune(bp);
+                        if(c == '\r')
+                                c = Bgetrune(bp);
+                        if (c < 0)
+                                break;
+                        if (c == '\n')
+                                mkinline++;
+                }
+                rinsert(buf, c);
+        }
+        SYNERR(line); fprint(2, "missing closing %c\n", esc);
+        return 0;
+}
+
+/*
+ *        copy a quoted string; s points to char after opening quote
+ */
+static char *
+copysingle(char *s, Rune q, Bufblock *buf)
+{
+        Rune r;
+
+        while(*s){
+                s += chartorune(&r, s);
+                rinsert(buf, r);
+                if(r == q)
+                        break;
+        }
+        return s;
+}
+/*
+ *        check for quoted strings.  backquotes are handled here; single quotes above.
+ *        s points to char after opening quote, q.
+ */
+char *
+copyq(char *s, Rune q, Bufblock *buf)
+{
+        if(q == '\'' || q == '"')                /* copy quoted string */
+                return copysingle(s, q, buf);
+
+        if(q != '`')                                /* not quoted */
+                return s;
+
+        while(*s){                                /* copy backquoted string */
+                s += chartorune(&q, s);
+                rinsert(buf, q);
+                if(q == '`')
+                        break;
+                if(q == '\'' || q == '"')
+                        s = copysingle(s, q, buf);        /* copy quoted string */
+        }
+        return s;
+}
diff --git a/src/cmd/mk/shprint.c b/src/cmd/mk/shprint.c
t@@ -0,0 +1,123 @@
+#include        "mk.h"
+
+static char *vexpand(char*, Envy*, Bufblock*);
+
+static 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;
+}
+
+void
+shprint(char *s, Envy *env, Bufblock *buf)
+{
+        int n;
+        Rune r;
+
+        while(*s) {
+                n = chartorune(&r, s);
+                if (r == '$')
+                        s = vexpand(s, env, buf);
+                else {
+                        rinsert(buf, r);
+                        s += n;
+                        s = copyq(s, r, buf);        /*handle quoted strings*/
+                }
+        }
+        insert(buf, 0);
+}
+
+static char *
+mygetenv(char *name, Envy *env)
+{
+        if (!env)
+                return 0;
+        if (symlook(name, S_WESET, 0) == 0 && symlook(name, S_INTERNAL, 0) == 0)
+                return 0;
+                /* only resolve internal variables and variables we've set */
+        for(; env->name; env++){
+                if (strcmp(env->name, name) == 0)
+                        return wtos(env->values, ' ');
+        }
+        return 0;
+}
+
+static char *
+vexpand(char *w, Envy *env, Bufblock *buf)
+{
+        char *s, carry, *p, *q;
+
+        assert("vexpand no $", *w == '$');
+        p = w+1;        /* skip dollar sign */
+        if(*p == '{') {
+                p++;
+                q = utfrune(p, '}');
+                if (!q)
+                        q = strchr(p, 0);
+        } else
+                q = shname(p);
+        carry = *q;
+        *q = 0;
+        s = mygetenv(p, env);
+        *q = carry;
+        if (carry == '}')
+                q++;
+        if (s) {
+                bufcpy(buf, s, strlen(s));
+                free(s);
+        } else                 /* copy name intact*/
+                bufcpy(buf, w, q-w);
+        return(q);
+}
+
+void
+front(char *s)
+{
+        char *t, *q;
+        int i, j;
+        char *flds[512];
+
+        q = strdup(s);
+        i = getfields(q, flds, 512, 0, " \t\n");
+        if(i > 5){
+                flds[4] = flds[i-1];
+                flds[3] = "...";
+                i = 5;
+        }
+        t = s;
+        for(j = 0; j < i; j++){
+                for(s = flds[j]; *s; *t++ = *s++);
+                *t++ = ' ';
+        }
+        *t = 0;
+        free(q);
+}
diff --git a/src/cmd/mk/symtab.c b/src/cmd/mk/symtab.c
t@@ -0,0 +1,95 @@
+#include        "mk.h"
+
+#define        NHASH        4099
+#define        HASHMUL        79L        /* this is a good value */
+static Symtab *hash[NHASH];
+
+void
+syminit(void)
+{
+        Symtab **s, *ss;
+
+        for(s = hash; s < &hash[NHASH]; s++){
+                for(ss = *s; ss; ss = ss->next)
+                        free((char *)ss);
+                *s = 0;
+        }
+}
+
+Symtab *
+symlook(char *sym, int space, void *install)
+{
+        long h;
+        char *p;
+        Symtab *s;
+
+        for(p = sym, h = space; *p; h += *p++)
+                h *= HASHMUL;
+        if(h < 0)
+                h = ~h;
+        h %= NHASH;
+        for(s = hash[h]; s; s = s->next)
+                if((s->space == space) && (strcmp(s->name, sym) == 0))
+                        return(s);
+        if(install == 0)
+                return(0);
+        s = (Symtab *)Malloc(sizeof(Symtab));
+        s->space = space;
+        s->name = sym;
+        s->value = install;
+        s->next = hash[h];
+        hash[h] = s;
+        return(s);
+}
+
+void
+symdel(char *sym, int space)
+{
+        long h;
+        char *p;
+        Symtab *s, *ls;
+
+        /* multiple memory leaks */
+
+        for(p = sym, h = space; *p; h += *p++)
+                h *= HASHMUL;
+        if(h < 0)
+                h = ~h;
+        h %= NHASH;
+        for(s = hash[h], ls = 0; s; ls = s, s = s->next)
+                if((s->space == space) && (strcmp(s->name, sym) == 0)){
+                        if(ls)
+                                ls->next = s->next;
+                        else
+                                hash[h] = s->next;
+                        free((char *)s);
+                }
+}
+
+void
+symtraverse(int space, void (*fn)(Symtab*))
+{
+        Symtab **s, *ss;
+
+        for(s = hash; s < &hash[NHASH]; s++)
+                for(ss = *s; ss; ss = ss->next)
+                        if(ss->space == space)
+                                (*fn)(ss);
+}
+
+void
+symstat(void)
+{
+        Symtab **s, *ss;
+        int n;
+        int l[1000];
+
+        memset((char *)l, 0, sizeof(l));
+        for(s = hash; s < &hash[NHASH]; s++){
+                for(ss = *s, n = 0; ss; ss = ss->next)
+                        n++;
+                l[n]++;
+        }
+        for(n = 0; n < 1000; n++)
+                if(l[n]) Bprint(&bout, "%ld of length %d\n", l[n], n);
+}
diff --git a/src/cmd/mk/unix.c b/src/cmd/mk/unix.c
t@@ -0,0 +1,306 @@
+#include        "mk.h"
+#include        
+#include        
+#include        
+#include        
+
+char        *shell = "/bin/sh";
+char        *shellname = "sh";
+
+extern char **environ;
+
+static void
+mkperror(char *s)
+{
+        fprint(2, "%s: %r\n", s);
+}
+
+void
+readenv(void)
+{
+        char **p, *s;
+        Word *w;
+
+        for(p = environ; *p; p++){
+                s = shname(*p);
+                if(*s == '=') {
+                        *s = 0;
+                        w = newword(s+1);
+                } else
+                        w = newword("");
+                if (symlook(*p, S_INTERNAL, 0))
+                        continue;
+                s = strdup(*p);
+                setvar(s, (void *)w);
+                symlook(s, S_EXPORTED, (void*)"")->value = (void*)"";
+        }
+}
+
+/*
+ *        done on child side of fork, so parent's env is not affected
+ *        and we don't care about freeing memory because we're going
+ *        to exec immediately after this.
+ */
+void
+exportenv(Envy *e)
+{
+        int i;
+        char **p;
+        char buf[4096];
+
+        p = 0;
+        for(i = 0; e->name; e++, i++) {
+                p = (char**) Realloc(p, (i+2)*sizeof(char*));
+                if(e->values)
+                        sprint(buf, "%s=%s", e->name,  wtos(e->values, IWS));
+                else
+                        sprint(buf, "%s=", e->name);
+                p[i] = strdup(buf);
+        }
+        p[i] = 0;
+        environ = p;
+}
+
+int
+waitfor(char *msg)
+{
+        int status;
+        int pid;
+
+        *msg = 0;
+        pid = wait(&status);
+        if(pid > 0) {
+                if(status&0x7f) {
+                        if(status&0x80)
+                                snprint(msg, ERRMAX, "signal %d, core dumped", status&0x7f);
+                        else
+                                snprint(msg, ERRMAX, "signal %d", status&0x7f);
+                } else if(status&0xff00)
+                        snprint(msg, ERRMAX, "exit(%d)", (status>>8)&0xff);
+        }
+        return pid;
+}
+
+void
+expunge(int pid, char *msg)
+{
+        if(strcmp(msg, "interrupt"))
+                kill(pid, SIGINT);
+        else
+                kill(pid, SIGHUP);
+}
+
+int
+execsh(char *args, char *cmd, Bufblock *buf, Envy *e)
+{
+        char *p;
+        int tot, n, pid, in[2], out[2];
+
+        if(buf && pipe(out) < 0){
+                mkperror("pipe");
+                Exit();
+        }
+        pid = fork();
+        if(pid < 0){
+                mkperror("mk fork");
+                Exit();
+        }
+        if(pid == 0){
+                if(buf)
+                        close(out[0]);
+                if(pipe(in) < 0){
+                        mkperror("pipe");
+                        Exit();
+                }
+                pid = fork();
+                if(pid < 0){
+                        mkperror("mk fork");
+                        Exit();
+                }
+                if(pid != 0){
+                        dup2(in[0], 0);
+                        if(buf){
+                                dup2(out[1], 1);
+                                close(out[1]);
+                        }
+                        close(in[0]);
+                        close(in[1]);
+                        if (e)
+                                exportenv(e);
+                        if(shflags)
+                                execl(shell, shellname, shflags, args, 0);
+                        else
+                                execl(shell, shellname, args, 0);
+                        mkperror(shell);
+                        _exit(1);
+                }
+                close(out[1]);
+                close(in[0]);
+                if(DEBUG(D_EXEC))
+                        fprint(1, "starting: %s\n", cmd);
+                p = cmd+strlen(cmd);
+                while(cmd < p){
+                        n = write(in[1], cmd, p-cmd);
+                        if(n < 0)
+                                break;
+                        cmd += n;
+                }
+                close(in[1]);
+                _exit(0);
+        }
+        if(buf){
+                close(out[1]);
+                tot = 0;
+                for(;;){
+                        if (buf->current >= buf->end)
+                                growbuf(buf);
+                        n = read(out[0], buf->current, buf->end-buf->current);
+                        if(n <= 0)
+                                break;
+                        buf->current += n;
+                        tot += n;
+                }
+                if (tot && buf->current[-1] == '\n')
+                        buf->current--;
+                close(out[0]);
+        }
+        return pid;
+}
+
+int
+pipecmd(char *cmd, Envy *e, int *fd)
+{
+        int pid, pfd[2];
+
+        if(DEBUG(D_EXEC))
+                fprint(1, "pipecmd='%s'\n", cmd);/**/
+
+        if(fd && pipe(pfd) < 0){
+                mkperror("pipe");
+                Exit();
+        }
+        pid = fork();
+        if(pid < 0){
+                mkperror("mk fork");
+                Exit();
+        }
+        if(pid == 0){
+                if(fd){
+                        close(pfd[0]);
+                        dup2(pfd[1], 1);
+                        close(pfd[1]);
+                }
+                if(e)
+                        exportenv(e);
+                if(shflags)
+                        execl(shell, shellname, shflags, "-c", cmd, 0);
+                else
+                        execl(shell, shellname, "-c", cmd, 0);
+                mkperror(shell);
+                _exit(1);
+        }
+        if(fd){
+                close(pfd[1]);
+                *fd = pfd[0];
+        }
+        return pid;
+}
+
+void
+Exit(void)
+{
+        while(wait(0) >= 0)
+                ;
+        exits("error");
+}
+
+static        struct
+{
+        int        sig;
+        char        *msg;
+}        sigmsgs[] =
+{
+        SIGALRM,        "alarm",
+        SIGFPE,                "sys: fp: fptrap",
+        SIGPIPE,        "sys: write on closed pipe",
+        SIGILL,                "sys: trap: illegal instruction",
+        SIGSEGV,        "sys: segmentation violation",
+        0,                0
+};
+
+static void
+notifyf(int sig)
+{
+        int i;
+
+        for(i = 0; sigmsgs[i].msg; i++)
+                if(sigmsgs[i].sig == sig)
+                        killchildren(sigmsgs[i].msg);
+
+        /* should never happen */
+        signal(sig, SIG_DFL);
+        kill(getpid(), sig);
+}
+
+void
+catchnotes()
+{
+        int i;
+
+        for(i = 0; sigmsgs[i].msg; i++)
+                signal(sigmsgs[i].sig, notifyf);
+}
+
+char*
+maketmp(int *pfd)
+{
+        static char temp[] = "/tmp/mkargXXXXXX";
+        static char buf[100];
+        int fd;
+
+        strcpy(buf, temp);
+        fd = mkstemp(buf);
+        if(fd < 0)
+                return 0;
+        *pfd = fd;
+        return buf;
+}
+
+int
+chgtime(char *name)
+{
+        if(access(name, 0) >= 0)
+                return utimes(name, 0);
+        return close(creat(name, 0666));
+}
+
+void
+rcopy(char **to, Resub *match, int n)
+{
+        int c;
+        char *p;
+
+        *to = match->s.sp;                /* stem0 matches complete target */
+        for(to++, match++; --n > 0; to++, match++){
+                if(match->s.sp && match->e.ep){
+                        p = match->e.ep;
+                        c = *p;
+                        *p = 0;
+                        *to = strdup(match->s.sp);
+                        *p = c;
+                }
+                else
+                        *to = 0;
+        }
+}
+
+ulong
+mkmtime(char *name)
+{
+        struct stat st;
+
+        if(stat(name, &st) < 0)
+                return 0;
+
+        return st.st_mtime;
+}
diff --git a/src/cmd/mk/var.c b/src/cmd/mk/var.c
t@@ -0,0 +1,41 @@
+#include        "mk.h"
+
+void
+setvar(char *name, void *value)
+{
+        symlook(name, S_VAR, value)->value = value;
+        symlook(name, S_MAKEVAR, (void*)"");
+}
+
+static void
+print1(Symtab *s)
+{
+        Word *w;
+
+        Bprint(&bout, "\t%s=", s->name);
+        for (w = (Word *) s->value; w; w = w->next)
+                Bprint(&bout, "'%s'", w->s);
+        Bprint(&bout, "\n");
+}
+
+void
+dumpv(char *s)
+{
+        Bprint(&bout, "%s:\n", s);
+        symtraverse(S_VAR, print1);
+}
+
+char *
+shname(char *a)
+{
+        Rune r;
+        int n;
+
+        while (*a) {
+                n = chartorune(&r, a);
+                if (!WORDCHR(r))
+                        break;
+                a += n;
+        }
+        return a;
+}
diff --git a/src/cmd/mk/varsub.c b/src/cmd/mk/varsub.c
t@@ -0,0 +1,256 @@
+#include        "mk.h"
+
+static        Word                *subsub(Word*, char*, char*);
+static        Word                *expandvar(char**);
+static        Bufblock        *varname(char**);
+static        Word                *extractpat(char*, char**, char*, char*);
+static        int                submatch(char*, Word*, Word*, int*, char**);
+static        Word                *varmatch(char *, char**);
+
+Word *
+varsub(char **s)
+{
+        Bufblock *b;
+        Word *w;
+
+        if(**s == '{')                /* either ${name} or ${name: A%B==C%D}*/
+                return expandvar(s);
+
+        b = varname(s);
+        if(b == 0)
+                return 0;
+
+        w = varmatch(b->start, s);
+        freebuf(b);
+        return w;
+}
+
+/*
+ *        extract a variable name
+ */
+static Bufblock*
+varname(char **s)
+{
+        Bufblock *b;
+        char *cp;
+        Rune r;
+        int n;
+
+        b = newbuf();
+        cp = *s;
+        for(;;){
+                n = chartorune(&r, cp);
+                if (!WORDCHR(r))
+                        break;
+                rinsert(b, r);
+                cp += n;
+        }
+        if (b->current == b->start){
+                SYNERR(-1);
+                fprint(2, "missing variable name <%s>\n", *s);
+                freebuf(b);
+                return 0;
+        }
+        *s = cp;
+        insert(b, 0);
+        return b;
+}
+
+static Word*
+varmatch(char *name, char **s)
+{
+        Word *w;
+        Symtab *sym;
+        char *cp;
+        
+        sym = symlook(name, S_VAR, 0);
+        if(sym){
+                        /* check for at least one non-NULL value */
+                for (w = (Word*)sym->value; w; w = w->next)
+                        if(w->s && *w->s)
+                                return wdup(w);
+        }
+        for(cp = *s; *cp == ' ' || *cp == '\t'; cp++)        /* skip trailing whitespace */
+                        ;
+        *s = cp;
+        return 0;
+}
+
+static Word*
+expandvar(char **s)
+{
+        Word *w;
+        Bufblock *buf;
+        Symtab *sym;
+        char *cp, *begin, *end;
+
+        begin = *s;
+        (*s)++;                                                /* skip the '{' */
+        buf = varname(s);
+        if (buf == 0)
+                return 0;
+        cp = *s;
+        if (*cp == '}') {                                /* ${name} variant*/
+                (*s)++;                                        /* skip the '}' */
+                w = varmatch(buf->start, s);
+                freebuf(buf);
+                return w;
+        }
+        if (*cp != ':') {
+                SYNERR(-1);
+                fprint(2, "bad variable name <%s>\n", buf->start);
+                freebuf(buf);
+                return 0;
+        }
+        cp++;
+        end = charin(cp , "}");
+        if(end == 0){
+                SYNERR(-1);
+                fprint(2, "missing '}': %s\n", begin);
+                Exit();
+        }
+        *end = 0;
+        *s = end+1;
+        
+        sym = symlook(buf->start, S_VAR, 0);
+        if(sym == 0 || sym->value == 0)
+                w = newword(buf->start);
+        else
+                w = subsub((Word*) sym->value, cp, end);
+        freebuf(buf);
+        return w;
+}
+
+static Word*
+extractpat(char *s, char **r, char *term, char *end)
+{
+        int save;
+        char *cp;
+        Word *w;
+
+        cp = charin(s, term);
+        if(cp){
+                *r = cp;
+                if(cp == s)
+                        return 0;
+                save = *cp;
+                *cp = 0;
+                w = stow(s);
+                *cp = save;
+        } else {
+                *r = end;
+                w = stow(s);
+        }
+        return w;
+}
+
+static Word*
+subsub(Word *v, char *s, char *end)
+{
+        int nmid;
+        Word *head, *tail, *w, *h;
+        Word *a, *b, *c, *d;
+        Bufblock *buf;
+        char *cp, *enda;
+
+        a = extractpat(s, &cp, "=%&", end);
+        b = c = d = 0;
+        if(PERCENT(*cp))
+                b = extractpat(cp+1, &cp, "=", end);
+        if(*cp == '=')
+                c = extractpat(cp+1, &cp, "&%", end);
+        if(PERCENT(*cp))
+                d = stow(cp+1);
+        else if(*cp)
+                d = stow(cp);
+
+        head = tail = 0;
+        buf = newbuf();
+        for(; v; v = v->next){
+                h = w = 0;
+                if(submatch(v->s, a, b, &nmid, &enda)){
+                        /* enda points to end of A match in source;
+                         * nmid = number of chars between end of A and start of B
+                         */
+                        if(c){
+                                h = w = wdup(c);
+                                while(w->next)
+                                        w = w->next;
+                        }
+                        if(PERCENT(*cp) && nmid > 0){        
+                                if(w){
+                                        bufcpy(buf, w->s, strlen(w->s));
+                                        bufcpy(buf, enda, nmid);
+                                        insert(buf, 0);
+                                        free(w->s);
+                                        w->s = strdup(buf->start);
+                                } else {
+                                        bufcpy(buf, enda, nmid);
+                                        insert(buf, 0);
+                                        h = w = newword(buf->start);
+                                }
+                                buf->current = buf->start;
+                        }
+                        if(d && *d->s){
+                                if(w){
+
+                                        bufcpy(buf, w->s, strlen(w->s));
+                                        bufcpy(buf, d->s, strlen(d->s));
+                                        insert(buf, 0);
+                                        free(w->s);
+                                        w->s = strdup(buf->start);
+                                        w->next = wdup(d->next);
+                                        while(w->next)
+                                                w = w->next;
+                                        buf->current = buf->start;
+                                } else
+                                        h = w = wdup(d);
+                        }
+                }
+                if(w == 0)
+                        h = w = newword(v->s);
+        
+                if(head == 0)
+                        head = h;
+                else
+                        tail->next = h;
+                tail = w;
+        }
+        freebuf(buf);
+        delword(a);
+        delword(b);
+        delword(c);
+        delword(d);
+        return head;
+}
+
+static int
+submatch(char *s, Word *a, Word *b, int *nmid, char **enda)
+{
+        Word *w;
+        int n;
+        char *end;
+
+        n = 0;
+        for(w = a; w; w = w->next){
+                n = strlen(w->s);
+                if(strncmp(s, w->s, n) == 0)
+                        break;
+        }
+        if(a && w == 0)                /*  a == NULL matches everything*/
+                return 0;
+
+        *enda = s+n;                /* pointer to end a A part match */
+        *nmid = strlen(s)-n;        /* size of remainder of source */
+        end = *enda+*nmid;
+        for(w = b; w; w = w->next){
+                n = strlen(w->s);
+                if(strcmp(w->s, end-n) == 0){
+                        *nmid -= n;
+                        break;
+                }
+        }
+        if(b && w == 0)                /* b == NULL matches everything */
+                return 0;
+        return 1;
+}
diff --git a/src/cmd/mk/word.c b/src/cmd/mk/word.c
t@@ -0,0 +1,180 @@
+#include        "mk.h"
+
+static        Word        *nextword(char**);
+
+Word*
+newword(char *s)
+{
+        Word *w;
+
+        w = (Word *)Malloc(sizeof(Word));
+        w->s = strdup(s);
+        w->next = 0;
+        return(w);
+}
+
+Word *
+stow(char *s)
+{
+        Word *head, *w, *new;
+
+        w = head = 0;
+        while(*s){
+                new = nextword(&s);
+                if(new == 0)
+                        break;
+                if (w)
+                        w->next = new;
+                else
+                        head = w = new;
+                while(w->next)
+                        w = w->next;
+                
+        }
+        if (!head)
+                head = newword("");
+        return(head);
+}
+
+char *
+wtos(Word *w, int sep)
+{
+        Bufblock *buf;
+        char *cp;
+
+        buf = newbuf();
+        for(; w; w = w->next){
+                for(cp = w->s; *cp; cp++)
+                        insert(buf, *cp);
+                if(w->next)
+                        insert(buf, sep);
+        }
+        insert(buf, 0);
+        cp = strdup(buf->start);
+        freebuf(buf);
+        return(cp);
+}
+
+Word*
+wdup(Word *w)
+{
+        Word *v, *new, *base;
+
+        v = base = 0;
+        while(w){
+                new = newword(w->s);
+                if(v)
+                        v->next = new;
+                else
+                        base = new;
+                v = new;
+                w = w->next;
+        }
+        return base;
+}
+
+void
+delword(Word *w)
+{
+        Word *v;
+
+        while(v = w){
+                w = w->next;
+                if(v->s)
+                        free(v->s);
+                free(v);
+        }
+}
+
+/*
+ *        break out a word from a string handling quotes, executions,
+ *        and variable expansions.
+ */
+static Word*
+nextword(char **s)
+{
+        Bufblock *b;
+        Word *head, *tail, *w;
+        Rune r;
+        char *cp;
+
+        cp = *s;
+        b = newbuf();
+        head = tail = 0;
+        while(*cp == ' ' || *cp == '\t')                /* leading white space */
+                cp++;
+        while(*cp){
+                cp += chartorune(&r, cp);
+                switch(r)
+                {
+                case ' ':
+                case '\t':
+                case '\n':
+                        goto out;
+                case '\\':
+                case '\'':
+                case '"':
+                        cp = expandquote(cp, r, b);
+                        if(cp == 0){
+                                fprint(2, "missing closing quote: %s\n", *s);
+                                Exit();
+                        }
+                        break;
+                case '$':
+                        w = varsub(&cp);
+                        if(w == 0)
+                                break;
+                        if(b->current != b->start){
+                                bufcpy(b, w->s, strlen(w->s));
+                                insert(b, 0);
+                                free(w->s);
+                                w->s = strdup(b->start);
+                                b->current = b->start;
+                        }
+                        if(head){
+                                bufcpy(b, tail->s, strlen(tail->s));
+                                bufcpy(b, w->s, strlen(w->s));
+                                insert(b, 0);
+                                free(tail->s);
+                                tail->s = strdup(b->start);
+                                tail->next = w->next;
+                                free(w->s);
+                                free(w);
+                                b->current = b->start;
+                        } else
+                                tail = head = w;
+                        while(tail->next)
+                                tail = tail->next;
+                        break;
+                default:
+                        rinsert(b, r);
+                        break;
+                }
+        }
+out:
+        *s = cp;
+        if(b->current != b->start){
+                if(head){
+                        cp = b->current;
+                        bufcpy(b, tail->s, strlen(tail->s));
+                        bufcpy(b, b->start, cp-b->start);
+                        insert(b, 0);
+                        free(tail->s);
+                        tail->s = strdup(cp);
+                } else {
+                        insert(b, 0);
+                        head = newword(b->start);
+                }
+        }
+        freebuf(b);
+        return head;
+}
+
+void
+dumpw(char *s, Word *w)
+{
+        Bprint(&bout, "%s", s);
+        for(; w; w = w->next)
+                Bprint(&bout, " '%s'", w->s);
+        Bputc(&bout, '\n');
+}
diff --git a/src/cmd/sam/LICENSE b/src/cmd/sam/LICENSE
t@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+   to redistribute (other than with the Plan 9 Operating System)
+   the screen imprinter fonts identified in subdirectory
+   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+   covered by the Aladdin Free Public License, reproduced in the file
+   /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+     Program, and
+  b. in the case of each Contributor,
+
+     i. changes to the Program, and
+    ii. additions to the Program;
+
+    where such changes and/or additions to the Program were added to the
+    Program by such Contributor itself or anyone acting on such
+    Contributor's behalf, and the Contributor explicitly consents, in
+    accordance with Section 3C, to characterization of the changes and/or
+    additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free copyright
+    license to reproduce, prepare derivative works of, publicly display,
+    publicly perform, distribute and sublicense the Contribution of such
+    Contributor, if any, and such derivative works, in source code and
+    object code form.
+    
+ b. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free patent
+    license under Licensed Patents to make, use, sell, offer to sell,
+    import and otherwise transfer the Contribution of such Contributor, if
+    any, in source code and object code form. The patent license granted
+    by a Contributor shall also apply to the combination of the
+    Contribution of that Contributor and the Program if, at the time the
+    Contribution is added by the Contributor, such addition of the
+    Contribution causes such combination to be covered by the Licensed
+    Patents. The patent license granted by a Contributor shall not apply
+    to (i) any other combinations which include the Contribution, nor to
+    (ii) Contributions of other Contributors. No hardware per se is
+    licensed hereunder.
+    
+ c. Recipient understands that although each Contributor grants the
+    licenses to its Contributions set forth herein, no assurances are
+    provided by any Contributor that the Program does not infringe the
+    patent or other intellectual property rights of any other entity. Each
+    Contributor disclaims any liability to Recipient for claims brought by
+    any other entity based on infringement of intellectual property rights
+    or otherwise. As a condition to exercising the rights and licenses
+    granted hereunder, each Recipient hereby assumes sole responsibility
+    to secure any other intellectual property rights needed, if any. For
+    example, if a third party patent license is required to allow
+    Recipient to distribute the Program, it is Recipient's responsibility
+    to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+    copyright rights in its Contribution, if any, to grant the copyright
+    license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+    form, a copy of this Agreement or Distributor's own license agreement
+    is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+    license agreement:
+
+      i. effectively disclaims on behalf of all Contributors all warranties
+         and conditions, express and implied, including warranties or
+         conditions of title and non-infringement, and implied warranties or
+         conditions of merchantability and fitness for a particular purpose;
+     ii. effectively excludes on behalf of all Contributors all liability
+         for damages, including direct, indirect, special, incidental and
+         consequential damages, such as lost profits; and
+    iii. states that any provisions which differ from this Agreement are
+         offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+   location in the Program:
+
+   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+   Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), 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 OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/cmd/sam/Makefile b/src/cmd/sam/Makefile
t@@ -0,0 +1,18 @@
+H=errors.h mesg.h parse.h plumb.h sam.h
+SRC= address.c buff.c cmd.c disk.c error.c file.c io.c\
+     list.c mesg.c moveto.c multi.c unix.c rasp.c regexp.c\
+     sam.c shell.c string.c sys.c util.c xec.c plumb.c
+
+CC=gcc
+PREFIX=$(HOME)
+#PREFIX=/usr/local
+CFLAGS=-I. -I$(PREFIX)/include -O -g
+LDFLAGS=-L$(PREFIX)/lib
+LDLIBS=-l9 -lfmt -lutf
+
+all: sam
+sam: $(SRC) $(H)
+        $(CC) -o $@ $(CFLAGS) $(SRC) $(LDFLAGS) $(LDLIBS)
+clean:
+        rm -f *.o *~
+        rm -f sam
diff --git a/src/cmd/sam/address.c b/src/cmd/sam/address.c
t@@ -0,0 +1,240 @@
+#include "sam.h"
+#include "parse.h"
+
+Address        addr;
+String        lastpat;
+int        patset;
+File        *menu;
+
+File        *matchfile(String*);
+Address        charaddr(Posn, Address, int);
+
+Address
+address(Addr *ap, Address a, int sign)
+{
+        File *f = a.f;
+        Address a1, a2;
+
+        do{
+                switch(ap->type){
+                case 'l':
+                case '#':
+                        a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
+                        break;
+
+                case '.':
+                        a = f->dot;
+                        break;
+
+                case '$':
+                        a.r.p1 = a.r.p2 = f->_.nc;
+                        break;
+
+                case '\'':
+                        a.r = f->mark;
+                        break;
+
+                case '?':
+                        sign = -sign;
+                        if(sign == 0)
+                                sign = -1;
+                        /* fall through */
+                case '/':
+                        nextmatch(f, ap->are, sign>=0? a.r.p2 : a.r.p1, sign);
+                        a.r = sel.p[0];
+                        break;
+
+                case '"':
+                        a = matchfile(ap->are)->dot;
+                        f = a.f;
+                        if(f->unread)
+                                load(f);
+                        break;
+
+                case '*':
+                        a.r.p1 = 0, a.r.p2 = f->_.nc;
+                        return a;
+
+                case ',':
+                case ';':
+                        if(ap->left)
+                                a1 = address(ap->left, a, 0);
+                        else
+                                a1.f = a.f, a1.r.p1 = a1.r.p2 = 0;
+                        if(ap->type == ';'){
+                                f = a1.f;
+                                a = a1;
+                                f->dot = a1;
+                        }
+                        if(ap->next)
+                                a2 = address(ap->next, a, 0);
+                        else
+                                a2.f = a.f, a2.r.p1 = a2.r.p2 = f->_.nc;
+                        if(a1.f != a2.f)
+                                error(Eorder);
+                        a.f = a1.f, a.r.p1 = a1.r.p1, a.r.p2 = a2.r.p2;
+                        if(a.r.p2 < a.r.p1)
+                                error(Eorder);
+                        return a;
+
+                case '+':
+                case '-':
+                        sign = 1;
+                        if(ap->type == '-')
+                                sign = -1;
+                        if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
+                                a = lineaddr(1L, a, sign);
+                        break;
+                default:
+                        panic("address");
+                        return a;
+                }
+        }while(ap = ap->next);        /* assign = */
+        return a;
+}
+
+void
+nextmatch(File *f, String *r, Posn p, int sign)
+{
+        compile(r);
+        if(sign >= 0){
+                if(!execute(f, p, INFINITY))
+                        error(Esearch);
+                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p1==p){
+                        if(++p>f->_.nc)
+                                p = 0;
+                        if(!execute(f, p, INFINITY))
+                                panic("address");
+                }
+        }else{
+                if(!bexecute(f, p))
+                        error(Esearch);
+                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p2==p){
+                        if(--p<0)
+                                p = f->_.nc;
+                        if(!bexecute(f, p))
+                                panic("address");
+                }
+        }
+}
+
+File *
+matchfile(String *r)
+{
+        File *f;
+        File *match = 0;
+        int i;
+
+        for(i = 0; iname);
+        sprint(buf, "%c%c%c %s\n", " '"[f->mod],
+                "-+"[f->rasp!=0], " ."[f==curfile], c);
+        free(c);
+        t = tmpcstr(buf);
+        Strduplstr(&genstr, t);
+        freetmpstr(t);
+        /* A little dirty... */
+        if(menu == 0)
+                menu = fileopen();
+        bufreset(menu);
+        bufinsert(menu, 0, genstr.s, genstr.n);
+        compile(r);
+        return execute(menu, 0, menu->_.nc);
+}
+
+Address
+charaddr(Posn l, Address addr, int sign)
+{
+        if(sign == 0)
+                addr.r.p1 = addr.r.p2 = l;
+        else if(sign < 0)
+                addr.r.p2 = addr.r.p1-=l;
+        else if(sign > 0)
+                addr.r.p1 = addr.r.p2+=l;
+        if(addr.r.p1<0 || addr.r.p2>addr.f->_.nc)
+                error(Erange);
+        return addr;
+}
+
+Address
+lineaddr(Posn l, Address addr, int sign)
+{
+        int n;
+        int c;
+        File *f = addr.f;
+        Address a;
+        Posn p;
+
+        a.f = f;
+        if(sign >= 0){
+                if(l == 0){
+                        if(sign==0 || addr.r.p2==0){
+                                a.r.p1 = a.r.p2 = 0;
+                                return a;
+                        }
+                        a.r.p1 = addr.r.p2;
+                        p = addr.r.p2-1;
+                }else{
+                        if(sign==0 || addr.r.p2==0){
+                                p = (Posn)0;
+                                n = 1;
+                        }else{
+                                p = addr.r.p2-1;
+                                n = filereadc(f, p++)=='\n';
+                        }
+                        while(n < l){
+                                if(p >= f->_.nc)
+                                        error(Erange);
+                                if(filereadc(f, p++) == '\n')
+                                        n++;
+                        }
+                        a.r.p1 = p;
+                }
+                while(p < f->_.nc && filereadc(f, p++)!='\n')
+                        ;
+                a.r.p2 = p;
+        }else{
+                p = addr.r.p1;
+                if(l == 0)
+                        a.r.p2 = addr.r.p1;
+                else{
+                        for(n = 0; n 0)
+                                p--;
+                }
+                while(p > 0 && filereadc(f, p-1)!='\n')        /* lines start after a newline */
+                        p--;
+                a.r.p1 = p;
+        }
+        return a;
+}
diff --git a/src/cmd/sam/buff.c b/src/cmd/sam/buff.c
t@@ -0,0 +1,302 @@
+#include "sam.h"
+
+enum
+{
+        Slop = 100,        /* room to grow with reallocation */
+};
+
+static
+void
+sizecache(Buffer *b, uint n)
+{
+        if(n <= b->cmax)
+                return;
+        b->cmax = n+Slop;
+        b->c = runerealloc(b->c, b->cmax);
+}
+
+static
+void
+addblock(Buffer *b, uint i, uint n)
+{
+        if(i > b->nbl)
+                panic("internal error: addblock");
+
+        b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
+        if(i < b->nbl)
+                memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
+        b->bl[i] = disknewblock(disk, n);
+        b->nbl++;
+}
+
+
+static
+void
+delblock(Buffer *b, uint i)
+{
+        if(i >= b->nbl)
+                panic("internal error: delblock");
+
+        diskrelease(disk, b->bl[i]);
+        b->nbl--;
+        if(i < b->nbl)
+                memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
+        b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
+}
+
+/*
+ * Move cache so b->cq <= q0 < b->cq+b->cnc.
+ * If at very end, q0 will fall on end of cache block.
+ */
+
+static
+void
+flush(Buffer *b)
+{
+        if(b->cdirty || b->cnc==0){
+                if(b->cnc == 0)
+                        delblock(b, b->cbi);
+                else
+                        diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
+                b->cdirty = FALSE;
+        }
+}
+
+static
+void
+setcache(Buffer *b, uint q0)
+{
+        Block **blp, *bl;
+        uint i, q;
+
+        if(q0 > b->nc)
+                panic("internal error: setcache");
+        /*
+         * flush and reload if q0 is not in cache.
+         */
+        if(b->nc == 0 || (b->cq<=q0 && q0cq+b->cnc))
+                return;
+        /*
+         * if q0 is at end of file and end of cache, continue to grow this block
+         */
+        if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<=Maxblock)
+                return;
+        flush(b);
+        /* find block */
+        if(q0 < b->cq){
+                q = 0;
+                i = 0;
+        }else{
+                q = b->cq;
+                i = b->cbi;
+        }
+        blp = &b->bl[i];
+        while(q+(*blp)->_.n <= q0 && q+(*blp)->_.n < b->nc){
+                q += (*blp)->_.n;
+                i++;
+                blp++;
+                if(i >= b->nbl)
+                        panic("block not found");
+        }
+        bl = *blp;
+        /* remember position */
+        b->cbi = i;
+        b->cq = q;
+        sizecache(b, bl->_.n);
+        b->cnc = bl->_.n;
+        /*read block*/
+        diskread(disk, bl, b->c, b->cnc);
+}
+
+void
+bufinsert(Buffer *b, uint q0, Rune *s, uint n)
+{
+        uint i, m, t, off;
+
+        if(q0 > b->nc)
+                panic("internal error: bufinsert");
+
+        while(n > 0){
+                setcache(b, q0);
+                off = q0-b->cq;
+                if(b->cnc+n <= Maxblock){
+                        /* Everything fits in one block. */
+                        t = b->cnc+n;
+                        m = n;
+                        if(b->bl == nil){        /* allocate */
+                                if(b->cnc != 0)
+                                        panic("internal error: bufinsert1 cnc!=0");
+                                addblock(b, 0, t);
+                                b->cbi = 0;
+                        }
+                        sizecache(b, t);
+                        runemove(b->c+off+m, b->c+off, b->cnc-off);
+                        runemove(b->c+off, s, m);
+                        b->cnc = t;
+                        goto Tail;
+                }
+                /*
+                 * We must make a new block.  If q0 is at
+                 * the very beginning or end of this block,
+                 * just make a new block and fill it.
+                 */
+                if(q0==b->cq || q0==b->cq+b->cnc){
+                        if(b->cdirty)
+                                flush(b);
+                        m = min(n, Maxblock);
+                        if(b->bl == nil){        /* allocate */
+                                if(b->cnc != 0)
+                                        panic("internal error: bufinsert2 cnc!=0");
+                                i = 0;
+                        }else{
+                                i = b->cbi;
+                                if(q0 > b->cq)
+                                        i++;
+                        }
+                        addblock(b, i, m);
+                        sizecache(b, m);
+                        runemove(b->c, s, m);
+                        b->cq = q0;
+                        b->cbi = i;
+                        b->cnc = m;
+                        goto Tail;
+                }
+                /*
+                 * Split the block; cut off the right side and
+                 * let go of it.
+                 */
+                m = b->cnc-off;
+                if(m > 0){
+                        i = b->cbi+1;
+                        addblock(b, i, m);
+                        diskwrite(disk, &b->bl[i], b->c+off, m);
+                        b->cnc -= m;
+                }
+                /*
+                 * Now at end of block.  Take as much input
+                 * as possible and tack it on end of block.
+                 */
+                m = min(n, Maxblock-b->cnc);
+                sizecache(b, b->cnc+m);
+                runemove(b->c+b->cnc, s, m);
+                b->cnc += m;
+  Tail:
+                b->nc += m;
+                q0 += m;
+                s += m;
+                n -= m;
+                b->cdirty = TRUE;
+        }
+}
+
+void
+bufdelete(Buffer *b, uint q0, uint q1)
+{
+        uint m, n, off;
+
+        if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
+                panic("internal error: bufdelete");
+        while(q1 > q0){
+                setcache(b, q0);
+                off = q0-b->cq;
+                if(q1 > b->cq+b->cnc)
+                        n = b->cnc - off;
+                else
+                        n = q1-q0;
+                m = b->cnc - (off+n);
+                if(m > 0)
+                        runemove(b->c+off, b->c+off+n, m);
+                b->cnc -= n;
+                b->cdirty = TRUE;
+                q1 -= n;
+                b->nc -= n;
+        }
+}
+
+uint
+bufload(Buffer *b, uint q0, int fd, int *nulls)
+{
+        char *p;
+        Rune *r;
+        int l, m, n, nb, nr;
+        uint q1;
+
+        if(q0 > b->nc)
+                panic("internal error: bufload");
+        p = malloc((Maxblock+UTFmax+1)*sizeof p[0]);
+        if(p == nil)
+                panic("bufload: malloc failed");
+        r = runemalloc(Maxblock);
+        m = 0;
+        n = 1;
+        q1 = q0;
+        /*
+         * At top of loop, may have m bytes left over from
+         * last pass, possibly representing a partial rune.
+         */
+        while(n > 0){
+                n = read(fd, p+m, Maxblock);
+                if(n < 0){
+                        error(Ebufload);
+                        break;
+                }
+                m += n;
+                p[m] = 0;
+                l = m;
+                if(n > 0)
+                        l -= UTFmax;
+                cvttorunes(p, l, r, &nb, &nr, nulls);
+                memmove(p, p+nb, m-nb);
+                m -= nb;
+                bufinsert(b, q1, r, nr);
+                q1 += nr;
+        }
+        free(p);
+        free(r);
+        return q1-q0;
+}
+
+void
+bufread(Buffer *b, uint q0, Rune *s, uint n)
+{
+        uint m;
+
+        if(!(q0<=b->nc && q0+n<=b->nc))
+                panic("bufread: internal error");
+
+        while(n > 0){
+                setcache(b, q0);
+                m = min(n, b->cnc-(q0-b->cq));
+                runemove(s, b->c+(q0-b->cq), m);
+                q0 += m;
+                s += m;
+                n -= m;
+        }
+}
+
+void
+bufreset(Buffer *b)
+{
+        int i;
+
+        b->nc = 0;
+        b->cnc = 0;
+        b->cq = 0;
+        b->cdirty = 0;
+        b->cbi = 0;
+        /* delete backwards to avoid n² behavior */
+        for(i=b->nbl-1; --i>=0; )
+                delblock(b, i);
+}
+
+void
+bufclose(Buffer *b)
+{
+        bufreset(b);
+        free(b->c);
+        b->c = nil;
+        b->cnc = 0;
+        free(b->bl);
+        b->bl = nil;
+        b->nbl = 0;
+}
diff --git a/src/cmd/sam/cmd.c b/src/cmd/sam/cmd.c
t@@ -0,0 +1,594 @@
+#include "sam.h"
+#include "parse.h"
+
+static char        linex[]="\n";
+static char        wordx[]=" \t\n";
+struct cmdtab cmdtab[]={
+/*        cmdc        text        regexp        addr        defcmd        defaddr        count        token         fn        */
+        '\n',        0,        0,        0,        0,        aDot,        0,        0,        nl_cmd,
+        'a',        1,        0,        0,        0,        aDot,        0,        0,        a_cmd,
+        'b',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
+        'B',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
+        'c',        1,        0,        0,        0,        aDot,        0,        0,        c_cmd,
+        'd',        0,        0,        0,        0,        aDot,        0,        0,        d_cmd,
+        'D',        0,        0,        0,        0,        aNo,        0,        linex,        D_cmd,
+        'e',        0,        0,        0,        0,        aNo,        0,        wordx,        e_cmd,
+        'f',        0,        0,        0,        0,        aNo,        0,        wordx,        f_cmd,
+        'g',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
+        'i',        1,        0,        0,        0,        aDot,        0,        0,        i_cmd,
+        'k',        0,        0,        0,        0,        aDot,        0,        0,        k_cmd,
+        'm',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
+        'n',        0,        0,        0,        0,        aNo,        0,        0,        n_cmd,
+        'p',        0,        0,        0,        0,        aDot,        0,        0,        p_cmd,
+        'q',        0,        0,        0,        0,        aNo,        0,        0,        q_cmd,
+        'r',        0,        0,        0,        0,        aDot,        0,        wordx,        e_cmd,
+        's',        0,        1,        0,        0,        aDot,        1,        0,        s_cmd,
+        't',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
+        'u',        0,        0,        0,        0,        aNo,        2,        0,        u_cmd,
+        'v',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
+        'w',        0,        0,        0,        0,        aAll,        0,        wordx,        w_cmd,
+        'x',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
+        'y',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
+        'X',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
+        'Y',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
+        '!',        0,        0,        0,        0,        aNo,        0,        linex,        plan9_cmd,
+        '>',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
+        '<',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
+        '|',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
+        '=',        0,        0,        0,        0,        aDot,        0,        linex,        eq_cmd,
+        'c'|0x100,0,        0,        0,        0,        aNo,        0,        wordx,        cd_cmd,
+        0,        0,        0,        0,        0,        0,        0,        0,
+};
+Cmd        *parsecmd(int);
+Addr        *compoundaddr(void);
+Addr        *simpleaddr(void);
+void        freecmd(void);
+void        okdelim(int);
+
+Rune        line[BLOCKSIZE];
+Rune        termline[BLOCKSIZE];
+Rune        *linep = line;
+Rune        *terminp = termline;
+Rune        *termoutp = termline;
+List        cmdlist;
+List        addrlist;
+List        relist;
+List        stringlist;
+int        eof;
+
+void
+resetcmd(void)
+{
+        linep = line;
+        *linep = 0;
+        terminp = termoutp = termline;
+        freecmd();
+}
+
+int
+inputc(void)
+{
+        int n, nbuf;
+        char buf[3];
+        Rune r;
+
+    Again:
+        nbuf = 0;
+        if(downloaded){
+                while(termoutp == terminp){
+                        cmdupdate();
+                        if(patset)
+                                tellpat();
+                        while(termlocked > 0){
+                                outT0(Hunlock);
+                                termlocked--;
+                        }
+                        if(rcv() == 0)
+                                return -1;
+                }
+                r = *termoutp++;
+                if(termoutp == terminp)
+                        terminp = termoutp = termline;
+        }else{
+                   do{
+                        n = read(0, buf+nbuf, 1);
+                        if(n <= 0)
+                                return -1;
+                        nbuf += n;
+                }while(!fullrune(buf, nbuf));
+                chartorune(&r, buf);
+        }
+        if(r == 0){
+                warn(Wnulls);
+                goto Again;
+        }
+        return r;
+}
+
+int
+inputline(void)
+{
+        int i, c;
+
+        linep = line;
+        i = 0;
+        do{
+                if((c = inputc())<=0)
+                        return -1;
+                if(i == (sizeof line)/RUNESIZE-1)
+                        error(Etoolong);
+        }while((line[i++]=c) != '\n');
+        line[i] = 0;
+        return 1;
+}
+
+int
+getch(void)
+{
+        if(eof)
+                return -1;
+        if(*linep==0 && inputline()<0){
+                eof = TRUE;
+                return -1;
+        }
+        return *linep++;
+}
+
+int
+nextc(void)
+{
+        if(*linep == 0)
+                return -1;
+        return *linep;
+}
+
+void
+ungetch(void)
+{
+        if(--linep < line)
+                panic("ungetch");
+}
+
+Posn
+getnum(int signok)
+{
+        Posn n=0;
+        int c, sign;
+
+        sign = 1;
+        if(signok>1 && nextc()=='-'){
+                sign = -1;
+                getch();
+        }
+        if((c=nextc())<'0' || '9'= 0)
+                ungetch();
+        return c;
+}
+
+void
+termcommand(void)
+{
+        Posn p;
+
+        for(p=cmdpt; p_.nc; p++){
+                if(terminp >= &termline[BLOCKSIZE]){
+                        cmdpt = cmd->_.nc;
+                        error(Etoolong);
+                }
+                *terminp++ = filereadc(cmd, p);
+        }
+        cmdpt = cmd->_.nc;
+}
+
+void
+cmdloop(void)
+{
+        Cmd *cmdp;
+        File *ocurfile;
+        int loaded;
+
+        for(;;){
+                if(!downloaded && curfile && curfile->unread)
+                        load(curfile);
+                if((cmdp = parsecmd(0))==0){
+                        if(downloaded){
+                                rescue();
+                                exits("eof");
+                        }
+                        break;
+                }
+                ocurfile = curfile;
+                loaded = curfile && !curfile->unread;
+                if(cmdexec(curfile, cmdp) == 0)
+                        break;
+                freecmd();
+                cmdupdate();
+                update();
+                if(downloaded && curfile &&
+                    (ocurfile!=curfile || (!loaded && !curfile->unread)))
+                        outTs(Hcurrent, curfile->tag);
+                        /* don't allow type ahead on files that aren't bound */
+                if(downloaded && curfile && curfile->rasp == 0)
+                        terminp = termoutp;
+        }
+}
+
+Cmd *
+newcmd(void){
+        Cmd *p;
+
+        p = emalloc(sizeof(Cmd));
+        inslist(&cmdlist, cmdlist.nused, (long)p);
+        return p;
+}
+
+Addr*
+newaddr(void)
+{
+        Addr *p;
+
+        p = emalloc(sizeof(Addr));
+        inslist(&addrlist, addrlist.nused, (long)p);
+        return p;
+}
+
+String*
+newre(void)
+{
+        String *p;
+
+        p = emalloc(sizeof(String));
+        inslist(&relist, relist.nused, (long)p);
+        Strinit(p);
+        return p;
+}
+
+String*
+newstring(void)
+{
+        String *p;
+
+        p = emalloc(sizeof(String));
+        inslist(&stringlist, stringlist.nused, (long)p);
+        Strinit(p);
+        return p;
+}
+
+void
+freecmd(void)
+{
+        int i;
+
+        while(cmdlist.nused > 0)
+                free(cmdlist.ucharpptr[--cmdlist.nused]);
+        while(addrlist.nused > 0)
+                free(addrlist.ucharpptr[--addrlist.nused]);
+        while(relist.nused > 0){
+                i = --relist.nused;
+                Strclose(relist.stringpptr[i]);
+                free(relist.stringpptr[i]);
+        }
+        while(stringlist.nused>0){
+                i = --stringlist.nused;
+                Strclose(stringlist.stringpptr[i]);
+                free(stringlist.stringpptr[i]);
+        }
+}
+
+int
+lookup(int c)
+{
+        int i;
+
+        for(i=0; cmdtab[i].cmdc; i++)
+                if(cmdtab[i].cmdc == c)
+                        return i;
+        return -1;
+}
+
+void
+okdelim(int c)
+{
+        if(c=='\\' || ('a'<=c && c<='z')
+        || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
+                error_c(Edelim, c);
+}
+
+void
+atnl(void)
+{
+        skipbl();
+        if(getch() != '\n')
+                error(Enewline);
+}
+
+void
+getrhs(String *s, int delim, int cmd)
+{
+        int c;
+
+        while((c = getch())>0 && c!=delim && c!='\n'){
+                if(c == '\\'){
+                        if((c=getch()) <= 0)
+                                error(Ebadrhs);
+                        if(c == '\n'){
+                                ungetch();
+                                c='\\';
+                        }else if(c == 'n')
+                                c='\n';
+                        else if(c!=delim && (cmd=='s' || c!='\\'))        /* s does its own */
+                                Straddc(s, '\\');
+                }
+                Straddc(s, c);
+        }
+        ungetch();        /* let client read whether delimeter, '\n' or whatever */
+}
+
+String *
+collecttoken(char *end)
+{
+        String *s = newstring();
+        int c;
+
+        while((c=nextc())==' ' || c=='\t')
+                Straddc(s, getch()); /* blanks significant for getname() */
+        while((c=getch())>0 && utfrune(end, c)==0)
+                Straddc(s, c);
+        Straddc(s, 0);
+        if(c != '\n')
+                atnl();
+        return s;
+}
+
+String *
+collecttext(void)
+{
+        String *s = newstring();
+        int begline, i, c, delim;
+
+        if(skipbl()=='\n'){
+                getch();
+                i = 0;
+                do{
+                        begline = i;
+                        while((c = getch())>0 && c!='\n')
+                                i++, Straddc(s, c);
+                        i++, Straddc(s, '\n');
+                        if(c < 0)
+                                goto Return;
+                }while(s->s[begline]!='.' || s->s[begline+1]!='\n');
+                Strdelete(s, s->n-2, s->n);
+        }else{
+                okdelim(delim = getch());
+                getrhs(s, delim, 'a');
+                if(nextc()==delim)
+                        getch();
+                atnl();
+        }
+    Return:
+        Straddc(s, 0);                /* JUST FOR CMDPRINT() */
+        return s;
+}
+
+Cmd *
+parsecmd(int nest)
+{
+        int i, c;
+        struct cmdtab *ct;
+        Cmd *cp, *ncp;
+        Cmd cmd;
+
+        cmd.next = cmd.ccmd = 0;
+        cmd.re = 0;
+        cmd.flag = cmd.num = 0;
+        cmd.addr = compoundaddr();
+        if(skipbl() == -1)
+                return 0;
+        if((c=getch())==-1)
+                return 0;
+        cmd.cmdc = c;
+        if(cmd.cmdc=='c' && nextc()=='d'){        /* sleazy two-character case */
+                getch();                /* the 'd' */
+                cmd.cmdc='c'|0x100;
+        }
+        i = lookup(cmd.cmdc);
+        if(i >= 0){
+                if(cmd.cmdc == '\n')
+                        goto Return;        /* let nl_cmd work it all out */
+                ct = &cmdtab[i];
+                if(ct->defaddr==aNo && cmd.addr)
+                        error(Enoaddr);
+                if(ct->count)
+                        cmd.num = getnum(ct->count);
+                if(ct->regexp){
+                        /* x without pattern -> .*\n, indicated by cmd.re==0 */
+                        /* X without pattern is all files */
+                        if((ct->cmdc!='x' && ct->cmdc!='X') ||
+                           ((c = nextc())!=' ' && c!='\t' && c!='\n')){
+                                skipbl();
+                                if((c = getch())=='\n' || c<0)
+                                        error(Enopattern);
+                                okdelim(c);
+                                cmd.re = getregexp(c);
+                                if(ct->cmdc == 's'){
+                                        cmd.ctext = newstring();
+                                        getrhs(cmd.ctext, c, 's');
+                                        if(nextc() == c){
+                                                getch();
+                                                if(nextc() == 'g')
+                                                        cmd.flag = getch();
+                                        }
+                        
+                                }
+                        }
+                }
+                if(ct->addr && (cmd.caddr=simpleaddr())==0)
+                        error(Eaddress);
+                if(ct->defcmd){
+                        if(skipbl() == '\n'){
+                                getch();
+                                cmd.ccmd = newcmd();
+                                cmd.ccmd->cmdc = ct->defcmd;
+                        }else if((cmd.ccmd = parsecmd(nest))==0)
+                                panic("defcmd");
+                }else if(ct->text)
+                        cmd.ctext = collecttext();
+                else if(ct->token)
+                        cmd.ctext = collecttoken(ct->token);
+                else
+                        atnl();
+        }else
+                switch(cmd.cmdc){
+                case '{':
+                        cp = 0;
+                        do{
+                                if(skipbl()=='\n')
+                                        getch();
+                                ncp = parsecmd(nest+1);
+                                if(cp)
+                                        cp->next = ncp;
+                                else
+                                        cmd.ccmd = ncp;
+                        }while(cp = ncp);
+                        break;
+                case '}':
+                        atnl();
+                        if(nest==0)
+                                error(Enolbrace);
+                        return 0;
+                default:
+                        error_c(Eunk, cmd.cmdc);
+                }
+    Return:
+        cp = newcmd();
+        *cp = cmd;
+        return cp;
+}
+
+String*                                /* BUGGERED */
+getregexp(int delim)
+{
+        String *r = newre();
+        int c;
+
+        for(Strzero(&genstr); ; Straddc(&genstr, c))
+                if((c = getch())=='\\'){
+                        if(nextc()==delim)
+                                c = getch();
+                        else if(nextc()=='\\'){
+                                Straddc(&genstr, c);
+                                c = getch();
+                        }
+                }else if(c==delim || c=='\n')
+                        break;
+        if(c!=delim && c)
+                ungetch();
+        if(genstr.n > 0){
+                patset = TRUE;
+                Strduplstr(&lastpat, &genstr);
+                Straddc(&lastpat, '\0');
+        }
+        if(lastpat.n <= 1)
+                error(Epattern);
+        Strduplstr(r, &lastpat);
+        return r;
+}
+
+Addr *
+simpleaddr(void)
+{
+        Addr addr;
+        Addr *ap, *nap;
+
+        addr.next = 0;
+        addr.left = 0;
+        switch(skipbl()){
+        case '#':
+                addr.type = getch();
+                addr.num = getnum(1);
+                break;
+        case '0': case '1': case '2': case '3': case '4':
+        case '5': case '6': case '7': case '8': case '9': 
+                addr.num = getnum(1);
+                addr.type='l';
+                break;
+        case '/': case '?': case '"':
+                addr.are = getregexp(addr.type = getch());
+                break;
+        case '.':
+        case '$':
+        case '+':
+        case '-':
+        case '\'':
+                addr.type = getch();
+                break;
+        default:
+                return 0;
+        }
+        if(addr.next = simpleaddr())
+                switch(addr.next->type){
+                case '.':
+                case '$':
+                case '\'':
+                        if(addr.type!='"')
+                case '"':
+                                error(Eaddress);
+                        break;
+                case 'l':
+                case '#':
+                        if(addr.type=='"')
+                                break;
+                        /* fall through */
+                case '/':
+                case '?':
+                        if(addr.type!='+' && addr.type!='-'){
+                                /* insert the missing '+' */
+                                nap = newaddr();
+                                nap->type='+';
+                                nap->next = addr.next;
+                                addr.next = nap;
+                        }
+                        break;
+                case '+':
+                case '-':
+                        break;
+                default:
+                        panic("simpleaddr");
+                }
+        ap = newaddr();
+        *ap = addr;
+        return ap;
+}
+
+Addr *
+compoundaddr(void)
+{
+        Addr addr;
+        Addr *ap, *next;
+
+        addr.left = simpleaddr();
+        if((addr.type = skipbl())!=',' && addr.type!=';')
+                return addr.left;
+        getch();
+        next = addr.next = compoundaddr();
+        if(next && (next->type==',' || next->type==';') && next->left==0)
+                error(Eaddress);
+        ap = newaddr();
+        *ap = addr;
+        return ap;
+}
diff --git a/src/cmd/sam/disk.c b/src/cmd/sam/disk.c
t@@ -0,0 +1,122 @@
+#include "sam.h"
+
+static        Block        *blist;
+
+#if 0
+static int
+tempdisk(void)
+{
+        char buf[128];
+        int i, fd;
+
+        snprint(buf, sizeof buf, "/tmp/X%d.%.4ssam", getpid(), getuser());
+        for(i='A'; i<='Z'; i++){
+                buf[5] = i;
+                if(access(buf, AEXIST) == 0)
+                        continue;
+                fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
+                if(fd >= 0)
+                        return fd;
+        }
+        return -1;
+}
+#else
+extern int tempdisk(void);
+#endif
+
+Disk*
+diskinit()
+{
+        Disk *d;
+
+        d = emalloc(sizeof(Disk));
+        d->fd = tempdisk();
+        if(d->fd < 0){
+                fprint(2, "sam: can't create temp file: %r\n");
+                exits("diskinit");
+        }
+        return d;
+}
+
+static
+uint
+ntosize(uint n, uint *ip)
+{
+        uint size;
+
+        if(n > Maxblock)
+                panic("internal error: ntosize");
+        size = n;
+        if(size & (Blockincr-1))
+                size += Blockincr - (size & (Blockincr-1));
+        /* last bucket holds blocks of exactly Maxblock */
+        if(ip)
+                *ip = size/Blockincr;
+        return size * sizeof(Rune);
+}
+
+Block*
+disknewblock(Disk *d, uint n)
+{
+        uint i, j, size;
+        Block *b;
+
+        size = ntosize(n, &i);
+        b = d->free[i];
+        if(b)
+                d->free[i] = b->_.next;
+        else{
+                /* allocate in chunks to reduce malloc overhead */
+                if(blist == nil){
+                        blist = emalloc(100*sizeof(Block));
+                        for(j=0; j<100-1; j++)
+                                blist[j]._.next = &blist[j+1];
+                }
+                b = blist;
+                blist = b->_.next;
+                b->addr = d->addr;
+                d->addr += size;
+        }
+        b->_.n = n;
+        return b;
+}
+
+void
+diskrelease(Disk *d, Block *b)
+{
+        uint i;
+
+        ntosize(b->_.n, &i);
+        b->_.next = d->free[i];
+        d->free[i] = b;
+}
+
+void
+diskwrite(Disk *d, Block **bp, Rune *r, uint n)
+{
+        int size, nsize;
+        Block *b;
+
+        b = *bp;
+        size = ntosize(b->_.n, nil);
+        nsize = ntosize(n, nil);
+        if(size != nsize){
+                diskrelease(d, b);
+                b = disknewblock(d, n);
+                *bp = b;
+        }
+        if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+                panic("write error to temp file");
+        b->_.n = n;
+}
+
+void
+diskread(Disk *d, Block *b, Rune *r, uint n)
+{
+        if(n > b->_.n)
+                panic("internal error: diskread");
+
+        ntosize(b->_.n, nil);        /* called only for sanity check on Maxblock */
+        if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+                panic("read error from temp file");
+}
diff --git a/src/cmd/sam/error.c b/src/cmd/sam/error.c
t@@ -0,0 +1,144 @@
+#include "sam.h"
+
+static char *emsg[]={
+        /* error_s */
+        "can't open",
+        "can't create",
+        "not in menu:",
+        "changes to",
+        "I/O error:",
+        "can't write while changing:",
+        /* error_c */
+        "unknown command",
+        "no operand for",
+        "bad delimiter",
+        /* error */
+        "can't fork",
+        "interrupt",
+        "address",
+        "search",
+        "pattern",
+        "newline expected",
+        "blank expected",
+        "pattern expected",
+        "can't nest X or Y",
+        "unmatched `}'",
+        "command takes no address",
+        "addresses overlap",
+        "substitution",
+        "& match too long",
+        "bad \\ in rhs",
+        "address range",
+        "changes not in sequence",
+        "addresses out of order",
+        "no file name",
+        "unmatched `('",
+        "unmatched `)'",
+        "malformed `[]'",
+        "malformed regexp",
+        "reg. exp. list overflow",
+        "plan 9 command",
+        "can't pipe",
+        "no current file",
+        "string too long",
+        "changed files",
+        "empty string",
+        "file search",
+        "non-unique match for \"\"",
+        "tag match too long",
+        "too many subexpressions",
+        "temporary file too large",
+        "file is append-only",
+        "no destination for plumb message",
+        "internal read error in buffer load",
+};
+static char *wmsg[]={
+        /* warn_s */
+        "duplicate file name",
+        "no such file",
+        "write might change good version of",
+        /* warn_S */
+        "files might be aliased",
+        /* warn */
+        "null characters elided",
+        "can't run pwd",
+        "last char not newline",
+        "exit status not 0",
+};
+
+void
+error(Err s)
+{
+        char buf[512];
+
+        sprint(buf, "?%s", emsg[s]);
+        hiccough(buf);
+}
+
+void
+error_s(Err s, char *a)
+{
+        char buf[512];
+
+        sprint(buf, "?%s \"%s\"", emsg[s], a);
+        hiccough(buf);
+}
+
+void
+error_r(Err s, char *a)
+{
+        char buf[512];
+
+        sprint(buf, "?%s \"%s\": %r", emsg[s], a);
+        hiccough(buf);
+}
+
+void
+error_c(Err s, int c)
+{
+        char buf[512];
+
+        sprint(buf, "?%s `%C'", emsg[s], c);
+        hiccough(buf);
+}
+
+void
+warn(Warn s)
+{
+        dprint("?warning: %s\n", wmsg[s]);
+}
+
+void
+warn_S(Warn s, String *a)
+{
+        print_s(wmsg[s], a);
+}
+
+void
+warn_SS(Warn s, String *a, String *b)
+{
+        print_ss(wmsg[s], a, b);
+}
+
+void
+warn_s(Warn s, char *a)
+{
+        dprint("?warning: %s `%s'\n", wmsg[s], a);
+}
+
+void
+termwrite(char *s)
+{
+        String *p;
+
+        if(downloaded){
+                p = tmpcstr(s);
+                if(cmd)
+                        loginsert(cmd, cmdpt, p->s, p->n);
+                else
+                        Strinsert(&cmdstr, p, cmdstr.n);
+                cmdptadv += p->n;
+                free(p);
+        }else
+                Write(2, s, strlen(s));
+}
diff --git a/src/cmd/sam/errors.h b/src/cmd/sam/errors.h
t@@ -0,0 +1,65 @@
+typedef enum Err{
+        /* error_s */
+        Eopen,
+        Ecreate,
+        Emenu,
+        Emodified,
+        Eio,
+        Ewseq,
+        /* error_c */
+        Eunk,
+        Emissop,
+        Edelim,
+        /* error */
+        Efork,
+        Eintr,
+        Eaddress,
+        Esearch,
+        Epattern,
+        Enewline,
+        Eblank,
+        Enopattern,
+        EnestXY,
+        Enolbrace,
+        Enoaddr,
+        Eoverlap,
+        Enosub,
+        Elongrhs,
+        Ebadrhs,
+        Erange,
+        Esequence,
+        Eorder,
+        Enoname,
+        Eleftpar,
+        Erightpar,
+        Ebadclass,
+        Ebadregexp,
+        Eoverflow,
+        Enocmd,
+        Epipe,
+        Enofile,
+        Etoolong,
+        Echanges,
+        Eempty,
+        Efsearch,
+        Emanyfiles,
+        Elongtag,
+        Esubexp,
+        Etmpovfl,
+        Eappend,
+        Ecantplumb,
+        Ebufload,
+}Err;
+typedef enum Warn{
+        /* warn_s */
+        Wdupname,
+        Wfile,
+        Wdate,
+        /* warn_ss */
+        Wdupfile,
+        /* warn */
+        Wnulls,
+        Wpwd,
+        Wnotnewline,
+        Wbadstatus,
+}Warn;
diff --git a/src/cmd/sam/file.c b/src/cmd/sam/file.c
t@@ -0,0 +1,631 @@
+#include "sam.h"
+
+/*
+ * Structure of Undo list:
+ *         The Undo structure follows any associated data, so the list
+ *        can be read backwards: read the structure, then read whatever
+ *        data is associated (insert string, file name) and precedes it.
+ *        The structure includes the previous value of the modify bit
+ *        and a sequence number; successive Undo structures with the
+ *        same sequence number represent simultaneous changes.
+ */
+
+typedef struct Undo Undo;
+typedef struct Merge Merge;
+
+struct Undo
+{
+        short        type;                /* Delete, Insert, Filename, Dot, Mark */
+        short        mod;                /* modify bit */
+        uint        seq;                /* sequence number */
+        uint        p0;                /* location of change (unused in f) */
+        uint        n;                /* # runes in string or file name */
+};
+
+struct Merge
+{
+        File        *f;
+        uint        seq;                /* of logged change */
+        uint        p0;                /* location of change (unused in f) */
+        uint        n;                /* # runes to delete */
+        uint        nbuf;                /* # runes to insert */
+        Rune        buf[RBUFSIZE];
+};
+
+enum
+{
+        Maxmerge = 50,
+        Undosize = sizeof(Undo)/sizeof(Rune),
+};
+
+static Merge        merge;
+
+File*
+fileopen(void)
+{
+        File *f;
+
+        f = emalloc(sizeof(File));
+        f->dot.f = f;
+        f->ndot.f = f;
+        f->seq = 0;
+        f->mod = FALSE;
+        f->unread = TRUE;
+        Strinit0(&f->name);
+        return f;
+}
+
+int
+fileisdirty(File *f)
+{
+        return f->seq != f->cleanseq;
+}
+
+static void
+wrinsert(Buffer *delta, int seq, int mod, uint p0, Rune *s, uint ns)
+{
+        Undo u;
+
+        u.type = Insert;
+        u.mod = mod;
+        u.seq = seq;
+        u.p0 = p0;
+        u.n = ns;
+        bufinsert(delta, delta->nc, s, ns);
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+static void
+wrdelete(Buffer *delta, int seq, int mod, uint p0, uint p1)
+{
+        Undo u;
+
+        u.type = Delete;
+        u.mod = mod;
+        u.seq = seq;
+        u.p0 = p0;
+        u.n = p1 - p0;
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+flushmerge(void)
+{
+        File *f;
+
+        f = merge.f;
+        if(f == nil)
+                return;
+        if(merge.seq != f->seq)
+                panic("flushmerge seq mismatch");
+        if(merge.n != 0)
+                wrdelete(&f->epsilon, f->seq, TRUE, merge.p0, merge.p0+merge.n);
+        if(merge.nbuf != 0)
+                wrinsert(&f->epsilon, f->seq, TRUE, merge.p0+merge.n, merge.buf, merge.nbuf);
+        merge.f = nil;
+        merge.n = 0;
+        merge.nbuf = 0;
+}
+
+void
+mergeextend(File *f, uint p0)
+{
+        uint mp0n;
+
+        mp0n = merge.p0+merge.n;
+        if(mp0n != p0){
+                bufread(f, mp0n, merge.buf+merge.nbuf, p0-mp0n);
+                merge.nbuf += p0-mp0n;
+                merge.n = p0-merge.p0;
+        }
+}
+
+/*
+ * like fileundelete, but get the data from arguments
+ */
+void
+loginsert(File *f, uint p0, Rune *s, uint ns)
+{
+        if(f->rescuing)
+                return;
+        if(ns == 0)
+                return;
+        if(ns<0 || ns>STRSIZE)
+                panic("loginsert");
+        if(f->seq < seq)
+                filemark(f);
+        if(p0 < f->hiposn)
+                error(Esequence);
+
+        if(merge.f != f
+        || p0-(merge.p0+merge.n)>Maxmerge                        /* too far */
+        || merge.nbuf+((p0+ns)-(merge.p0+merge.n))>RBUFSIZE)        /* too long */
+                flushmerge();
+
+        if(ns>=RBUFSIZE){
+                if(!(merge.n == 0 && merge.nbuf == 0 && merge.f == nil))
+                        panic("loginsert bad merge state");
+                wrinsert(&f->epsilon, f->seq, TRUE, p0, s, ns);
+        }else{
+                if(merge.f != f){
+                        merge.f = f;
+                        merge.p0 = p0;
+                        merge.seq = f->seq;
+                }
+                mergeextend(f, p0);
+
+                /* append string to merge */
+                runemove(merge.buf+merge.nbuf, s, ns);
+                merge.nbuf += ns;
+        }
+
+        f->hiposn = p0;
+        if(!f->unread && !f->mod)
+                state(f, Dirty);
+}
+
+void
+logdelete(File *f, uint p0, uint p1)
+{
+        if(f->rescuing)
+                return;
+        if(p0 == p1)
+                return;
+        if(f->seq < seq)
+                filemark(f);
+        if(p0 < f->hiposn)
+                error(Esequence);
+
+        if(merge.f != f
+        || p0-(merge.p0+merge.n)>Maxmerge                        /* too far */
+        || merge.nbuf+(p0-(merge.p0+merge.n))>RBUFSIZE){        /* too long */
+                flushmerge();
+                merge.f = f;
+                merge.p0 = p0;
+                merge.seq = f->seq;
+        }
+
+        mergeextend(f, p0);
+
+        /* add to deletion */
+        merge.n = p1-merge.p0;
+
+        f->hiposn = p1;
+        if(!f->unread && !f->mod)
+                state(f, Dirty);
+}
+
+/*
+ * like fileunsetname, but get the data from arguments
+ */
+void
+logsetname(File *f, String *s)
+{
+        Undo u;
+        Buffer *delta;
+
+        if(f->rescuing)
+                return;
+
+        if(f->unread){        /* This is setting initial file name */
+                filesetname(f, s);
+                return;
+        }
+
+        if(f->seq < seq)
+                filemark(f);
+
+        /* undo a file name change by restoring old name */
+        delta = &f->epsilon;
+        u.type = Filename;
+        u.mod = TRUE;
+        u.seq = f->seq;
+        u.p0 = 0;        /* unused */
+        u.n = s->n;
+        if(s->n)
+                bufinsert(delta, delta->nc, s->s, s->n);
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+        if(!f->unread && !f->mod)
+                state(f, Dirty);
+}
+
+#ifdef NOTEXT
+File*
+fileaddtext(File *f, Text *t)
+{
+        if(f == nil){
+                f = emalloc(sizeof(File));
+                f->unread = TRUE;
+        }
+        f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
+        f->text[f->ntext++] = t;
+        f->curtext = t;
+        return f;
+}
+
+void
+filedeltext(File *f, Text *t)
+{
+        int i;
+
+        for(i=0; intext; i++)
+                if(f->text[i] == t)
+                        goto Found;
+        panic("can't find text in filedeltext");
+
+    Found:
+        f->ntext--;
+        if(f->ntext == 0){
+                fileclose(f);
+                return;
+        }
+        memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
+        if(f->curtext == t)
+                f->curtext = f->text[0];
+}
+#endif
+
+void
+fileinsert(File *f, uint p0, Rune *s, uint ns)
+{
+        if(p0 > f->_.nc)
+                panic("internal error: fileinsert");
+        if(f->seq > 0)
+                fileuninsert(f, &f->delta, p0, ns);
+        bufinsert(f, p0, s, ns);
+        if(ns)
+                f->mod = TRUE;
+}
+
+void
+fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
+{
+        Undo u;
+
+        /* undo an insertion by deleting */
+        u.type = Delete;
+        u.mod = f->mod;
+        u.seq = f->seq;
+        u.p0 = p0;
+        u.n = ns;
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+filedelete(File *f, uint p0, uint p1)
+{
+        if(!(p0<=p1 && p0<=f->_.nc && p1<=f->_.nc))
+                panic("internal error: filedelete");
+        if(f->seq > 0)
+                fileundelete(f, &f->delta, p0, p1);
+        bufdelete(f, p0, p1);
+        if(p1 > p0)
+                f->mod = TRUE;
+}
+
+void
+fileundelete(File *f, Buffer *delta, uint p0, uint p1)
+{
+        Undo u;
+        Rune *buf;
+        uint i, n;
+
+        /* undo a deletion by inserting */
+        u.type = Insert;
+        u.mod = f->mod;
+        u.seq = f->seq;
+        u.p0 = p0;
+        u.n = p1-p0;
+        buf = fbufalloc();
+        for(i=p0; i RBUFSIZE)
+                        n = RBUFSIZE;
+                bufread(f, i, buf, n);
+                bufinsert(delta, delta->nc, buf, n);
+        }
+        fbuffree(buf);
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+
+}
+
+int
+filereadc(File *f, uint q)
+{
+        Rune r;
+
+        if(q >= f->_.nc)
+                return -1;
+        bufread(f, q, &r, 1);
+        return r;
+}
+
+void
+filesetname(File *f, String *s)
+{
+        if(!f->unread)        /* This is setting initial file name */
+                fileunsetname(f, &f->delta);
+        Strduplstr(&f->name, s);
+        sortname(f);
+        f->unread = TRUE;
+}
+
+void
+fileunsetname(File *f, Buffer *delta)
+{
+        String s;
+        Undo u;
+
+        /* undo a file name change by restoring old name */
+        u.type = Filename;
+        u.mod = f->mod;
+        u.seq = f->seq;
+        u.p0 = 0;        /* unused */
+        Strinit(&s);
+        Strduplstr(&s, &f->name);
+        fullname(&s);
+        u.n = s.n;
+        if(s.n)
+                bufinsert(delta, delta->nc, s.s, s.n);
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+        Strclose(&s);
+}
+
+void
+fileunsetdot(File *f, Buffer *delta, Range dot)
+{
+        Undo u;
+
+        u.type = Dot;
+        u.mod = f->mod;
+        u.seq = f->seq;
+        u.p0 = dot.p1;
+        u.n = dot.p2 - dot.p1;
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+fileunsetmark(File *f, Buffer *delta, Range mark)
+{
+        Undo u;
+
+        u.type = Mark;
+        u.mod = f->mod;
+        u.seq = f->seq;
+        u.p0 = mark.p1;
+        u.n = mark.p2 - mark.p1;
+        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+uint
+fileload(File *f, uint p0, int fd, int *nulls)
+{
+        if(f->seq > 0)
+                panic("undo in file.load unimplemented");
+        return bufload(f, p0, fd, nulls);
+}
+
+int
+fileupdate(File *f, int notrans, int toterm)
+{
+        uint p1, p2;
+        int mod;
+
+        if(f->rescuing)
+                return FALSE;
+
+        flushmerge();
+
+        /*
+         * fix the modification bit
+         * subtle point: don't save it away in the log.
+         *
+         * if another change is made, the correct f->mod
+         * state is saved  in the undo log by filemark
+         * when setting the dot and mark.
+         *
+         * if the change is undone, the correct state is
+         * saved from f in the fileun... routines.
+         */
+        mod = f->mod;
+        f->mod = f->prevmod;
+        if(f == cmd)
+                notrans = TRUE;
+        else{
+                fileunsetdot(f, &f->delta, f->prevdot);
+                fileunsetmark(f, &f->delta, f->prevmark);
+        }
+        f->dot = f->ndot;
+        fileundo(f, FALSE, !notrans, &p1, &p2, toterm);
+        f->mod = mod;
+
+        if(f->delta.nc == 0)
+                f->seq = 0;
+
+        if(f == cmd)
+                return FALSE;
+
+        if(f->mod){
+                f->closeok = 0;
+                quitok = 0;
+        }else
+                f->closeok = 1;
+        return TRUE;
+}
+
+long
+prevseq(Buffer *b)
+{
+        Undo u;
+        uint up;
+
+        up = b->nc;
+        if(up == 0)
+                return 0;
+        up -= Undosize;
+        bufread(b, up, (Rune*)&u, Undosize);
+        return u.seq;
+}
+
+long
+undoseq(File *f, int isundo)
+{
+        if(isundo)
+                return f->seq;
+
+        return prevseq(&f->epsilon);
+}
+
+void
+fileundo(File *f, int isundo, int canredo, uint *q0p, uint *q1p, int flag)
+{
+        Undo u;
+        Rune *buf;
+        uint i, n, up;
+        uint stop;
+        Buffer *delta, *epsilon;
+
+        if(isundo){
+                /* undo; reverse delta onto epsilon, seq decreases */
+                delta = &f->delta;
+                epsilon = &f->epsilon;
+                stop = f->seq;
+        }else{
+                /* redo; reverse epsilon onto delta, seq increases */
+                delta = &f->epsilon;
+                epsilon = &f->delta;
+                stop = 0;        /* don't know yet */
+        }
+
+        raspstart(f);
+        while(delta->nc > 0){
+                up = delta->nc-Undosize;
+                bufread(delta, up, (Rune*)&u, Undosize);
+                if(isundo){
+                        if(u.seq < stop){
+                                f->seq = u.seq;
+                                raspdone(f, flag);
+                                return;
+                        }
+                }else{
+                        if(stop == 0)
+                                stop = u.seq;
+                        if(u.seq > stop){
+                                raspdone(f, flag);
+                                return;
+                        }
+                }
+                switch(u.type){
+                default:
+                        panic("undo unknown u.type");
+                        break;
+
+                case Delete:
+                        f->seq = u.seq;
+                        if(canredo)
+                                fileundelete(f, epsilon, u.p0, u.p0+u.n);
+                        f->mod = u.mod;
+                        bufdelete(f, u.p0, u.p0+u.n);
+                        raspdelete(f, u.p0, u.p0+u.n, flag);
+                        *q0p = u.p0;
+                        *q1p = u.p0;
+                        break;
+
+                case Insert:
+                        f->seq = u.seq;
+                        if(canredo)
+                                fileuninsert(f, epsilon, u.p0, u.n);
+                        f->mod = u.mod;
+                        up -= u.n;
+                        buf = fbufalloc();
+                        for(i=0; i RBUFSIZE)
+                                        n = RBUFSIZE;
+                                bufread(delta, up+i, buf, n);
+                                bufinsert(f, u.p0+i, buf, n);
+                                raspinsert(f, u.p0+i, buf, n, flag);
+                        }
+                        fbuffree(buf);
+                        *q0p = u.p0;
+                        *q1p = u.p0+u.n;
+                        break;
+
+                case Filename:
+                        f->seq = u.seq;
+                        if(canredo)
+                                fileunsetname(f, epsilon);
+                        f->mod = u.mod;
+                        up -= u.n;
+
+                        Strinsure(&f->name, u.n+1);
+                        bufread(delta, up, f->name.s, u.n);
+                        f->name.s[u.n] = 0;
+                        f->name.n = u.n;
+                        fixname(&f->name);
+                        sortname(f);
+                        break;
+                case Dot:
+                        f->seq = u.seq;
+                        if(canredo)
+                                fileunsetdot(f, epsilon, f->dot.r);
+                        f->mod = u.mod;
+                        f->dot.r.p1 = u.p0;
+                        f->dot.r.p2 = u.p0 + u.n;
+                        break;
+                case Mark:
+                        f->seq = u.seq;
+                        if(canredo)
+                                fileunsetmark(f, epsilon, f->mark);
+                        f->mod = u.mod;
+                        f->mark.p1 = u.p0;
+                        f->mark.p2 = u.p0 + u.n;
+                        break;
+                }
+                bufdelete(delta, up, delta->nc);
+        }
+        if(isundo)
+                f->seq = 0;
+        raspdone(f, flag);
+}
+
+void
+filereset(File *f)
+{
+        bufreset(&f->delta);
+        bufreset(&f->epsilon);
+        f->seq = 0;
+}
+
+void
+fileclose(File *f)
+{
+        Strclose(&f->name);
+        bufclose(f);
+        bufclose(&f->delta);
+        bufclose(&f->epsilon);
+        if(f->rasp)
+                listfree(f->rasp);
+        free(f);
+}
+
+void
+filemark(File *f)
+{
+
+        if(f->unread)
+                return;
+        if(f->epsilon.nc)
+                bufdelete(&f->epsilon, 0, f->epsilon.nc);
+
+        if(f != cmd){
+                f->prevdot = f->dot.r;
+                f->prevmark = f->mark;
+                f->prevseq = f->seq;
+                f->prevmod = f->mod;
+        }
+
+        f->ndot = f->dot;
+        f->seq = seq;
+        f->hiposn = 0;
+}
diff --git a/src/cmd/sam/io.c b/src/cmd/sam/io.c
t@@ -0,0 +1,262 @@
+#include "sam.h"
+
+#define        NSYSFILE        3
+#define        NOFILE                128
+
+void
+checkqid(File *f)
+{
+        int i, w;
+        File *g;
+
+        w = whichmenu(f);
+        for(i=1; idev==g->dev && f->qidpath==g->qidpath)
+                        warn_SS(Wdupfile, &f->name, &g->name);
+        }
+}
+
+void
+writef(File *f)
+{
+        Posn n;
+        char *name;
+        int i, samename, newfile;
+        ulong dev;
+        uvlong qid;
+        long mtime, appendonly, length;
+
+        newfile = 0;
+        samename = Strcmp(&genstr, &f->name) == 0;
+        name = Strtoc(&f->name);
+        i = statfile(name, &dev, &qid, &mtime, 0, 0);
+        if(i == -1)
+                newfile++;
+        else if(samename &&
+                (f->dev!=dev || f->qidpath!=qid || f->mtimedev = dev;
+                f->qidpath = qid;
+                f->mtime = mtime;
+                warn_S(Wdate, &genstr);
+                return;
+        }
+        if(genc)
+                free(genc);
+        genc = Strtoc(&genstr);
+        if((io=create(genc, 1, 0666L)) < 0)
+                error_r(Ecreate, genc);
+        dprint("%s: ", genc);
+        if(statfd(io, 0, 0, 0, &length, &appendonly) > 0 && appendonly && length>0)
+                error(Eappend);
+        n = writeio(f);
+        if(f->name.s[0]==0 || samename){
+                if(addr.r.p1==0 && addr.r.p2==f->_.nc)
+                        f->cleanseq = f->seq;
+                state(f, f->cleanseq==f->seq? Clean : Dirty);
+        }
+        if(newfile)
+                dprint("(new file) ");
+        if(addr.r.p2>0 && filereadc(f, addr.r.p2-1)!='\n')
+                warn(Wnotnewline);
+        closeio(n);
+        if(f->name.s[0]==0 || samename){
+                if(statfile(name, &dev, &qid, &mtime, 0, 0) > 0){
+                        f->dev = dev;
+                        f->qidpath = qid;
+                        f->mtime = mtime;
+                        checkqid(f);
+                }
+        }
+}
+
+Posn
+readio(File *f, int *nulls, int setdate, int toterm)
+{
+        int n, b, w;
+        Rune *r;
+        Posn nt;
+        Posn p = addr.r.p2;
+        ulong dev;
+        uvlong qid;
+        long mtime;
+        char buf[BLOCKSIZE+1], *s;
+
+        *nulls = FALSE;
+        b = 0;
+        if(f->unread){
+                nt = bufload(f, 0, io, nulls);
+                if(toterm)
+                        raspload(f);
+        }else
+                for(nt = 0; (n = read(io, buf+b, BLOCKSIZE-b))>0; nt+=(r-genbuf)){
+                        n += b;
+                        b = 0;
+                        r = genbuf;
+                        s = buf;
+                        while(n > 0){
+                                if((*r = *(uchar*)s) < Runeself){
+                                        if(*r)
+                                                r++;
+                                        else
+                                                *nulls = TRUE;
+                                        --n;
+                                        s++;
+                                        continue;
+                                }
+                                if(fullrune(s, n)){
+                                        w = chartorune(r, s);
+                                        if(*r)
+                                                r++;
+                                        else
+                                                *nulls = TRUE;
+                                        n -= w;
+                                        s += w;
+                                        continue;
+                                }
+                                b = n;
+                                memmove(buf, s, b);
+                                break;
+                        }
+                        loginsert(f, p, genbuf, r-genbuf);
+                }
+        if(b)
+                *nulls = TRUE;
+        if(*nulls)
+                warn(Wnulls);
+        if(setdate){
+                if(statfd(io, &dev, &qid, &mtime, 0, 0) > 0){
+                        f->dev = dev;
+                        f->qidpath = qid;
+                        f->mtime = mtime;
+                        checkqid(f);
+                }
+        }
+        return nt;
+}
+
+Posn
+writeio(File *f)
+{
+        int m, n;
+        Posn p = addr.r.p1;
+        char *c;
+
+        while(p < addr.r.p2){
+                if(addr.r.p2-p>BLOCKSIZE)
+                        n = BLOCKSIZE;
+                else
+                        n = addr.r.p2-p;
+                bufread(f, p, genbuf, n);
+                c = Strtoc(tmprstr(genbuf, n));
+                m = strlen(c);
+                if(Write(io, c, m) != m){
+                        free(c);
+                        if(p > 0)
+                                p += n;
+                        break;
+                }
+                free(c);
+                p += n;
+        }
+        return p-addr.r.p1;
+}
+void
+closeio(Posn p)
+{
+        close(io);
+        io = 0;
+        if(p >= 0)
+                dprint("#%lud\n", p);
+}
+
+int        remotefd0 = 0;
+int        remotefd1 = 1;
+
+void
+bootterm(char *machine, char **argv, char **end)
+{
+        int ph2t[2], pt2h[2];
+
+        if(machine){
+                dup(remotefd0, 0);
+                dup(remotefd1, 1);
+                close(remotefd0);
+                close(remotefd1);
+                argv[0] = "samterm";
+                *end = 0;
+                exec(samterm, argv);
+                fprint(2, "can't exec: ");
+                perror(samterm);
+                _exits("damn");
+        }
+        if(pipe(ph2t)==-1 || pipe(pt2h)==-1)
+                panic("pipe");
+        switch(fork()){
+        case 0:
+                dup(ph2t[0], 0);
+                dup(pt2h[1], 1);
+                close(ph2t[0]);
+                close(ph2t[1]);
+                close(pt2h[0]);
+                close(pt2h[1]);
+                argv[0] = "samterm";
+                *end = 0;
+                exec(samterm, argv);
+                fprint(2, "can't exec: ");
+                perror(samterm);
+                _exits("damn");
+        case -1:
+                panic("can't fork samterm");
+        }
+        dup(pt2h[0], 0);
+        dup(ph2t[1], 1);
+        close(ph2t[0]);
+        close(ph2t[1]);
+        close(pt2h[0]);
+        close(pt2h[1]);
+}
+
+void
+connectto(char *machine)
+{
+        int p1[2], p2[2];
+
+        if(pipe(p1)<0 || pipe(p2)<0){
+                dprint("can't pipe\n");
+                exits("pipe");
+        }
+        remotefd0 = p1[0];
+        remotefd1 = p2[1];
+        switch(fork()){
+        case 0:
+                dup(p2[0], 0);
+                dup(p1[1], 1);
+                close(p1[0]);
+                close(p1[1]);
+                close(p2[0]);
+                close(p2[1]);
+                execl(RXPATH, RX, machine, rsamname, "-R", (char*)0);
+                dprint("can't exec %s\n", RXPATH);
+                exits("exec");
+
+        case -1:
+                dprint("can't fork\n");
+                exits("fork");
+        }
+        close(p1[1]);
+        close(p2[0]);
+}
+
+void
+startup(char *machine, int Rflag, char **argv, char **end)
+{
+        if(machine)
+                connectto(machine);
+        if(!Rflag)
+                bootterm(machine, argv, end);
+        downloaded = 1;
+        outTs(Hversion, VERSION);
+}
diff --git a/src/cmd/sam/list.c b/src/cmd/sam/list.c
t@@ -0,0 +1,47 @@
+#include "sam.h"
+
+/*
+ * Check that list has room for one more element.
+ */
+void
+growlist(List *l)
+{
+        if(l->listptr==0 || l->nalloc==0){
+                l->nalloc = INCR;
+                l->listptr = emalloc(INCR*sizeof(long));
+                l->nused = 0;
+        }else if(l->nused == l->nalloc){
+                l->listptr = erealloc(l->listptr, (l->nalloc+INCR)*sizeof(long));
+                memset((void*)(l->longptr+l->nalloc), 0, INCR*sizeof(long));
+                l->nalloc += INCR;
+        }
+}
+
+/*
+ * Remove the ith element from the list
+ */
+void
+dellist(List *l, int i)
+{
+        memmove(&l->longptr[i], &l->longptr[i+1], (l->nused-(i+1))*sizeof(long));
+        l->nused--;
+}
+
+/*
+ * Add a new element, whose position is i, to the list
+ */
+void
+inslist(List *l, int i, long val)
+{
+        growlist(l);
+        memmove(&l->longptr[i+1], &l->longptr[i], (l->nused-i)*sizeof(long));
+        l->longptr[i] = val;
+        l->nused++;
+}
+
+void
+listfree(List *l)
+{
+        free(l->listptr);
+        free(l);
+}
diff --git a/src/cmd/sam/mesg.c b/src/cmd/sam/mesg.c
t@@ -0,0 +1,821 @@
+#include "sam.h"
+
+Header        h;
+uchar        indata[DATASIZE];
+uchar        outdata[2*DATASIZE+3];        /* room for overflow message */
+uchar        *inp;
+uchar        *outp;
+uchar        *outmsg = outdata;
+Posn        cmdpt;
+Posn        cmdptadv;
+Buffer        snarfbuf;
+int        waitack;
+int        noflush;
+int        tversion;
+
+long        inlong(void);
+long        invlong(void);
+int        inshort(void);
+int        inmesg(Tmesg);
+void        setgenstr(File*, Posn, Posn);
+
+#ifdef DEBUG
+char *hname[] = {
+        [Hversion]        "Hversion",
+        [Hbindname]        "Hbindname",
+        [Hcurrent]        "Hcurrent",
+        [Hnewname]        "Hnewname",
+        [Hmovname]        "Hmovname",
+        [Hgrow]                "Hgrow",
+        [Hcheck0]        "Hcheck0",
+        [Hcheck]        "Hcheck",
+        [Hunlock]        "Hunlock",
+        [Hdata]                "Hdata",
+        [Horigin]        "Horigin",
+        [Hunlockfile]        "Hunlockfile",
+        [Hsetdot]        "Hsetdot",
+        [Hgrowdata]        "Hgrowdata",
+        [Hmoveto]        "Hmoveto",
+        [Hclean]        "Hclean",
+        [Hdirty]        "Hdirty",
+        [Hcut]                "Hcut",
+        [Hsetpat]        "Hsetpat",
+        [Hdelname]        "Hdelname",
+        [Hclose]        "Hclose",
+        [Hsetsnarf]        "Hsetsnarf",
+        [Hsnarflen]        "Hsnarflen",
+        [Hack]                "Hack",
+        [Hexit]                "Hexit",
+        [Hplumb]                "Hplumb",
+};
+
+char *tname[] = {
+        [Tversion]        "Tversion",
+        [Tstartcmdfile]        "Tstartcmdfile",
+        [Tcheck]        "Tcheck",
+        [Trequest]        "Trequest",
+        [Torigin]        "Torigin",
+        [Tstartfile]        "Tstartfile",
+        [Tworkfile]        "Tworkfile",
+        [Ttype]                "Ttype",
+        [Tcut]                "Tcut",
+        [Tpaste]        "Tpaste",
+        [Tsnarf]        "Tsnarf",
+        [Tstartnewfile]        "Tstartnewfile",
+        [Twrite]        "Twrite",
+        [Tclose]        "Tclose",
+        [Tlook]                "Tlook",
+        [Tsearch]        "Tsearch",
+        [Tsend]                "Tsend",
+        [Tdclick]        "Tdclick",
+        [Tstartsnarf]        "Tstartsnarf",
+        [Tsetsnarf]        "Tsetsnarf",
+        [Tack]                "Tack",
+        [Texit]                "Texit",
+        [Tplumb]                "Tplumb",
+};
+
+void
+journal(int out, char *s)
+{
+        static int fd = 0;
+
+        if(fd <= 0)
+                fd = create("/tmp/sam.out", 1, 0666L);
+        fprint(fd, "%s%s\n", out? "out: " : "in:  ", s);
+}
+
+void
+journaln(int out, long n)
+{
+        char buf[32];
+
+        sprint(buf, "%ld", n);
+        journal(out, buf);
+}
+#else
+#define        journal(a, b)
+#define journaln(a, b)
+#endif
+
+int
+rcvchar(void){
+        static uchar buf[64];
+        static i, nleft = 0;
+
+        if(nleft <= 0){
+                nleft = read(0, (char *)buf, sizeof buf);
+                if(nleft <= 0)
+                        return -1;
+                i = 0;
+        }
+        --nleft;
+        return buf[i++];
+}
+
+int
+rcv(void){
+        int c;
+        static state = 0;
+        static count = 0;
+        static i = 0;
+
+        while((c=rcvchar()) != -1)
+                switch(state){
+                case 0:
+                        h.type = c;
+                        state++;
+                        break;
+
+                case 1:
+                        h.count0 = c;
+                        state++;
+                        break;
+
+                case 2:
+                        h.count1 = c;
+                        count = h.count0|(h.count1<<8);
+                        i = 0;
+                        if(count > DATASIZE)
+                                panic("count>DATASIZE");
+                        if(count == 0)
+                                goto zerocount;
+                        state++;
+                        break;
+
+                case 3:
+                        indata[i++] = c;
+                        if(i == count){
+                zerocount:
+                                indata[i] = 0;
+                                state = count = 0;
+                                return inmesg(h.type);
+                        }
+                        break;
+                }
+        return 0;
+}
+
+File *
+whichfile(int tag)
+{
+        int i;
+
+        for(i = 0; itag==tag)
+                        return file.filepptr[i];
+        hiccough((char *)0);
+        return 0;
+}
+
+int
+inmesg(Tmesg type)
+{
+        Rune buf[1025];
+        char cbuf[64];
+        int i, m;
+        short s;
+        long l, l1;
+        File *f;
+        Posn p0, p1, p;
+        Range r;
+        String *str;
+        char *c, *wdir;
+        Rune *rp;
+        Plumbmsg *pm;
+
+        if(type > TMAX)
+                panic("inmesg");
+
+        journal(0, tname[type]);
+
+        inp = indata;
+        switch(type){
+        case -1:
+                panic("rcv error");
+
+        default:
+                fprint(2, "unknown type %d\n", type);
+                panic("rcv unknown");
+
+        case Tversion:
+                tversion = inshort();
+                journaln(0, tversion);
+                break;
+
+        case Tstartcmdfile:
+                l = invlong();                /* for 64-bit pointers */
+                journaln(0, l);
+                Strdupl(&genstr, samname);
+                cmd = newfile();
+                cmd->unread = 0;
+                outTsv(Hbindname, cmd->tag, l);
+                outTs(Hcurrent, cmd->tag);
+                logsetname(cmd, &genstr);
+                cmd->rasp = emalloc(sizeof(List));
+                cmd->mod = 0;
+                if(cmdstr.n){
+                        loginsert(cmd, 0L, cmdstr.s, cmdstr.n);
+                        Strdelete(&cmdstr, 0L, (Posn)cmdstr.n);
+                }
+                fileupdate(cmd, FALSE, TRUE);
+                outT0(Hunlock);
+                break;
+
+        case Tcheck:
+                /* go through whichfile to check the tag */
+                outTs(Hcheck, whichfile(inshort())->tag);
+                break;
+
+        case Trequest:
+                f = whichfile(inshort());
+                p0 = inlong();
+                p1 = p0+inshort();
+                journaln(0, p0);
+                journaln(0, p1-p0);
+                if(f->unread)
+                        panic("Trequest: unread");
+                if(p1>f->_.nc)
+                        p1 = f->_.nc;
+                if(p0>f->_.nc) /* can happen e.g. scrolling during command */
+                        p0 = f->_.nc;
+                if(p0 == p1){
+                        i = 0;
+                        r.p1 = r.p2 = p0;
+                }else{
+                        r = rdata(f->rasp, p0, p1-p0);
+                        i = r.p2-r.p1;
+                        bufread(f, r.p1, buf, i);
+                }
+                buf[i]=0;
+                outTslS(Hdata, f->tag, r.p1, tmprstr(buf, i+1));
+                break;
+
+        case Torigin:
+                s = inshort();
+                l = inlong();
+                l1 = inlong();
+                journaln(0, l1);
+                lookorigin(whichfile(s), l, l1);
+                break;
+
+        case Tstartfile:
+                termlocked++;
+                f = whichfile(inshort());
+                if(!f->rasp)        /* this might be a duplicate message */
+                        f->rasp = emalloc(sizeof(List));
+                current(f);
+                outTsv(Hbindname, f->tag, invlong());        /* for 64-bit pointers */
+                outTs(Hcurrent, f->tag);
+                journaln(0, f->tag);
+                if(f->unread)
+                        load(f);
+                else{
+                        if(f->_.nc>0){
+                                rgrow(f->rasp, 0L, f->_.nc);
+                                outTsll(Hgrow, f->tag, 0L, f->_.nc);
+                        }
+                        outTs(Hcheck0, f->tag);
+                        moveto(f, f->dot.r);
+                }
+                break;
+
+        case Tworkfile:
+                i = inshort();
+                f = whichfile(i);
+                current(f);
+                f->dot.r.p1 = inlong();
+                f->dot.r.p2 = inlong();
+                f->tdot = f->dot.r;
+                journaln(0, i);
+                journaln(0, f->dot.r.p1);
+                journaln(0, f->dot.r.p2);
+                break;
+
+        case Ttype:
+                f = whichfile(inshort());
+                p0 = inlong();
+                journaln(0, p0);
+                journal(0, (char*)inp);
+                str = tmpcstr((char*)inp);
+                i = str->n;
+                loginsert(f, p0, str->s, str->n);
+                if(fileupdate(f, FALSE, FALSE))
+                        seq++;
+                if(f==cmd && p0==f->_.nc-i && i>0 && str->s[i-1]=='\n'){
+                        freetmpstr(str);
+                        termlocked++;
+                        termcommand();
+                }else
+                        freetmpstr(str);
+                f->dot.r.p1 = f->dot.r.p2 = p0+i; /* terminal knows this already */
+                f->tdot = f->dot.r;
+                break;
+
+        case Tcut:
+                f = whichfile(inshort());
+                p0 = inlong();
+                p1 = inlong();
+                journaln(0, p0);
+                journaln(0, p1);
+                logdelete(f, p0, p1);
+                if(fileupdate(f, FALSE, FALSE))
+                        seq++;
+                f->dot.r.p1 = f->dot.r.p2 = p0;
+                f->tdot = f->dot.r;   /* terminal knows the value of dot already */
+                break;
+
+        case Tpaste:
+                f = whichfile(inshort());
+                p0 = inlong();
+                journaln(0, p0);
+                for(l=0; lBLOCKSIZE)
+                                m = BLOCKSIZE;
+                        bufread(&snarfbuf, l, genbuf, m);
+                        loginsert(f, p0, tmprstr(genbuf, m)->s, m);
+                }
+                if(fileupdate(f, FALSE, TRUE))
+                        seq++;
+                f->dot.r.p1 = p0;
+                f->dot.r.p2 = p0+snarfbuf.nc;
+                f->tdot.p1 = -1; /* force telldot to tell (arguably a BUG) */
+                telldot(f);
+                outTs(Hunlockfile, f->tag);
+                break;
+
+        case Tsnarf:
+                i = inshort();
+                p0 = inlong();
+                p1 = inlong();
+                snarf(whichfile(i), p0, p1, &snarfbuf, 0);
+                break;
+
+        case Tstartnewfile:
+                l = invlong();
+                Strdupl(&genstr, empty);
+                f = newfile();
+                f->rasp = emalloc(sizeof(List));
+                outTsv(Hbindname, f->tag, l);
+                logsetname(f, &genstr);
+                outTs(Hcurrent, f->tag);
+                current(f);
+                load(f);
+                break;
+
+        case Twrite:
+                termlocked++;
+                i = inshort();
+                journaln(0, i);
+                f = whichfile(i);
+                addr.r.p1 = 0;
+                addr.r.p2 = f->_.nc;
+                if(f->name.s[0] == 0)
+                        error(Enoname);
+                Strduplstr(&genstr, &f->name);
+                writef(f);
+                break;
+
+        case Tclose:
+                termlocked++;
+                i = inshort();
+                journaln(0, i);
+                f = whichfile(i);
+                current(f);
+                trytoclose(f);
+                /* if trytoclose fails, will error out */
+                delete(f);
+                break;
+
+        case Tlook:
+                f = whichfile(inshort());
+                termlocked++;
+                p0 = inlong();
+                p1 = inlong();
+                journaln(0, p0);
+                journaln(0, p1);
+                setgenstr(f, p0, p1);
+                for(l = 0; ldot.r.p2, 1);
+                moveto(curfile, sel.p[0]);
+                break;
+
+        case Tsend:
+                termlocked++;
+                inshort();        /* ignored */
+                p0 = inlong();
+                p1 = inlong();
+                setgenstr(cmd, p0, p1);
+                bufreset(&snarfbuf);
+                bufinsert(&snarfbuf, (Posn)0, genstr.s, genstr.n);
+                outTl(Hsnarflen, genstr.n);
+                if(genstr.s[genstr.n-1] != '\n')
+                        Straddc(&genstr, '\n');
+                loginsert(cmd, cmd->_.nc, genstr.s, genstr.n);
+                fileupdate(cmd, FALSE, TRUE);
+                cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->_.nc;
+                telldot(cmd);
+                termcommand();
+                break;
+
+        case Tdclick:
+                f = whichfile(inshort());
+                p1 = inlong();
+                doubleclick(f, p1);
+                f->tdot.p1 = f->tdot.p2 = p1;
+                telldot(f);
+                outTs(Hunlockfile, f->tag);
+                break;
+
+        case Tstartsnarf:
+                if (snarfbuf.nc <= 0) {        /* nothing to export */
+                        outTs(Hsetsnarf, 0);
+                        break;
+                }
+                c = 0;
+                i = 0;
+                m = snarfbuf.nc;
+                if(m > SNARFSIZE) {
+                        m = SNARFSIZE;
+                        dprint("?warning: snarf buffer truncated\n");
+                }
+                rp = malloc(m*sizeof(Rune));
+                if(rp){
+                        bufread(&snarfbuf, 0, rp, m);
+                        c = Strtoc(tmprstr(rp, m));
+                        free(rp);
+                        i = strlen(c);
+                }
+                outTs(Hsetsnarf, i);
+                if(c){
+                        Write(1, c, i);
+                        free(c);
+                } else
+                        dprint("snarf buffer too long\n");
+                break;
+
+        case Tsetsnarf:
+                m = inshort();
+                if(m > SNARFSIZE)
+                        error(Etoolong);
+                c = malloc(m+1);
+                if(c){
+                        for(i=0; is, str->n);
+                        freetmpstr(str);
+                        outT0(Hunlock);
+                }
+                break;
+
+        case Tack:
+                waitack = 0;
+                break;
+
+        case Tplumb:
+                f = whichfile(inshort());
+                p0 = inlong();
+                p1 = inlong();
+                pm = emalloc(sizeof(Plumbmsg));
+                pm->src = strdup("sam");
+                pm->dst = 0;
+                /* construct current directory */
+                c = Strtoc(&f->name);
+                if(c[0] == '/')
+                        pm->wdir = c;
+                else{
+                        wdir = emalloc(1024);
+                        getwd(wdir, 1024);
+                        pm->wdir = emalloc(1024);
+                        snprint(pm->wdir, 1024, "%s/%s", wdir, c);
+                        cleanname(pm->wdir);
+                        free(wdir);
+                        free(c);
+                }
+                c = strrchr(pm->wdir, '/');
+                if(c)
+                        *c = '\0';
+                pm->type = strdup("text");
+                if(p1 > p0)
+                        pm->attr = nil;
+                else{
+                        p = p0;
+                        while(p0>0 && (i=filereadc(f, p0 - 1))!=' ' && i!='\t' && i!='\n')
+                                p0--;
+                        while(p1_.nc && (i=filereadc(f, p1))!=' ' && i!='\t' && i!='\n')
+                                p1++;
+                        sprint(cbuf, "click=%ld", p-p0);
+                        pm->attr = plumbunpackattr(cbuf);
+                }
+                if(p0==p1 || p1-p0>=BLOCKSIZE){
+                        plumbfree(pm);
+                        break;
+                }
+                setgenstr(f, p0, p1);
+                pm->data = Strtoc(&genstr);
+                pm->ndata = strlen(pm->data);
+                c = plumbpack(pm, &i);
+                if(c != 0){
+                        outTs(Hplumb, i);
+                        Write(1, c, i);
+                        free(c);
+                }
+                plumbfree(pm);
+                break;
+
+        case Texit:
+                exits(0);
+        }
+        return TRUE;
+}
+
+void
+snarf(File *f, Posn p1, Posn p2, Buffer *buf, int emptyok)
+{
+        Posn l;
+        int i;
+
+        if(!emptyok && p1==p2)
+                return;
+        bufreset(buf);
+        /* Stage through genbuf to avoid compaction problems (vestigial) */
+        if(p2 > f->_.nc){
+                fprint(2, "bad snarf addr p1=%ld p2=%ld f->_.nc=%d\n", p1, p2, f->_.nc); /*ZZZ should never happen, can remove */
+                p2 = f->_.nc;
+        }
+        for(l=p1; lBLOCKSIZE? BLOCKSIZE : p2-l;
+                bufread(f, l, genbuf, i);
+                bufinsert(buf, buf->nc, tmprstr(genbuf, i)->s, i);
+        }
+}
+
+int
+inshort(void)
+{
+        ushort n;
+
+        n = inp[0] | (inp[1]<<8);
+        inp += 2;
+        return n;
+}
+
+long
+inlong(void)
+{
+        ulong n;
+
+        n = inp[0] | (inp[1]<<8) | (inp[2]<<16) | (inp[3]<<24);
+        inp += 4;
+        return n;
+}
+
+long
+invlong(void)
+{
+        ulong n;
+        
+        n = (inp[7]<<24) | (inp[6]<<16) | (inp[5]<<8) | inp[4];
+        n = (n<<16) | (inp[3]<<8) | inp[2];
+        n = (n<<16) | (inp[1]<<8) | inp[0];
+        inp += 8;
+        return n;
+}
+
+void
+setgenstr(File *f, Posn p0, Posn p1)
+{
+        if(p0 != p1){
+                if(p1-p0 >= TBLOCKSIZE)
+                        error(Etoolong);
+                Strinsure(&genstr, p1-p0);
+                bufread(f, p0, genbuf, p1-p0);
+                memmove(genstr.s, genbuf, RUNESIZE*(p1-p0));
+                genstr.n = p1-p0;
+        }else{
+                if(snarfbuf.nc == 0)
+                        error(Eempty);
+                if(snarfbuf.nc > TBLOCKSIZE)
+                        error(Etoolong);
+                bufread(&snarfbuf, (Posn)0, genbuf, snarfbuf.nc);
+                Strinsure(&genstr, snarfbuf.nc);
+                memmove(genstr.s, genbuf, RUNESIZE*snarfbuf.nc);
+                genstr.n = snarfbuf.nc;
+        }
+}
+
+void
+outT0(Hmesg type)
+{
+        outstart(type);
+        outsend();
+}
+
+void
+outTl(Hmesg type, long l)
+{
+        outstart(type);
+        outlong(l);
+        outsend();
+}
+
+void
+outTs(Hmesg type, int s)
+{
+        outstart(type);
+        journaln(1, s);
+        outshort(s);
+        outsend();
+}
+
+void
+outS(String *s)
+{
+        char *c;
+        int i;
+
+        c = Strtoc(s);
+        i = strlen(c);
+        outcopy(i, c);
+        if(i > 99)
+                c[99] = 0;
+        journaln(1, i);
+        journal(1, c);
+        free(c);
+}
+
+void
+outTsS(Hmesg type, int s1, String *s)
+{
+        outstart(type);
+        outshort(s1);
+        outS(s);
+        outsend();
+}
+
+void
+outTslS(Hmesg type, int s1, Posn l1, String *s)
+{
+        outstart(type);
+        outshort(s1);
+        journaln(1, s1);
+        outlong(l1);
+        journaln(1, l1);
+        outS(s);
+        outsend();
+}
+
+void
+outTS(Hmesg type, String *s)
+{
+        outstart(type);
+        outS(s);
+        outsend();
+}
+
+void
+outTsllS(Hmesg type, int s1, Posn l1, Posn l2, String *s)
+{
+        outstart(type);
+        outshort(s1);
+        outlong(l1);
+        outlong(l2);
+        journaln(1, l1);
+        journaln(1, l2);
+        outS(s);
+        outsend();
+}
+
+void
+outTsll(Hmesg type, int s, Posn l1, Posn l2)
+{
+        outstart(type);
+        outshort(s);
+        outlong(l1);
+        outlong(l2);
+        journaln(1, l1);
+        journaln(1, l2);
+        outsend();
+}
+
+void
+outTsl(Hmesg type, int s, Posn l)
+{
+        outstart(type);
+        outshort(s);
+        outlong(l);
+        journaln(1, l);
+        outsend();
+}
+
+void
+outTsv(Hmesg type, int s, Posn l)
+{
+        outstart(type);
+        outshort(s);
+        outvlong((void*)l);
+        journaln(1, l);
+        outsend();
+}
+
+void
+outstart(Hmesg type)
+{
+        journal(1, hname[type]);
+        outmsg[0] = type;
+        outp = outmsg+3;
+}
+
+void
+outcopy(int count, void *data)
+{
+        memmove(outp, data, count);
+        outp += count;
+}
+
+void
+outshort(int s)
+{
+        *outp++ = s;
+        *outp++ = s>>8; 
+}
+
+void
+outlong(long l)
+{
+        *outp++ = l;
+        *outp++ = l>>8;
+        *outp++ = l>>16;
+        *outp++ = l>>24;
+}
+
+void
+outvlong(void *v)
+{
+        int i;
+        ulong l;
+
+        l = (ulong) v;
+        for(i = 0; i < 8; i++, l >>= 8)
+                *outp++ = l;
+}
+
+void
+outsend(void)
+{
+        int outcount;
+
+        outcount = outp-outmsg;
+        outcount -= 3;
+        outmsg[1] = outcount;
+        outmsg[2] = outcount>>8;
+        outmsg = outp;
+        if(!noflush){
+                outcount = outmsg-outdata;
+                if (write(1, (char*) outdata, outcount) != outcount)
+                        rescue();
+                outmsg = outdata;
+                return;
+        }
+        if(outmsg < outdata+DATASIZE)
+                return;
+        outflush();
+}
+
+void
+outflush(void)
+{
+        if(outmsg == outdata)
+                return;
+        noflush = 0;
+        outT0(Hack);
+        waitack = 1;
+        do
+                if(rcv() == 0){
+                        rescue();
+                        exits("eof");
+                }
+        while(waitack);
+        outmsg = outdata;
+        noflush = 1;
+}
diff --git a/src/cmd/sam/mesg.h b/src/cmd/sam/mesg.h
t@@ -0,0 +1,131 @@
+/* VERSION 1 introduces plumbing
+        2 increases SNARFSIZE from 4096 to 32000
+ */
+#define        VERSION        2
+
+#define        TBLOCKSIZE 512                  /* largest piece of text sent to terminal */
+#define        DATASIZE  (UTFmax*TBLOCKSIZE+30) /* ... including protocol header stuff */
+#define        SNARFSIZE 32000                /* maximum length of exchanged snarf buffer, must fit in 15 bits */
+/*
+ * Messages originating at the terminal
+ */
+typedef enum Tmesg
+{
+        Tversion,        /* version */
+        Tstartcmdfile,        /* terminal just opened command frame */
+        Tcheck,                /* ask host to poke with Hcheck */
+        Trequest,        /* request data to fill a hole */
+        Torigin,        /* gimme an Horigin near here */
+        Tstartfile,        /* terminal just opened a file's frame */
+        Tworkfile,        /* set file to which commands apply */
+        Ttype,                /* add some characters, but terminal already knows */
+        Tcut,
+        Tpaste,
+        Tsnarf,
+        Tstartnewfile,        /* terminal just opened a new frame */
+        Twrite,                /* write file */
+        Tclose,                /* terminal requests file close; check mod. status */
+        Tlook,                /* search for literal current text */
+        Tsearch,        /* search for last regular expression */
+        Tsend,                /* pretend he typed stuff */
+        Tdclick,        /* double click */
+        Tstartsnarf,        /* initiate snarf buffer exchange */
+        Tsetsnarf,        /* remember string in snarf buffer */
+        Tack,                /* acknowledge Hack */
+        Texit,                /* exit */
+        Tplumb,                /* send plumb message */
+        TMAX,
+}Tmesg;
+/*
+ * Messages originating at the host
+ */
+typedef enum Hmesg
+{
+        Hversion,        /* version */
+        Hbindname,        /* attach name[0] to text in terminal */
+        Hcurrent,        /* make named file the typing file */
+        Hnewname,        /* create "" name in menu */
+        Hmovname,        /* move file name in menu */
+        Hgrow,                /* insert space in rasp */
+        Hcheck0,        /* see below */
+        Hcheck,                /* ask terminal to check whether it needs more data */
+        Hunlock,        /* command is finished; user can do things */
+        Hdata,                /* store this data in previously allocated space */
+        Horigin,        /* set origin of file/frame in terminal */
+        Hunlockfile,        /* unlock file in terminal */
+        Hsetdot,        /* set dot in terminal */
+        Hgrowdata,        /* Hgrow + Hdata folded together */
+        Hmoveto,        /* scrolling, context search, etc. */
+        Hclean,                /* named file is now 'clean' */
+        Hdirty,                /* named file is now 'dirty' */
+        Hcut,                /* remove space from rasp */
+        Hsetpat,        /* set remembered regular expression */
+        Hdelname,        /* delete file name from menu */
+        Hclose,                /* close file and remove from menu */
+        Hsetsnarf,        /* remember string in snarf buffer */
+        Hsnarflen,        /* report length of implicit snarf */
+        Hack,                /* request acknowledgement */
+        Hexit,
+        Hplumb,                /* return plumb message to terminal */
+        HMAX,
+}Hmesg;
+typedef struct Header{
+        uchar        type;                /* one of the above */
+        uchar        count0;                /* low bits of data size */
+        uchar        count1;                /* high bits of data size */
+        uchar        data[1];        /* variable size */
+}Header;
+
+/*
+ * File transfer protocol schematic, a la Holzmann
+ * #define N        6
+ * 
+ * chan h = [4] of { mtype };
+ * chan t = [4] of { mtype };
+ * 
+ * mtype = {        Hgrow, Hdata,
+ *                 Hcheck, Hcheck0,
+ *                 Trequest, Tcheck,
+ *         };
+ * 
+ * active proctype host()
+ * {        byte n;
+ * 
+ *         do
+ *         :: n <  N -> n++; t!Hgrow
+ *         :: n == N -> n++; t!Hcheck0
+ * 
+ *         :: h?Trequest -> t!Hdata
+ *         :: h?Tcheck   -> t!Hcheck
+ *         od
+ * }
+ * 
+ * active proctype term()
+ * {
+ *         do
+ *         :: t?Hgrow   -> h!Trequest
+ *         :: t?Hdata   -> skip
+ *         :: t?Hcheck0 -> h!Tcheck
+ *         :: t?Hcheck  ->
+ *                 if
+ *                 :: h!Trequest -> progress: h!Tcheck
+ *                 :: break
+ *                 fi
+ *         od;
+ *         printf("term exits\n")
+ * }
+ *
+ * From: gerard@research.bell-labs.com
+ * Date: Tue Jul 17 13:47:23 EDT 2001
+ * To: rob@research.bell-labs.com
+ * 
+ * spin -c         (or -a) spec
+ * pcc -DNP -o pan pan.c
+ * pan -l
+ * 
+ * proves that there are no non-progress cycles
+ * (infinite executions *not* passing through
+ * the statement marked with a label starting
+ * with the prefix "progress")
+ * 
+ */
diff --git a/src/cmd/sam/mkfile b/src/cmd/sam/mkfile
t@@ -0,0 +1,40 @@
+
diff --git a/src/cmd/sam/moveto.c b/src/cmd/sam/moveto.c
t@@ -0,0 +1,173 @@
+#include "sam.h"
+
+void
+moveto(File *f, Range r)
+{
+        Posn p1 = r.p1, p2 = r.p2;
+
+        f->dot.r.p1 = p1;
+        f->dot.r.p2 = p2;
+        if(f->rasp){
+                telldot(f);
+                outTsl(Hmoveto, f->tag, f->dot.r.p1);
+        }
+}
+
+void
+telldot(File *f)
+{
+        if(f->rasp == 0)
+                panic("telldot");
+        if(f->dot.r.p1==f->tdot.p1 && f->dot.r.p2==f->tdot.p2)
+                return;
+        outTsll(Hsetdot, f->tag, f->dot.r.p1, f->dot.r.p2);
+        f->tdot = f->dot.r;
+}
+
+void
+tellpat(void)
+{
+        outTS(Hsetpat, &lastpat);
+        patset = FALSE;
+}
+
+#define        CHARSHIFT        128
+
+void
+lookorigin(File *f, Posn p0, Posn ls)
+{
+        int nl, nc, c;
+        Posn p, oldp0;
+
+        if(p0 > f->_.nc)
+                p0 = f->_.nc;
+        oldp0 = p0;
+        p = p0;
+        for(nl=nc=c=0; c!=-1 && nl=CHARSHIFT/2)
+                        p0-=CHARSHIFT/2;
+                else
+                        p0 = 0;
+        }else
+                p0 = oldp0;
+        outTsl(Horigin, f->tag, p0);
+}
+
+int
+alnum(int c)
+{
+        /*
+         * Hard to get absolutely right.  Use what we know about ASCII
+         * and assume anything above the Latin control characters is
+         * potentially an alphanumeric.
+         */
+        if(c<=' ')
+                return 0;
+        if(0x7F<=c && c<=0xA0)
+                return 0;
+        if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+                return 0;
+        return 1;
+}
+
+int
+clickmatch(File *f, int cl, int cr, int dir, Posn *p)
+{
+        int c;
+        int nest = 1;
+
+        for(;;){
+                if(dir > 0){
+                        if(*p >= f->_.nc)
+                                break;
+                        c = filereadc(f, (*p)++);
+                }else{
+                        if(*p == 0)
+                                break;
+                        c = filereadc(f, --(*p));
+                }
+                if(c == cr){
+                        if(--nest==0)
+                                return 1;
+                }else if(c == cl)
+                        nest++;
+        }
+        return cl=='\n' && nest==1;
+}
+
+Rune*
+strrune(Rune *s, Rune c)
+{
+        Rune c1;
+
+        if(c == 0) {
+                while(*s++)
+                        ;
+                return s-1;
+        }
+
+        while(c1 = *s++)
+                if(c1 == c)
+                        return s-1;
+        return 0;
+}
+
+void
+doubleclick(File *f, Posn p1)
+{
+        int c, i;
+        Rune *r, *l;
+        Posn p;
+
+        if(p1 > f->_.nc)
+                return;
+        f->dot.r.p1 = f->dot.r.p2 = p1;
+        for(i=0; left[i]; i++){
+                l = left[i];
+                r = right[i];
+                /* try left match */
+                p = p1;
+                if(p1 == 0)
+                        c = '\n';
+                else
+                        c = filereadc(f, p - 1);
+                if(strrune(l, c)){
+                        if(clickmatch(f, c, r[strrune(l, c)-l], 1, &p)){
+                                f->dot.r.p1 = p1;
+                                f->dot.r.p2 = p-(c!='\n');
+                        }
+                        return;
+                }
+                /* try right match */
+                p = p1;
+                if(p1 == f->_.nc)
+                        c = '\n';
+                else
+                        c = filereadc(f, p);
+                if(strrune(r, c)){
+                        if(clickmatch(f, c, l[strrune(r, c)-r], -1, &p)){
+                                f->dot.r.p1 = p;
+                                if(c!='\n' || p!=0 || filereadc(f, 0)=='\n')
+                                        f->dot.r.p1++;
+                                f->dot.r.p2 = p1+(p1_.nc && c=='\n');
+                        }
+                        return;
+                }
+        }
+        /* try filling out word to right */
+        p = p1;
+        while(p < f->_.nc && alnum(filereadc(f, p++)))
+                f->dot.r.p2++;
+        /* try filling out word to left */
+        p = p1;
+        while(--p >= 0 && alnum(filereadc(f, p)))
+                f->dot.r.p1--;
+}
+
diff --git a/src/cmd/sam/multi.c b/src/cmd/sam/multi.c
t@@ -0,0 +1,123 @@
+#include "sam.h"
+
+List        file;
+ushort        tag;
+
+File *
+newfile(void)
+{
+        File *f;
+
+        f = fileopen();
+        inslist(&file, 0, (long)f);
+        f->tag = tag++;
+        if(downloaded)
+                outTs(Hnewname, f->tag);
+        /* already sorted; file name is "" */
+        return f;
+}
+
+int
+whichmenu(File *f)
+{
+        int i;
+
+        for(i=0; itag);
+        dellist(&file, w);
+        fileclose(f);
+}
+
+void
+fullname(String *name)
+{
+        if(name->n > 0 && name->s[0]!='/' && name->s[0]!=0)
+                Strinsert(name, &curwd, (Posn)0);
+}
+
+void
+fixname(String *name)
+{
+        String *t;
+        char *s;
+
+        fullname(name);
+        s = Strtoc(name);
+        if(strlen(s) > 0)
+                s = cleanname(s);
+        t = tmpcstr(s);
+        Strduplstr(name, t);
+        free(s);
+        freetmpstr(t);
+
+        if(Strispre(&curwd, name))
+                Strdelete(name, 0, curwd.n);
+}
+
+void
+sortname(File *f)
+{
+        int i, cmp, w;
+        int dupwarned;
+
+        w = whichmenu(f);
+        dupwarned = FALSE;
+        dellist(&file, w);
+        if(f == cmd)
+                i = 0;
+        else{
+                for(i=0; iname, &file.filepptr[i]->name);
+                        if(cmp==0 && !dupwarned){
+                                dupwarned = TRUE;
+                                warn_S(Wdupname, &f->name);
+                        }else if(cmp<0 && (i>0 || cmd==0))
+                                break;
+                }
+        }
+        inslist(&file, i, (long)f);
+        if(downloaded)
+                outTsS(Hmovname, f->tag, &f->name);
+}
+
+void
+state(File *f, int cleandirty)
+{
+        if(f == cmd)
+                return;
+        f->unread = FALSE;
+        if(downloaded && whichmenu(f)>=0){        /* else flist or menu */
+                if(f->mod && cleandirty!=Dirty)
+                        outTs(Hclean, f->tag);
+                else if(!f->mod && cleandirty==Dirty)
+                        outTs(Hdirty, f->tag);
+        }
+        if(cleandirty == Clean)
+                f->mod = FALSE;
+        else
+                f->mod = TRUE;
+}
+
+File *
+lookfile(String *s)
+{
+        int i;
+
+        for(i=0; iname, s) == 0)
+                        return file.filepptr[i];
+        return 0;
+}
diff --git a/src/cmd/sam/parse.h b/src/cmd/sam/parse.h
t@@ -0,0 +1,68 @@
+typedef struct Addr Addr;
+typedef struct Cmd Cmd;
+struct Addr
+{
+        char        type;        /* # (char addr), l (line addr), / ? . $ + - , ; */
+        union{
+                String        *re;
+                Addr        *aleft;                /* left side of , and ; */
+        } g;
+        Posn        num;
+        Addr        *next;                        /* or right side of , and ; */
+};
+
+#define        are        g.re
+#define        left        g.aleft
+
+struct Cmd
+{
+        Addr        *addr;                        /* address (range of text) */
+        String        *re;                        /* regular expression for e.g. 'x' */
+        union{
+                Cmd        *cmd;                /* target of x, g, {, etc. */
+                String        *text;                /* text of a, c, i; rhs of s */
+                Addr        *addr;                /* address for m, t */
+        } g;
+        Cmd        *next;                        /* pointer to next element in {} */
+        short        num;
+        ushort        flag;                        /* whatever */
+        ushort        cmdc;                        /* command character; 'x' etc. */
+};
+
+#define        ccmd        g.cmd
+#define        ctext        g.text
+#define        caddr        g.addr
+
+extern struct cmdtab{
+        ushort        cmdc;                /* command character */
+        uchar        text;                /* takes a textual argument? */
+        uchar        regexp;                /* takes a regular expression? */
+        uchar        addr;                /* takes an address (m or t)? */
+        uchar        defcmd;                /* default command; 0==>none */
+        uchar        defaddr;        /* default address */
+        uchar        count;                /* takes a count e.g. s2/// */
+        char        *token;                /* takes text terminated by one of these */
+        int        (*fn)(File*, Cmd*);        /* function to call with parse tree */
+}cmdtab[];
+
+enum Defaddr{        /* default addresses */
+        aNo,
+        aDot,
+        aAll,
+};
+
+int        nl_cmd(File*, Cmd*), a_cmd(File*, Cmd*), b_cmd(File*, Cmd*);
+int        c_cmd(File*, Cmd*), cd_cmd(File*, Cmd*), d_cmd(File*, Cmd*);
+int        D_cmd(File*, Cmd*), e_cmd(File*, Cmd*);
+int        f_cmd(File*, Cmd*), g_cmd(File*, Cmd*), i_cmd(File*, Cmd*);
+int        k_cmd(File*, Cmd*), m_cmd(File*, Cmd*), n_cmd(File*, Cmd*);
+int        p_cmd(File*, Cmd*), q_cmd(File*, Cmd*);
+int        s_cmd(File*, Cmd*), u_cmd(File*, Cmd*), w_cmd(File*, Cmd*);
+int        x_cmd(File*, Cmd*), X_cmd(File*, Cmd*), plan9_cmd(File*, Cmd*);
+int        eq_cmd(File*, Cmd*);
+
+
+String        *getregexp(int);
+Addr        *newaddr(void);
+Address        address(Addr*, Address, int);
+int        cmdexec(File*, Cmd*);
diff --git a/src/cmd/sam/plan9.c b/src/cmd/sam/plan9.c
t@@ -0,0 +1,185 @@
+#include "sam.h"
+
+Rune        samname[] = L"~~sam~~";
+
+Rune *left[]= {
+        L"{[(<«",
+        L"\n",
+        L"'\"`",
+        0
+};
+Rune *right[]= {
+        L"}])>»",
+        L"\n",
+        L"'\"`",
+        0
+};
+
+char        RSAM[] = "sam";
+char        SAMTERM[] = "/bin/aux/samterm";
+char        HOME[] = "home";
+char        TMPDIR[] = "/tmp";
+char        SH[] = "rc";
+char        SHPATH[] = "/bin/rc";
+char        RX[] = "rx";
+char        RXPATH[] = "/bin/rx";
+char        SAMSAVECMD[] = "/bin/rc\n/sys/lib/samsave";
+
+void
+dprint(char *z, ...)
+{
+        char buf[BLOCKSIZE];
+        va_list arg;
+
+        va_start(arg, z);
+        vseprint(buf, &buf[BLOCKSIZE], z, arg);
+        va_end(arg);
+        termwrite(buf);
+}
+
+void
+print_ss(char *s, String *a, String *b)
+{
+        dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
+}
+
+void
+print_s(char *s, String *a)
+{
+        dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
+}
+
+char*
+getuser(void)
+{
+        static char user[64];
+        int fd;
+
+        if(user[0] == 0){
+                fd = open("/dev/user", 0);
+                if(fd<0 || read(fd, user, sizeof user-1)<=0)
+                        strcpy(user, "none");
+                close(fd);
+        }
+        return user;
+}
+
+int
+statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+        Dir *dirb;
+
+        dirb = dirstat(name);
+        if(dirb == nil)
+                return -1;
+        if(dev)
+                *dev = dirb->type|(dirb->dev<<16);
+        if(id)
+                *id = dirb->qid.path;
+        if(time)
+                *time = dirb->mtime;
+        if(length)
+                *length = dirb->length;
+        if(appendonly)
+                *appendonly = dirb->mode & DMAPPEND;
+        free(dirb);
+        return 1;
+}
+
+int
+statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+        Dir *dirb;
+
+        dirb = dirfstat(fd);
+        if(dirb == nil)
+                return -1;
+        if(dev)
+                *dev = dirb->type|(dirb->dev<<16);
+        if(id)
+                *id = dirb->qid.path;
+        if(time)
+                *time = dirb->mtime;
+        if(length)
+                *length = dirb->length;
+        if(appendonly)
+                *appendonly = dirb->mode & DMAPPEND;
+        free(dirb);
+        return 1;
+}
+
+void
+notifyf(void *a, char *s)
+{
+        USED(a);
+        if(bpipeok && strcmp(s, "sys: write on closed pipe") == 0)
+                noted(NCONT);
+        if(strcmp(s, "interrupt") == 0)
+                noted(NCONT);
+        panicking = 1;
+        rescue();
+        noted(NDFLT);
+}
+
+int
+newtmp(int num)
+{
+        int i, fd;
+        static char        tempnam[30];
+
+        i = getpid();
+        do
+                snprint(tempnam, sizeof tempnam, "%s/%d%.4s%dsam", TMPDIR, num, getuser(), i++);
+        while(access(tempnam, 0) == 0);
+        fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
+        if(fd < 0){
+                remove(tempnam);
+                fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
+        }
+        return fd;
+}
+
+int
+waitfor(int pid)
+{
+        int msg;
+        Waitmsg *w;
+
+        while((w = wait()) != nil){
+                if(w->pid != pid){
+                        free(w);
+                        continue;
+                }
+                msg = (w->msg[0] != '\0');
+                free(w);
+                return msg;
+        }
+        return -1;
+}
+
+void
+samerr(char *buf)
+{
+        sprint(buf, "%s/sam.err", TMPDIR);
+}
+
+void*
+emalloc(ulong n)
+{
+        void *p;
+
+        p = malloc(n);
+        if(p == 0)
+                panic("malloc fails");
+        memset(p, 0, n);
+        return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+        p = realloc(p, n);
+        if(p == 0)
+                panic("realloc fails");
+        return p;
+}
diff --git a/src/cmd/sam/plumb.c b/src/cmd/sam/plumb.c
t@@ -0,0 +1,9 @@
+#include 
+#include "plumb.h"
+
+/* XXX - Can we do better than this? */
+char *cleanname(char *s) { return s; }
+char *plumbunpackattr(char *cbuf) {  return 0; }
+char *plumbpack(Plumbmsg *pm, int *i) { return 0; }
+int plumbfree(Plumbmsg *pm) { return 0; }
+
diff --git a/src/cmd/sam/rasp.c b/src/cmd/sam/rasp.c
t@@ -0,0 +1,325 @@
+#include "sam.h"
+/*
+ * GROWDATASIZE must be big enough that all errors go out as Hgrowdata's,
+ * so they will be scrolled into visibility in the ~~sam~~ window (yuck!).
+ */
+#define        GROWDATASIZE        50        /* if size is > this, send data with grow */
+
+void        rcut(List*, Posn, Posn);
+int        rterm(List*, Posn);
+void        rgrow(List*, Posn, Posn);
+
+static        Posn        growpos;
+static        Posn        grown;
+static        Posn        shrinkpos;
+static        Posn        shrunk;
+
+/*
+ * rasp routines inform the terminal of changes to the file.
+ *
+ * a rasp is a list of spans within the file, and an indication
+ * of whether the terminal knows about the span.
+ *
+ * optimize by coalescing multiple updates to the same span
+ * if it is not known by the terminal.
+ *
+ * other possible optimizations: flush terminal's rasp by cut everything,
+ * insert everything if rasp gets too large.
+ */
+
+/*
+ * only called for initial load of file
+ */
+void
+raspload(File *f)
+{
+        if(f->rasp == nil)
+                return;
+        grown = f->_.nc;
+        growpos = 0;
+        if(f->_.nc)
+                rgrow(f->rasp, 0, f->_.nc);
+        raspdone(f, 1);
+}
+
+void
+raspstart(File *f)
+{
+        if(f->rasp == nil)
+                return;
+        grown = 0;
+        shrunk = 0;
+        noflush = 1;
+}
+
+void
+raspdone(File *f, int toterm)
+{
+        if(f->dot.r.p1 > f->_.nc)
+                f->dot.r.p1 = f->_.nc;
+        if(f->dot.r.p2 > f->_.nc)
+                f->dot.r.p2 = f->_.nc;
+        if(f->mark.p1 > f->_.nc)
+                f->mark.p1 = f->_.nc;
+        if(f->mark.p2 > f->_.nc)
+                f->mark.p2 = f->_.nc;
+        if(f->rasp == nil)
+                return;
+        if(grown)
+                outTsll(Hgrow, f->tag, growpos, grown);
+        else if(shrunk)
+                outTsll(Hcut, f->tag, shrinkpos, shrunk);
+        if(toterm)
+                outTs(Hcheck0, f->tag);
+        outflush();
+        noflush = 0;
+        if(f == cmd){
+                cmdpt += cmdptadv;
+                cmdptadv = 0;
+        }
+}
+
+void
+raspdelete(File *f, uint p1, uint p2, int toterm)
+{
+        long n;
+
+        n = p2 - p1;
+        if(n == 0)
+                return;
+
+        if(p2 <= f->dot.r.p1){
+                f->dot.r.p1 -= n;
+                f->dot.r.p2 -= n;
+        }
+        if(p2 <= f->mark.p1){
+                f->mark.p1 -= n;
+                f->mark.p2 -= n;
+        }
+
+        if(f->rasp == nil)
+                return;
+
+        if(f==cmd && p1tag, growpos, grown);
+                        grown = 0;
+                }else if(shrunk && shrinkpos!=p1 && shrinkpos!=p2){
+                        outTsll(Hcut, f->tag, shrinkpos, shrunk);
+                        shrunk = 0;
+                }
+                if(!shrunk || shrinkpos==p2)
+                        shrinkpos = p1;
+                shrunk += n;
+        }
+        rcut(f->rasp, p1, p2);
+}
+
+void
+raspinsert(File *f, uint p1, Rune *buf, uint n, int toterm)
+{
+        Range r;
+
+        if(n == 0)
+                return;
+
+        if(p1 < f->dot.r.p1){
+                f->dot.r.p1 += n;
+                f->dot.r.p2 += n;
+        }
+        if(p1 < f->mark.p1){
+                f->mark.p1 += n;
+                f->mark.p2 += n;
+        }
+
+
+        if(f->rasp == nil)
+                return;
+        if(f==cmd && p1tag, shrinkpos, shrunk);
+                        shrunk = 0;
+                }
+                if(n>GROWDATASIZE || !rterm(f->rasp, p1)){
+                        rgrow(f->rasp, p1, n);
+                        if(grown && growpos+grown!=p1 && growpos!=p1){
+                                outTsll(Hgrow, f->tag, growpos, grown);
+                                grown = 0;
+                        }
+                        if(!grown)
+                                growpos = p1;
+                        grown += n;
+                }else{
+                        if(grown){
+                                outTsll(Hgrow, f->tag, growpos, grown);
+                                grown = 0;
+                        }
+                        rgrow(f->rasp, p1, n);
+                        r = rdata(f->rasp, p1, n);
+                        if(r.p1!=p1 || r.p2!=p1+n)
+                                panic("rdata in toterminal");
+                        outTsllS(Hgrowdata, f->tag, p1, n, tmprstr(buf, n));
+                }
+        }else{
+                rgrow(f->rasp, p1, n);
+                r = rdata(f->rasp, p1, n);
+                if(r.p1!=p1 || r.p2!=p1+n)
+                        panic("rdata in toterminal");
+        }
+}
+
+#define        M        0x80000000L
+#define        P(i)        r->longptr[i]
+#define        T(i)        (P(i)&M)        /* in terminal */
+#define        L(i)        (P(i)&~M)        /* length of this piece */
+
+void
+rcut(List *r, Posn p1, Posn p2)
+{
+        Posn p, x;
+        int i;
+
+        if(p1 == p2)
+                panic("rcut 0");
+        for(p=0,i=0; inused && p+L(i)<=p1; p+=L(i++))
+                ;
+        if(i == r->nused)
+                panic("rcut 1");
+        if(p < p1){        /* chop this piece */
+                if(p+L(i) < p2){
+                        x = p1-p;
+                        p += L(i);
+                }else{
+                        x = L(i)-(p2-p1);
+                        p = p2;
+                }
+                if(T(i))
+                        P(i) = x|M;
+                else
+                        P(i) = x;
+                i++;
+        }
+        while(inused && p+L(i)<=p2){
+                p += L(i);
+                dellist(r, i);
+        }
+        if(p < p2){
+                if(i == r->nused)
+                        panic("rcut 2");
+                x = L(i)-(p2-p);
+                if(T(i))
+                        P(i) = x|M;
+                else
+                        P(i) = x;
+        }
+        /* can we merge i and i-1 ? */
+        if(i>0 && inused && T(i-1)==T(i)){
+                x = L(i-1)+L(i);
+                dellist(r, i--);
+                if(T(i))
+                        P(i)=x|M;
+                else
+                        P(i)=x;
+        }
+}
+
+void
+rgrow(List *r, Posn p1, Posn n)
+{
+        Posn p;
+        int i;
+
+        if(n == 0)
+                panic("rgrow 0");
+        for(p=0,i=0; inused && p+L(i)<=p1; p+=L(i++))
+                ;
+        if(i == r->nused){        /* stick on end of file */
+                if(p!=p1)
+                        panic("rgrow 1");
+                if(i>0 && !T(i-1))
+                        P(i-1)+=n;
+                else
+                        inslist(r, i, n);
+        }else if(!T(i))                /* goes in this empty piece */
+                P(i)+=n;
+        else if(p==p1 && i>0 && !T(i-1))        /* special case; simplifies life */
+                P(i-1)+=n;
+        else if(p==p1)
+                inslist(r, i, n);
+        else{                        /* must break piece in terminal */
+                inslist(r, i+1, (L(i)-(p1-p))|M);
+                inslist(r, i+1, n);
+                P(i) = (p1-p)|M;
+        }
+}
+
+int
+rterm(List *r, Posn p1)
+{
+        Posn p;
+        int i;
+
+        for(p = 0,i = 0; inused && p+L(i)<=p1; p+=L(i++))
+                ;
+        if(i==r->nused && (i==0 || !T(i-1)))
+                return 0;
+        return T(i);
+}
+
+Range
+rdata(List *r, Posn p1, Posn n)
+{
+        Posn p;
+        int i;
+        Range rg;
+
+        if(n==0)
+                panic("rdata 0");
+        for(p = 0,i = 0; inused && p+L(i)<=p1; p+=L(i++))
+                ;
+        if(i==r->nused)
+                panic("rdata 1");
+        if(T(i)){
+                n-=L(i)-(p1-p);
+                if(n<=0){
+                        rg.p1 = rg.p2 = p1;
+                        return rg;
+                }
+                p+=L(i++);
+                p1 = p;
+        }
+        if(T(i) || i==r->nused)
+                panic("rdata 2");
+        if(p+L(i)nused-1 && T(i+1)){
+                P(i)=(n+=L(i+1))|M;
+                dellist(r, i+1);
+        }
+        if(i>0 && T(i-1)){
+                P(i)=(n+L(i-1))|M;
+                dellist(r, i-1);
+        }
+        return rg;
+}
diff --git a/src/cmd/sam/regexp.c b/src/cmd/sam/regexp.c
t@@ -0,0 +1,801 @@
+#include "sam.h"
+
+Rangeset        sel;
+String                lastregexp;
+/*
+ * Machine Information
+ */
+typedef struct Inst Inst;
+
+struct Inst
+{
+        long        type;        /* < 0x10000 ==> literal, otherwise action */
+        union {
+                int rsid;
+                int rsubid;
+                int class;
+                struct Inst *rother;
+                struct Inst *rright;
+        } r;
+        union{
+                struct Inst *lleft;
+                struct Inst *lnext;
+        } l;
+};
+#define        sid        r.rsid
+#define        subid        r.rsubid
+#define        rclass        r.class
+#define        other        r.rother
+#define        right        r.rright
+#define        left        l.lleft
+#define        next        l.lnext
+
+#define        NPROG        1024
+Inst        program[NPROG];
+Inst        *progp;
+Inst        *startinst;        /* First inst. of program; might not be program[0] */
+Inst        *bstartinst;        /* same for backwards machine */
+
+typedef struct Ilist Ilist;
+struct Ilist
+{
+        Inst        *inst;                /* Instruction of the thread */
+        Rangeset se;
+        Posn        startp;                /* first char of match */
+};
+
+#define        NLIST        128
+
+Ilist        *tl, *nl;        /* This list, next list */
+Ilist        list[2][NLIST];
+static        Rangeset sempty;
+
+/*
+ * Actions and Tokens
+ *
+ *        0x100xx are operators, value == precedence
+ *        0x200xx are tokens, i.e. operands for operators
+ */
+#define        OPERATOR        0x10000        /* Bitmask of all operators */
+#define        START                0x10000        /* Start, used for marker on stack */
+#define        RBRA                0x10001        /* Right bracket, ) */
+#define        LBRA                0x10002        /* Left bracket, ( */
+#define        OR                0x10003        /* Alternation, | */
+#define        CAT                0x10004        /* Concatentation, implicit operator */
+#define        STAR                0x10005        /* Closure, * */
+#define        PLUS                0x10006        /* a+ == aa* */
+#define        QUEST                0x10007        /* a? == a|nothing, i.e. 0 or 1 a's */
+#define        ANY                0x20000        /* Any character but newline, . */
+#define        NOP                0x20001        /* No operation, internal use only */
+#define        BOL                0x20002        /* Beginning of line, ^ */
+#define        EOL                0x20003        /* End of line, $ */
+#define        CCLASS                0x20004        /* Character class, [] */
+#define        NCCLASS                0x20005        /* Negated character class, [^] */
+#define        END                0x20077        /* Terminate: match found */
+
+#define        ISATOR                0x10000
+#define        ISAND                0x20000
+
+/*
+ * Parser Information
+ */
+typedef struct Node Node;
+struct Node
+{
+        Inst        *first;
+        Inst        *last;
+};
+
+#define        NSTACK        20
+Node        andstack[NSTACK];
+Node        *andp;
+int        atorstack[NSTACK];
+int        *atorp;
+int        lastwasand;        /* Last token was operand */
+int        cursubid;
+int        subidstack[NSTACK];
+int        *subidp;
+int        backwards;
+int        nbra;
+Rune        *exprp;                /* pointer to next character in source expression */
+#define        DCLASS        10        /* allocation increment */
+int        nclass;                /* number active */
+int        Nclass;                /* high water mark */
+Rune        **class;
+int        negateclass;
+
+void        addinst(Ilist *l, Inst *inst, Rangeset *sep);
+void        newmatch(Rangeset*);
+void        bnewmatch(Rangeset*);
+void        pushand(Inst*, Inst*);
+void        pushator(int);
+Node        *popand(int);
+int        popator(void);
+void        startlex(Rune*);
+int        lex(void);
+void        operator(int);
+void        operand(int);
+void        evaluntil(int);
+void        optimize(Inst*);
+void        bldcclass(void);
+
+void
+regerror(Err e)
+{
+        Strzero(&lastregexp);
+        error(e);
+}
+
+void
+regerror_c(Err e, int c)
+{
+        Strzero(&lastregexp);
+        error_c(e, c);
+}
+
+Inst *
+newinst(int t)
+{
+        if(progp >= &program[NPROG])
+                regerror(Etoolong);
+        progp->type = t;
+        progp->left = 0;
+        progp->right = 0;
+        return progp++;
+}
+
+Inst *
+realcompile(Rune *s)
+{
+        int token;
+
+        startlex(s);
+        atorp = atorstack;
+        andp = andstack;
+        subidp = subidstack;
+        cursubid = 0;
+        lastwasand = FALSE;
+        /* Start with a low priority operator to prime parser */
+        pushator(START-1);
+        while((token=lex()) != END){
+                if((token&ISATOR) == OPERATOR)
+                        operator(token);
+                else
+                        operand(token);
+        }
+        /* Close with a low priority operator */
+        evaluntil(START);
+        /* Force END */
+        operand(END);
+        evaluntil(START);
+        if(nbra)
+                regerror(Eleftpar);
+        --andp;        /* points to first and only operand */
+        return andp->first;
+}
+
+void
+compile(String *s)
+{
+        int i;
+        Inst *oprogp;
+
+        if(Strcmp(s, &lastregexp)==0)
+                return;
+        for(i=0; is);
+        optimize(program);
+        oprogp = progp;
+        backwards = TRUE;
+        bstartinst = realcompile(s->s);
+        optimize(oprogp);
+        Strduplstr(&lastregexp, s);
+}
+
+void
+operand(int t)
+{
+        Inst *i;
+        if(lastwasand)
+                operator(CAT);        /* catenate is implicit */
+        i = newinst(t);
+        if(t == CCLASS){
+                if(negateclass)
+                        i->type = NCCLASS;        /* UGH */
+                i->rclass = nclass-1;                /* UGH */
+        }
+        pushand(i, i);
+        lastwasand = TRUE;
+}
+
+void
+operator(int t)
+{
+        if(t==RBRA && --nbra<0)
+                regerror(Erightpar);
+        if(t==LBRA){
+/*
+ *                if(++cursubid >= NSUBEXP)
+ *                        regerror(Esubexp);
+ */
+                cursubid++;        /* silently ignored */
+                nbra++;
+                if(lastwasand)
+                        operator(CAT);
+        }else
+                evaluntil(t);
+        if(t!=RBRA)
+                pushator(t);
+        lastwasand = FALSE;
+        if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
+                lastwasand = TRUE;        /* these look like operands */
+}
+
+void
+cant(char *s)
+{
+        char buf[100];
+
+        sprint(buf, "regexp: can't happen: %s", s);
+        panic(buf);
+}
+
+void
+pushand(Inst *f, Inst *l)
+{
+        if(andp >= &andstack[NSTACK])
+                cant("operand stack overflow");
+        andp->first = f;
+        andp->last = l;
+        andp++;
+}
+
+void
+pushator(int t)
+{
+        if(atorp >= &atorstack[NSTACK])
+                cant("operator stack overflow");
+        *atorp++=t;
+        if(cursubid >= NSUBEXP)
+                *subidp++= -1;
+        else
+                *subidp++=cursubid;
+}
+
+Node *
+popand(int op)
+{
+        if(andp <= &andstack[0])
+                if(op)
+                        regerror_c(Emissop, op);
+                else
+                        regerror(Ebadregexp);
+        return --andp;
+}
+
+int
+popator(void)
+{
+        if(atorp <= &atorstack[0])
+                cant("operator stack underflow");
+        --subidp;
+        return *--atorp;
+}
+
+void
+evaluntil(int pri)
+{
+        Node *op1, *op2, *t;
+        Inst *inst1, *inst2;
+
+        while(pri==RBRA || atorp[-1]>=pri){
+                switch(popator()){
+                case LBRA:
+                        op1 = popand('(');
+                        inst2 = newinst(RBRA);
+                        inst2->subid = *subidp;
+                        op1->last->next = inst2;
+                        inst1 = newinst(LBRA);
+                        inst1->subid = *subidp;
+                        inst1->next = op1->first;
+                        pushand(inst1, inst2);
+                        return;                /* must have been RBRA */
+                default:
+                        panic("unknown regexp operator");
+                        break;
+                case OR:
+                        op2 = popand('|');
+                        op1 = popand('|');
+                        inst2 = newinst(NOP);
+                        op2->last->next = inst2;
+                        op1->last->next = inst2;
+                        inst1 = newinst(OR);
+                        inst1->right = op1->first;
+                        inst1->left = op2->first;
+                        pushand(inst1, inst2);
+                        break;
+                case CAT:
+                        op2 = popand(0);
+                        op1 = popand(0);
+                        if(backwards && op2->first->type!=END)
+                                t = op1, op1 = op2, op2 = t;
+                        op1->last->next = op2->first;
+                        pushand(op1->first, op2->last);
+                        break;
+                case STAR:
+                        op2 = popand('*');
+                        inst1 = newinst(OR);
+                        op2->last->next = inst1;
+                        inst1->right = op2->first;
+                        pushand(inst1, inst1);
+                        break;
+                case PLUS:
+                        op2 = popand('+');
+                        inst1 = newinst(OR);
+                        op2->last->next = inst1;
+                        inst1->right = op2->first;
+                        pushand(op2->first, inst1);
+                        break;
+                case QUEST:
+                        op2 = popand('?');
+                        inst1 = newinst(OR);
+                        inst2 = newinst(NOP);
+                        inst1->left = inst2;
+                        inst1->right = op2->first;
+                        op2->last->next = inst2;
+                        pushand(inst1, inst2);
+                        break;
+                }
+        }
+}
+
+
+void
+optimize(Inst *start)
+{
+        Inst *inst, *target;
+
+        for(inst=start; inst->type!=END; inst++){
+                target = inst->next;
+                while(target->type == NOP)
+                        target = target->next;
+                inst->next = target;
+        }
+}
+
+#ifdef        DEBUG
+void
+dumpstack(void){
+        Node *stk;
+        int *ip;
+
+        dprint("operators\n");
+        for(ip = atorstack; ipfirst->type, stk->last->type);
+}
+void
+dump(void){
+        Inst *l;
+
+        l = program;
+        do{
+                dprint("%d:\t0%o\t%d\t%d\n", l-program, l->type,
+                        l->left-program, l->right-program);
+        }while(l++->type);
+}
+#endif
+
+void
+startlex(Rune *s)
+{
+        exprp = s;
+        nbra = 0;
+}
+
+
+int
+lex(void){
+        int c= *exprp++;
+
+        switch(c){
+        case '\\':
+                if(*exprp)
+                        if((c= *exprp++)=='n')
+                                c='\n';
+                break;
+        case 0:
+                c = END;
+                --exprp;        /* In case we come here again */
+                break;
+        case '*':
+                c = STAR;
+                break;
+        case '?':
+                c = QUEST;
+                break;
+        case '+':
+                c = PLUS;
+                break;
+        case '|':
+                c = OR;
+                break;
+        case '.':
+                c = ANY;
+                break;
+        case '(':
+                c = LBRA;
+                break;
+        case ')':
+                c = RBRA;
+                break;
+        case '^':
+                c = BOL;
+                break;
+        case '$':
+                c = EOL;
+                break;
+        case '[':
+                c = CCLASS;
+                bldcclass();
+                break;
+        }
+        return c;
+}
+
+long
+nextrec(void){
+        if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
+                regerror(Ebadclass);
+        if(exprp[0] == '\\'){
+                exprp++;
+                if(*exprp=='n'){
+                        exprp++;
+                        return '\n';
+                }
+                return *exprp++|0x10000;
+        }
+        return *exprp++;
+}
+
+void
+bldcclass(void)
+{
+        long c1, c2, n, na;
+        Rune *classp;
+
+        classp = emalloc(DCLASS*RUNESIZE);
+        n = 0;
+        na = DCLASS;
+        /* we have already seen the '[' */
+        if(*exprp == '^'){
+                classp[n++] = '\n';        /* don't match newline in negate case */
+                negateclass = TRUE;
+                exprp++;
+        }else
+                negateclass = FALSE;
+        while((c1 = nextrec()) != ']'){
+                if(c1 == '-'){
+    Error:
+                        free(classp);
+                        regerror(Ebadclass);
+                }
+                if(n+4 >= na){                /* 3 runes plus NUL */
+                        na += DCLASS;
+                        classp = erealloc(classp, na*RUNESIZE);
+                }
+                if(*exprp == '-'){
+                        exprp++;        /* eat '-' */
+                        if((c2 = nextrec()) == ']')
+                                goto Error;
+                        classp[n+0] = 0xFFFF;
+                        classp[n+1] = c1;
+                        classp[n+2] = c2;
+                        n += 3;
+                }else
+                        classp[n++] = c1;
+        }
+        classp[n] = 0;
+        if(nclass == Nclass){
+                Nclass += DCLASS;
+                class = erealloc(class, Nclass*sizeof(Rune*));
+        }
+        class[nclass++] = classp;
+}
+
+int
+classmatch(int classno, int c, int negate)
+{
+        Rune *p;
+
+        p = class[classno];
+        while(*p){
+                if(*p == 0xFFFF){
+                        if(p[1]<=c && c<=p[2])
+                                return !negate;
+                        p += 3;
+                }else if(*p++ == c)
+                        return !negate;
+        }
+        return negate;
+}
+
+/*
+ * Note optimization in addinst:
+ *         *l must be pending when addinst called; if *l has been looked
+ *                at already, the optimization is a bug.
+ */
+void
+addinst(Ilist *l, Inst *inst, Rangeset *sep)
+{
+        Ilist *p;
+
+        for(p = l; p->inst; p++){
+                if(p->inst==inst){
+                        if((sep)->p[0].p1 < p->se.p[0].p1)
+                                p->se= *sep;        /* this would be bug */
+                        return;        /* It's already there */
+                }
+        }
+        p->inst = inst;
+        p->se= *sep;
+        (p+1)->inst = 0;
+}
+
+int
+execute(File *f, Posn startp, Posn eof)
+{
+        int flag = 0;
+        Inst *inst;
+        Ilist *tlp;
+        Posn p = startp;
+        int nnl = 0, ntl;
+        int c;
+        int wrapped = 0;
+        int startchar = startinst->typetype : 0;
+
+        list[0][0].inst = list[1][0].inst = 0;
+        sel.p[0].p1 = -1;
+        /* Execute machine once for each character */
+        for(;;p++){
+        doloop:
+                c = filereadc(f, p);
+                if(p>=eof || c<0){
+                        switch(wrapped++){
+                        case 0:                /* let loop run one more click */
+                        case 2:
+                                break;
+                        case 1:                /* expired; wrap to beginning */
+                                if(sel.p[0].p1>=0 || eof!=INFINITY)
+                                        goto Return;
+                                list[0][0].inst = list[1][0].inst = 0;
+                                p = 0;
+                                goto doloop;
+                        default:
+                                goto Return;
+                        }
+                }else if(((wrapped && p>=startp) || sel.p[0].p1>0) && nnl==0)
+                        break;
+                /* fast check for first char */
+                if(startchar && nnl==0 && c!=startchar)
+                        continue;
+                tl = list[flag];
+                nl = list[flag^=1];
+                nl->inst = 0;
+                ntl = nnl;
+                nnl = 0;
+                if(sel.p[0].p1<0 && (!wrapped || p= NLIST)
+        Overflow:
+                                error(Eoverflow);
+                        sempty.p[0].p1 = p;
+                        addinst(tl, startinst, &sempty);
+                }
+                /* Execute machine until this list is empty */
+                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
+        Switchstmt:
+                        switch(inst->type){
+                        default:        /* regular character */
+                                if(inst->type==c){
+        Addinst:
+                                        if(++nnl >= NLIST)
+                                                goto Overflow;
+                                        addinst(nl, inst->next, &tlp->se);
+                                }
+                                break;
+                        case LBRA:
+                                if(inst->subid>=0)
+                                        tlp->se.p[inst->subid].p1 = p;
+                                inst = inst->next;
+                                goto Switchstmt;
+                        case RBRA:
+                                if(inst->subid>=0)
+                                        tlp->se.p[inst->subid].p2 = p;
+                                inst = inst->next;
+                                goto Switchstmt;
+                        case ANY:
+                                if(c!='\n')
+                                        goto Addinst;
+                                break;
+                        case BOL:
+                                if(p==0 || filereadc(f, p - 1)=='\n'){
+        Step:
+                                        inst = inst->next;
+                                        goto Switchstmt;
+                                }
+                                break;
+                        case EOL:
+                                if(c == '\n')
+                                        goto Step;
+                                break;
+                        case CCLASS:
+                                if(c>=0 && classmatch(inst->rclass, c, 0))
+                                        goto Addinst;
+                                break;
+                        case NCCLASS:
+                                if(c>=0 && classmatch(inst->rclass, c, 1))
+                                        goto Addinst;
+                                break;
+                        case OR:
+                                /* evaluate right choice later */
+                                if(++ntl >= NLIST)
+                                        goto Overflow;
+                                addinst(tlp, inst->right, &tlp->se);
+                                /* efficiency: advance and re-evaluate */
+                                inst = inst->left;
+                                goto Switchstmt;
+                        case END:        /* Match! */
+                                tlp->se.p[0].p2 = p;
+                                newmatch(&tlp->se);
+                                break;
+                        }
+                }
+        }
+    Return:
+        return sel.p[0].p1>=0;
+}
+
+void
+newmatch(Rangeset *sp)
+{
+        int i;
+
+        if(sel.p[0].p1<0 || sp->p[0].p1p[0].p1==sel.p[0].p1 && sp->p[0].p2>sel.p[0].p2))
+                for(i = 0; ip[i];
+}
+
+int
+bexecute(File *f, Posn startp)
+{
+        int flag = 0;
+        Inst *inst;
+        Ilist *tlp;
+        Posn p = startp;
+        int nnl = 0, ntl;
+        int c;
+        int wrapped = 0;
+        int startchar = bstartinst->typetype : 0;
+
+        list[0][0].inst = list[1][0].inst = 0;
+        sel.p[0].p1= -1;
+        /* Execute machine once for each character, including terminal NUL */
+        for(;;--p){
+        doloop:
+                if((c = filereadc(f, p - 1))==-1){
+                        switch(wrapped++){
+                        case 0:                /* let loop run one more click */
+                        case 2:
+                                break;
+                        case 1:                /* expired; wrap to end */
+                                if(sel.p[0].p1>=0)
+                        case 3:
+                                        goto Return;
+                                list[0][0].inst = list[1][0].inst = 0;
+                                p = f->_.nc;
+                                goto doloop;
+                        default:
+                                goto Return;
+                        }
+                }else if(((wrapped && p<=startp) || sel.p[0].p1>0) && nnl==0)
+                        break;
+                /* fast check for first char */
+                if(startchar && nnl==0 && c!=startchar)
+                        continue;
+                tl = list[flag];
+                nl = list[flag^=1];
+                nl->inst = 0;
+                ntl = nnl;
+                nnl = 0;
+                if(sel.p[0].p1<0 && (!wrapped || p>startp)){
+                        /* Add first instruction to this list */
+                        if(++ntl >= NLIST)
+        Overflow:
+                                error(Eoverflow);
+                        /* the minus is so the optimizations in addinst work */
+                        sempty.p[0].p1 = -p;
+                        addinst(tl, bstartinst, &sempty);
+                }
+                /* Execute machine until this list is empty */
+                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
+        Switchstmt:
+                        switch(inst->type){
+                        default:        /* regular character */
+                                if(inst->type == c){
+        Addinst:
+                                        if(++nnl >= NLIST)
+                                                goto Overflow;
+                                        addinst(nl, inst->next, &tlp->se);
+                                }
+                                break;
+                        case LBRA:
+                                if(inst->subid>=0)
+                                        tlp->se.p[inst->subid].p1 = p;
+                                inst = inst->next;
+                                goto Switchstmt;
+                        case RBRA:
+                                if(inst->subid >= 0)
+                                        tlp->se.p[inst->subid].p2 = p;
+                                inst = inst->next;
+                                goto Switchstmt;
+                        case ANY:
+                                if(c != '\n')
+                                        goto Addinst;
+                                break;
+                        case BOL:
+                                if(c=='\n' || p==0){
+        Step:
+                                        inst = inst->next;
+                                        goto Switchstmt;
+                                }
+                                break;
+                        case EOL:
+                                if(p==f->_.nc || filereadc(f, p)=='\n')
+                                        goto Step;
+                                break;
+                        case CCLASS:
+                                if(c>=0 && classmatch(inst->rclass, c, 0))
+                                        goto Addinst;
+                                break;
+                        case NCCLASS:
+                                if(c>=0 && classmatch(inst->rclass, c, 1))
+                                        goto Addinst;
+                                break;
+                        case OR:
+                                /* evaluate right choice later */
+                                if(++ntl >= NLIST)
+                                        goto Overflow;
+                                addinst(tlp, inst->right, &tlp->se);
+                                /* efficiency: advance and re-evaluate */
+                                inst = inst->left;
+                                goto Switchstmt;
+                        case END:        /* Match! */
+                                tlp->se.p[0].p1 = -tlp->se.p[0].p1; /* minus sign */
+                                tlp->se.p[0].p2 = p;
+                                bnewmatch(&tlp->se);
+                                break;
+                        }
+                }
+        }
+    Return:
+        return sel.p[0].p1>=0;
+}
+
+void
+bnewmatch(Rangeset *sp)
+{
+        int  i;
+        if(sel.p[0].p1<0 || sp->p[0].p1>sel.p[0].p2 || (sp->p[0].p1==sel.p[0].p2 && sp->p[0].p2p[i].p2;
+                        sel.p[i].p2 = sp->p[i].p1;
+                }
+}
diff --git a/src/cmd/sam/sam b/src/cmd/sam/sam
Binary files differ.
diff --git a/src/cmd/sam/sam.c b/src/cmd/sam/sam.c
t@@ -0,0 +1,739 @@
+#include "sam.h"
+
+Rune        genbuf[BLOCKSIZE];
+int        io;
+int        panicking;
+int        rescuing;
+String        genstr;
+String        rhs;
+String        curwd;
+String        cmdstr;
+Rune        empty[] = { 0 };
+char        *genc;
+File        *curfile;
+File        *flist;
+File        *cmd;
+jmp_buf        mainloop;
+List        tempfile;
+int        quitok = TRUE;
+int        downloaded;
+int        dflag;
+int        Rflag;
+char        *machine;
+char        *home;
+int        bpipeok;
+int        termlocked;
+char        *samterm = SAMTERM;
+char        *rsamname = RSAM;
+File        *lastfile;
+Disk        *disk;
+long        seq;
+
+Rune        baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};
+
+void        usage(void);
+
+int main(int argc, char *argv[])
+{
+        int i;
+        String *t;
+        char **ap, **arg;
+
+        { // libfmt-2.0 uses %lu where we need %lud
+          extern int __flagfmt(Fmt*);
+          fmtinstall('u', __flagfmt);
+        }
+
+        arg = argv++;
+        ap = argv;
+        while(argc>1 && argv[0] && argv[0][0]=='-'){
+                switch(argv[0][1]){
+                case 'd':
+                        dflag++;
+                        break;
+
+                case 'r':
+                        --argc, argv++;
+                        if(argc == 1)
+                                usage();
+                        machine = *argv;
+                        break;
+
+                case 'R':
+                        Rflag++;
+                        break;
+
+                case 't':
+                        --argc, argv++;
+                        if(argc == 1)
+                                usage();
+                        samterm = *argv;
+                        break;
+
+                case 's':
+                        --argc, argv++;
+                        if(argc == 1)
+                                usage();
+                        rsamname = *argv;
+                        break;
+
+                case 'x':       /* x11 option - strip the x */
+                        strcpy(*argv+1, *argv+2);
+                        *ap++ = *argv++;
+                        *ap++ = *argv;
+                        argc--;
+                        break;
+
+                default:
+                        dprint("sam: unknown flag %c\n", argv[0][1]);
+                        exits("usage");
+                }
+                --argc, argv++;
+        }
+        Strinit(&cmdstr);
+        Strinit0(&lastpat);
+        Strinit0(&lastregexp);
+        Strinit0(&genstr);
+        Strinit0(&rhs);
+        Strinit0(&curwd);
+        tempfile.listptr = emalloc(1);        /* so it can be freed later */
+        Strinit0(&plan9cmd);
+        home = getenv(HOME);
+        disk = diskinit();
+        if(home == 0)
+                home = "/";
+        if(!dflag)
+                startup(machine, Rflag, arg, ap);
+        notify(notifyf);
+        getcurwd();
+        if(argc>1){
+                for(i=0; i_.nc==0 || !fileisdirty(f))
+                        continue;
+                if(io == -1){
+                        sprint(buf, "%s/sam.save", home);
+                        io = create(buf, 1, 0777);
+                        if(io<0)
+                                return;
+                }
+                if(f->name.s[0]){
+                        c = Strtoc(&f->name);
+                        strncpy(buf, c, sizeof buf-1);
+                        buf[sizeof buf-1] = 0;
+                        free(c);
+                }else
+                        sprint(buf, "nameless.%d", nblank++);
+                fprint(io, "#!%s '%s' $* <<'---%s'\n", SAMSAVECMD, buf, buf);
+                addr.r.p1 = 0, addr.r.p2 = f->_.nc;
+                writeio(f);
+                fprint(io, "\n---%s\n", (char *)buf);
+        }
+}
+
+void
+panic(char *s)
+{
+        int wasd;
+
+        if(!panicking++ && !setjmp(mainloop)){
+                wasd = downloaded;
+                downloaded = 0;
+                dprint("sam: panic: %s: %r\n", s);
+                if(wasd)
+                        fprint(2, "sam: panic: %s: %r\n", s);
+                rescue();
+                abort();
+        }
+}
+
+void
+hiccough(char *s)
+{
+        File *f;
+        int i;
+
+        if(rescuing)
+                exits("rescue");
+        if(s)
+                dprint("%s\n", s);
+        resetcmd();
+        resetxec();
+        resetsys();
+        if(io > 0)
+                close(io);
+
+        /*
+         * back out any logged changes & restore old sequences
+         */
+        for(i=0; iseq==seq){
+                        bufdelete(&f->epsilon, 0, f->epsilon.nc);
+                        f->seq = f->prevseq;
+                        f->dot.r = f->prevdot;
+                        f->mark = f->prevmark;
+                        state(f, f->prevmod ? Dirty: Clean);
+                }
+        }
+
+        update();
+        if (curfile) {
+                if (curfile->unread)
+                        curfile->unread = FALSE;
+                else if (downloaded)
+                        outTs(Hcurrent, curfile->tag);
+        }
+        longjmp(mainloop, 1);
+}
+
+void
+intr(void)
+{
+        error(Eintr);
+}
+
+void
+trytoclose(File *f)
+{
+        char *t;
+        char buf[256];
+
+        if(f == cmd)        /* possible? */
+                return;
+        if(f->deleted)
+                return;
+        if(fileisdirty(f) && !f->closeok){
+                f->closeok = TRUE;
+                if(f->name.s[0]){
+                        t = Strtoc(&f->name);
+                        strncpy(buf, t, sizeof buf-1);
+                        free(t);
+                }else
+                        strcpy(buf, "nameless file");
+                error_s(Emodified, buf);
+        }
+        f->deleted = TRUE;
+}
+
+void
+trytoquit(void)
+{
+        int c;
+        File *f;
+
+        if(!quitok){
+                for(c = 0; cname);
+        filename(f);
+        if(f->name.s[0]){
+                saveaddr = addr;
+                edit(f, 'I');
+                addr = saveaddr;
+        }else{
+                f->unread = 0;
+                f->cleanseq = f->seq;
+        }
+
+        fileupdate(f, TRUE, TRUE);
+}
+
+void
+cmdupdate(void)
+{
+        if(cmd && cmd->seq!=0){
+                fileupdate(cmd, FALSE, downloaded);
+                cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->_.nc;
+                telldot(cmd);
+        }
+}
+
+void
+delete(File *f)
+{
+        if(downloaded && f->rasp)
+                outTs(Hclose, f->tag);
+        delfile(f);
+        if(f == curfile)
+                current(0);
+}
+
+void
+update(void)
+{
+        int i, anymod;
+        File *f;
+
+        settempfile();
+        for(anymod = i=0; ideleted) {
+                        delete(f);
+                        continue;
+                }
+                if(f->seq==seq && fileupdate(f, FALSE, downloaded))
+                        anymod++;
+                if(f->rasp)
+                        telldot(f);
+        }
+        if(anymod)
+                seq++;
+}
+
+File *
+current(File *f)
+{
+        return curfile = f;
+}
+
+void
+edit(File *f, int cmd)
+{
+        int empty = TRUE;
+        Posn p;
+        int nulls;
+
+        if(cmd == 'r')
+                logdelete(f, addr.r.p1, addr.r.p2);
+        if(cmd=='e' || cmd=='I'){
+                logdelete(f, (Posn)0, f->_.nc);
+                addr.r.p2 = f->_.nc;
+        }else if(f->_.nc!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
+                empty = FALSE;
+        if((io = open(genc, OREAD))<0) {
+                if (curfile && curfile->unread)
+                        curfile->unread = FALSE;
+                error_r(Eopen, genc);
+        }
+        p = readio(f, &nulls, empty, TRUE);
+        closeio((cmd=='e' || cmd=='I')? -1 : p);
+        if(cmd == 'r')
+                f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
+        else
+                f->ndot.r.p1 = f->ndot.r.p2 = 0;
+        f->closeok = empty;
+        if (quitok)
+                quitok = empty;
+        else
+                quitok = FALSE;
+        state(f, empty && !nulls? Clean : Dirty);
+        if(empty && !nulls)
+                f->cleanseq = f->seq;
+        if(cmd == 'e')
+                filename(f);
+}
+
+int
+getname(File *f, String *s, int save)
+{
+        int c, i;
+
+        Strzero(&genstr);
+        if(genc){
+                free(genc);
+                genc = 0;
+        }
+        if(s==0 || (c = s->s[0])==0){                /* no name provided */
+                if(f)
+                        Strduplstr(&genstr, &f->name);
+                goto Return;
+        }
+        if(c!=' ' && c!='\t')
+                error(Eblank);
+        for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
+                ;
+        while(s->s[i] > ' ')
+                Straddc(&genstr, s->s[i++]);
+        if(s->s[i])
+                error(Enewline);
+        fixname(&genstr);
+        if(f && (save || f->name.s[0]==0)){
+                logsetname(f, &genstr);
+                if(Strcmp(&f->name, &genstr)){
+                        quitok = f->closeok = FALSE;
+                        f->qidpath = 0;
+                        f->mtime = 0;
+                        state(f, Dirty); /* if it's 'e', fix later */
+                }
+        }
+    Return:
+        genc = Strtoc(&genstr);
+        i = genstr.n;
+        if(i && genstr.s[i-1]==0)
+                i--;
+        return i;        /* strlen(name) */
+}
+
+void
+filename(File *f)
+{
+        if(genc)
+                free(genc);
+        genc = Strtoc(&genstr);
+        dprint("%c%c%c %s\n", " '"[f->mod],
+                "-+"[f->rasp!=0], " ."[f==curfile], genc);
+}
+
+void
+undostep(File *f, int isundo)
+{
+        uint p1, p2;
+        int mod;
+
+        mod = f->mod;
+        fileundo(f, isundo, 1, &p1, &p2, TRUE);
+        f->ndot = f->dot;
+        if(f->mod){
+                f->closeok = 0;
+                quitok = 0;
+        }else
+                f->closeok = 1;
+
+        if(f->mod != mod){
+                f->mod = mod;
+                if(mod)
+                        mod = Clean;
+                else
+                        mod = Dirty;
+                state(f, mod);
+        }
+}
+
+int
+undo(int isundo)
+{
+        File *f;
+        int i;
+        Mod max;
+
+        max = undoseq(curfile, isundo);
+        if(max == 0)
+                return 0;
+        settempfile();
+        for(i = 0; i_.nc;
+        retcode = plan9(flist, '<', s, FALSE);
+        fileupdate(flist, FALSE, FALSE);
+        flist->seq = 0;
+        if (flist->_.nc > BLOCKSIZE)
+                error(Etoolong);
+        Strzero(&genstr);
+        Strinsure(&genstr, flist->_.nc);
+        bufread(flist, (Posn)0, genbuf, flist->_.nc);
+        memmove(genstr.s, genbuf, flist->_.nc*RUNESIZE);
+        genstr.n = flist->_.nc;
+        Straddc(&genstr, '\0');
+        return retcode;
+}
+
+void
+getcurwd(void)
+{
+        String *t;
+        char buf[256];
+
+        buf[0] = 0;
+        getwd(buf, sizeof(buf));
+        t = tmpcstr(buf);
+        Strduplstr(&curwd, t);
+        freetmpstr(t);
+        if(curwd.n == 0)
+                warn(Wpwd);
+        else if(curwd.s[curwd.n-1] != '/')
+                Straddc(&curwd, '/');
+}
+
+void
+cd(String *str)
+{
+        int i, fd;
+        char *s;
+        File *f;
+        String owd;
+
+        getcurwd();
+        if(getname((File *)0, str, FALSE))
+                s = genc;
+        else
+                s = home;
+        if(chdir(s))
+                syserror("chdir");
+        fd = open("/dev/wdir", OWRITE);
+        if(fd > 0)
+                write(fd, s, strlen(s));
+        dprint("!\n");
+        Strinit(&owd);
+        Strduplstr(&owd, &curwd);
+        getcurwd();
+        settempfile();
+        for(i=0; iname.s[0]!='/' && f->name.s[0]!=0){
+                        Strinsert(&f->name, &owd, (Posn)0);
+                        fixname(&f->name);
+                        sortname(f);
+                }else if(f != cmd && Strispre(&curwd, &f->name)){
+                        fixname(&f->name);
+                        sortname(f);
+                }
+        }
+        Strclose(&owd);
+}
+
+int
+loadflist(String *s)
+{
+        int c, i;
+
+        c = s->s[0];
+        for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++)
+                ;
+        if((c==' ' || c=='\t') && s->s[i]!='\n'){
+                if(s->s[i]=='<'){
+                        Strdelete(s, 0L, (long)i+1);
+                        readcmd(s);
+                }else{
+                        Strzero(&genstr);
+                        while((c = s->s[i++]) && c!='\n')
+                                Straddc(&genstr, c);
+                        Straddc(&genstr, '\0');
+                }
+        }else{
+                if(c != '\n')
+                        error(Eblank);
+                Strdupl(&genstr, empty);
+        }
+        if(genc)
+                free(genc);
+        genc = Strtoc(&genstr);
+        return genstr.s[0];
+}
+
+File *
+readflist(int readall, int delete)
+{
+        Posn i;
+        int c;
+        File *f;
+        String t;
+
+        Strinit(&t);
+        for(i=0,f=0; f==0 || readall || delete; i++){        /* ++ skips blank */
+                Strdelete(&genstr, (Posn)0, i);
+                for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
+                        ;
+                if(i >= genstr.n)
+                        break;
+                Strdelete(&genstr, (Posn)0, i);
+                for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
+                        ;
+
+                if(i == 0)
+                        break;
+                genstr.s[i] = 0;
+                Strduplstr(&t, tmprstr(genstr.s, i+1));
+                fixname(&t);
+                f = lookfile(&t);
+                if(delete){
+                        if(f == 0)
+                                warn_S(Wfile, &t);
+                        else
+                                trytoclose(f);
+                }else if(f==0 && readall)
+                        logsetname(f = newfile(), &t);
+        }
+        Strclose(&t);
+        return f;
+}
+
+File *
+tofile(String *s)
+{
+        File *f;
+
+        if(s->s[0] != ' ')
+                error(Eblank);
+        if(loadflist(s) == 0){
+                f = lookfile(&genstr);        /* empty string ==> nameless file */
+                if(f == 0)
+                        error_s(Emenu, genc);
+        }else if((f=readflist(FALSE, FALSE)) == 0)
+                error_s(Emenu, genc);
+        return current(f);
+}
+
+File *
+getfile(String *s)
+{
+        File *f;
+
+        if(loadflist(s) == 0)
+                logsetname(f = newfile(), &genstr);
+        else if((f=readflist(TRUE, FALSE)) == 0)
+                error(Eblank);
+        return current(f);
+}
+
+void
+closefiles(File *f, String *s)
+{
+        if(s->s[0] == 0){
+                if(f == 0)
+                        error(Enofile);
+                trytoclose(f);
+                return;
+        }
+        if(s->s[0] != ' ')
+                error(Eblank);
+        if(loadflist(s) == 0)
+                error(Enewline);
+        readflist(FALSE, TRUE);
+}
+
+void
+copy(File *f, Address addr2)
+{
+        Posn p;
+        int ni;
+        for(p=addr.r.p1; p BLOCKSIZE)
+                        ni = BLOCKSIZE;
+                bufread(f, p, genbuf, ni);
+                loginsert(addr2.f, addr2.r.p2, tmprstr(genbuf, ni)->s, ni);
+        }
+        addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
+        addr2.f->ndot.r.p1 = addr2.r.p2;
+}
+
+void
+move(File *f, Address addr2)
+{
+        if(addr.r.p2 <= addr2.r.p2){
+                logdelete(f, addr.r.p1, addr.r.p2);
+                copy(f, addr2);
+        }else if(addr.r.p1 >= addr2.r.p2){
+                copy(f, addr2);
+                logdelete(f, addr.r.p1, addr.r.p2);
+        }else
+                error(Eoverlap);
+}
+
+Posn
+nlcount(File *f, Posn p0, Posn p1)
+{
+        Posn nl = 0;
+
+        while(p0 < p1)
+                if(filereadc(f, p0++)=='\n')
+                        nl++;
+        return nl;
+}
+
+void
+printposn(File *f, int charsonly)
+{
+        Posn l1, l2;
+
+        if(!charsonly){
+                l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
+                l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
+                /* check if addr ends with '\n' */
+                if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && filereadc(f, addr.r.p2-1)=='\n')
+                        --l2;
+                dprint("%lud", l1);
+                if(l2 != l1)
+                        dprint(",%lud", l2);
+                dprint("; ");
+        }
+        dprint("#%lud", addr.r.p1);
+        if(addr.r.p2 != addr.r.p1)
+                dprint(",#%lud", addr.r.p2);
+        dprint("\n");
+}
+
+void
+settempfile(void)
+{
+        if(tempfile.nalloc < file.nused){
+                free(tempfile.listptr);
+                tempfile.listptr = emalloc(sizeof(*tempfile.filepptr)*file.nused);
+                tempfile.nalloc = file.nused;
+        }
+        tempfile.nused = file.nused;
+        memmove(&tempfile.filepptr[0], &file.filepptr[0], file.nused*sizeof(File*));
+}
diff --git a/src/cmd/sam/sam.h b/src/cmd/sam/sam.h
t@@ -0,0 +1,407 @@
+#include 
+#include 
+#include 
+#include "errors.h"
+
+/*
+ * BLOCKSIZE is relatively small to keep memory consumption down.
+ */
+
+#define        BLOCKSIZE        2048
+#define        RUNESIZE        sizeof(Rune)
+#define        NDISC                5
+#define        NBUFFILES        3+2*NDISC        /* plan 9+undo+snarf+NDISC*(transcript+buf) */
+#define NSUBEXP        10
+
+#define        TRUE                1
+#define        FALSE                0
+
+#define        INFINITY        0x7FFFFFFFL
+#define        INCR                25
+#define        STRSIZE                (2*BLOCKSIZE)
+
+typedef long                Posn;                /* file position or address */
+typedef        ushort                Mod;                /* modification number */
+
+typedef struct Address        Address;
+typedef struct Block        Block;
+typedef struct Buffer        Buffer;
+typedef struct Disk        Disk;
+typedef struct Discdesc        Discdesc;
+typedef struct File        File;
+typedef struct List        List;
+typedef struct Range        Range;
+typedef struct Rangeset        Rangeset;
+typedef struct String        String;
+
+enum State
+{
+        Clean =                ' ',
+        Dirty =                '\'',
+        Unread =        '-',
+};
+
+struct Range
+{
+        Posn        p1, p2;
+};
+
+struct Rangeset
+{
+        Range        p[NSUBEXP];
+};
+
+struct Address
+{
+        Range        r;
+        File        *f;
+};
+
+struct String
+{
+        short        n;
+        short        size;
+        Rune        *s;
+};
+
+struct List        /* code depends on a long being able to hold a pointer */
+{
+        int        nalloc;
+        int        nused;
+        union{
+                void        *listp;
+                Block        *blkp;
+                long        *longp;
+                uchar*        *ucharp;
+                String*        *stringp;
+                File*        *filep;
+                long        listv;
+        }g;
+};
+
+#define        listptr                g.listp
+#define        blkptr                g.blkp
+#define        longptr                g.longp
+#define        ucharpptr        g.ucharp
+#define        stringpptr        g.stringp
+#define        filepptr        g.filep
+#define        listval                g.listv
+
+enum
+{
+        Blockincr =        256,
+        Maxblock =         8*1024,
+
+        BUFSIZE = Maxblock,        /* size from fbufalloc() */
+        RBUFSIZE = BUFSIZE/sizeof(Rune),
+};
+
+
+enum
+{
+        Null                = '-',
+        Delete                = 'd',
+        Insert                = 'i',
+        Filename        = 'f',
+        Dot                = 'D',
+        Mark                = 'm',
+};
+
+struct Block
+{
+        uint                addr;        /* disk address in bytes */
+        union
+        {
+                uint        n;        /* number of used runes in block */
+                Block        *next;        /* pointer to next in free list */
+        } _;
+};
+
+struct Disk
+{
+        int                fd;
+        uint                addr;        /* length of temp file */
+        Block                *free[Maxblock/Blockincr+1];
+};
+
+Disk*                diskinit(void);
+Block*                disknewblock(Disk*, uint);
+void                diskrelease(Disk*, Block*);
+void                diskread(Disk*, Block*, Rune*, uint);
+void                diskwrite(Disk*, Block**, Rune*, uint);
+
+struct Buffer
+{
+        uint                nc;
+        Rune                *c;        /* cache */
+        uint                cnc;        /* bytes in cache */
+        uint                cmax;        /* size of allocated cache */
+        uint                cq;        /* position of cache */
+        int                cdirty;        /* cache needs to be written */
+        uint                cbi;        /* index of cache Block */
+        Block                **bl;        /* array of blocks */
+        uint                nbl;        /* number of blocks */
+};
+void                bufinsert(Buffer*, uint, Rune*, uint);
+void                bufdelete(Buffer*, uint, uint);
+uint                bufload(Buffer*, uint, int, int*);
+void                bufread(Buffer*, uint, Rune*, uint);
+void                bufclose(Buffer*);
+void                bufreset(Buffer*);
+
+struct File
+{
+        Buffer _;                                /* the data */
+        Buffer                delta;                /* transcript of changes */
+        Buffer                epsilon;        /* inversion of delta for redo */
+        String                name;                /* name of associated file */
+        uvlong                qidpath;        /* of file when read */
+        uint                mtime;                /* of file when read */
+        int                dev;                /* of file when read */
+        int                unread;                /* file has not been read from disk */
+
+        long                seq;                /* if seq==0, File acts like Buffer */
+        long                cleanseq;        /* f->seq at last read/write of file */
+        int                mod;                /* file appears modified in menu */
+        char                rescuing;        /* sam exiting; this file unusable */
+
+//        Text                *curtext;        /* most recently used associated text */
+//        Text                **text;                /* list of associated texts */
+//        int                ntext;
+//        int                dumpid;                /* used in dumping zeroxed windows */
+
+        Posn                hiposn;                /* highest address touched this Mod */
+        Address                dot;                /* current position */
+        Address                ndot;                /* new current position after update */
+        Range                tdot;                /* what terminal thinks is current range */
+        Range                mark;                /* tagged spot in text (don't confuse with Mark) */
+        List                *rasp;                /* map of what terminal's got */
+        short                tag;                /* for communicating with terminal */
+        char                closeok;        /* ok to close file? */
+        char                deleted;        /* delete at completion of command */
+        Range                prevdot;        /* state before start of change */
+        Range                prevmark;
+        long                prevseq;
+        int                prevmod;
+};
+//File*                fileaddtext(File*, Text*);
+void                fileclose(File*);
+void                filedelete(File*, uint, uint);
+//void                filedeltext(File*, Text*);
+void                fileinsert(File*, uint, Rune*, uint);
+uint                fileload(File*, uint, int, int*);
+void                filemark(File*);
+void                filereset(File*);
+void                filesetname(File*, String*);
+void                fileundelete(File*, Buffer*, uint, uint);
+void                fileuninsert(File*, Buffer*, uint, uint);
+void                fileunsetname(File*, Buffer*);
+void                fileundo(File*, int, int, uint*, uint*, int);
+int                fileupdate(File*, int, int);
+
+int                filereadc(File*, uint);
+File                *fileopen(void);
+void                loginsert(File*, uint, Rune*, uint);
+void                logdelete(File*, uint, uint);
+void                logsetname(File*, String*);
+int                fileisdirty(File*);
+long                undoseq(File*, int);
+long                prevseq(Buffer*);
+
+void                raspload(File*);
+void                raspstart(File*);
+void                raspdelete(File*, uint, uint, int);
+void                raspinsert(File*, uint, Rune*, uint, int);
+void                raspdone(File*, int);
+
+/*
+ * acme fns
+ */
+void*        fbufalloc(void);
+void        fbuffree(void*);
+uint        min(uint, uint);
+void        cvttorunes(char*, int, Rune*, int*, int*, int*);
+
+#define        runemalloc(a)                (Rune*)emalloc((a)*sizeof(Rune))
+#define        runerealloc(a, b)        (Rune*)realloc((a), (b)*sizeof(Rune))
+#define        runemove(a, b, c)        memmove((a), (b), (c)*sizeof(Rune))
+
+int        alnum(int);
+int        Read(int, void*, int);
+void        Seek(int, long, int);
+int        plan9(File*, int, String*, int);
+int        Write(int, void*, int);
+int        bexecute(File*, Posn);
+void        cd(String*);
+void        closefiles(File*, String*);
+void        closeio(Posn);
+void        cmdloop(void);
+void        cmdupdate(void);
+void        compile(String*);
+void        copy(File*, Address);
+File        *current(File*);
+void        delete(File*);
+void        delfile(File*);
+void        dellist(List*, int);
+void        doubleclick(File*, Posn);
+void        dprint(char*, ...);
+void        edit(File*, int);
+void        *emalloc(ulong);
+void        *erealloc(void*, ulong);
+void        error(Err);
+void        error_c(Err, int);
+void        error_r(Err, char*);
+void        error_s(Err, char*);
+int        execute(File*, Posn, Posn);
+int        filematch(File*, String*);
+void        filename(File*);
+void        fixname(String*);
+void        fullname(String*);
+void        getcurwd(void);
+File        *getfile(String*);
+int        getname(File*, String*, int);
+long        getnum(int);
+void        hiccough(char*);
+void        inslist(List*, int, long);
+Address        lineaddr(Posn, Address, int);
+void        listfree(List*);
+void        load(File*);
+File        *lookfile(String*);
+void        lookorigin(File*, Posn, Posn);
+int        lookup(int);
+void        move(File*, Address);
+void        moveto(File*, Range);
+File        *newfile(void);
+void        nextmatch(File*, String*, Posn, int);
+int        newtmp(int);
+void        notifyf(void*, char*);
+void        panic(char*);
+void        printposn(File*, int);
+void        print_ss(char*, String*, String*);
+void        print_s(char*, String*);
+int        rcv(void);
+Range        rdata(List*, Posn, Posn);
+Posn        readio(File*, int*, int, int);
+void        rescue(void);
+void        resetcmd(void);
+void        resetsys(void);
+void        resetxec(void);
+void        rgrow(List*, Posn, Posn);
+void        samerr(char*);
+void        settempfile(void);
+int        skipbl(void);
+void        snarf(File*, Posn, Posn, Buffer*, int);
+void        sortname(File*);
+void        startup(char*, int, char**, char**);
+void        state(File*, int);
+int        statfd(int, ulong*, uvlong*, long*, long*, long*);
+int        statfile(char*, ulong*, uvlong*, long*, long*, long*);
+void        Straddc(String*, int);
+void        Strclose(String*);
+int        Strcmp(String*, String*);
+void        Strdelete(String*, Posn, Posn);
+void        Strdupl(String*, Rune*);
+void        Strduplstr(String*, String*);
+void        Strinit(String*);
+void        Strinit0(String*);
+void        Strinsert(String*, String*, Posn);
+void        Strinsure(String*, ulong);
+int        Strispre(String*, String*);
+void        Strzero(String*);
+int        Strlen(Rune*);
+char        *Strtoc(String*);
+void        syserror(char*);
+void        telldot(File*);
+void        tellpat(void);
+String        *tmpcstr(char*);
+String        *tmprstr(Rune*, int);
+void        freetmpstr(String*);
+void        termcommand(void);
+void        termwrite(char*);
+File        *tofile(String*);
+void        trytoclose(File*);
+void        trytoquit(void);
+int        undo(int);
+void        update(void);
+int        waitfor(int);
+void        warn(Warn);
+void        warn_s(Warn, char*);
+void        warn_SS(Warn, String*, String*);
+void        warn_S(Warn, String*);
+int        whichmenu(File*);
+void        writef(File*);
+Posn        writeio(File*);
+Discdesc *Dstart(void);
+
+extern Rune        samname[];        /* compiler dependent */
+extern Rune        *left[];
+extern Rune        *right[];
+
+extern char        RSAM[];                /* system dependent */
+extern char        SAMTERM[];
+extern char        HOME[];
+extern char        TMPDIR[];
+extern char        SH[];
+extern char        SHPATH[];
+extern char        RX[];
+extern char        RXPATH[];
+extern char        SAMSAVECMD[];
+
+/*
+ * acme globals
+ */
+extern long                seq;
+extern Disk                *disk;
+
+extern char        *rsamname;        /* globals */
+extern char        *samterm;
+extern Rune        genbuf[];
+extern char        *genc;
+extern int        io;
+extern int        patset;
+extern int        quitok;
+extern Address        addr;
+extern Buffer        snarfbuf;
+extern Buffer        plan9buf;
+extern List        file;
+extern List        tempfile;
+extern File        *cmd;
+extern File        *curfile;
+extern File        *lastfile;
+extern Mod        modnum;
+extern Posn        cmdpt;
+extern Posn        cmdptadv;
+extern Rangeset        sel;
+extern String        curwd;
+extern String        cmdstr;
+extern String        genstr;
+extern String        lastpat;
+extern String        lastregexp;
+extern String        plan9cmd;
+extern int        downloaded;
+extern int        eof;
+extern int        bpipeok;
+extern int        panicking;
+extern Rune        empty[];
+extern int        termlocked;
+extern int        noflush;
+
+#include "mesg.h"
+
+void        outTs(Hmesg, int);
+void        outT0(Hmesg);
+void        outTl(Hmesg, long);
+void        outTslS(Hmesg, int, long, String*);
+void        outTS(Hmesg, String*);
+void        outTsS(Hmesg, int, String*);
+void        outTsllS(Hmesg, int, long, long, String*);
+void        outTsll(Hmesg, int, long, long);
+void        outTsl(Hmesg, int, long);
+void        outTsv(Hmesg, int, long);
+void        outstart(Hmesg);
+void        outcopy(int, void*);
+void        outshort(int);
+void        outlong(long);
+void        outvlong(void*);
+void        outsend(void);
+void        outflush(void);
diff --git a/src/cmd/sam/shell.c b/src/cmd/sam/shell.c
t@@ -0,0 +1,152 @@
+#include "sam.h"
+#include "parse.h"
+
+extern        jmp_buf        mainloop;
+
+char        errfile[64];
+String        plan9cmd;        /* null terminated */
+Buffer        plan9buf;
+void        checkerrs(void);
+
+int
+plan9(File *f, int type, String *s, int nest)
+{
+        long l;
+        int m;
+        int pid, fd;
+        int retcode;
+        int pipe1[2], pipe2[2];
+
+        if(s->s[0]==0 && plan9cmd.s[0]==0)
+                error(Enocmd);
+        else if(s->s[0])
+                Strduplstr(&plan9cmd, s);
+        if(downloaded){
+                samerr(errfile);
+                remove(errfile);
+        }
+        if(type!='!' && pipe(pipe1)==-1)
+                error(Epipe);
+        if(type=='|')
+                snarf(f, addr.r.p1, addr.r.p2, &plan9buf, 1);
+        if((pid=fork()) == 0){
+                if(downloaded){        /* also put nasty fd's into errfile */
+                        fd = create(errfile, 1, 0666L);
+                        if(fd < 0)
+                                fd = create("/dev/null", 1, 0666L);
+                        dup(fd, 2);
+                        close(fd);
+                        /* 2 now points at err file */
+                        if(type == '>')
+                                dup(2, 1);
+                        else if(type=='!'){
+                                dup(2, 1);
+                                fd = open("/dev/null", 0);
+                                dup(fd, 0);
+                                close(fd);
+                        }
+                }
+                if(type != '!') {
+                        if(type=='<' || type=='|')
+                                dup(pipe1[1], 1);
+                        else if(type == '>')
+                                dup(pipe1[0], 0);
+                        close(pipe1[0]);
+                        close(pipe1[1]);
+                }
+                if(type == '|'){
+                        if(pipe(pipe2) == -1)
+                                exits("pipe");
+                        if((pid = fork())==0){
+                                /*
+                                 * It's ok if we get SIGPIPE here
+                                 */
+                                close(pipe2[0]);
+                                io = pipe2[1];
+                                if(retcode=!setjmp(mainloop)){        /* assignment = */
+                                        char *c;
+                                        for(l = 0; lBLOCKSIZE-1)
+                                                        m = BLOCKSIZE-1;
+                                                bufread(&plan9buf, l, genbuf, m);
+                                                genbuf[m] = 0;
+                                                c = Strtoc(tmprstr(genbuf, m+1));
+                                                Write(pipe2[1], c, strlen(c));
+                                                free(c);
+                                        }
+                                }
+                                exits(retcode? "error" : 0);
+                        }
+                        if(pid==-1){
+                                fprint(2, "Can't fork?!\n");
+                                exits("fork");
+                        }
+                        dup(pipe2[0], 0);
+                        close(pipe2[0]);
+                        close(pipe2[1]);
+                }
+                if(type=='<'){
+                        close(0);        /* so it won't read from terminal */
+                        open("/dev/null", 0);
+                }
+                execl(SHPATH, SH, "-c", Strtoc(&plan9cmd), (char *)0);
+                exits("exec");
+        }
+        if(pid == -1)
+                error(Efork);
+        if(type=='<' || type=='|'){
+                int nulls;
+                if(downloaded && addr.r.p1 != addr.r.p2)
+                        outTl(Hsnarflen, addr.r.p2-addr.r.p1);
+                snarf(f, addr.r.p1, addr.r.p2, &snarfbuf, 0);
+                logdelete(f, addr.r.p1, addr.r.p2);
+                close(pipe1[1]);
+                io = pipe1[0];
+                f->tdot.p1 = -1;
+                f->ndot.r.p2 = addr.r.p2+readio(f, &nulls, 0, FALSE);
+                f->ndot.r.p1 = addr.r.p2;
+                closeio((Posn)-1);
+        }else if(type=='>'){
+                close(pipe1[0]);
+                io = pipe1[1];
+                bpipeok = 1;
+                writeio(f);
+                bpipeok = 0;
+                closeio((Posn)-1);
+        }
+        retcode = waitfor(pid);
+        if(type=='|' || type=='<')
+                if(retcode!=0)
+                        warn(Wbadstatus);
+        if(downloaded)
+                checkerrs();
+        if(!nest)
+                dprint("!\n");
+        return retcode;
+}
+
+void
+checkerrs(void)
+{
+        char buf[256];
+        int f, n, nl;
+        char *p;
+        long l;
+
+        if(statfile(errfile, 0, 0, 0, &l, 0) > 0 && l != 0){
+                if((f=open((char *)errfile, 0)) != -1){
+                        if((n=read(f, buf, sizeof buf-1)) > 0){
+                                for(nl=0,p=buf; nl<3 && p<&buf[n]; p++)
+                                        if(*p=='\n')
+                                                nl++;
+                                *p = 0;
+                                dprint("%s", buf);
+                                if(p-buf < l-1)
+                                        dprint("(sam: more in %s)\n", errfile);
+                        }
+                        close(f);
+                }
+        }else
+                remove((char *)errfile);
+}
diff --git a/src/cmd/sam/unix.c b/src/cmd/sam/unix.c
t@@ -0,0 +1,272 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "sam.h"
+
+Rune    samname[] = { '~', '~', 's', 'a', 'm', '~', '~', 0 };
+ 
+static Rune l1[] = { '{', '[', '(', '<', 0253, 0};
+static Rune l2[] = { '\n', 0};
+static Rune l3[] = { '\'', '"', '`', 0};
+Rune *left[]= { l1, l2, l3, 0};
+
+static Rune r1[] = {'}', ']', ')', '>', 0273, 0};
+static Rune r2[] = {'\n', 0};
+static Rune r3[] = {'\'', '"', '`', 0};
+Rune *right[]= { r1, r2, r3, 0};
+
+#ifndef SAMTERMNAME
+#define SAMTERMNAME "/usr/local/bin/samterm"
+#endif
+#ifndef TMPDIRNAME
+#define TMPDIRNAME "/tmp"
+#endif
+#ifndef SHNAME
+#define SHNAME "rc"
+#endif
+#ifndef SHPATHNAME
+#define SHPATHNAME "/bin/rc"
+#endif
+#ifndef RXNAME
+#define RXNAME "ssh"
+#endif
+#ifndef RXPATHNAME
+#define RXPATHNAME "/usr/local/bin/ssh"
+#endif
+#ifndef SAMSAVECMDNAME
+#define SAMSAVECMDNAME "/bin/rc\n/usr/local/bin/samsave"
+#endif
+
+char        RSAM[] = "sam";
+char        SAMTERM[] = SAMTERMNAME;
+char        HOME[] = "HOME";
+char        TMPDIR[] = TMPDIRNAME;
+char        SH[] = SHNAME;
+char        SHPATH[] = SHPATHNAME;
+char        RX[] = RXNAME;
+char        RXPATH[] = RXPATHNAME;
+char        SAMSAVECMD[] = SAMSAVECMDNAME;
+
+
+void
+dprint(char *z, ...)
+{
+        char buf[BLOCKSIZE];
+        va_list arg;
+
+        va_start(arg, z);
+        vseprint(buf, &buf[BLOCKSIZE], z, arg);
+        va_end(arg);
+        termwrite(buf);
+}
+
+void
+print_ss(char *s, String *a, String *b)
+{
+        dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
+}
+
+void
+print_s(char *s, String *a)
+{
+        dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
+}
+
+char*
+getuser(void)
+{
+        static char user[64];
+        if(user[0] == 0){
+                struct passwd *pw = getpwuid(getuid());
+                strcpy(user, pw ? pw->pw_name : "nobody");
+        }
+        return user;
+}
+
+int     
+statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly) 
+{
+        struct stat dirb;
+
+        if (stat(name, &dirb) == -1)
+                return -1;
+        if (dev)
+                *dev = dirb.st_dev;   
+        if (id)
+                *id = dirb.st_ino;
+        if (time)
+                *time = dirb.st_mtime;
+        if (length)
+                *length = dirb.st_size;
+        if(appendonly)
+                *appendonly = 0;
+        return 1;
+}
+
+int
+statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+        struct stat dirb;
+
+        if (fstat(fd, &dirb) == -1)   
+                return -1;
+        if (dev)
+                *dev = dirb.st_dev;
+        if (id) 
+                *id = dirb.st_ino;
+        if (time)
+                *time = dirb.st_mtime;
+        if (length)
+                *length = dirb.st_size;
+        if(appendonly)
+                *appendonly = 0;
+        return 1;
+}
+
+void
+hup(int sig)
+{
+        panicking = 1; // ???
+        rescue();
+        exit(1);
+}
+
+int
+notify (void(*f)(void *, char *))
+{
+        signal(SIGINT, SIG_IGN);
+        signal(SIGPIPE, SIG_IGN);  // XXX - bpipeok?
+        signal(SIGHUP, hup);
+        return 1;
+}
+
+void
+notifyf(void *a, char *b)       /* never called; hup is instead */
+{
+}
+
+static int
+temp_file(char *buf, int bufsize)
+{
+        char *tmp;
+        int n, fd;
+
+        tmp = getenv("TMPDIR");
+        if (!tmp)
+                tmp = TMPDIR;
+
+        n = snprint(buf, bufsize, "%s/sam.%d.XXXXXXX", tmp, getuid());
+        if (bufsize <= n)
+                return -1;
+        if ((fd = mkstemp(buf)) < 0)  /* SES - linux sometimes uses mode 0666 */
+                return -1;
+        if (fcntl(fd, F_SETFD, fcntl(fd,F_GETFD,0) | FD_CLOEXEC) < 0)
+                return -1;
+        return fd;
+}
+
+int
+tempdisk(void)
+{
+        char buf[4096];
+        int fd = temp_file(buf, sizeof buf);
+        if (fd >= 0)
+                remove(buf);
+        return fd; 
+}
+
+#undef wait
+int     
+waitfor(int pid)
+{
+        int wm; 
+        int rpid;
+                
+        do; while((rpid = wait(&wm)) != pid && rpid != -1);
+        return (WEXITSTATUS(wm));
+}       
+
+void
+samerr(char *buf)
+{
+        sprint(buf, "%s/sam.%s.err", TMPDIR, getuser());
+}
+
+void*
+emalloc(ulong n)
+{
+        void *p;
+
+        p = malloc(n);
+        if(p == 0)
+                panic("malloc fails");
+        memset(p, 0, n);
+        return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+        p = realloc(p, n);
+        if(p == 0)
+                panic("realloc fails");
+        return p;
+}
+
+#if 0
+char *
+strdup(const char *s)
+{
+        return strcpy(emalloc(strlen(s)), s);
+}
+#endif
+
+/*
+void exits(const char *s)
+{
+    if (s) fprint(2, "exit: %s\n", s);
+    exit(s != 0);
+}
+
+void
+_exits(const char *s)
+{
+    if (s) fprint(2, "exit: %s\n", s);
+    _exit(s != 0);
+}
+
+int errstr(char *buf, int size)
+{
+    extern int errno;
+                
+    snprint(buf, size, "%s", strerror(errno));
+    return 1;       
+}                       
+*/
+                    
+int create(char *name, int omode, int perm)
+{
+    int mode;
+    int fd; 
+        
+    if (omode & OWRITE) mode = O_WRONLY;
+    else if (omode & OREAD) mode = O_RDONLY;
+    else mode = O_RDWR;
+
+    if ((fd = open(name, mode|O_CREAT|O_TRUNC, perm)) < 0)
+        return fd;
+
+    if (omode & OCEXEC)
+        fcntl(fd, F_SETFD, fcntl(fd,F_GETFD,0) | FD_CLOEXEC);
+
+    /* SES - not exactly right, but hopefully good enough. */
+    if (omode & ORCLOSE)
+        remove(name);
+
+    return fd;                          
+}
diff --git a/src/cmd/sam/xec.c b/src/cmd/sam/xec.c
t@@ -0,0 +1,508 @@
+#include "sam.h"
+#include "parse.h"
+
+int        Glooping;
+int        nest;
+
+int        append(File*, Cmd*, Posn);
+int        display(File*);
+void        looper(File*, Cmd*, int);
+void        filelooper(Cmd*, int);
+void        linelooper(File*, Cmd*);
+
+void
+resetxec(void)
+{
+        Glooping = nest = 0;
+}
+
+int
+cmdexec(File *f, Cmd *cp)
+{
+        int i;
+        Addr *ap;
+        Address a;
+
+        if(f && f->unread)
+                load(f);
+        if(f==0 && (cp->addr==0 || cp->addr->type!='"') &&
+            !utfrune("bBnqUXY!", cp->cmdc) &&
+            cp->cmdc!=('c'|0x100) && !(cp->cmdc=='D' && cp->ctext))
+                error(Enofile);
+        i = lookup(cp->cmdc);
+        if(i >= 0 && cmdtab[i].defaddr != aNo){
+                if((ap=cp->addr)==0 && cp->cmdc!='\n'){
+                        cp->addr = ap = newaddr();
+                        ap->type = '.';
+                        if(cmdtab[i].defaddr == aAll)
+                                ap->type = '*';
+                }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
+                        ap->next = newaddr();
+                        ap->next->type = '.';
+                        if(cmdtab[i].defaddr == aAll)
+                                ap->next->type = '*';
+                }
+                if(cp->addr){        /* may be false for '\n' (only) */
+                        static Address none = {0,0,0};
+                        if(f)
+                                addr = address(ap, f->dot, 0);
+                        else        /* a " */
+                                addr = address(ap, none, 0);
+                        f = addr.f;
+                }
+        }
+        current(f);
+        switch(cp->cmdc){
+        case '{':
+                a = cp->addr? address(cp->addr, f->dot, 0): f->dot;
+                for(cp = cp->ccmd; cp; cp = cp->next){
+                        a.f->dot = a;
+                        cmdexec(a.f, cp);
+                }
+                break;
+        default:
+                i=(*cmdtab[i].fn)(f, cp);
+                return i;
+        }
+        return 1;
+}
+
+
+int
+a_cmd(File *f, Cmd *cp)
+{
+        return append(f, cp, addr.r.p2);
+}
+
+int
+b_cmd(File *f, Cmd *cp)
+{
+        USED(f);
+        f = cp->cmdc=='b'? tofile(cp->ctext) : getfile(cp->ctext);
+        if(f->unread)
+                load(f);
+        else if(nest == 0)
+                filename(f);
+        return TRUE;
+}
+
+int
+c_cmd(File *f, Cmd *cp)
+{
+        logdelete(f, addr.r.p1, addr.r.p2);
+        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p2;
+        return append(f, cp, addr.r.p2);
+}
+
+int
+d_cmd(File *f, Cmd *cp)
+{
+        USED(cp);
+        logdelete(f, addr.r.p1, addr.r.p2);
+        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p1;
+        return TRUE;
+}
+
+int
+D_cmd(File *f, Cmd *cp)
+{
+        closefiles(f, cp->ctext);
+        return TRUE;
+}
+
+int
+e_cmd(File *f, Cmd *cp)
+{
+        if(getname(f, cp->ctext, cp->cmdc=='e')==0)
+                error(Enoname);
+        edit(f, cp->cmdc);
+        return TRUE;
+}
+
+int
+f_cmd(File *f, Cmd *cp)
+{
+        getname(f, cp->ctext, TRUE);
+        filename(f);
+        return TRUE;
+}
+
+int
+g_cmd(File *f, Cmd *cp)
+{
+        if(f!=addr.f)panic("g_cmd f!=addr.f");
+        compile(cp->re);
+        if(execute(f, addr.r.p1, addr.r.p2) ^ cp->cmdc=='v'){
+                f->dot = addr;
+                return cmdexec(f, cp->ccmd);
+        }
+        return TRUE;
+}
+
+int
+i_cmd(File *f, Cmd *cp)
+{
+        return append(f, cp, addr.r.p1);
+}
+
+int
+k_cmd(File *f, Cmd *cp)
+{
+        USED(cp);
+        f->mark = addr.r;
+        return TRUE;
+}
+
+int
+m_cmd(File *f, Cmd *cp)
+{
+        Address addr2;
+
+        addr2 = address(cp->caddr, f->dot, 0);
+        if(cp->cmdc=='m')
+                move(f, addr2);
+        else
+                copy(f, addr2);
+        return TRUE;
+}
+
+int
+n_cmd(File *f, Cmd *cp)
+{
+        int i;
+        USED(f);
+        USED(cp);
+        for(i = 0; iname);
+                filename(f);
+        }
+        return TRUE;
+}
+
+int
+p_cmd(File *f, Cmd *cp)
+{
+        USED(cp);
+        return display(f);
+}
+
+int
+q_cmd(File *f, Cmd *cp)
+{
+        USED(cp);
+        USED(f);
+        trytoquit();
+        if(downloaded){
+                outT0(Hexit);
+                return TRUE;
+        }
+        return FALSE;
+}
+
+int
+s_cmd(File *f, Cmd *cp)
+{
+        int i, j, c, n;
+        Posn p1, op, didsub = 0, delta = 0;
+
+        n = cp->num;
+        op= -1;
+        compile(cp->re);
+        for(p1 = addr.r.p1; p1<=addr.r.p2 && execute(f, p1, addr.r.p2); ){
+                if(sel.p[0].p1==sel.p[0].p2){        /* empty match? */
+                        if(sel.p[0].p1==op){
+                                p1++;
+                                continue;
+                        }
+                        p1 = sel.p[0].p2+1;
+                }else
+                        p1 = sel.p[0].p2;
+                op = sel.p[0].p2;
+                if(--n>0)
+                        continue;
+                Strzero(&genstr);
+                for(i = 0; ictext->n; i++)
+                        if((c = cp->ctext->s[i])=='\\' && ictext->n-1){
+                                c = cp->ctext->s[++i];
+                                if('1'<=c && c<='9') {
+                                        j = c-'0';
+                                        if(sel.p[j].p2-sel.p[j].p1>BLOCKSIZE)
+                                                error(Elongtag);
+                                        bufread(f, sel.p[j].p1, genbuf, sel.p[j].p2-sel.p[j].p1);
+                                        Strinsert(&genstr, tmprstr(genbuf, (sel.p[j].p2-sel.p[j].p1)), genstr.n);
+                                }else
+                                         Straddc(&genstr, c);
+                        }else if(c!='&')
+                                Straddc(&genstr, c);
+                        else{
+                                if(sel.p[0].p2-sel.p[0].p1>BLOCKSIZE)
+                                        error(Elongrhs);
+                                bufread(f, sel.p[0].p1, genbuf, sel.p[0].p2-sel.p[0].p1);
+                                Strinsert(&genstr,
+                                        tmprstr(genbuf, (int)(sel.p[0].p2-sel.p[0].p1)),
+                                        genstr.n);
+                        }
+                if(sel.p[0].p1!=sel.p[0].p2){
+                        logdelete(f, sel.p[0].p1, sel.p[0].p2);
+                        delta-=sel.p[0].p2-sel.p[0].p1;
+                }
+                if(genstr.n){
+                        loginsert(f, sel.p[0].p2, genstr.s, genstr.n);
+                        delta+=genstr.n;
+                }
+                didsub = 1;
+                if(!cp->flag)
+                        break;
+        }
+        if(!didsub && nest==0)
+                error(Enosub);
+        f->ndot.r.p1 = addr.r.p1, f->ndot.r.p2 = addr.r.p2+delta;
+        return TRUE;
+}
+
+int
+u_cmd(File *f, Cmd *cp)
+{
+        int n;
+
+        USED(f);
+        USED(cp);
+        n = cp->num;
+        if(n >= 0)
+                while(n-- && undo(TRUE))
+                        ;
+        else
+                while(n++ && undo(FALSE))
+                        ;
+        return TRUE;
+}
+
+int
+w_cmd(File *f, Cmd *cp)
+{
+        int fseq;
+
+        fseq = f->seq;
+        if(getname(f, cp->ctext, FALSE)==0)
+                error(Enoname);
+        if(fseq == seq)
+                error_s(Ewseq, genc);
+        writef(f);
+        return TRUE;
+}
+
+int
+x_cmd(File *f, Cmd *cp)
+{
+        if(cp->re)
+                looper(f, cp, cp->cmdc=='x');
+        else
+                linelooper(f, cp);
+        return TRUE;
+}
+
+int
+X_cmd(File *f, Cmd *cp)
+{
+        USED(f);
+        filelooper(cp, cp->cmdc=='X');
+        return TRUE;
+}
+
+int
+plan9_cmd(File *f, Cmd *cp)
+{
+        plan9(f, cp->cmdc, cp->ctext, nest);
+        return TRUE;
+}
+
+int
+eq_cmd(File *f, Cmd *cp)
+{
+        int charsonly;
+
+        switch(cp->ctext->n){
+        case 1:
+                charsonly = FALSE;
+                break;
+        case 2:
+                if(cp->ctext->s[0]=='#'){
+                        charsonly = TRUE;
+                        break;
+                }
+        default:
+                SET(charsonly);
+                error(Enewline);
+        }
+        printposn(f, charsonly);
+        return TRUE;
+}
+
+int
+nl_cmd(File *f, Cmd *cp)
+{
+        Address a;
+
+        if(cp->addr == 0){
+                /* First put it on newline boundaries */
+                addr = lineaddr((Posn)0, f->dot, -1);
+                a = lineaddr((Posn)0, f->dot, 1);
+                addr.r.p2 = a.r.p2;
+                if(addr.r.p1==f->dot.r.p1 && addr.r.p2==f->dot.r.p2)
+                        addr = lineaddr((Posn)1, f->dot, 1);
+                display(f);
+        }else if(downloaded)
+                moveto(f, addr.r);
+        else
+                display(f);
+        return TRUE;
+}
+
+int
+cd_cmd(File *f, Cmd *cp)
+{
+        USED(f);
+        cd(cp->ctext);
+        return TRUE;
+}
+
+int
+append(File *f, Cmd *cp, Posn p)
+{
+        if(cp->ctext->n>0 && cp->ctext->s[cp->ctext->n-1]==0)
+                --cp->ctext->n;
+        if(cp->ctext->n>0)
+                loginsert(f, p, cp->ctext->s, cp->ctext->n);
+        f->ndot.r.p1 = p;
+        f->ndot.r.p2 = p+cp->ctext->n;
+        return TRUE;
+}
+
+int
+display(File *f)
+{
+        Posn p1, p2;
+        int np;
+        char *c;
+
+        p1 = addr.r.p1;
+        p2 = addr.r.p2;
+        if(p2 > f->_.nc){
+                fprint(2, "bad display addr p1=%ld p2=%ld f->_.nc=%d\n", p1, p2, f->_.nc); /*ZZZ should never happen, can remove */
+                p2 = f->_.nc;
+        }
+        while(p1 < p2){
+                np = p2-p1;
+                if(np>BLOCKSIZE-1)
+                        np = BLOCKSIZE-1;
+                bufread(f, p1, genbuf, np);
+                genbuf[np] = 0;
+                c = Strtoc(tmprstr(genbuf, np+1));
+                if(downloaded)
+                        termwrite(c);
+                else
+                        Write(1, c, strlen(c));
+                free(c);
+                p1 += np;
+        }
+        f->dot = addr;
+        return TRUE;
+}
+
+void
+looper(File *f, Cmd *cp, int xy)
+{
+        Posn p, op;
+        Range r;
+
+        r = addr.r;
+        op= xy? -1 : r.p1;
+        nest++;
+        compile(cp->re);
+        for(p = r.p1; p<=r.p2; ){
+                if(!execute(f, p, r.p2)){ /* no match, but y should still run */
+                        if(xy || op>r.p2)
+                                break;
+                        f->dot.r.p1 = op, f->dot.r.p2 = r.p2;
+                        p = r.p2+1;        /* exit next loop */
+                }else{
+                        if(sel.p[0].p1==sel.p[0].p2){        /* empty match? */
+                                if(sel.p[0].p1==op){
+                                        p++;
+                                        continue;
+                                }
+                                p = sel.p[0].p2+1;
+                        }else
+                                p = sel.p[0].p2;
+                        if(xy)
+                                f->dot.r = sel.p[0];
+                        else
+                                f->dot.r.p1 = op, f->dot.r.p2 = sel.p[0].p1;
+                }
+                op = sel.p[0].p2;
+                cmdexec(f, cp->ccmd);
+                compile(cp->re);
+        }
+        --nest;
+}
+
+void
+linelooper(File *f, Cmd *cp)
+{
+        Posn p;
+        Range r, linesel;
+        Address a, a3;
+
+        nest++;
+        r = addr.r;
+        a3.f = f;
+        a3.r.p1 = a3.r.p2 = r.p1;
+        for(p = r.p1; p= r.p2)
+                        break;
+                if(linesel.p2 >= r.p2)
+                        linesel.p2 = r.p2;
+                if(linesel.p2 > linesel.p1)
+                        if(linesel.p1>=a3.r.p2 && linesel.p2>a3.r.p2){
+                                f->dot.r = linesel;
+                                cmdexec(f, cp->ccmd);
+                                a3.r = linesel;
+                                continue;
+                        }
+                break;
+        }
+        --nest;
+}
+
+void
+filelooper(Cmd *cp, int XY)
+{
+        File *f, *cur;
+        int i;
+
+        if(Glooping++)
+                error(EnestXY);
+        nest++;
+        settempfile();
+        cur = curfile;
+        for(i = 0; ire==0 || filematch(f, cp->re)==XY)
+                        cmdexec(f, cp->ccmd);
+        }
+        if(cur && whichmenu(cur)>=0)        /* check that cur is still a file */
+                current(cur);
+        --Glooping;
+        --nest;
+}
diff --git a/src/libdraw/BOT b/src/libdraw/BOT
diff --git a/src/libdraw/Make.Darwin-PowerMacintosh b/src/libdraw/Make.Darwin-PowerMacintosh
t@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libdraw/Make.FreeBSD-386 b/src/libdraw/Make.FreeBSD-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libdraw/Make.HP-UX-9000 b/src/libdraw/Make.HP-UX-9000
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libdraw/Make.Linux-386 b/src/libdraw/Make.Linux-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libdraw/Make.NetBSD-386 b/src/libdraw/Make.NetBSD-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libdraw/Make.OSF1-alpha b/src/libdraw/Make.OSF1-alpha
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libdraw/Make.SunOS-sun4u b/src/libdraw/Make.SunOS-sun4u
t@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/libdraw/Make.SunOS-sun4u-cc b/src/libdraw/Make.SunOS-sun4u-cc
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libdraw/Make.SunOS-sun4u-gcc b/src/libdraw/Make.SunOS-sun4u-gcc
t@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libdraw/Makefile b/src/libdraw/Makefile
t@@ -0,0 +1,194 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+LIB=libdraw.a
+VERSION=2.0
+PORTPLACE=devel/libdraw
+NAME=libdraw
+
+# keyboard.$O\
+# newwindow.$O\
+OFILES=\
+        alloc.$O\
+        allocimagemix.$O\
+        arith.$O\
+        bezier.$O\
+        border.$O\
+        buildfont.$O\
+        bytesperline.$O\
+        chan.$O\
+        cloadimage.$O\
+        computil.$O\
+        creadimage.$O\
+        debug.$O\
+        defont.$O\
+        draw.$O\
+        drawrepl.$O\
+        egetrect.$O\
+        ellipse.$O\
+        emenuhit.$O\
+        font.$O\
+        freesubfont.$O\
+        getdefont.$O\
+        getrect.$O\
+        getsubfont.$O\
+        icossin.$O\
+        icossin2.$O\
+        init.$O\
+        line.$O\
+        loadimage.$O\
+        menuhit.$O\
+        mkfont.$O\
+        openfont.$O\
+        poly.$O\
+        readcolmap.$O\
+        readimage.$O\
+        readsubfont.$O\
+        rectclip.$O\
+        replclipr.$O\
+        rgb.$O\
+        string.$O\
+        stringbg.$O\
+        stringsubfont.$O\
+        stringwidth.$O\
+        subfont.$O\
+        subfontcache.$O\
+        subfontname.$O\
+        unloadimage.$O\
+        window.$O\
+        writecolmap.$O\
+        writeimage.$O\
+        writesubfont.$O\
+        md-alloc.$O\
+        md-arc.$O\
+        md-cload.$O\
+        md-cmap.$O\
+        md-cread.$O\
+        md-defont.$O\
+        md-draw.$O\
+        md-ellipse.$O\
+        md-fillpoly.$O\
+        md-hwdraw.$O\
+        md-iprint.$O\
+        md-line.$O\
+        md-load.$O\
+        md-openmemsubfont.$O\
+        md-poly.$O\
+        md-read.$O\
+        md-string.$O\
+        md-subfont.$O\
+        md-unload.$O\
+        md-write.$O\
+        ml-draw.$O\
+        ml-lalloc.$O\
+        ml-layerop.$O\
+        ml-ldelete.$O\
+        ml-lhide.$O\
+        ml-line.$O\
+        ml-load.$O\
+        ml-lorigin.$O\
+        ml-lsetrefresh.$O\
+        ml-ltofront.$O\
+        ml-ltorear.$O\
+        ml-unload.$O\
+        x11-alloc.$O\
+        x11-cload.$O\
+        x11-draw.$O\
+        x11-event.$O\
+        x11-fill.$O\
+        x11-get.$O\
+        x11-init.$O\
+        x11-itrans.$O\
+        x11-keyboard.$O\
+        x11-load.$O\
+        x11-mouse.$O\
+        x11-pixelbits.$O\
+        x11-unload.$O\
+        devdraw.$O\
+        unix.$O\
+
+HFILES=\
+        draw.h\
+        memdraw.h
+
+all: $(LIB)
+
+install: $(LIB)
+        install -c -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+        install -c -m 0644 draw.h $(PREFIX)/include/draw.h
+        install -c -m 0644 event.h $(PREFIX)/include/event.h
+        install -c -m 0644 cursor.h $(PREFIX)/include/cursor.h
+        install -c -m 0644 mouse.h $(PREFIX)/include/mouse.h
+        install -c -m 0644 keyboard.h $(PREFIX)/include/keyboard.h
+
+test: test.o $(LIB)
+        gcc -o test test.o $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf -L/usr/X11R6/lib -lX11 -lm
+
+$(LIB): $(OFILES)
+        $(AR) $(ARFLAGS) $(LIB) $(OFILES)
+
+NUKEFILES+=$(LIB)
+.c.$O:
+        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+        rm -rf $(NAME)-$(VERSION)
+        mkdir $(NAME)-$(VERSION)
+        cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+        tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+        rm -rf $(NAME)-$(VERSION)
+
+clean:
+        rm -f $(OFILES) $(LIB)
+
+nuke:
+        rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+        make tgz
+        cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+        rpm -ba rpm.spec
+        cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+        cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+        scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+        make tgz
+        rm -rf $(PORTDIR)
+        mkdir $(PORTDIR)
+        cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+        cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+        (cd $(PORTDIR); make makesum)
+        (cd $(PORTDIR); make)
+        (cd $(PORTDIR); /usr/local/bin/portlint)
+        rm -rf $(PORTDIR)/work
+        shar `find $(PORTDIR)` > ports.shar
+        (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+        scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/libdraw/Makefile.MID b/src/libdraw/Makefile.MID
t@@ -0,0 +1,123 @@
+LIB=libdraw.a
+VERSION=2.0
+PORTPLACE=devel/libdraw
+NAME=libdraw
+
+# keyboard.$O\
+# newwindow.$O\
+OFILES=\
+        alloc.$O\
+        allocimagemix.$O\
+        arith.$O\
+        bezier.$O\
+        border.$O\
+        buildfont.$O\
+        bytesperline.$O\
+        chan.$O\
+        cloadimage.$O\
+        computil.$O\
+        creadimage.$O\
+        debug.$O\
+        defont.$O\
+        draw.$O\
+        drawrepl.$O\
+        egetrect.$O\
+        ellipse.$O\
+        emenuhit.$O\
+        font.$O\
+        freesubfont.$O\
+        getdefont.$O\
+        getrect.$O\
+        getsubfont.$O\
+        icossin.$O\
+        icossin2.$O\
+        init.$O\
+        line.$O\
+        loadimage.$O\
+        menuhit.$O\
+        mkfont.$O\
+        openfont.$O\
+        poly.$O\
+        readcolmap.$O\
+        readimage.$O\
+        readsubfont.$O\
+        rectclip.$O\
+        replclipr.$O\
+        rgb.$O\
+        string.$O\
+        stringbg.$O\
+        stringsubfont.$O\
+        stringwidth.$O\
+        subfont.$O\
+        subfontcache.$O\
+        subfontname.$O\
+        unloadimage.$O\
+        window.$O\
+        writecolmap.$O\
+        writeimage.$O\
+        writesubfont.$O\
+        md-alloc.$O\
+        md-arc.$O\
+        md-cload.$O\
+        md-cmap.$O\
+        md-cread.$O\
+        md-defont.$O\
+        md-draw.$O\
+        md-ellipse.$O\
+        md-fillpoly.$O\
+        md-hwdraw.$O\
+        md-iprint.$O\
+        md-line.$O\
+        md-load.$O\
+        md-openmemsubfont.$O\
+        md-poly.$O\
+        md-read.$O\
+        md-string.$O\
+        md-subfont.$O\
+        md-unload.$O\
+        md-write.$O\
+        ml-draw.$O\
+        ml-lalloc.$O\
+        ml-layerop.$O\
+        ml-ldelete.$O\
+        ml-lhide.$O\
+        ml-line.$O\
+        ml-load.$O\
+        ml-lorigin.$O\
+        ml-lsetrefresh.$O\
+        ml-ltofront.$O\
+        ml-ltorear.$O\
+        ml-unload.$O\
+        x11-alloc.$O\
+        x11-cload.$O\
+        x11-draw.$O\
+        x11-event.$O\
+        x11-fill.$O\
+        x11-get.$O\
+        x11-init.$O\
+        x11-itrans.$O\
+        x11-keyboard.$O\
+        x11-load.$O\
+        x11-mouse.$O\
+        x11-pixelbits.$O\
+        x11-unload.$O\
+        devdraw.$O\
+        unix.$O\
+
+HFILES=\
+        draw.h\
+        memdraw.h
+
+all: $(LIB)
+
+install: $(LIB)
+        install -c -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+        install -c -m 0644 draw.h $(PREFIX)/include/draw.h
+        install -c -m 0644 event.h $(PREFIX)/include/event.h
+        install -c -m 0644 cursor.h $(PREFIX)/include/cursor.h
+        install -c -m 0644 mouse.h $(PREFIX)/include/mouse.h
+        install -c -m 0644 keyboard.h $(PREFIX)/include/keyboard.h
+
+test: test.o $(LIB)
+        gcc -o test test.o $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf -L/usr/X11R6/lib -lX11 -lm
+
diff --git a/src/libdraw/alloc.c b/src/libdraw/alloc.c
t@@ -0,0 +1,237 @@
+#include 
+#include 
+#include 
+
+Image*
+allocimage(Display *d, Rectangle r, u32int chan, int repl, u32int val)
+{
+        return _allocimage(nil, d, r, chan, repl, val, 0, 0);
+}
+
+Image*
+_allocimage(Image *ai, Display *d, Rectangle r, u32int chan, int repl, u32int val, int screenid, int refresh)
+{
+        uchar *a;
+        char *err;
+        Image *i;
+        Rectangle clipr;
+        int id;
+        int depth;
+
+        err = 0;
+        i = 0;
+
+        if(chan == 0){
+                werrstr("bad channel descriptor");
+                return nil;
+        }
+
+        depth = chantodepth(chan);
+        if(depth == 0){
+                err = "bad channel descriptor";
+    Error:
+                if(err)
+                        werrstr("allocimage: %s", err);
+                else
+                        werrstr("allocimage: %r");
+                free(i);
+                return 0;
+        }
+
+        /* flush pending data so we don't get error allocating the image */
+        flushimage(d, 0);
+        a = bufimage(d, 1+4+4+1+4+1+4*4+4*4+4);
+        if(a == 0)
+                goto Error;
+        d->imageid++;
+        id = d->imageid;
+        a[0] = 'b';
+        BPLONG(a+1, id);
+        BPLONG(a+5, screenid);
+        a[9] = refresh;
+        BPLONG(a+10, chan);
+        a[14] = repl;
+        BPLONG(a+15, r.min.x);
+        BPLONG(a+19, r.min.y);
+        BPLONG(a+23, r.max.x);
+        BPLONG(a+27, r.max.y);
+        if(repl)
+                /* huge but not infinite, so various offsets will leave it huge, not overflow */
+                clipr = Rect(-0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF);
+        else
+                clipr = r;
+        BPLONG(a+31, clipr.min.x);
+        BPLONG(a+35, clipr.min.y);
+        BPLONG(a+39, clipr.max.x);
+        BPLONG(a+43, clipr.max.y);
+        BPLONG(a+47, val);
+        if(flushimage(d, 0) < 0)
+                goto Error;
+
+        if(ai)
+                i = ai;
+        else{
+                i = malloc(sizeof(Image));
+                if(i == nil){
+                        a = bufimage(d, 1+4);
+                        if(a){
+                                a[0] = 'f';
+                                BPLONG(a+1, id);
+                                flushimage(d, 0);
+                        }
+                        goto Error;
+                }
+        }
+        i->display = d;
+        i->id = id;
+        i->depth = depth;
+        i->chan = chan;
+        i->r = r;
+        i->clipr = clipr;
+        i->repl = repl;
+        i->screen = 0;
+        i->next = 0;
+        return i;
+}
+
+Image*
+namedimage(Display *d, char *name)
+{
+        uchar *a;
+        char *err, buf[12*12+1];
+        Image *i;
+        int id, n;
+        u32int chan;
+
+        err = 0;
+        i = 0;
+
+        n = strlen(name);
+        if(n >= 256){
+                err = "name too long";
+    Error:
+                if(err)
+                        werrstr("namedimage: %s", err);
+                else
+                        werrstr("namedimage: %r");
+                if(i)
+                        free(i);
+                return 0;
+        }
+        /* flush pending data so we don't get error allocating the image */
+        flushimage(d, 0);
+        a = bufimage(d, 1+4+1+n+1);
+        if(a == 0)
+                goto Error;
+        d->imageid++;
+        id = d->imageid;
+        a[0] = 'n';
+        BPLONG(a+1, id);
+        a[5] = n;
+        memmove(a+6, name, n);
+        a[6+n] = 'I';
+        if(flushimage(d, 0) < 0)
+                goto Error;
+        if(_drawmsgread(d, buf, sizeof buf) < 12*12)
+                goto Error;
+        buf[12*12] = '\0';
+
+        i = malloc(sizeof(Image));
+        if(i == nil){
+        Error1:
+                a = bufimage(d, 1+4);
+                if(a){
+                        a[0] = 'f';
+                        BPLONG(a+1, id);
+                        flushimage(d, 0);
+                }
+                goto Error;
+        }
+        i->display = d;
+        i->id = id;
+        if((chan=strtochan(buf+2*12))==0){
+                werrstr("bad channel '%.12s' from devdraw", buf+2*12);
+                goto Error1;
+        }
+        i->chan = chan;
+        i->depth = chantodepth(chan);
+        i->repl = atoi(buf+3*12);
+        i->r.min.x = atoi(buf+4*12);
+        i->r.min.y = atoi(buf+5*12);
+        i->r.max.x = atoi(buf+6*12);
+        i->r.max.y = atoi(buf+7*12);
+        i->clipr.min.x = atoi(buf+8*12);
+        i->clipr.min.y = atoi(buf+9*12);
+        i->clipr.max.x = atoi(buf+10*12);
+        i->clipr.max.y = atoi(buf+11*12);
+        i->screen = 0;
+        i->next = 0;
+        return i;
+}
+
+int
+nameimage(Image *i, char *name, int in)
+{
+        uchar *a;
+        int n;
+
+        n = strlen(name);
+        a = bufimage(i->display, 1+4+1+1+n);
+        if(a == 0)
+                return 0;
+        a[0] = 'N';
+        BPLONG(a+1, i->id);
+        a[5] = in;
+        a[6] = n;
+        memmove(a+7, name, n);
+        if(flushimage(i->display, 0) < 0)
+                return 0;
+        return 1;
+}
+
+int
+_freeimage1(Image *i)
+{
+        uchar *a;
+        Display *d;
+        Image *w;
+
+        if(i == 0)
+                return 0;
+        /* make sure no refresh events occur on this if we block in the write */
+        d = i->display;
+        /* flush pending data so we don't get error deleting the image */
+        flushimage(d, 0);
+        a = bufimage(d, 1+4);
+        if(a == 0)
+                return -1;
+        a[0] = 'f';
+        BPLONG(a+1, i->id);
+        if(i->screen){
+                w = d->windows;
+                if(w == i)
+                        d->windows = i->next;
+                else
+                        while(w){
+                                if(w->next == i){
+                                        w->next = i->next;
+                                        break;
+                                }
+                                w = w->next;
+                        }
+        }
+        if(flushimage(d, i->screen!=0) < 0)
+                return -1;
+
+        return 0;
+}
+
+int
+freeimage(Image *i)
+{
+        int ret;
+
+        ret = _freeimage1(i);
+        free(i);
+        return ret;
+}
diff --git a/src/libdraw/arith.c b/src/libdraw/arith.c
t@@ -0,0 +1,206 @@
+#include 
+#include 
+#include 
+
+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=r.min.x && p.x=r.min.y && p.ymin.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;
+}
+
+u32int
+drawld2chan[] = {
+        GREY1,
+        GREY2,
+        GREY4,
+        CMAP8,
+};
+
+int log2[] = { -1, 0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, 4 /* BUG */, -1, -1, -1, -1, -1, -1, -1, 5 };
+
+u32int
+setalpha(u32int color, uchar alpha)
+{
+        int red, green, blue;
+
+        red = (color >> 3*8) & 0xFF;
+        green = (color >> 2*8) & 0xFF;
+        blue = (color >> 1*8) & 0xFF;
+        /* ignore incoming alpha */
+        red = (red * alpha)/255;
+        green = (green * alpha)/255;
+        blue = (blue * alpha)/255;
+        return (red<<3*8) | (green<<2*8) | (blue<<1*8) | (alpha<<0*8);
+}
+
+Point        ZP;
+Rectangle ZR;
+int
+Rfmt(Fmt *f)
+{
+        Rectangle r;
+
+        r = va_arg(f->args, Rectangle);
+        return fmtprint(f, "%P %P", r.min, r.max);
+}
+
+int
+Pfmt(Fmt *f)
+{
+        Point p;
+
+        p = va_arg(f->args, Point);
+        return fmtprint(f, "[%d %d]", p.x, p.y);
+}
+
diff --git a/src/libdraw/buildfont.c b/src/libdraw/buildfont.c
t@@ -0,0 +1,141 @@
+#include 
+#include 
+#include 
+
+static char*
+skip(char *s)
+{
+        while(*s==' ' || *s=='\n' || *s=='\t')
+                s++;
+        return s;
+}
+
+Font*
+buildfont(Display *d, char *buf, char *name)
+{
+        Font *fnt;
+        Cachefont *c;
+        char *s, *t;
+        ulong min, max;
+        int offset;
+        char badform[] = "bad font format: number expected (char position %d)";
+
+        s = buf;
+        fnt = malloc(sizeof(Font));
+        if(fnt == 0)
+                return 0;
+        memset(fnt, 0, sizeof(Font));
+        fnt->display = d;
+        fnt->name = strdup(name);
+        fnt->ncache = NFCACHE+NFLOOK;
+        fnt->nsubf = NFSUBF;
+        fnt->cache = malloc(fnt->ncache * sizeof(fnt->cache[0]));
+        fnt->subf = malloc(fnt->nsubf * sizeof(fnt->subf[0]));
+        if(fnt->name==0 || fnt->cache==0 || fnt->subf==0){
+    Err2:
+                free(fnt->name);
+                free(fnt->cache);
+                free(fnt->subf);
+                free(fnt->sub);
+                free(fnt);
+                return 0;
+        }
+        fnt->height = strtol(s, &s, 0);
+        s = skip(s);
+        fnt->ascent = strtol(s, &s, 0);
+        s = skip(s);
+        if(fnt->height<=0 || fnt->ascent<=0){
+                werrstr("bad height or ascent in font file");
+                goto Err2;
+        }
+        fnt->width = 0;
+        fnt->nsub = 0;
+        fnt->sub = 0;
+
+        memset(fnt->subf, 0, fnt->nsubf * sizeof(fnt->subf[0]));
+        memset(fnt->cache, 0, fnt->ncache*sizeof(fnt->cache[0]));
+        fnt->age = 1;
+        do{
+                /* must be looking at a number now */
+                if(*s<'0' || '9'<*s){
+                        werrstr(badform, s-buf);
+                        goto Err3;
+                }
+                min = strtol(s, &s, 0);
+                s = skip(s);
+                /* must be looking at a number now */
+                if(*s<'0' || '9'<*s){
+                        werrstr(badform, s-buf);
+                        goto Err3;
+                }
+                max = strtol(s, &s, 0);
+                s = skip(s);
+                if(*s==0 || min>=65536 || max>=65536 || min>max){
+                        werrstr("illegal subfont range");
+    Err3:
+                        freefont(fnt);
+                        return 0;
+                }
+                t = s;
+                offset = strtol(s, &t, 0);
+                if(t>s && (*t==' ' || *t=='\t' || *t=='\n'))
+                        s = skip(t);
+                else
+                        offset = 0;
+                fnt->sub = realloc(fnt->sub, (fnt->nsub+1)*sizeof(Cachefont*));
+                if(fnt->sub == 0){
+                        /* realloc manual says fnt->sub may have been destroyed */
+                        fnt->nsub = 0;
+                        goto Err3;
+                }
+                c = malloc(sizeof(Cachefont));
+                if(c == 0)
+                        goto Err3;
+                fnt->sub[fnt->nsub] = c;
+                c->min = min;
+                c->max = max;
+                c->offset = offset;
+                t = s;
+                while(*s && *s!=' ' && *s!='\n' && *s!='\t')
+                        s++;
+                *s++ = 0;
+                c->subfontname = 0;
+                c->name = strdup(t);
+                if(c->name == 0){
+                        free(c);
+                        goto Err3;
+                }
+                s = skip(s);
+                fnt->nsub++;
+        }while(*s);
+        return fnt;
+}
+
+void
+freefont(Font *f)
+{
+        int i;
+        Cachefont *c;
+        Subfont *s;
+
+        if(f == 0)
+                return;
+
+        for(i=0; insub; i++){
+                c = f->sub[i];
+                free(c->subfontname);
+                free(c->name);
+                free(c);
+        }
+        for(i=0; insubf; i++){
+                s = f->subf[i].f;
+                if(s && s!=display->defaultsubfont)
+                        freesubfont(s);
+        }
+        freeimage(f->cacheimage);
+        free(f->name);
+        free(f->cache);
+        free(f->subf);
+        free(f->sub);
+        free(f);
+}
diff --git a/src/libdraw/bytesperline.c b/src/libdraw/bytesperline.c
t@@ -0,0 +1,34 @@
+#include 
+#include 
+#include 
+
+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);
+}
diff --git a/src/libdraw/chan.c b/src/libdraw/chan.c
t@@ -0,0 +1,77 @@
+#include 
+#include 
+#include 
+
+static char channames[] = "rgbkamx";
+char*
+chantostr(char *buf, u32int cc)
+{
+        u32int c, rc;
+        char *p;
+
+        if(chantodepth(cc) == 0)
+                return nil;
+
+        /* reverse the channel descriptor so we can easily generate the string in the right order */
+        rc = 0;
+        for(c=cc; c; c>>=8){
+                rc <<= 8;
+                rc |= c&0xFF;
+        }
+
+        p = buf;
+        for(c=rc; c; c>>=8) {
+                *p++ = channames[TYPE(c)];
+                *p++ = '0'+NBITS(c);
+        }
+        *p = 0;
+
+        return buf;
+}
+
+/* avoid pulling in ctype when using with drawterm etc. */
+static int
+isspace(char c)
+{
+        return c==' ' || c== '\t' || c=='\r' || c=='\n';
+}
+
+u32int
+strtochan(char *s)
+{
+        char *p, *q;
+        u32int c;
+        int t, n;
+
+        c = 0;
+        p=s;
+        while(*p && isspace(*p))
+                p++;
+
+        while(*p && !isspace(*p)){
+                if((q = strchr(channames, p[0])) == nil) 
+                        return 0;
+                t = q-channames;
+                if(p[1] < '0' || p[1] > '9')
+                        return 0;
+                n = p[1]-'0';
+                c = (c<<8) | __DC(t, n);
+                p += 2;
+        }
+        return c;
+}
+
+int
+chantodepth(u32int 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;
+}
diff --git a/src/libdraw/creadimage.c b/src/libdraw/creadimage.c
t@@ -0,0 +1,113 @@
+#include 
+#include 
+#include 
+
+Image *
+creadimage(Display *d, int fd, int dolock)
+{
+        char hdr[5*12+1];
+        Rectangle r;
+        int m, nb, miny, maxy, new, ldepth, ncblock;
+        uchar *buf, *a;
+        Image *i;
+        u32int chan;
+
+        if(readn(fd, hdr, 5*12) != 5*12)
+                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;
+        }
+
+        if(dolock)
+                lockdisplay(d);
+        i = allocimage(d, r, chan, 0, 0);
+        if(dolock)
+                unlockdisplay(d);
+        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){
+                Errout:
+                        if(dolock)
+                                lockdisplay(d);
+                Erroutlock:
+                        freeimage(i);
+                        if(dolock)
+                                unlockdisplay(d);
+                        free(buf);
+                        return nil;
+                }
+                maxy = atoi(hdr+0*12);
+                nb = atoi(hdr+1*12);
+                if(maxy<=miny || r.max.ydisplay, 21+nb);
+                if(a == nil)
+                        goto Erroutlock;
+                a[0] = 'Y';
+                BPLONG(a+1, i->id);
+                BPLONG(a+5, r.min.x);
+                BPLONG(a+9, miny);
+                BPLONG(a+13, r.max.x);
+                BPLONG(a+17, maxy);
+                if(!new)        /* old image: flip the data bits */
+                        _twiddlecompressed(buf, nb);
+                memmove(a+21, buf, nb);
+                if(dolock)
+                        unlockdisplay(d);
+                miny = maxy;
+        }
+        free(buf);
+        return i;
+}
diff --git a/src/libdraw/cursor.h b/src/libdraw/cursor.h
t@@ -0,0 +1,7 @@
+typedef struct Cursor Cursor;
+struct        Cursor
+{
+        Point        offset;
+        uchar        clr[2*16];
+        uchar        set[2*16];
+};
diff --git a/src/libdraw/devdraw.c b/src/libdraw/devdraw.c
t@@ -0,0 +1,1587 @@
+/*
+ * /dev/draw simulator -- handles the messages prepared by the draw library.
+ * Includes all the memlayer code even though most programs don't use it.
+ * This whole approach is overkill, but cpu is cheap and it keeps things simple.
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define NHASH (1<<5)
+#define HASHMASK (NHASH-1)
+
+extern void flushmemscreen(Rectangle);
+
+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
+{
+        QLock                lk;
+        int                clientid;
+        int                nclient;
+        Client*                client[1];
+        int                nname;
+        DName*                name;
+        int                vers;
+        int                softscreen;
+};
+
+struct Client
+{
+        /*Ref                r;*/
+        DImage*                dimage[NHASH];
+        CScreen*        cscreen;
+        Refresh*        refresh;
+        Rendez                refrend;
+        uchar*                readdata;
+        int                nreaddata;
+        int                busy;
+        int                clientid;
+        int                slot;
+        int                refreshme;
+        int                infoid;
+        int                op;
+};
+
+struct Refresh
+{
+        DImage*                dimage;
+        Rectangle        r;
+        Refresh*        next;
+};
+
+struct Refx
+{
+        Client*                client;
+        DImage*                dimage;
+};
+
+struct DName
+{
+        char                        *name;
+        Client        *client;
+        DImage*                dimage;
+        int                        vers;
+};
+
+struct FChar
+{
+        int                minx;        /* left edge of bits */
+        int                maxx;        /* right edge of bits */
+        uchar                miny;        /* first non-zero scan-line */
+        uchar                maxy;        /* last non-zero scan-line + 1 */
+        schar                left;        /* offset of baseline */
+        uchar                width;        /* width of baseline */
+};
+
+/*
+ * Reference counts in DImages:
+ *        one per open by original client
+ *        one per screen image or fill
+ *         one per image derived from this one by name
+ */
+struct DImage
+{
+        int                id;
+        int                ref;
+        char                *name;
+        int                vers;
+        Memimage*        image;
+        int                ascent;
+        int                nfchar;
+        FChar*                fchar;
+        DScreen*        dscreen;        /* 0 if not a window */
+        DImage*        fromname;        /* image this one is derived from, by name */
+        DImage*                next;
+};
+
+struct CScreen
+{
+        DScreen*        dscreen;
+        CScreen*        next;
+};
+
+struct DScreen
+{
+        int                id;
+        int                public;
+        int                ref;
+        DImage        *dimage;
+        DImage        *dfill;
+        Memscreen*        screen;
+        Client*                owner;
+        DScreen*        next;
+};
+
+static        Draw                sdraw;
+static        Client                *client0;
+static        Memimage        *screenimage;
+static        Rectangle        flushrect;
+static        int                waste;
+static        DScreen*        dscreen;
+static        int                drawuninstall(Client*, int);
+static        Memimage*        drawinstall(Client*, int, Memimage*, DScreen*);
+static        void                drawfreedimage(DImage*);
+
+void
+_initdisplaymemimage(Display *d, Memimage *m)
+{
+        screenimage = m;
+        client0 = mallocz(sizeof(Client), 1);
+        if(client0 == nil){
+                fprint(2, "initdraw: allocating client0: out of memory");
+                abort();
+        }
+        client0->slot = 0;
+        client0->clientid = ++sdraw.clientid;
+        client0->op = SoverD;
+        sdraw.client[0] = client0;
+        sdraw.nclient = 1;
+}
+
+void
+_drawreplacescreenimage(Memimage *m)
+{
+        /*
+         * Replace the screen image because the screen
+         * was resized.
+         * 
+         * In theory there should only be one reference
+         * to the current screen image, and that's through
+         * client0's image 0, installed a few lines above.
+         * Once the client drops the image, the underlying backing 
+         * store freed properly.  The client is being notified
+         * about the resize through external means, so all we
+         * need to do is this assignment.
+         */
+        screenimage = m;
+}
+
+static
+void
+drawrefreshscreen(DImage *l, Client *client)
+{
+        while(l != nil && l->dscreen == nil)
+                l = l->fromname;
+        if(l != nil && l->dscreen->owner != client)
+                l->dscreen->owner->refreshme = 1;
+}
+
+static
+void
+drawrefresh(Memimage *m, Rectangle r, void *v)
+{
+        Refx *x;
+        DImage *d;
+        Client *c;
+        Refresh *ref;
+
+        USED(m);
+
+        if(v == 0)
+                return;
+        x = v;
+        c = x->client;
+        d = x->dimage;
+        for(ref=c->refresh; ref; ref=ref->next)
+                if(ref->dimage == d){
+                        combinerect(&ref->r, r);
+                        return;
+                }
+        ref = mallocz(sizeof(Refresh), 1);
+        if(ref){
+                ref->dimage = d;
+                ref->r = r;
+                ref->next = c->refresh;
+                c->refresh = ref;
+        }
+}
+
+static void
+addflush(Rectangle r)
+{
+        int abb, ar, anbb;
+        Rectangle nbb;
+
+        if(sdraw.softscreen==0 || !rectclip(&r, screenimage->r))
+                return;
+
+        if(flushrect.min.x >= flushrect.max.x){
+                flushrect = r;
+                waste = 0;
+                return;
+        }
+        nbb = flushrect;
+        combinerect(&nbb, r);
+        ar = Dx(r)*Dy(r);
+        abb = Dx(flushrect)*Dy(flushrect);
+        anbb = Dx(nbb)*Dy(nbb);
+        /*
+         * Area of new waste is area of new bb minus area of old bb,
+         * less the area of the new segment, which we assume is not waste.
+         * This could be negative, but that's OK.
+         */
+        waste += anbb-abb - ar;
+        if(waste < 0)
+                waste = 0;
+        /*
+         * absorb if:
+         *        total area is small
+         *        waste is less than half total area
+         *         rectangles touch
+         */
+        if(anbb<=1024 || waste*2layer;
+        if(l == nil)
+                return;
+        do{
+                if(l->screen->image->data != screenimage->data)
+                        return;
+                r = rectaddpt(r, l->delta);
+                l = l->screen->image->layer;
+        }while(l);
+        addflush(r);
+}
+
+static
+void
+drawflush(void)
+{
+        if(flushrect.min.x < flushrect.max.x)
+                flushmemscreen(flushrect);
+        flushrect = Rect(10000, 10000, -10000, -10000);
+}
+
+static
+int
+drawcmp(char *a, char *b, int n)
+{
+        if(strlen(a) != n)
+                return 1;
+        return memcmp(a, b, n);
+}
+
+static
+DName*
+drawlookupname(int n, char *str)
+{
+        DName *name, *ename;
+
+        name = sdraw.name;
+        ename = &name[sdraw.nname];
+        for(; namename, str, n) == 0)
+                        return name;
+        return 0;
+}
+
+static
+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;
+}
+
+static
+DImage*
+drawlookup(Client *client, int id, int checkname)
+{
+        DImage *d;
+
+        d = client->dimage[id&HASHMASK];
+        while(d){
+                if(d->id == id){
+                        /*
+                         * BUG: should error out but too hard.
+                         * Return 0 instead.
+                         */
+                        if(checkname && !drawgoodname(d))
+                                return 0;
+                        return d;
+                }
+                d = d->next;
+        }
+        return 0;
+}
+
+static
+DScreen*
+drawlookupdscreen(int id)
+{
+        DScreen *s;
+
+        s = dscreen;
+        while(s){
+                if(s->id == id)
+                        return s;
+                s = s->next;
+        }
+        return 0;
+}
+
+static
+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;
+        }
+        /* caller must check! */
+        return 0;
+}
+
+static
+Memimage*
+drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen)
+{
+        DImage *d;
+
+        d = mallocz(sizeof(DImage), 1);
+        if(d == 0)
+                return 0;
+        d->id = id;
+        d->ref = 1;
+        d->name = 0;
+        d->vers = 0;
+        d->image = i;
+        d->nfchar = 0;
+        d->fchar = 0;
+        d->fromname = 0;
+        d->dscreen = dscreen;
+        d->next = client->dimage[id&HASHMASK];
+        client->dimage[id&HASHMASK] = d;
+        return i;
+}
+
+static
+Memscreen*
+drawinstallscreen(Client *client, DScreen *d, int id, DImage *dimage, DImage *dfill, int public)
+{
+        Memscreen *s;
+        CScreen *c;
+
+        c = mallocz(sizeof(CScreen), 1);
+        if(dimage && dimage->image && dimage->image->chan == 0){
+                print("bad image %p in drawinstallscreen", dimage->image);
+                abort();
+        }
+
+        if(c == 0)
+                return 0;
+        if(d == 0){
+                d = mallocz(sizeof(DScreen), 1);
+                if(d == 0){
+                        free(c);
+                        return 0;
+                }
+                s = mallocz(sizeof(Memscreen), 1);
+                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;
+}
+
+static
+void
+drawdelname(DName *name)
+{
+        int i;
+
+        i = name-sdraw.name;
+        memmove(name, name+1, (sdraw.nname-(i+1))*sizeof(DName));
+        sdraw.nname--;
+}
+
+static
+void
+drawfreedscreen(DScreen *this)
+{
+        DScreen *ds, *next;
+
+        this->ref--;
+        if(this->ref < 0)
+                print("negative ref in drawfreedscreen\n");
+        if(this->ref > 0)
+                return;
+        ds = dscreen;
+        if(ds == this){
+                dscreen = this->next;
+                goto Found;
+        }
+        while(next = ds->next){        /* assign = */
+                if(next == this){
+                        ds->next = this->next;
+                        goto Found;
+                }
+                ds = next;
+        }
+        /*
+         * Should signal Enodrawimage, but too hard.
+         */
+        return;
+
+    Found:
+        if(this->dimage)
+                drawfreedimage(this->dimage);
+        if(this->dfill)
+                drawfreedimage(this->dfill);
+        free(this->screen);
+        free(this);
+}
+
+static
+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; ifromname){        /* acquired by name; owned by someone else*/
+                drawfreedimage(dimage->fromname);
+                goto Return;
+        }
+        //if(dimage->image == screenimage)        /* don't free the display */
+        //        goto Return;
+        ds = dimage->dscreen;
+        if(ds){
+                l = dimage->image;
+                if(l->data == screenimage->data)
+                        addflush(l->layer->screenr);
+                if(l->layer->refreshfn == drawrefresh)        /* else true owner will clean up */
+                        free(l->layer->refreshptr);
+                l->layer->refreshptr = nil;
+                if(drawgoodname(dimage))
+                        memldelete(l);
+                else
+                        memlfree(l);
+                drawfreedscreen(ds);
+        }else
+                freememimage(dimage->image);
+    Return:
+        free(dimage->fchar);
+        free(dimage);
+}
+
+static
+void
+drawuninstallscreen(Client *client, CScreen *this)
+{
+        CScreen *cs, *next;
+
+        cs = client->cscreen;
+        if(cs == this){
+                client->cscreen = this->next;
+                drawfreedscreen(this->dscreen);
+                free(this);
+                return;
+        }
+        while(next = cs->next){        /* assign = */
+                if(next == this){
+                        cs->next = this->next;
+                        drawfreedscreen(this->dscreen);
+                        free(this);
+                        return;
+                }
+                cs = next;
+        }
+}
+
+static
+int
+drawuninstall(Client *client, int id)
+{
+        DImage *d, *next;
+
+        d = client->dimage[id&HASHMASK];
+        if(d == 0)
+                return -1;
+        if(d->id == id){
+                client->dimage[id&HASHMASK] = d->next;
+                drawfreedimage(d);
+                return 0;
+        }
+        while(next = d->next){        /* assign = */
+                if(next->id == id){
+                        d->next = next->next;
+                        drawfreedimage(next);
+                        return 0;
+                }
+                d = next;
+        }
+        return -1;
+}
+
+static
+int
+drawaddname(Client *client, DImage *di, int n, char *str, char **err)
+{
+        DName *name, *ename, *new, *t;
+        char *ns;
+
+        name = sdraw.name;
+        ename = &name[sdraw.nname];
+        for(; namename, str, n) == 0){
+                        *err = "image name in use";
+                        return -1;
+                }
+        t = mallocz((sdraw.nname+1)*sizeof(DName), 1);
+        ns = malloc(n+1);
+        if(t == nil || ns == nil){
+                free(t);
+                free(ns);
+                *err = "out of memory";
+                return -1;
+        }
+        memmove(t, sdraw.name, sdraw.nname*sizeof(DName));
+        free(sdraw.name);
+        sdraw.name = t;
+        new = &sdraw.name[sdraw.nname++];
+        new->name = ns;
+        memmove(new->name, str, n);
+        new->name[n] = 0;
+        new->dimage = di;
+        new->client = client;
+        new->vers = ++sdraw.vers;
+        return 0;
+}
+
+static int
+drawclientop(Client *cl)
+{
+        int op;
+
+        op = cl->op;
+        cl->op = SoverD;
+        return op;
+}
+
+static
+Memimage*
+drawimage(Client *client, uchar *a)
+{
+        DImage *d;
+
+        d = drawlookup(client, BGLONG(a), 1);
+        if(d == nil)
+                return nil;        /* caller must check! */
+        return d->image;
+}
+
+static
+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);
+}
+
+static
+void
+drawpoint(Point *p, uchar *a)
+{
+        p->x = BGLONG(a+0*4);
+        p->y = BGLONG(a+1*4);
+}
+
+static
+Point
+drawchar(Memimage *dst, Point p, Memimage *src, Point *sp, DImage *font, int index, int op)
+{
+        FChar *fc;
+        Rectangle r;
+        Point sp1;
+
+        fc = &font->fchar[index];
+        r.min.x = p.x+fc->left;
+        r.min.y = p.y-(font->ascent-fc->miny);
+        r.max.x = r.min.x+(fc->maxx-fc->minx);
+        r.max.y = r.min.y+(fc->maxy-fc->miny);
+        sp1.x = sp->x+fc->left;
+        sp1.y = sp->y+fc->miny;
+        memdraw(dst, r, src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+        p.x += fc->width;
+        sp->x += fc->width;
+        return p;
+}
+
+static
+uchar*
+drawcoord(uchar *p, uchar *maxp, int oldx, int *newx)
+{
+        int b, x;
+
+        if(p >= maxp)
+                return nil;
+        b = *p++;
+        x = b & 0x7F;
+        if(b & 0x80){
+                if(p+1 >= maxp)
+                        return nil;
+                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;
+}
+
+int
+_drawmsgread(Display *d, void *a, int n)
+{
+        int inbuf;
+
+        inbuf = d->obufp - d->obuf; 
+        if(n > inbuf)
+                n = inbuf;
+        memmove(a, d->obuf, n);
+        inbuf -= n;
+        if(inbuf)
+                memmove(d->obuf, d->obufp-inbuf, inbuf);
+        d->obufp = d->obuf+inbuf;
+        return n;
+}
+
+static void
+drawmsgsquirrel(Display *d, void *a, int n)
+{
+        uchar *ep;
+
+        ep = d->obuf + d->obufsize;
+        if(d->obufp + n > ep)
+                abort();
+        memmove(d->obufp, a, n);
+        d->obufp += n;
+}
+
+int
+_drawmsgwrite(Display *d, void *v, int n)
+{
+        char cbuf[40], *err, ibuf[12*12+1], *s;
+        int c, ci, doflush, dstid, e0, e1, esize, j, m;
+        int ni, nw, oesize, oldn, op, ox, oy, repl, scrnid, y; 
+        uchar *a, refresh, *u;
+        u32int chan, value;
+        Client *client;
+        CScreen *cs;
+        DImage *di, *ddst, *dsrc, *font, *ll;
+        DName *dn;
+        DScreen *dscrn;
+        FChar *fc;
+        Memimage *dst, *i, *l, **lp, *mask, *src;
+        Memscreen *scrn;
+        Point p, *pp, q, sp;
+        Rectangle clipr, r;
+        Refreshfn reffn;
+        Refx *refx;
+
+        d->obufp = d->obuf;
+        a = v;
+        m = 0;
+        oldn = n;
+        client = client0;
+
+        while((n-=m) > 0){
+                a += m;
+//fprint(2, "msgwrite %d(%d)...", n, *a);
+                switch(*a){
+                default:
+//fprint(2, "bad command %d\n", *a);
+                        err = "bad draw command";
+                        goto error;
+
+                /* allocate: 'b' id[4] screenid[4] refresh[1] chan[4] repl[1]
+                        R[4*4] clipR[4*4] rrggbbaa[4]
+                 */
+                case 'b':
+                        m = 1+4+4+1+4+1+4*4+4*4+4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dstid = BGLONG(a+1);
+                        scrnid = BGSHORT(a+5);
+                        refresh = a[9];
+                        chan = BGLONG(a+10);
+                        repl = a[14];
+                        drawrectangle(&r, a+15);
+                        drawrectangle(&clipr, a+31);
+                        value = BGLONG(a+47);
+                        if(drawlookup(client, dstid, 0))
+                                goto Eimageexists;
+                        if(scrnid){
+                                dscrn = drawlookupscreen(client, scrnid, &cs);
+                                if(!dscrn)
+                                        goto Enodrawscreen;
+                                scrn = dscrn->screen;
+                                if(repl || chan!=scrn->image->chan){
+                                        err = "image parameters incompatibile with screen";
+                                        goto error;
+                                }
+                                reffn = nil;
+                                switch(refresh){
+                                case Refbackup:
+                                        break;
+                                case Refnone:
+                                        reffn = memlnorefresh;
+                                        break;
+                                case Refmesg:
+                                        reffn = drawrefresh;
+                                        break;
+                                default:
+                                        err = "unknown refresh method";
+                                        goto error;
+                                }
+                                l = memlalloc(scrn, r, reffn, 0, value);
+                                if(l == 0)
+                                        goto Edrawmem;
+                                addflush(l->layer->screenr);
+                                l->clipr = clipr;
+                                rectclip(&l->clipr, r);
+                                if(drawinstall(client, dstid, l, dscrn) == 0){
+                                        memldelete(l);
+                                        goto Edrawmem;
+                                }
+                                dscrn->ref++;
+                                if(reffn){
+                                        refx = nil;
+                                        if(reffn == drawrefresh){
+                                                refx = mallocz(sizeof(Refx), 1);
+                                                if(refx == 0){
+                                                        if(drawuninstall(client, dstid) < 0)
+                                                                goto Enodrawimage;
+                                                        goto Edrawmem;
+                                                }
+                                                refx->client = client;
+                                                refx->dimage = drawlookup(client, dstid, 1);
+                                        }
+                                        memlsetrefresh(l, reffn, refx);
+                                }
+                                continue;
+                        }
+                        i = allocmemimage(r, chan);
+                        if(i == 0)
+                                goto Edrawmem;
+                        if(repl)
+                                i->flags |= Frepl;
+                        i->clipr = clipr;
+                        if(!repl)
+                                rectclip(&i->clipr, r);
+                        if(drawinstall(client, dstid, i, 0) == 0){
+                                freememimage(i);
+                                goto Edrawmem;
+                        }
+                        memfillcolor(i, value);
+                        continue;
+
+                /* allocate screen: 'A' id[4] imageid[4] fillid[4] public[1] */
+                case 'A':
+                        m = 1+4+4+4+1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dstid = BGLONG(a+1);
+                        if(dstid == 0)
+                                goto Ebadarg;
+                        if(drawlookupdscreen(dstid))
+                                goto Escreenexists;
+                        ddst = drawlookup(client, BGLONG(a+5), 1);
+                        dsrc = drawlookup(client, BGLONG(a+9), 1);
+                        if(ddst==0 || dsrc==0)
+                                goto Enodrawimage;
+                        if(drawinstallscreen(client, 0, dstid, ddst, dsrc, a[13]) == 0)
+                                goto Edrawmem;
+                        continue;
+
+                /* set repl and clip: 'c' dstid[4] repl[1] clipR[4*4] */
+                case 'c':
+                        m = 1+4+1+4*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        ddst = drawlookup(client, BGLONG(a+1), 1);
+                        if(ddst == nil)
+                                goto Enodrawimage;
+                        if(ddst->name){
+                                err = "can't change repl/clipr of shared image";
+                                goto error;
+                        }
+                        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':
+                        m = 1+4+4+4+4*4+2*4+2*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dst = drawimage(client, a+1);
+                        dstid = BGLONG(a+1);
+                        src = drawimage(client, a+5);
+                        mask = drawimage(client, a+9);
+                        if(!dst || !src || !mask)
+                                goto Enodrawimage;
+                        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':
+                        m = 1+1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        drawdebug = a[1];
+                        continue;
+
+                /* ellipse: 'e' dstid[4] srcid[4] center[2*4] a[4] b[4] thick[4] sp[2*4] alpha[4] phi[4]*/
+                case 'e':
+                case 'E':
+                        m = 1+4+4+2*4+4+4+4+2*4+2*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dst = drawimage(client, a+1);
+                        dstid = BGLONG(a+1);
+                        src = drawimage(client, a+5);
+                        if(!dst || !src)
+                                goto Enodrawimage;
+                        drawpoint(&p, a+9);
+                        e0 = BGLONG(a+17);
+                        e1 = BGLONG(a+21);
+                        if(e0<0 || e1<0){
+                                err = "invalid ellipse semidiameter";
+                                goto error;
+                        }
+                        j = BGLONG(a+25);
+                        if(j < 0){
+                                err = "negative ellipse thickness";
+                                goto error;
+                        }
+                        
+                        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':
+                        m = 1+4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        ll = drawlookup(client, BGLONG(a+1), 0);
+                        if(ll && ll->dscreen && ll->dscreen->owner != client)
+                                ll->dscreen->owner->refreshme = 1;
+                        if(drawuninstall(client, BGLONG(a+1)) < 0)
+                                goto Enodrawimage;
+                        continue;
+
+                /* free screen: 'F' id[4] */
+                case 'F':
+                        m = 1+4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        if(!drawlookupscreen(client, BGLONG(a+1), &cs))
+                                goto Enodrawscreen;
+                        drawuninstallscreen(client, cs);
+                        continue;
+
+                /* initialize font: 'i' fontid[4] nchars[4] ascent[1] */
+                case 'i':
+                        m = 1+4+4+1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dstid = BGLONG(a+1);
+                        if(dstid == 0){
+                                err = "can't use display as font";
+                                goto error;
+                        }
+                        font = drawlookup(client, dstid, 1);
+                        if(font == 0)
+                                goto Enodrawimage;
+                        if(font->image->layer){
+                                err = "can't use window as font";
+                                goto error;
+                        }
+                        ni = BGLONG(a+5);
+                        if(ni<=0 || ni>4096){
+                                err = "bad font size (4096 chars max)";
+                                goto error;
+                        }
+                        free(font->fchar);        /* should we complain if non-zero? */
+                        font->fchar = mallocz(ni*sizeof(FChar), 1);
+                        if(font->fchar == 0){
+                                err = "no memory for font";
+                                goto error;
+                        }
+                        memset(font->fchar, 0, ni*sizeof(FChar));
+                        font->nfchar = ni;
+                        font->ascent = a[9];
+                        continue;
+
+                /* set image 0 to screen image */
+                case 'J':
+                        m = 1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        drawinstall(client, 0, screenimage, 0);
+                        client->infoid = 0;
+                        continue;
+
+                /* get image info: 'I' */
+                case 'I':
+                        m = 1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        if(client->infoid < 0)
+                                goto Enodrawimage;
+                        if(client->infoid == 0){
+                                i = screenimage;
+                                if(i == nil)
+                                        goto Enodrawimage;
+                        }else{
+                                di = drawlookup(client, client->infoid, 1);
+                                if(di == nil)
+                                        goto Enodrawimage;
+                                i = di->image;
+                        }
+                        ni = sprint(ibuf, "%11d %11d %11s %11d %11d %11d %11d %11d"
+                                        " %11d %11d %11d %11d ",
+                                        client->clientid,
+                                        client->infoid,        
+                                        chantostr(cbuf, 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);
+                        drawmsgsquirrel(d, ibuf, ni);
+                        client->infoid = -1;
+                        continue;        
+
+                /* load character: 'l' fontid[4] srcid[4] index[2] R[4*4] P[2*4] left[1] width[1] */
+                case 'l':
+                        m = 1+4+4+2+4*4+2*4+1+1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        font = drawlookup(client, BGLONG(a+1), 1);
+                        if(font == 0)
+                                goto Enodrawimage;
+                        if(font->nfchar == 0)
+                                goto Enotfont;
+                        src = drawimage(client, a+5);
+                        if(!src)
+                                goto Enodrawimage;
+                        ci = BGSHORT(a+9);
+                        if(ci >= font->nfchar)
+                                goto 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':
+                        m = 1+4+2*4+2*4+4+4+4+4+2*4;
+                        if(n < m)
+                                goto 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){
+                                err = "negative line width";
+                                goto error;
+                        }
+                        src = drawimage(client, a+33);
+                        if(!dst || !src)
+                                goto Enodrawimage;
+                        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':
+                        m = 4+4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        break;
+ *
+ */
+
+                /* attach to a named image: 'n' dstid[4] j[1] name[j] */
+                case 'n':
+                        m = 1+4+1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        j = a[5];
+                        if(j == 0)        /* give me a non-empty name please */
+                                goto Eshortdraw;
+                        m += j;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dstid = BGLONG(a+1);
+                        if(drawlookup(client, dstid, 0))
+                                goto Eimageexists;
+                        dn = drawlookupname(j, (char*)a+6);
+                        if(dn == nil)
+                                goto Enoname;
+                        s = malloc(j+1);
+                        if(s == nil)
+                                goto Enomem;
+                        if(drawinstall(client, dstid, dn->dimage->image, 0) == 0)
+                                goto Edrawmem;
+                        di = drawlookup(client, dstid, 0);
+                        if(di == 0)
+                                goto Eoldname;
+                        di->vers = dn->vers;
+                        di->name = s;
+                        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':
+                        m = 1+4+1+1;
+                        if(n < m)
+                                goto Eshortdraw;
+                        c = a[5];
+                        j = a[6];
+                        if(j == 0)        /* give me a non-empty name please */
+                                goto Eshortdraw;
+                        m += j;
+                        if(n < m)
+                                goto Eshortdraw;
+                        di = drawlookup(client, BGLONG(a+1), 0);
+                        if(di == 0)
+                                goto Enodrawimage;
+                        if(di->name)
+                                goto Enamed;
+                        if(c)
+                                if(drawaddname(client, di, j, (char*)a+7, &err) < 0)
+                                        goto error;
+                        else{
+                                dn = drawlookupname(j, (char*)a+7);
+                                if(dn == nil)
+                                        goto Enoname;
+                                if(dn->dimage != di)
+                                        goto Ewrongname;
+                                drawdelname(dn);
+                        }
+                        continue;
+
+                /* position window: 'o' id[4] r.min [2*4] screenr.min [2*4] */
+                case 'o':
+                        m = 1+4+2*4+2*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dst = drawimage(client, a+1);
+                        if(!dst)
+                                goto Enodrawimage;
+                        if(dst->layer){
+                                drawpoint(&p, a+5);
+                                drawpoint(&q, a+13);
+                                r = dst->layer->screenr;
+                                ni = memlorigin(dst, p, q);
+                                if(ni < 0){
+                                        err = "image origin failed";
+                                        goto error;
+                                }
+                                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':
+                        m = 1+1;
+                        if(n < m)
+                                goto 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':
+                        m = 1+4+2+4+4+4+4+2*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dstid = BGLONG(a+1);
+                        dst = drawimage(client, a+1);
+                        ni = BGSHORT(a+5);
+                        if(ni < 0){
+                                err = "negative cout in polygon";
+                                goto error;
+                        }
+                        e0 = BGLONG(a+7);
+                        e1 = BGLONG(a+11);
+                        j = 0;
+                        if(*a == 'p'){
+                                j = BGLONG(a+15);
+                                if(j < 0){
+                                        err = "negative polygon line width";
+                                        goto error;
+                                }
+                        }
+                        src = drawimage(client, a+19);
+                        if(!dst || !src)
+                                goto Enodrawimage;
+                        drawpoint(&sp, a+23);
+                        drawpoint(&p, a+31);
+                        ni++;
+                        pp = mallocz(ni*sizeof(Point), 1);
+                        if(pp == nil)
+                                goto Enomem;
+                        doflush = 0;
+                        if(dstid==0 || (dst->layer && dst->layer->screen->image->data == screenimage->data))
+                                doflush = 1;        /* simplify test in loop */
+                        ox = oy = 0;
+                        esize = 0;
+                        u = a+m;
+                        for(y=0; y 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':
+                        m = 1+4+4*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        i = drawimage(client, a+1);
+                        if(!i)
+                                goto Enodrawimage;
+                        drawrectangle(&r, a+5);
+                        if(!rectinrect(r, i->r))
+                                goto Ereadoutside;
+                        c = bytesperline(r, i->depth);
+                        c *= Dy(r);
+                        free(client->readdata);
+                        client->readdata = mallocz(c, 0);
+                        if(client->readdata == nil){
+                                err = "readimage malloc failed";
+                                goto error;
+                        }
+                        client->nreaddata = memunload(i, r, client->readdata, c);
+                        if(client->nreaddata < 0){
+                                free(client->readdata);
+                                client->readdata = nil;
+                                err = "bad readimage call";
+                                goto error;
+                        }
+                        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':
+                        m = 1+4+4+4+2*4+4*4+2*4+2;
+                        if(*a == 'x')
+                                m += 4+2*4;
+                        if(n < m)
+                                goto Eshortdraw;
+
+                        dst = drawimage(client, a+1);
+                        dstid = BGLONG(a+1);
+                        src = drawimage(client, a+5);
+                        if(!dst || !src)
+                                goto Enodrawimage;
+                        font = drawlookup(client, BGLONG(a+9), 1);
+                        if(font == 0)
+                                goto Enodrawimage;
+                        if(font->nfchar == 0)
+                                goto 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)
+                                goto Eshortdraw;
+                        clipr = dst->clipr;
+                        dst->clipr = r;
+                        op = drawclientop(client);
+                        if(*a == 'x'){
+                                /* paint background */
+                                l = drawimage(client, a+47);
+                                if(!l)
+                                        goto Enodrawimage;
+                                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;
+                                                goto Eindex;
+                                        }
+                                        r.max.x += font->fchar[ci].width;
+                                        u += 2;
+                                }
+                                memdraw(dst, r, l, q, memopaque, ZP, op);
+                                u -= 2*ni;
+                        }
+                        q = p;
+                        while(--ni >= 0){
+                                ci = BGSHORT(u);
+                                if(ci<0 || ci>=font->nfchar){
+                                        dst->clipr = clipr;
+                                        goto Eindex;
+                                }
+                                q = drawchar(dst, q, src, &sp, font, ci, op);
+                                u += 2;
+                        }
+                        dst->clipr = clipr;
+                        p.y -= font->ascent;
+                        dstflush(dstid, dst, Rect(p.x, p.y, q.x, p.y+Dy(font->image->r)));
+                        continue;
+
+                /* use public screen: 'S' id[4] chan[4] */
+                case 'S':
+                        m = 1+4+4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dstid = BGLONG(a+1);
+                        if(dstid == 0)
+                                goto Ebadarg;
+                        dscrn = drawlookupdscreen(dstid);
+                        if(dscrn==0 || (dscrn->public==0 && dscrn->owner!=client))
+                                goto Enodrawscreen;
+                        if(dscrn->screen->image->chan != BGLONG(a+5)){
+                                err = "inconsistent chan";
+                                goto error;
+                        }
+                        if(drawinstallscreen(client, dscrn, 0, 0, 0, 0) == 0)
+                                goto Edrawmem;
+                        continue;
+
+                /* top or bottom windows: 't' top[1] nw[2] n*id[4] */
+                case 't':
+                        m = 1+1+2;
+                        if(n < m)
+                                goto Eshortdraw;
+                        nw = BGSHORT(a+2);
+                        if(nw < 0)
+                                goto Ebadarg;
+                        if(nw == 0)
+                                continue;
+                        m += nw*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        lp = mallocz(nw*sizeof(Memimage*), 1);
+                        if(lp == 0)
+                                goto Enomem;
+                        for(j=0; jlayer == 0){
+                                err = "images are not windows";
+                                free(lp);
+                                goto error;
+                        }
+                        for(j=1; jlayer->screen != lp[0]->layer->screen){
+                                        err = "images not on same screen";
+                                        free(lp);
+                                        goto error;
+                                }
+                        if(a[1])
+                                memltofrontn(lp, nw);
+                        else
+                                memltorearn(lp, nw);
+                        if(lp[0]->layer->screen->image->data == screenimage->data)
+                                for(j=0; jlayer->screenr);
+                        free(lp);
+                        ll = drawlookup(client, BGLONG(a+1+1+2), 1);
+                        drawrefreshscreen(ll, client);
+                        continue;
+
+                /* visible: 'v' */
+                case 'v':
+                        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':
+                        m = 1+4+4*4;
+                        if(n < m)
+                                goto Eshortdraw;
+                        dstid = BGLONG(a+1);
+                        dst = drawimage(client, a+1);
+                        if(!dst)
+                                goto Enodrawimage;
+                        drawrectangle(&r, a+5);
+                        if(!rectinrect(r, dst->r))
+                                goto Ewriteoutside;
+                        y = memload(dst, r, a+m, n-m, *a=='Y');
+                        if(y < 0){
+                                err = "bad writeimage call";
+                                goto error;
+                        }
+                        dstflush(dstid, dst, r);
+                        m += y;
+                        continue;
+                }
+        }
+        return oldn - n;
+
+Enodrawimage:
+        err = "unknown id for draw image";
+        goto error;
+Enodrawscreen:
+        err = "unknown id for draw screen";
+        goto error;
+Eshortdraw:
+        err = "short draw message";
+        goto error;
+Eshortread:
+        err = "draw read too short";
+        goto error;
+Eimageexists:
+        err = "image id in use";
+        goto error;
+Escreenexists:
+        err = "screen id in use";
+        goto error;
+Edrawmem:
+        err = "image memory allocation failed";
+        goto error;
+Ereadoutside:
+        err = "readimage outside image";
+        goto error;
+Ewriteoutside:
+        err = "writeimage outside image";
+        goto error;
+Enotfont:
+        err = "image not a font";
+        goto error;
+Eindex:
+        err = "character index out of range";
+        goto error;
+Enoclient:
+        err = "no such draw client";
+        goto error;
+Edepth:
+        err = "image has bad depth";
+        goto error;
+Enameused:
+        err = "image name in use";
+        goto error;
+Enoname:
+        err = "no image with that name";
+        goto error;
+Eoldname:
+        err = "named image no longer valid";
+        goto error;
+Enamed:
+        err = "image already has name";
+        goto error;
+Ewrongname:
+        err = "wrong name for image";
+        goto error;
+Enomem:
+        err = "out of memory";
+        goto error;
+Ebadarg:
+        err = "bad argument in draw message";
+        goto error;
+
+error:
+        drawerror(display, err);
+        return -1;
+}
+
+
diff --git a/src/libdraw/draw.h b/src/libdraw/draw.h
t@@ -0,0 +1,520 @@
+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),
+        BGR24        = CHAN3(CBlue, 8, CGreen, 8, CRed, 8),
+        RGBA32        = CHAN4(CRed, 8, CGreen, 8, CBlue, 8, CAlpha, 8),
+        ARGB32        = CHAN4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8),        /* stupid VGAs */
+        XRGB32  = CHAN4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8),
+        XBGR32  = CHAN4(CIgnore, 8, CBlue, 8, CGreen, 8, CRed, 8),
+};
+
+extern        char*        chantostr(char*, u32int);
+extern        u32int        strtochan(char*);
+extern        int                chantodepth(u32int);
+
+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                imageid;
+        int                local;
+        void                (*error)(Display*, char*);
+        char                *devdir;
+        char                *windir;
+        char                oldlabel[64];
+        u32int                dataqid;
+        Image                *image;
+        Image                *white;
+        Image                *black;
+        Image                *opaque;
+        Image                *transparent;
+        uchar                *buf;
+        int                bufsize;
+        uchar                *bufp;
+        uchar                *obuf;
+        int                obufsize;
+        uchar                *obufp;
+        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 */
+        u32int        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
+{
+        u32int        red;
+        u32int        green;
+        u32int        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<>8))
+#define        BPLONG(p, v)                (BPSHORT(p, (v)), BPSHORT(p+2, (v)>>16))
+
+/*
+ * Compressed image file parameters and helper routines
+ */
+#define        NMATCH        3                /* shortest match possible */
+#define        NRUN        (NMATCH+31)        /* longest match possible */
+#define        NMEM        1024                /* window size */
+#define        NDUMP        128                /* maximum length of dump */
+#define        NCBLOCK        6000                /* size of compressed blocks */
+extern        void        _twiddlecompressed(uchar*, int);
+extern        int        _compblocksize(Rectangle, int);
+
+/* XXX backwards helps; should go */
+extern        int                log2[];
+extern        u32int        drawld2chan[];
+extern        void                drawsetdebug(int);
+
+/*
+ * Port magic.
+ */
+int        _drawmsgread(Display*, void*, int);
+int        _drawmsgwrite(Display*, void*, int);
diff --git a/src/libdraw/ellipse.c b/src/libdraw/ellipse.c
t@@ -0,0 +1,82 @@
+#include 
+#include 
+#include 
+
+static
+void
+doellipse(int cmd, Image *dst, Point *c, int xr, int yr, int thick, Image *src, Point *sp, int alpha, int phi, Drawop op)
+{
+        uchar *a;
+
+        _setdrawop(dst->display, op);
+
+        a = bufimage(dst->display, 1+4+4+2*4+4+4+4+2*4+2*4);
+        if(a == 0){
+                fprint(2, "image ellipse: %r\n");
+                return;
+        }
+        a[0] = cmd;
+        BPLONG(a+1, dst->id);
+        BPLONG(a+5, src->id);
+        BPLONG(a+9, c->x);
+        BPLONG(a+13, c->y);
+        BPLONG(a+17, xr);
+        BPLONG(a+21, yr);
+        BPLONG(a+25, thick);
+        BPLONG(a+29, sp->x);
+        BPLONG(a+33, sp->y);
+        BPLONG(a+37, alpha);
+        BPLONG(a+41, phi);
+}
+
+void
+ellipse(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp)
+{
+        doellipse('e', dst, &c, a, b, thick, src, &sp, 0, 0, SoverD);
+}
+
+void
+ellipseop(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp, Drawop op)
+{
+        doellipse('e', dst, &c, a, b, thick, src, &sp, 0, 0, op);
+}
+
+void
+fillellipse(Image *dst, Point c, int a, int b, Image *src, Point sp)
+{
+        doellipse('E', dst, &c, a, b, 0, src, &sp, 0, 0, SoverD);
+}
+
+void
+fillellipseop(Image *dst, Point c, int a, int b, Image *src, Point sp, Drawop op)
+{
+        doellipse('E', dst, &c, a, b, 0, src, &sp, 0, 0, op);
+}
+
+void
+arc(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp, int alpha, int phi)
+{
+        alpha |= 1<<31;
+        doellipse('e', dst, &c, a, b, thick, src, &sp, alpha, phi, SoverD);
+}
+
+void
+arcop(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp, int alpha, int phi, Drawop op)
+{
+        alpha |= 1<<31;
+        doellipse('e', dst, &c, a, b, thick, src, &sp, alpha, phi, op);
+}
+
+void
+fillarc(Image *dst, Point c, int a, int b, Image *src, Point sp, int alpha, int phi)
+{
+        alpha |= 1<<31;
+        doellipse('E', dst, &c, a, b, 0, src, &sp, alpha, phi, SoverD);
+}
+
+void
+fillarcop(Image *dst, Point c, int a, int b, Image *src, Point sp, int alpha, int phi, Drawop op)
+{
+        alpha |= 1<<31;
+        doellipse('E', dst, &c, a, b, 0, src, &sp, alpha, phi, op);
+}
diff --git a/src/libdraw/emenuhit.c b/src/libdraw/emenuhit.c
t@@ -0,0 +1,271 @@
+#include 
+#include 
+#include 
+#include 
+
+enum
+{
+        Margin = 4,                /* outside to text */
+        Border = 2,                /* outside to selection boxes */
+        Blackborder = 2,        /* width of outlining border */
+        Vspacing = 2,                /* extra spacing between lines of text */
+        Maxunscroll = 25,        /* maximum #entries before scrolling turns on */
+        Nscroll = 20,                /* number entries in scrolling part */
+        Scrollwid = 14,                /* width of scroll bar */
+        Gap = 4,                        /* between text and scroll bar */
+};
+
+static        Image        *menutxt;
+static        Image        *back;
+static        Image        *high;
+static        Image        *bord;
+static        Image        *text;
+static        Image        *htext;
+
+static
+void
+menucolors(void)
+{
+        /* Main tone is greenish, with negative selection */
+        back = allocimagemix(display, DPalegreen, DWhite);
+        high = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DDarkgreen);        /* dark green */
+        bord = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DMedgreen);        /* not as dark green */
+        if(back==nil || high==nil || bord==nil)
+                goto Error;
+        text = display->black;
+        htext = back;
+        return;
+
+    Error:
+        freeimage(back);
+        freeimage(high);
+        freeimage(bord);
+        back = display->white;
+        high = display->black;
+        bord = display->black;
+        text = display->black;
+        htext = display->white;
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the rectangle, including its black edge, holding element i.
+ */
+static Rectangle
+menurect(Rectangle r, int i)
+{
+        if(i < 0)
+                return Rect(0, 0, 0, 0);
+        r.min.y += (font->height+Vspacing)*i;
+        r.max.y = r.min.y+font->height+Vspacing;
+        return insetrect(r, Border-Margin);
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the element number containing p.
+ */
+static int
+menusel(Rectangle r, Point p)
+{
+        r = insetrect(r, Margin);
+        if(!ptinrect(p, r))
+                return -1;
+        return (p.y-r.min.y)/(font->height+Vspacing);
+}
+
+static
+void
+paintitem(Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
+{
+        char *item;
+        Rectangle r;
+        Point pt;
+
+        if(i < 0)
+                return;
+        r = menurect(textr, i);
+        if(restore){
+                draw(screen, r, restore, nil, restore->r.min);
+                return;
+        }
+        if(save)
+                draw(save, save->r, screen, nil, r.min);
+        item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
+        pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
+        pt.y = textr.min.y+i*(font->height+Vspacing);
+        draw(screen, r, highlight? high : back, nil, pt);
+        string(screen, pt, highlight? htext : text, pt, font, item);
+}
+
+/*
+ * menur is a rectangle holding all the highlightable text elements.
+ * track mouse while inside the box, return what's selected when button
+ * is raised, -1 as soon as it leaves box.
+ * invariant: nothing is highlighted on entry or exit.
+ */
+static int
+menuscan(Menu *menu, int but, Mouse *m, Rectangle textr, int off, int lasti, Image *save)
+{
+        int i;
+
+        paintitem(menu, textr, off, lasti, 1, save, nil);
+        flushimage(display, 1);        /* in case display->locking is set */
+        *m = emouse();
+        while(m->buttons & (1<<(but-1))){
+                flushimage(display, 1);        /* in case display->locking is set */
+                *m = emouse();
+                i = menusel(textr, m->xy);
+                if(i != -1 && i == lasti)
+                        continue;
+                paintitem(menu, textr, off, lasti, 0, nil, save);
+                if(i == -1)
+                        return i;
+                lasti = i;
+                paintitem(menu, textr, off, lasti, 1, save, nil);
+        }
+        return lasti;
+}
+
+static void
+menupaint(Menu *menu, Rectangle textr, int off, int nitemdrawn)
+{
+        int i;
+
+        draw(screen, insetrect(textr, Border-Margin), back, nil, ZP);
+        for(i = 0; iclipr;
+        replclipr(screen, 0, screen->r);
+        maxwid = 0;
+        for(nitem = 0;
+            item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
+            nitem++){
+                i = stringwidth(font, item);
+                if(i > maxwid)
+                        maxwid = i;
+        }
+        if(menu->lasthit<0 || menu->lasthit>=nitem)
+                menu->lasthit = 0;
+        screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
+        if(nitem>Maxunscroll || nitem>screenitem){
+                scrolling = 1;
+                nitemdrawn = Nscroll;
+                if(nitemdrawn > screenitem)
+                        nitemdrawn = screenitem;
+                wid = maxwid + Gap + Scrollwid;
+                off = menu->lasthit - nitemdrawn/2;
+                if(off < 0)
+                        off = 0;
+                if(off > nitem-nitemdrawn)
+                        off = nitem-nitemdrawn;
+                lasti = menu->lasthit-off;
+        }else{
+                scrolling = 0;
+                nitemdrawn = nitem;
+                wid = maxwid;
+                off = 0;
+                lasti = menu->lasthit;
+        }
+        r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
+        r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
+        r = rectaddpt(r, m->xy);
+        pt = ZP;
+        if(r.max.x>screen->r.max.x)
+                pt.x = screen->r.max.x-r.max.x;
+        if(r.max.y>screen->r.max.y)
+                pt.y = screen->r.max.y-r.max.y;
+        if(r.min.xr.min.x)
+                pt.x = screen->r.min.x-r.min.x;
+        if(r.min.yr.min.y)
+                pt.y = screen->r.min.y-r.min.y;
+        menur = rectaddpt(r, pt);
+        textr.max.x = menur.max.x-Margin;
+        textr.min.x = textr.max.x-maxwid;
+        textr.min.y = menur.min.y+Margin;
+        textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
+        if(scrolling){
+                scrollr = insetrect(menur, Border);
+                scrollr.max.x = scrollr.min.x+Scrollwid;
+        }else
+                scrollr = Rect(0, 0, 0, 0);
+
+        b = allocimage(display, menur, screen->chan, 0, 0);
+        if(b == 0)
+                b = screen;
+        draw(b, menur, screen, nil, menur.min);
+        draw(screen, menur, back, nil, ZP);
+        border(screen, menur, Blackborder, bord, ZP);
+        save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
+        r = menurect(textr, lasti);
+        emoveto(divpt(addpt(r.min, r.max), 2));
+        menupaint(menu, textr, off, nitemdrawn);
+        if(scrolling)
+                menuscrollpaint(scrollr, off, nitem, nitemdrawn);
+        while(m->buttons & (1<<(but-1))){
+                lasti = menuscan(menu, but, m, textr, off, lasti, save);
+                if(lasti >= 0)
+                        break;
+                while(!ptinrect(m->xy, textr) && (m->buttons & (1<<(but-1)))){
+                        if(scrolling && ptinrect(m->xy, scrollr)){
+                                noff = ((m->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
+                                noff -= nitemdrawn/2;
+                                if(noff < 0)
+                                        noff = 0;
+                                if(noff > nitem-nitemdrawn)
+                                        noff = nitem-nitemdrawn;
+                                if(noff != off){
+                                        off = noff;
+                                        menupaint(menu, textr, off, nitemdrawn);
+                                        menuscrollpaint(scrollr, off, nitem, nitemdrawn);
+                                }
+                        }
+                        flushimage(display, 1);        /* in case display->locking is set */
+                        *m = emouse();
+                }
+        }
+        draw(screen, menur, b, nil, menur.min);
+        if(b != screen)
+                freeimage(b);
+        freeimage(save);
+        replclipr(screen, 0, sc);
+        flushimage(display, 1);
+        if(lasti >= 0){
+                menu->lasthit = lasti+off;
+                return menu->lasthit;
+        }
+        return -1;
+}
diff --git a/src/libdraw/event.c b/src/libdraw/event.c
t@@ -0,0 +1,486 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+typedef struct        Slave Slave;
+typedef struct        Ebuf Ebuf;
+
+struct Slave
+{
+        int        pid;
+        Ebuf        *head;                /* ueue of messages for this descriptor */
+        Ebuf        *tail;
+        int        (*fn)(int, Event*, uchar*, int);
+};
+
+struct Ebuf
+{
+        Ebuf        *next;
+        int        n;                /* number of bytes in buf */
+        uchar        buf[EMAXMSG];
+};
+
+static        Slave        eslave[MAXSLAVE];
+static        int        Skeyboard = -1;
+static        int        Smouse = -1;
+static        int        Stimer = -1;
+static        int        logfid;
+
+static        int        nslave;
+static        int        parentpid;
+static        int        epipe[2];
+static        int        eforkslave(ulong);
+static        void        extract(void);
+static        void        ekill(void);
+static        int        enote(void *, char *);
+static        int        mousefd;
+static        int        cursorfd;
+
+static
+Ebuf*
+ebread(Slave *s)
+{
+        Ebuf *eb;
+        Dir *d;
+        ulong l;
+
+        for(;;){
+                d = dirfstat(epipe[0]);
+                if(d == nil)
+                        drawerror(display, "events: eread stat error");
+                l = d->length;
+                free(d);
+                if(s->head && l==0)
+                        break;
+                extract();
+        }
+        eb = s->head;
+        s->head = s->head->next;
+        if(s->head == 0)
+                s->tail = 0;
+        return eb;
+}
+
+ulong
+event(Event *e)
+{
+        return eread(~0UL, e);
+}
+
+ulong
+eread(ulong keys, Event *e)
+{
+        Ebuf *eb;
+        int i, id;
+
+        if(keys == 0)
+                return 0;
+        for(;;){
+                for(i=0; imouse = emouse();
+                                else if(i == Skeyboard)
+                                        e->kbdc = ekbd();
+                                else if(i == Stimer)
+                                        eslave[i].head = 0;
+                                else{
+                                        eb = ebread(&eslave[i]);
+                                        e->n = eb->n;
+                                        if(eslave[i].fn)
+                                                id = (*eslave[i].fn)(id, e, eb->buf, eb->n);
+                                        else
+                                                memmove(e->data, eb->buf, eb->n);
+                                        free(eb);
+                                }
+                                return id;
+                        }
+                extract();
+        }
+        return 0;
+}
+
+int
+ecanmouse(void)
+{
+        if(Smouse < 0)
+                drawerror(display, "events: mouse not initialized");
+        return ecanread(Emouse);
+}
+
+int
+ecankbd(void)
+{
+        if(Skeyboard < 0)
+                drawerror(display, "events: keyboard not initialzed");
+        return ecanread(Ekeyboard);
+}
+
+int
+ecanread(ulong keys)
+{
+        Dir *d;
+        int i;
+        ulong l;
+
+        for(;;){
+                for(i=0; ilength;
+                free(d);
+                if(l == 0)
+                        return 0;
+                extract();
+        }
+        return -1;
+}
+
+ulong
+estartfn(ulong key, int fd, int n, int (*fn)(int, Event*, uchar*, int))
+{
+        char buf[EMAXMSG+1];
+        int i, r;
+
+        if(fd < 0)
+                drawerror(display, "events: bad file descriptor");
+        if(n <= 0 || n > EMAXMSG)
+                n = EMAXMSG;
+        i = eforkslave(key);
+        if(i < MAXSLAVE){
+                eslave[i].fn = fn;
+                return 1<0)
+                if(write(epipe[1], buf, r+1)!=r+1)
+                        break;
+        buf[0] = MAXSLAVE;
+        write(epipe[1], buf, 1);
+        _exits(0);
+        return 0;
+}
+
+ulong
+estart(ulong key, int fd, int n)
+{
+        return estartfn(key, fd, n, nil);
+}
+
+ulong
+etimer(ulong key, int n)
+{
+        char t[2];
+
+        if(Stimer != -1)
+                drawerror(display, "events: timer started twice");
+        Stimer = eforkslave(key);
+        if(Stimer < MAXSLAVE)
+                return 1<>8;
+                if(write(epipe[1], t, 3) != 3)
+                        break;
+        }
+breakout:;
+        t[0] = MAXSLAVE;
+        write(epipe[1], t, 1);
+        _exits(0);
+}
+
+void
+einit(ulong keys)
+{
+        int ctl, fd;
+        char buf[256];
+
+        parentpid = getpid();
+        if(pipe(epipe) < 0)
+                drawerror(display, "events: einit pipe");
+        atexit(ekill);
+        atnotify(enote, 1);
+        snprint(buf, sizeof buf, "%s/mouse", display->devdir);
+        mousefd = open(buf, ORDWR|OCEXEC);
+        if(mousefd < 0)
+                drawerror(display, "einit: can't open mouse\n");
+        snprint(buf, sizeof buf, "%s/cursor", display->devdir);
+        cursorfd = open(buf, ORDWR|OCEXEC);
+        if(cursorfd < 0)
+                drawerror(display, "einit: can't open cursor\n");
+        if(keys&Ekeyboard){
+                snprint(buf, sizeof buf, "%s/cons", display->devdir);
+                fd = open(buf, OREAD);
+                if(fd < 0)
+                        drawerror(display, "events: can't open console");
+                snprint(buf, sizeof buf, "%s/consctl", display->devdir);
+                ctl = open("/dev/consctl", OWRITE|OCEXEC);
+                if(ctl < 0)
+                        drawerror(display, "events: can't open consctl");
+                write(ctl, "rawon", 5);
+                for(Skeyboard=0; Ekeyboard & ~(1<locking){
+                /* if locking is being done by program, this means it can't depend on automatic flush in emouse() etc. */
+                if(canqlock(&display->qlock)){
+                        if(display->bufp > display->buf)
+                                flushimage(display, 1);
+                        unlockdisplay(display);
+                }
+        }else
+                if(display->bufp > display->buf)
+                        flushimage(display, 1);
+loop:
+        if((n=read(epipe[0], ebuf, EMAXMSG+1)) < 0
+        || ebuf[0] >= MAXSLAVE)
+                drawerror(display, "eof on event pipe");
+        if(n == 0)
+                goto loop;
+        i = ebuf[0];
+        if(i >= nslave || n <= 1)
+                drawerror(display, "events: protocol error: short read");
+        s = &eslave[i];
+        if(i == Stimer){
+                s->head = (Ebuf *)1;
+                return;
+        }
+        if(i == Skeyboard && n != 3)
+                drawerror(display, "events: protocol error: keyboard");
+        if(i == Smouse){
+                if(n < 1+1+2*12)
+                        drawerror(display, "events: protocol error: mouse");
+                if(ebuf[1] == 'r')
+                        eresized(1);
+                /* squash extraneous mouse events */
+                if((eb=s->tail) && memcmp(eb->buf+1+2*12, ebuf+1+1+2*12, 12)==0){
+                        memmove(eb->buf, &ebuf[1], n - 1);
+                        return;
+                }
+        }
+        /* try to save space by only allocating as much buffer as we need */
+        eb = malloc(sizeof(*eb) - sizeof(eb->buf) + n - 1);
+        if(eb == 0)
+                drawerror(display, "events: protocol error 4");
+        eb->n = n - 1;
+        memmove(eb->buf, &ebuf[1], n - 1);
+        eb->next = 0;
+        if(s->head)
+                s->tail = s->tail->next = eb;
+        else
+                s->head = s->tail = eb;
+}
+
+static int
+eforkslave(ulong key)
+{
+        int i, pid;
+
+        for(i=0; ibuf+1+0*12);
+        m.xy.y = atoi((char*)eb->buf+1+1*12);
+        b = atoi((char*)eb->buf+1+2*12);
+        m.buttons = b&7;
+        m.msec = atoi((char*)eb->buf+1+3*12);
+        if (logfid)
+                fprint(logfid, "b: %d xy: %P\n", m.buttons, m.xy);
+        free(eb);
+        return m;
+}
+
+int
+ekbd(void)
+{
+        Ebuf *eb;
+        int c;
+
+        if(Skeyboard < 0)
+                drawerror(display, "events: keyboard not initialzed");
+        eb = ebread(&eslave[Skeyboard]);
+        c = eb->buf[0] + (eb->buf[1]<<8);
+        free(eb);
+        return c;
+}
+
+void
+emoveto(Point pt)
+{
+        char buf[2*12+2];
+        int n;
+
+        n = sprint(buf, "m%d %d", pt.x, pt.y);
+        write(mousefd, buf, n);
+}
+
+void
+esetcursor(Cursor *c)
+{
+        uchar curs[2*4+2*2*16];
+
+        if(c == 0)
+                write(cursorfd, curs, 0);
+        else{
+                BPLONG(curs+0*4, c->offset.x);
+                BPLONG(curs+1*4, c->offset.y);
+                memmove(curs+2*4, c->clr, 2*2*16);
+                write(cursorfd, curs, sizeof curs);
+        }
+}
+
+int
+ereadmouse(Mouse *m)
+{
+        int n;
+        char buf[128];
+
+        do{
+                n = read(mousefd, buf, sizeof(buf));
+                if(n < 0)        /* probably interrupted */
+                        return -1;
+                n = eatomouse(m, buf, n);
+        }while(n == 0);
+        return n;
+}
+
+int
+eatomouse(Mouse *m, char *buf, int n)
+{
+        if(n != 1+4*12){
+                werrstr("atomouse: bad count");
+                return -1;
+        }
+
+        if(buf[0] == 'r')
+                eresized(1);
+        m->xy.x = atoi(buf+1+0*12);
+        m->xy.y = atoi(buf+1+1*12);
+        m->buttons = atoi(buf+1+2*12);
+        m->msec = atoi(buf+1+3*12);
+        return n;
+}
diff --git a/src/libdraw/event.h b/src/libdraw/event.h
t@@ -0,0 +1,63 @@
+typedef struct        Event Event;
+typedef struct        Menu Menu;
+
+enum
+{
+        Emouse                = 1,
+        Ekeyboard        = 2,
+};
+
+enum
+{
+        MAXSLAVE = 32,
+        EMAXMSG = 128+8192,        /* size of 9p header+data */
+};
+
+struct        Mouse
+{
+        int        buttons;        /* bit array: LMR=124 */
+        Point        xy;
+        ulong        msec;
+};
+
+struct        Event
+{
+        int        kbdc;
+        Mouse        mouse;
+        int        n;                /* number of characters in message */
+        void        *v;                /* data unpacked by general event-handling function */
+        uchar        data[EMAXMSG];        /* message from an arbitrary file descriptor */
+};
+
+struct Menu
+{
+        char        **item;
+        char        *(*gen)(int);
+        int        lasthit;
+};
+
+/*
+ * Events
+ */
+extern void         einit(ulong);
+extern ulong         estart(ulong, int, int);
+extern ulong         estartfn(ulong, int, int, int (*fn)(int, Event*, uchar*, int));
+extern ulong         etimer(ulong, int);
+extern ulong         event(Event*);
+extern ulong         eread(ulong, Event*);
+extern Mouse         emouse(void);
+extern int         ekbd(void);
+extern int         ecanread(ulong);
+extern int         ecanmouse(void);
+extern int         ecankbd(void);
+extern void         eresized(int);        /* supplied by user */
+extern int         emenuhit(int, Mouse*, Menu*);
+extern int        eatomouse(Mouse*, char*, int);
+extern Rectangle        getrect(int, Mouse*);
+struct Cursor;
+extern void         esetcursor(struct Cursor*);
+extern void         emoveto(Point);
+extern Rectangle        egetrect(int, Mouse*);
+extern void                edrawgetrect(Rectangle, int);
+extern int                ereadmouse(Mouse*);
+extern int                eatomouse(Mouse*, char*, int);
diff --git a/src/libdraw/font.c b/src/libdraw/font.c
t@@ -0,0 +1,401 @@
+#include 
+#include 
+#include 
+
+static int        fontresize(Font*, int, int, int);
+#if 0
+static int        freeup(Font*);
+#endif
+
+#define        PJW        0        /* use NUL==pjw for invisible characters */
+
+static Rune empty[] = { 0 };
+int
+cachechars(Font *f, char **ss, Rune **rr, ushort *cp, int max, int *wp, char **subfontname)
+{
+        int i, th, sh, h, ld, w, rw, wid, nc;
+        char *sp;
+        Rune r, *rp, vr;
+        ulong a;
+        Cacheinfo *c, *tc, *ec;
+
+        if(ss){
+                sp = *ss;
+                rp = empty;
+        }else{
+                sp = "";
+                rp = *rr;
+        }
+        wid = 0;
+        *subfontname = 0;
+        for(i=0; (*sp || *rp) && incache-NFLOOK-1);
+                c = &f->cache[sh];
+                ec = c+NFLOOK;
+                h = sh;
+                while(c < ec){
+                        if(c->value==r && c->age)
+                                goto Found;
+                        c++;
+                        h++;
+                }
+        
+                /*
+                 * Not found; toss out oldest entry
+                 */
+                a = ~0;
+                th = sh;
+                tc = &f->cache[th];
+                while(tc < ec){
+                        if(tc->age < a){
+                                a = tc->age;
+                                h = th;
+                                c = tc;
+                        }
+                        tc++;
+                        th++;
+                }
+
+                if(a && (f->age-a)<500){        /* kicking out too recent; resize */
+                        nc = 2*(f->ncache-NFLOOK) + NFLOOK;
+                        if(nc <= MAXFCACHE){
+                                if(i == 0)
+                                        fontresize(f, f->width, nc, f->maxdepth);
+                                /* else flush first; retry will resize */
+                                break;
+                        }
+                }
+
+                if(c->age == f->age)        /* flush pending string output */
+                        break;
+
+                ld = loadchar(f, r, c, h, i, subfontname);
+                if(ld <= 0){
+                        if(ld == 0)
+                                continue;
+                        break;
+                }
+                c = &f->cache[h];        /* may have reallocated f->cache */
+        
+            Found:
+                wid += c->width;
+                c->age = f->age;
+                cp[i] = h;
+                i++;
+        }
+        if(ss)
+                *ss = sp;
+        else
+                *rr = rp;
+        *wp = wid;
+        return i;
+}
+
+void
+agefont(Font *f)
+{
+        Cacheinfo *c, *ec;
+        Cachesubf *s, *es;
+
+        f->age++;
+        if(f->age == 65536){
+                /*
+                 * Renormalize ages
+                 */
+                c = f->cache;
+                ec = c+f->ncache;
+                while(c < ec){
+                        if(c->age){
+                                c->age >>= 2;
+                                c->age++;
+                        }
+                        c++;
+                }
+                s = f->subf;
+                es = s+f->nsubf;
+                while(s < es){
+                        if(s->age){
+                                if(s->agecf->name != nil){
+                                        /* clean up */
+                                        if(s->f != display->defaultsubfont)
+                                                freesubfont(s->f);
+                                        s->cf = nil;
+                                        s->f = nil;
+                                        s->age = 0;
+                                }else{
+                                        s->age >>= 2;
+                                        s->age++;
+                                }
+                        }
+                        s++;
+                }
+                f->age = (65536>>2) + 1;
+        }
+}
+
+static Subfont*
+cf2subfont(Cachefont *cf, Font *f)
+{
+        int depth;
+        char *name;
+        Subfont *sf;
+
+        name = cf->subfontname;
+        if(name == nil){
+                depth = 0;
+                if(f->display){
+                        if(f->display->screenimage)
+                                depth = f->display->screenimage->depth;
+                }
+                name = subfontname(cf->name, f->name, depth);
+                if(name == nil)
+                        return nil;
+                cf->subfontname = name;
+        }
+        sf = lookupsubfont(f->display, name);
+        return sf;
+}
+
+/* return 1 if load succeeded, 0 if failed, -1 if must retry */
+int
+loadchar(Font *f, Rune r, Cacheinfo *c, int h, int noflush, char **subfontname)
+{
+        int i, oi, wid, top, bottom;
+        Rune pic;
+        Fontchar *fi;
+        Cachefont *cf;
+        Cachesubf *subf, *of;
+        uchar *b;
+
+        pic = r;
+    Again:
+        for(i=0; insub; i++){
+                cf = f->sub[i];
+                if(cf->min<=pic && pic<=cf->max)
+                        goto Found;
+        }
+    TryPJW:
+        if(pic != PJW){
+                pic = PJW;
+                goto Again;
+        }
+        return 0;
+
+    Found:
+        /*
+         * Choose exact or oldest
+         */
+        oi = 0;
+        subf = &f->subf[0];
+        for(i=0; insubf; i++){
+                if(cf == subf->cf)
+                        goto Found2;
+                if(subf->age < f->subf[oi].age)
+                        oi = i;
+                subf++;
+        }
+        subf = &f->subf[oi];
+
+        if(subf->f){
+                if(f->age-subf->age>SUBFAGE || f->nsubf>MAXSUBF){
+    Toss:
+                        /* ancient data; toss */
+                        freesubfont(subf->f);
+                        subf->cf = nil;
+                        subf->f = nil;
+                        subf->age = 0;
+                }else{                                /* too recent; grow instead */
+                        of = f->subf;
+                        f->subf = malloc((f->nsubf+DSUBF)*sizeof *subf);
+                        if(f->subf == nil){
+                                f->subf = of;
+                                goto Toss;
+                        }
+                        memmove(f->subf, of, (f->nsubf+DSUBF)*sizeof *subf);
+                        memset(f->subf+f->nsubf, 0, DSUBF*sizeof *subf);
+                        subf = &f->subf[f->nsubf];
+                        f->nsubf += DSUBF;
+                        free(of);
+                }
+        }
+        subf->age = 0;
+        subf->cf = nil;
+        subf->f = cf2subfont(cf, f);
+        if(subf->f == nil){
+                if(cf->subfontname == nil)
+                        goto TryPJW;
+                *subfontname = cf->subfontname;
+                return -1;
+        }
+
+        subf->cf = cf;
+        if(subf->f->ascent > f->ascent){
+                /* should print something? this is a mistake in the font file */
+                /* must prevent c->top from going negative when loading cache */
+                Image *b;
+                int d, t;
+                d = subf->f->ascent - f->ascent;
+                b = subf->f->bits;
+                draw(b, b->r, b, nil, addpt(b->r.min, Pt(0, d)));
+                draw(b, Rect(b->r.min.x, b->r.max.y-d, b->r.max.x, b->r.max.y), f->display->black, nil, b->r.min);
+                for(i=0; if->n; i++){
+                        t = subf->f->info[i].top-d;
+                        if(t < 0)
+                                t = 0;
+                        subf->f->info[i].top = t;
+                        t = subf->f->info[i].bottom-d;
+                        if(t < 0)
+                                t = 0;
+                        subf->f->info[i].bottom = t;
+                }
+                subf->f->ascent = f->ascent;
+        }
+
+    Found2:
+        subf->age = f->age;
+
+        pic += cf->offset;
+        if(pic-cf->min >= subf->f->n)
+                goto TryPJW;
+        fi = &subf->f->info[pic - cf->min];
+        if(fi->width == 0)
+                goto TryPJW;
+        wid = (fi+1)->x - fi->x;
+        if(f->width < wid || f->width == 0 || f->maxdepth < subf->f->bits->depth){
+                /*
+                 * Flush, free, reload (easier than reformatting f->b)
+                 */
+                if(noflush)
+                        return -1;
+                if(f->width < wid)
+                        f->width = wid;
+                if(f->maxdepth < subf->f->bits->depth)
+                        f->maxdepth = subf->f->bits->depth;
+                i = fontresize(f, f->width, f->ncache, f->maxdepth);
+                if(i <= 0)
+                        return i;
+                /* c is still valid as didn't reallocate f->cache */
+        }
+        c->value = r;
+        top = fi->top + (f->ascent-subf->f->ascent);
+        bottom = fi->bottom + (f->ascent-subf->f->ascent);
+        c->width = fi->width;
+        c->x = h*f->width;
+        c->left = fi->left;
+        flushimage(f->display, 0);        /* flush any pending errors */
+        b = bufimage(f->display, 37);
+        if(b == 0)
+                return 0;
+        b[0] = 'l';
+        BPLONG(b+1, f->cacheimage->id);
+        BPLONG(b+5, subf->f->bits->id);
+        BPSHORT(b+9, c-f->cache);
+        BPLONG(b+11, c->x);
+        BPLONG(b+15, top);
+        BPLONG(b+19, c->x+((fi+1)->x-fi->x));
+        BPLONG(b+23, bottom);
+        BPLONG(b+27, fi->x);
+        BPLONG(b+31, fi->top);
+        b[35] = fi->left;
+        b[36] = fi->width;
+        return 1;
+}
+
+/* release all subfonts, return number freed */
+#if 0
+static
+int
+freeup(Font *f)
+{
+        Cachesubf *s, *es;
+        int nf;
+
+        if(f->sub[0]->name == nil)        /* font from mkfont; don't free */
+                return 0;
+        s = f->subf;
+        es = s+f->nsubf;
+        nf = 0;
+        while(s < es){
+                if(s->age){
+                        freesubfont(s->f);
+                        s->cf = nil;
+                        s->f = nil;
+                        s->age = 0;
+                        nf++;
+                }
+                s++;
+        }
+        return nf;
+}
+#endif
+
+/* return whether resize succeeded && f->cache is unchanged */
+static int
+fontresize(Font *f, int wid, int ncache, int depth)
+{
+        Cacheinfo *i;
+        int ret;
+        Image *new;
+        uchar *b;
+        Display *d;
+
+        ret = 0;
+        d = f->display;
+        if(depth <= 0)
+                depth = 1;
+
+        new = allocimage(d, Rect(0, 0, ncache*wid, f->height), CHAN1(CGrey, depth), 0, 0);
+        if(new == nil){
+                fprint(2, "font cache resize failed: %r\n");
+                abort();
+                goto Return;
+        }
+        flushimage(d, 0);        /* flush any pending errors */
+        b = bufimage(d, 1+4+4+1);
+        if(b == 0){
+                freeimage(new);
+                goto Return;
+        }
+        b[0] = 'i';
+        BPLONG(b+1, new->id);
+        BPLONG(b+5, ncache);
+        b[9] = f->ascent;
+        if(flushimage(d, 0) < 0){
+                fprint(2, "resize: init failed: %r\n");
+                freeimage(new);
+                goto Return;
+        }
+        freeimage(f->cacheimage);
+        f->cacheimage = new;
+        f->width = wid;
+        f->maxdepth = depth;
+        ret = 1;
+        if(f->ncache != ncache){
+                i = malloc(ncache*sizeof f->cache[0]);
+                if(i != nil){
+                        ret = 0;
+                        free(f->cache);
+                        f->ncache = ncache;
+                        f->cache = i;
+                }
+                /* else just wipe the cache clean and things will be ok */
+        }
+    Return:
+        memset(f->cache, 0, f->ncache*sizeof f->cache[0]);
+        return ret;
+}
diff --git a/src/libdraw/getsubfont.c b/src/libdraw/getsubfont.c
t@@ -0,0 +1,36 @@
+#include 
+#include 
+#include 
+
+/*
+ * Default version: treat as file name
+ */
+
+Subfont*
+_getsubfont(Display *d, char *name)
+{
+        int fd;
+        Subfont *f;
+
+        fd = open(name, OREAD);
+                
+        if(fd < 0){
+                fprint(2, "getsubfont: can't open %s: %r\n", name);
+                return 0;
+        }
+        /*
+         * unlock display so i/o happens with display released, unless
+         * user is doing his own locking, in which case this could break things.
+         * _getsubfont is called only from string.c and stringwidth.c,
+         * which are known to be safe to have this done.
+         */
+        if(d->locking == 0)
+                unlockdisplay(d);
+        f = readsubfont(d, name, fd, d->locking==0);
+        if(d->locking == 0)
+                lockdisplay(d);
+        if(f == 0)
+                fprint(2, "getsubfont: can't read %s: %r\n", name);
+        close(fd);
+        return f;
+}
diff --git a/src/libdraw/init.c b/src/libdraw/init.c
t@@ -0,0 +1,203 @@
+#include 
+#include 
+#include 
+
+Display        *display;
+Font        *font;
+Image        *screen;
+int        _drawdebug;
+
+static char deffontname[] = "*default*";
+Screen        *_screen;
+
+int                debuglockdisplay = 0;
+
+static void
+drawshutdown(void)
+{
+        Display *d;
+
+        d = display;
+        if(d){
+                display = nil;
+                closedisplay(d);
+        }
+}
+
+int
+initdraw(void (*error)(Display*, char*), char *fontname, char *label)
+{
+        Subfont *df;
+        char buf[128];
+
+        display = _initdisplay(error, label); /* sets screen too */
+        if(display == nil)
+                return -1;
+
+        display->image = display->screenimage;
+        screen = display->screenimage;
+
+        /*
+         * Set up default font
+         */
+        df = getdefont(display);
+        display->defaultsubfont = df;
+        if(df == nil){
+                fprint(2, "imageinit: can't open default subfont: %r\n");
+    Error:
+                closedisplay(display);
+                display = nil;
+                return -1;
+        }
+        if(fontname == nil)
+                fontname = getenv("font");        /* leak */
+
+        /*
+         * Build fonts with caches==depth of screen, for speed.
+         * If conversion were faster, we'd use 0 and save memory.
+         */
+        if(fontname == nil){
+                snprint(buf, sizeof buf, "%d %d\n0 %d\t%s\n", df->height, df->ascent,
+                        df->n-1, deffontname);
+//BUG: Need something better for this        installsubfont("*default*", df);
+                font = buildfont(display, buf, deffontname);
+                if(font == nil){
+                        fprint(2, "initdraw: can't open default font: %r\n");
+                        goto Error;
+                }
+        }else{
+                font = openfont(display, fontname);        /* BUG: grey fonts */
+                if(font == nil){
+                        fprint(2, "initdraw: can't open font %s: %r\n", fontname);
+                        goto Error;
+                }
+        }
+        display->defaultfont = font;
+
+        display->white = allocimage(display, Rect(0,0,1,1), GREY1, 1, DWhite);
+        display->black = allocimage(display, Rect(0,0,1,1), GREY1, 1, DBlack);
+        if(display->white == nil || display->black == nil){
+                fprint(2, "initdraw: can't allocate white and black");
+                goto Error;
+        }
+        display->opaque = display->white;
+        display->transparent = display->black;
+        atexit(drawshutdown);
+        return 1;
+}
+
+/*
+ * Call with d unlocked.
+ * Note that disp->defaultfont and defaultsubfont are not freed here.
+ */
+void
+closedisplay(Display *disp)
+{
+        int fd;
+        char buf[128];
+
+        if(disp == nil)
+                return;
+        if(disp == display)
+                display = nil;
+        if(disp->oldlabel[0]){
+                snprint(buf, sizeof buf, "%s/label", disp->windir);
+                fd = open(buf, OWRITE);
+                if(fd >= 0){
+                        write(fd, disp->oldlabel, strlen(disp->oldlabel));
+                        close(fd);
+                }
+        }
+
+        free(disp->devdir);
+        free(disp->windir);
+        freeimage(disp->white);
+        freeimage(disp->black);
+        qunlock(&disp->qlock);
+        free(disp);
+}
+
+void
+lockdisplay(Display *disp)
+{
+        if(debuglockdisplay){
+                /* avoid busy looping; it's rare we collide anyway */
+                while(!canqlock(&disp->qlock)){
+                        fprint(1, "proc %d waiting for display lock...\n", getpid());
+                        sleep(1000);
+                }
+        }else
+                qlock(&disp->qlock);
+}
+
+void
+unlockdisplay(Display *disp)
+{
+        qunlock(&disp->qlock);
+}
+
+void
+drawerror(Display *d, char *s)
+{
+        char err[ERRMAX];
+
+        if(d->error)
+                d->error(d, s);
+        else{
+                errstr(err, sizeof err);
+                fprint(2, "draw: %s: %s\n", s, err);
+                exits(s);
+        }
+}
+
+static
+int
+doflush(Display *d)
+{
+        int n;
+
+        n = d->bufp-d->buf;
+        if(n <= 0)
+                return 1;
+
+        if(_drawmsgwrite(d, d->buf, n) != n){
+                if(_drawdebug)
+                        fprint(2, "flushimage fail: d=%p: %r\n", d); /**/
+                d->bufp = d->buf;        /* might as well; chance of continuing */
+                return -1;
+        }
+        d->bufp = d->buf;
+        return 1;
+}
+
+int
+flushimage(Display *d, int visible)
+{
+        if(visible){
+                *d->bufp++ = 'v';        /* five bytes always reserved for this */
+                if(d->_isnewdisplay){
+                        BPLONG(d->bufp, d->screenimage->id);
+                        d->bufp += 4;
+                }
+        }
+        return doflush(d);
+}
+
+uchar*
+bufimage(Display *d, int n)
+{
+        uchar *p;
+
+        if(n<0 || n>d->bufsize){
+abort();
+                werrstr("bad count in bufimage");
+                return 0;
+        }
+        if(d->bufp+n > d->buf+d->bufsize)
+                if(doflush(d) < 0)
+                        return 0;
+        p = d->bufp;
+        d->bufp += n;
+        return p;
+}
+
diff --git a/src/libdraw/keyboard.c b/src/libdraw/keyboard.c
t@@ -0,0 +1,102 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+void
+closekeyboard(Keyboardctl *kc)
+{
+        if(kc == nil)
+                return;
+
+        postnote(PNPROC, kc->pid, "kill");
+
+#ifdef BUG
+        /* Drain the channel */
+        while(?kc->c)
+                <-kc->c;
+#endif
+
+        close(kc->ctlfd);
+        close(kc->consfd);
+        free(kc->file);
+        free(kc->c);
+        free(kc);
+}
+
+static
+void
+_ioproc(void *arg)
+{
+        int m, n;
+        char buf[20];
+        Rune r;
+        Keyboardctl *kc;
+
+        kc = arg;
+        threadsetname("kbdproc");
+        kc->pid = getpid();
+        n = 0;
+        for(;;){
+                while(n>0 && fullrune(buf, n)){
+                        m = chartorune(&r, buf);
+                        n -= m;
+                        memmove(buf, buf+m, n);
+                        send(kc->c, &r);
+                }
+                m = read(kc->consfd, buf+n, sizeof buf-n);
+                if(m <= 0){
+                        yield();        /* if error is due to exiting, we'll exit here */
+                        fprint(2, "keyboard read error: %r\n");
+                        threadexits("error");
+                }
+                n += m;
+        }
+}
+
+Keyboardctl*
+initkeyboard(char *file)
+{
+        Keyboardctl *kc;
+        char *t;
+
+        kc = mallocz(sizeof(Keyboardctl), 1);
+        if(kc == nil)
+                return nil;
+        if(file == nil)
+                file = "/dev/cons";
+        kc->file = strdup(file);
+        kc->consfd = open(file, ORDWR|OCEXEC);
+        t = malloc(strlen(file)+16);
+        if(kc->consfd<0 || t==nil){
+Error1:
+                free(kc);
+                return nil;
+        }
+        sprint(t, "%sctl", file);
+        kc->ctlfd = open(t, OWRITE|OCEXEC);
+        if(kc->ctlfd < 0){
+                fprint(2, "initkeyboard: can't open %s: %r\n", t);
+Error2:
+                close(kc->consfd);
+                free(t);
+                goto Error1;
+        }
+        if(ctlkeyboard(kc, "rawon") < 0){
+                fprint(2, "initkeyboard: can't turn on raw mode on %s: %r\n", t);
+                close(kc->ctlfd);
+                goto Error2;
+        }
+        free(t);
+        kc->c = chancreate(sizeof(Rune), 20);
+        proccreate(_ioproc, kc, 4096);
+        return kc;
+}
+
+int
+ctlkeyboard(Keyboardctl *kc, char *m)
+{
+        return write(kc->ctlfd, m, strlen(m));
+}
diff --git a/src/libdraw/keyboard.h b/src/libdraw/keyboard.h
t@@ -0,0 +1,36 @@
+typedef struct         Keyboardctl Keyboardctl;
+
+struct        Keyboardctl
+{
+        struct Channel        *c;        /* chan(Rune)[20] */
+
+        char                *file;
+        int                consfd;                /* to cons file */
+        int                ctlfd;                /* to ctl file */
+        int                pid;                /* of slave proc */
+};
+
+
+extern        Keyboardctl*        initkeyboard(char*);
+extern        int                        ctlkeyboard(Keyboardctl*, char*);
+extern        void                        closekeyboard(Keyboardctl*);
+
+enum {
+        KF=        0xF000,        /* Rune: beginning of private Unicode space */
+        /* KF|1, KF|2, ..., KF|0xC is F1, F2, ..., F12 */
+        Khome=        KF|0x0D,
+        Kup=        KF|0x0E,
+        Kpgup=        KF|0x0F,
+        Kprint=        KF|0x10,
+        Kleft=        KF|0x11,
+        Kright=        KF|0x12,
+        Kdown=        0x80,
+        Kview=        0x80,
+        Kpgdown=        KF|0x13,
+        Kins=        KF|0x14,
+        Kend=        '\r',        /* [sic] */
+
+        Kalt=                KF|0x15,
+        Kshift=        KF|0x16,
+        Kctl=                KF|0x17,
+};
diff --git a/src/libdraw/libdraw.x b/src/libdraw/libdraw.x
t@@ -0,0 +1 @@
+!
diff --git a/src/libdraw/md-alloc.c b/src/libdraw/md-alloc.c
t@@ -0,0 +1,200 @@
+#include 
+#include 
+#include 
+#include 
+
+#define poolalloc(a, b) malloc(b)
+#define poolfree(a, b) free(b)
+
+void
+memimagemove(void *from, void *to)
+{
+        Memdata *md;
+
+        md = *(Memdata**)to;
+        if(md->base != from){
+                print("compacted data not right: #%p\n", md->base);
+                abort();
+        }
+        md->base = to;
+
+        /* if allocmemimage changes this must change too */
+        md->bdata = (uchar*)&md->base[2];
+}
+
+Memimage*
+allocmemimaged(Rectangle r, u32int chan, Memdata *md, void *X)
+{
+        int d;
+        u32int l;
+        Memimage *i;
+
+        if(Dx(r) <= 0 || Dy(r) <= 0){
+                werrstr("bad rectangle %R", r);
+                return nil;
+        }
+        if((d = chantodepth(chan)) == 0) {
+                werrstr("bad channel descriptor %.8lux", chan);
+                return nil;
+        }
+
+        l = wordsperline(r, d);
+
+        i = mallocz(sizeof(Memimage), 1);
+        if(i == nil)
+                return nil;
+
+        i->X = X;
+        i->data = md;
+        i->zero = sizeof(u32int)*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, u32int chan)
+{
+        int d;
+        u32int l, nw;
+        Memdata *md;
+        Memimage *i;
+
+        if((d = chantodepth(chan)) == 0) {
+                werrstr("bad channel descriptor %.8lux", chan);
+                return nil;
+        }
+
+        l = wordsperline(r, d);
+        nw = l*Dy(r);
+        md = malloc(sizeof(Memdata));
+        if(md == nil)
+                return nil;
+
+        md->ref = 1;
+        md->base = poolalloc(imagmem, (2+nw)*sizeof(u32int));
+        if(md->base == nil){
+                free(md);
+                return nil;
+        }
+
+        md->base[0] = (u32int)md;
+        md->base[1] = getcallerpc(&r);
+
+        /* if this changes, memimagemove must change too */
+        md->bdata = (uchar*)&md->base[2];
+
+        md->allocd = 1;
+
+        i = allocmemimaged(r, chan, md, nil);
+        if(i == nil){
+                poolfree(imagmem, md->base);
+                free(md);
+                return nil;
+        }
+        md->imref = i;
+        return i;
+}
+
+void
+_freememimage(Memimage *i)
+{
+        if(i == nil)
+                return;
+        if(i->data->ref-- == 1 && i->data->allocd){
+                if(i->data->base)
+                        poolfree(imagmem, i->data->base);
+                free(i->data);
+        }
+        free(i);
+}
+
+/*
+ * Wordaddr is deprecated.
+ */
+u32int*
+wordaddr(Memimage *i, Point p)
+{
+        return (u32int*) ((u32int)byteaddr(i, p) & ~(sizeof(u32int)-1));
+}
+
+uchar*
+byteaddr(Memimage *i, Point p)
+{
+        uchar *a;
+
+        a = i->data->bdata+i->zero+sizeof(u32int)*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, u32int chan)
+{
+        int d;
+        int t, j, k;
+        u32int 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[t] = NBITS(cc);
+                if(NBITS(cc) != 8)
+                        bytes = 0;
+        }
+        i->nchan = k;
+        if(bytes)
+                i->flags |= Fbytes;
+        return 0;
+}
diff --git a/src/libdraw/md-arc.c b/src/libdraw/md-arc.c
t@@ -0,0 +1,116 @@
+#include 
+#include 
+#include 
+#include 
+
+/*
+ * elarc(dst,c,a,b,t,src,sp,alpha,phi)
+ *   draws the part of an ellipse between rays at angles alpha and alpha+phi
+ *   measured counterclockwise from the positive x axis. other
+ *   arguments are as for ellipse(dst,c,a,b,t,src,sp)
+ */
+
+enum
+{
+        R, T, L, B        /* right, top, left, bottom */
+};
+
+static
+Point corners[] = {
+        {1,1},
+        {-1,1},
+        {-1,-1},
+        {1,-1}
+};
+
+static
+Point p00;
+
+/*
+ * make a "wedge" mask covering the desired angle and contained in
+ * a surrounding square; draw a full ellipse; intersect that with the
+ * wedge to make a mask through which to copy src to dst.
+ */
+void
+memarc(Memimage *dst, Point c, int a, int b, int t, Memimage *src, Point sp, int alpha, int phi, int op)
+{
+        int i, w, beta, tmp, c1, c2, m, m1;
+        Rectangle rect;
+        Point p,        bnd[8];
+        Memimage *wedge, *figure, *mask;
+
+        if(a < 0)
+                a = -a;
+        if(b < 0)
+                b = -b;
+        w = t;
+        if(w < 0)
+                w = 0;
+        alpha = -alpha;                /* compensate for upside-down coords */
+        phi = -phi;
+        beta = alpha + phi;
+        if(phi < 0){
+                tmp = alpha;
+                alpha = beta;
+                beta = tmp;
+                phi = -phi;
+        }
+        if(phi >= 360){
+                memellipse(dst, c, a, b, t, src, sp, op);
+                return;
+        }
+        while(alpha < 0)
+                alpha += 360;
+        while(beta < 0)
+                beta += 360;
+        c1 = alpha/90 & 3;        /* number of nearest corner */
+        c2 = beta/90 & 3;
+                /*
+                 * icossin returns point at radius ICOSSCALE.
+                 * multiplying by m1 moves it outside the ellipse
+                */
+        rect = Rect(-a-w, -b-w, a+w+1, b+w+1);
+        m = rect.max.x;        /* inradius of bounding square */
+        if(m < rect.max.y)
+                m = rect.max.y;
+        m1 = (m+ICOSSCALE-1) >> 10;
+        m = m1 << 10;                /* assure m1*cossin is inside */
+        i = 0;
+        bnd[i++] = Pt(0,0);
+        icossin(alpha, &p.x, &p.y);
+        bnd[i++] = mulpt(p, m1);
+        for(;;) {
+                bnd[i++] = mulpt(corners[c1], m);
+                if(c1==c2 && phi<180)
+                        break;
+                c1 = (c1+1) & 3;
+                phi -= 90;
+        }
+        icossin(beta, &p.x, &p.y);
+        bnd[i++] = mulpt(p, m1);
+
+        figure = nil;
+        mask = nil;
+        wedge = allocmemimage(rect, GREY1);
+        if(wedge == nil)
+                goto Return;
+        memfillcolor(wedge, DTransparent);
+        memfillpoly(wedge, bnd, i, ~0, memopaque, p00, S);
+        figure = allocmemimage(rect, GREY1);
+        if(figure == nil)
+                goto Return;
+        memfillcolor(figure, DTransparent);
+        memellipse(figure, p00, a, b, t, memopaque, p00, S);
+        mask = allocmemimage(rect, GREY1);
+        if(mask == nil)
+                goto Return;
+        memfillcolor(mask, DTransparent);
+        memimagedraw(mask, rect, figure, rect.min, wedge, rect.min, S);
+        c = subpt(c, dst->r.min);
+        memdraw(dst, dst->r, src, subpt(sp, c), mask, subpt(p00, c), op);
+
+    Return:
+        freememimage(wedge);
+        freememimage(figure);
+        freememimage(mask);
+}
diff --git a/src/libdraw/md-arctest.c b/src/libdraw/md-arctest.c
t@@ -0,0 +1,61 @@
+#include 
+#include 
+#include 
+#include 
+
+extern int drawdebug;
+void
+main(int argc, char **argv)
+{
+        char cc;
+        Memimage *x;
+        Point c = {208,871};
+        int a = 441;
+        int b = 441;
+        int thick = 0;
+        Point sp = {0,0};
+        int alpha = 51;
+        int phi = 3;
+        vlong t0, t1;
+        int i, n;
+        vlong del;
+
+        memimageinit();
+
+        x = allocmemimage(Rect(0,0,1000,1000), CMAP8);
+        n = atoi(argv[1]);
+
+        t0 = nsec();
+        t0 = nsec();
+        t0 = nsec();
+        t1 = nsec();
+        del = t1-t0;
+        t0 = nsec();
+        for(i=0; i
diff --git a/src/libdraw/md-cload.c b/src/libdraw/md-cload.c
t@@ -0,0 +1,68 @@
+#include 
+#include 
+#include 
+#include 
+
+int
+_cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+        int y, bpl, c, cnt, offs;
+        uchar mem[NMEM], *memp, *omemp, *emem, *linep, *elinep, *u, *eu;
+
+        if(!rectinrect(r, i->r))
+                return -1;
+        bpl = bytesperline(r, i->depth);
+        u = data;
+        eu = data+ndata;
+        memp = mem;
+        emem = mem+NMEM;
+        y = r.min.y;
+        linep = byteaddr(i, Pt(r.min.x, y));
+        elinep = linep+bpl;
+        for(;;){
+                if(linep == elinep){
+                        if(++y == r.max.y)
+                                break;
+                        linep = byteaddr(i, Pt(r.min.x, y));
+                        elinep = linep+bpl;
+                }
+                if(u == eu){        /* buffer too small */
+                        return -1;
+                }
+                c = *u++;
+                if(c >= 128){
+                        for(cnt=c-128+1; cnt!=0 ;--cnt){
+                                if(u == eu){                /* buffer too small */
+                                        return -1;
+                                }
+                                if(linep == elinep){        /* phase error */
+                                        return -1;
+                                }
+                                *linep++ = *u;
+                                *memp++ = *u++;
+                                if(memp == emem)
+                                        memp = mem;
+                        }
+                }
+                else{
+                        if(u == eu)        /* short buffer */
+                                return -1;
+                        offs = *u++ + ((c&3)<<8)+1;
+                        if(memp-mem < offs)
+                                omemp = memp+(NMEM-offs);
+                        else
+                                omemp = memp-offs;
+                        for(cnt=(c>>2)+NMATCH; cnt!=0; --cnt){
+                                if(linep == elinep)        /* phase error */
+                                        return -1;
+                                *linep++ = *omemp;
+                                *memp++ = *omemp++;
+                                if(omemp == emem)
+                                        omemp = mem;
+                                if(memp == emem)
+                                        memp = mem;
+                        }
+                }
+        }
+        return u-data;
+}
diff --git a/src/libdraw/md-cmap.c b/src/libdraw/md-cmap.c
t@@ -0,0 +1,320 @@
+/*
+ * generated by mkcmap.c
+ */
+#include 
+#include 
+#include 
+#include 
+
+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){}
diff --git a/src/libdraw/md-cread.c b/src/libdraw/md-cread.c
t@@ -0,0 +1,96 @@
+#include 
+#include 
+#include 
+#include 
+
+Memimage*
+creadmemimage(int fd)
+{
+        char hdr[5*12+1];
+        Rectangle r;
+        int m, nb, miny, maxy, new, ldepth, ncblock;
+        uchar *buf;
+        Memimage *i;
+        u32int 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
diff --git a/src/libdraw/md-defont.c b/src/libdraw/md-defont.c
t@@ -0,0 +1,68 @@
+#include 
+#include 
+#include 
+#include 
+
+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 u32int*.
+         */
+        p = (char*)defontdata;
+        n = (u32int)p & 3;
+        if(n != 0){
+                memmove(p+(4-n), p, sizeofdefont-n);
+                p += 4-n;
+        }
+        ld = atoi(p+0*12);
+        r.min.x = atoi(p+1*12);
+        r.min.y = atoi(p+2*12);
+        r.max.x = atoi(p+3*12);
+        r.max.y = atoi(p+4*12);
+
+        md = mallocz(sizeof(Memdata), 1);
+        if(md == nil)
+                return nil;
+        
+        p += 5*12;
+
+        md->base = nil;                /* so freememimage doesn't free p */
+        md->bdata = (uchar*)p;        /* ick */
+        md->ref = 1;
+        md->allocd = 1;                /* so freememimage does free md */
+
+        i = allocmemimaged(r, drawld2chan[ld], md, nil);
+        if(i == nil){
+                free(md);
+                return nil;
+        }
+
+        hdr = p+Dy(r)*i->width*sizeof(u32int);
+        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;
+}
diff --git a/src/libdraw/md-draw.c b/src/libdraw/md-draw.c
t@@ -0,0 +1,2487 @@
+#include 
+#include 
+#include 
+#include 
+
+int drawdebug;
+static int        tablesbuilt;
+
+/* perfect approximation to NTSC = .299r+.587g+.114b when 0 ≤ r,g,b < 256 */
+#define RGB2K(r,g,b)        ((156763*(r)+307758*(g)+59769*(b))>>19)
+
+/*
+ * for 0 ≤ x ≤ 255*255, (x*0x0101+0x100)>>16 is a perfect approximation.
+ * for 0 ≤ x < (1<<16), x/255 = ((x+1)*0x0101)>>16 is a perfect approximation.
+ * the last one is perfect for all up to 1<<16, avoids a multiply, but requires a rathole.
+ */
+/* #define DIV255(x) (((x)*257+256)>>16)  */
+#define DIV255(x) ((((x)+1)*257)>>16)
+/* #define DIV255(x) (tmp=(x)+1, (tmp+(tmp>>8))>>8) */
+
+#define MUL(x, y, t)        (t = (x)*(y)+128, (t+(t>>8))>>8)
+#define MASK13        0xFF00FF00
+#define MASK02        0x00FF00FF
+#define MUL13(a, x, t)                (t = (a)*(((x)&MASK13)>>8)+128, ((t+((t>>8)&MASK02))>>8)&MASK02)
+#define MUL02(a, x, t)                (t = (a)*(((x)&MASK02)>>0)+128, ((t+((t>>8)&MASK02))>>8)&MASK02)
+#define MUL0123(a, x, s, t)        ((MUL13(a, x, s)<<8)|MUL02(a, x, t))
+
+#define MUL2(u, v, x, y)        (t = (u)*(v)+(x)*(y)+256, (t+(t>>8))>>8)
+
+static void mktables(void);
+typedef int Subdraw(Memdrawparam*);
+static Subdraw chardraw, alphadraw, memoptdraw;
+
+static Memimage*        memones;
+static Memimage*        memzeros;
+Memimage *memwhite;
+Memimage *memblack;
+Memimage *memtransparent;
+Memimage *memopaque;
+
+int        __ifmt(Fmt*);
+
+void
+memimageinit(void)
+{
+        static int didinit = 0;
+
+        if(didinit)
+                return;
+
+        didinit = 1;
+
+        mktables();
+        _memmkcmap();
+
+        fmtinstall('R', Rfmt); 
+        fmtinstall('P', Pfmt);
+        fmtinstall('b', __ifmt);
+
+        memones = allocmemimage(Rect(0,0,1,1), GREY1);
+        memones->flags |= Frepl;
+        memones->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+        *byteaddr(memones, ZP) = ~0;
+
+        memzeros = allocmemimage(Rect(0,0,1,1), GREY1);
+        memzeros->flags |= Frepl;
+        memzeros->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+        *byteaddr(memzeros, ZP) = 0;
+
+        if(memones == nil || memzeros == nil)
+                assert(0 /*cannot initialize memimage library */);        /* RSC BUG */
+
+        memwhite = memones;
+        memblack = memzeros;
+        memopaque = memones;
+        memtransparent = memzeros;
+}
+
+u32int _imgtorgba(Memimage*, u32int);
+u32int _rgbatoimg(Memimage*, u32int);
+u32int _pixelbits(Memimage*, Point);
+
+#define DBG if(0)
+static Memdrawparam par;
+
+Memdrawparam*
+_memimagedrawsetup(Memimage *dst, Rectangle r, Memimage *src, Point p0, Memimage *mask, Point p1, int op)
+{
+        if(mask == nil)
+                mask = memopaque;
+
+DBG        print("memimagedraw %p/%luX %R @ %p %p/%luX %P %p/%luX %P... ", dst, dst->chan, r, dst->data->bdata, src, src->chan, p0, mask, mask->chan, p1);
+
+        if(drawclip(dst, &r, src, &p0, mask, &p1, &par.sr, &par.mr) == 0){
+//                if(drawdebug)
+//                        iprint("empty clipped rectangle\n");
+                return nil;
+        }
+
+        if(op < Clear || op > SoverD){
+//                if(drawdebug)
+//                        iprint("op out of range: %d\n", op);
+                return nil;
+        }
+
+        par.op = op;
+        par.dst = dst;
+        par.r = r;
+        par.src = src;
+        /* par.sr set by drawclip */
+        par.mask = mask;
+        /* par.mr set by drawclip */
+
+        par.state = 0;
+        if(src->flags&Frepl){
+                par.state |= Replsrc;
+                if(Dx(src->r)==1 && Dy(src->r)==1){
+                        par.sval = pixelbits(src, src->r.min);
+                        par.state |= Simplesrc;
+                        par.srgba = _imgtorgba(src, par.sval);
+                        par.sdval = _rgbatoimg(dst, par.srgba);
+                        if((par.srgba&0xFF) == 0 && (op&DoutS)){
+//                                if (drawdebug) iprint("fill with transparent source\n");
+                                return nil;        /* no-op successfully handled */
+                        }
+                }
+        }
+
+        if(mask->flags & Frepl){
+                par.state |= Replmask;
+                if(Dx(mask->r)==1 && Dy(mask->r)==1){
+                        par.mval = pixelbits(mask, mask->r.min);
+                        if(par.mval == 0 && (op&DoutS)){
+//                                if(drawdebug) iprint("fill with zero mask\n");
+                                return nil;        /* no-op successfully handled */
+                        }
+                        par.state |= Simplemask;
+                        if(par.mval == ~0)
+                                par.state |= Fullmask;
+                        par.mrgba = _imgtorgba(mask, par.mval);
+                }
+        }
+
+//        if(drawdebug)
+//                iprint("dr %R sr %R mr %R...", r, par.sr, par.mr);
+DBG print("draw dr %R sr %R mr %R %lux\n", r, par.sr, par.mr, par.state);
+
+        return ∥
+}
+
+void
+_memimagedraw(Memdrawparam *par)
+{
+        /*
+         * Now that we've clipped the parameters down to be consistent, we 
+         * simply try sub-drawing routines in order until we find one that was able
+         * to handle us.  If the sub-drawing routine returns zero, it means it was
+         * unable to satisfy the request, so we do not return.
+         */
+
+        /*
+         * Hardware support.  Each video driver provides this function,
+         * which checks to see if there is anything it can help with.
+         * There could be an if around this checking to see if dst is in video memory.
+         */
+DBG print("test hwdraw\n");
+        if(hwdraw(par)){
+//if(drawdebug) iprint("hw handled\n");
+DBG print("hwdraw handled\n");
+                return;
+        }
+        /*
+         * Optimizations using memmove and memset.
+         */
+DBG print("test memoptdraw\n");
+        if(memoptdraw(par)){
+//if(drawdebug) iprint("memopt handled\n");
+DBG print("memopt handled\n");
+                return;
+        }
+
+        /*
+         * Character drawing.
+         * Solid source color being painted through a boolean mask onto a high res image.
+         */
+DBG print("test chardraw\n");
+        if(chardraw(par)){
+//if(drawdebug) iprint("chardraw handled\n");
+DBG print("chardraw handled\n");
+                return;
+        }
+
+        /*
+         * General calculation-laden case that does alpha for each pixel.
+         */
+DBG print("do alphadraw\n");
+        alphadraw(par);
+//if(drawdebug) iprint("alphadraw handled\n");
+DBG print("alphadraw handled\n");
+}
+#undef DBG
+
+/*
+ * Clip the destination rectangle further based on the properties of the 
+ * source and mask rectangles.  Once the destination rectangle is properly
+ * clipped, adjust the source and mask rectangles to be the same size.
+ * Then if source or mask is replicated, move its clipped rectangle
+ * so that its minimum point falls within the repl rectangle.
+ *
+ * Return zero if the final rectangle is null.
+ */
+int
+drawclip(Memimage *dst, Rectangle *r, Memimage *src, Point *p0, Memimage *mask, Point *p1, Rectangle *sr, Rectangle *mr)
+{
+        Point rmin, delta;
+        int splitcoords;
+        Rectangle omr;
+
+        if(r->min.x>=r->max.x || r->min.y>=r->max.y)
+                return 0;
+        splitcoords = (p0->x!=p1->x) || (p0->y!=p1->y);
+        /* clip to destination */
+        rmin = r->min;
+        if(!rectclip(r, dst->r) || !rectclip(r, dst->clipr))
+                return 0;
+        /* move mask point */
+        p1->x += r->min.x-rmin.x;
+        p1->y += r->min.y-rmin.y;
+        /* move source point */
+        p0->x += r->min.x-rmin.x;
+        p0->y += r->min.y-rmin.y;
+        /* map destination rectangle into source */
+        sr->min = *p0;
+        sr->max.x = p0->x+Dx(*r);
+        sr->max.y = p0->y+Dy(*r);
+        /* sr is r in source coordinates; clip to source */
+        if(!(src->flags&Frepl) && !rectclip(sr, src->r))
+                return 0;
+        if(!rectclip(sr, src->clipr))
+                return 0;
+        /* compute and clip rectangle in mask */
+        if(splitcoords){
+                /* move mask point with source */
+                p1->x += sr->min.x-p0->x;
+                p1->y += sr->min.y-p0->y;
+                mr->min = *p1;
+                mr->max.x = p1->x+Dx(*sr);
+                mr->max.y = p1->y+Dy(*sr);
+                omr = *mr;
+                /* mr is now rectangle in mask; clip it */
+                if(!(mask->flags&Frepl) && !rectclip(mr, mask->r))
+                        return 0;
+                if(!rectclip(mr, mask->clipr))
+                        return 0;
+                /* reflect any clips back to source */
+                sr->min.x += mr->min.x-omr.min.x;
+                sr->min.y += mr->min.y-omr.min.y;
+                sr->max.x += mr->max.x-omr.max.x;
+                sr->max.y += mr->max.y-omr.max.y;
+                *p1 = mr->min;
+        }else{
+                if(!(mask->flags&Frepl) && !rectclip(sr, mask->r))
+                        return 0;
+                if(!rectclip(sr, mask->clipr))
+                        return 0;
+                *p1 = sr->min;
+        }
+
+        /* move source clipping back to destination */
+        delta.x = r->min.x - p0->x;
+        delta.y = r->min.y - p0->y;
+        r->min.x = sr->min.x + delta.x;
+        r->min.y = sr->min.y + delta.y;
+        r->max.x = sr->max.x + delta.x;
+        r->max.y = sr->max.y + delta.y;
+
+        /* move source rectangle so sr->min is in src->r */
+        if(src->flags&Frepl) {
+                delta.x = drawreplxy(src->r.min.x, src->r.max.x, sr->min.x) - sr->min.x;
+                delta.y = drawreplxy(src->r.min.y, src->r.max.y, sr->min.y) - sr->min.y;
+                sr->min.x += delta.x;
+                sr->min.y += delta.y;
+                sr->max.x += delta.x;
+                sr->max.y += delta.y;
+        }
+        *p0 = sr->min;
+
+        /* move mask point so it is in mask->r */
+        *p1 = drawrepl(mask->r, *p1);
+        mr->min = *p1;
+        mr->max.x = p1->x+Dx(*sr);
+        mr->max.y = p1->y+Dy(*sr);
+
+        assert(Dx(*sr) == Dx(*mr) && Dx(*mr) == Dx(*r));
+        assert(Dy(*sr) == Dy(*mr) && Dy(*mr) == Dy(*r));
+        assert(ptinrect(*p0, src->r));
+        assert(ptinrect(*p1, mask->r));
+        assert(ptinrect(r->min, dst->r));
+
+        return 1;
+}
+
+/*
+ * Conversion tables.
+ */
+static uchar replbit[1+8][256];                /* replbit[x][y] is the replication of the x-bit quantity y to 8-bit depth */
+static uchar conv18[256][8];                /* conv18[x][y] is the yth pixel in the depth-1 pixel x */
+static uchar conv28[256][4];                /* ... */
+static uchar conv48[256][2];
+
+/*
+ * bitmap of how to replicate n bits to fill 8, for 1 ≤ n ≤ 8.
+ * the X's are where to put the bottom (ones) bit of the n-bit pattern.
+ * only the top 8 bits of the result are actually used.
+ * (the lower 8 bits are needed to get bits in the right place
+ * when n is not a divisor of 8.)
+ *
+ * Should check to see if its easier to just refer to replmul than
+ * use the precomputed values in replbit.  On PCs it may well
+ * be; on machines with slow multiply instructions it probably isn't.
+ */
+#define a ((((((((((((((((0
+#define X *2+1)
+#define _ *2)
+static int replmul[1+8] = {
+        0,
+        a X X X X X X X X X X X X X X X X,
+        a _ X _ X _ X _ X _ X _ X _ X _ X,
+        a _ _ X _ _ X _ _ X _ _ X _ _ X _,
+        a _ _ _ X _ _ _ X _ _ _ X _ _ _ X,
+        a _ _ _ _ X _ _ _ _ X _ _ _ _ X _,
+        a _ _ _ _ _ X _ _ _ _ _ X _ _ _ _, 
+        a _ _ _ _ _ _ X _ _ _ _ _ _ X _ _,
+        a _ _ _ _ _ _ _ X _ _ _ _ _ _ _ X,
+};
+#undef a
+#undef X
+#undef _
+
+static void
+mktables(void)
+{
+        int i, j, mask, sh, small;
+                
+        if(tablesbuilt)
+                return;
+
+        fmtinstall('R', Rfmt);
+        fmtinstall('P', Pfmt);
+        tablesbuilt = 1;
+
+        /* bit replication up to 8 bits */
+        for(i=0; i<256; i++){
+                for(j=0; j<=8; j++){        /* j <= 8 [sic] */
+                        small = i & ((1<>8;
+                }
+        }
+
+        /* bit unpacking up to 8 bits, only powers of 2 */
+        for(i=0; i<256; i++){
+                for(j=0, sh=7, mask=1; j<8; j++, sh--)
+                        conv18[i][j] = replbit[1][(i>>sh)&mask];
+
+                for(j=0, sh=6, mask=3; j<4; j++, sh-=2)
+                        conv28[i][j] = replbit[2][(i>>sh)&mask];
+
+                for(j=0, sh=4, mask=15; j<2; j++, sh-=4)
+                        conv48[i][j] = replbit[4][(i>>sh)&mask];
+        }
+}
+
+static uchar ones = 0xff;
+
+/*
+ * General alpha drawing case.  Can handle anything.
+ */
+typedef struct        Buffer        Buffer;
+struct Buffer {
+        /* used by most routines */
+        uchar        *red;
+        uchar        *grn;
+        uchar        *blu;
+        uchar        *alpha;
+        uchar        *grey;
+        u32int        *rgba;
+        int        delta;        /* number of bytes to add to pointer to get next pixel to the right */
+
+        /* used by boolcalc* for mask data */
+        uchar        *m;                /* ptr to mask data r.min byte; like p->bytermin */
+        int                mskip;        /* no. of left bits to skip in *m */
+        uchar        *bm;                /* ptr to mask data img->r.min byte; like p->bytey0s */
+        int                bmskip;        /* no. of left bits to skip in *bm */
+        uchar        *em;                /* ptr to mask data img->r.max.x byte; like p->bytey0e */
+        int                emskip;        /* no. of right bits to skip in *em */
+};
+
+typedef struct        Param        Param;
+typedef Buffer        Readfn(Param*, uchar*, int);
+typedef void        Writefn(Param*, uchar*, Buffer);
+typedef Buffer        Calcfn(Buffer, Buffer, Buffer, int, int, int);
+
+enum {
+        MAXBCACHE = 16
+};
+
+/* giant rathole to customize functions with */
+struct Param {
+        Readfn        *replcall;
+        Readfn        *greymaskcall;        
+        Readfn        *convreadcall;
+        Writefn        *convwritecall;
+
+        Memimage *img;
+        Rectangle        r;
+        int        dx;        /* of r */
+        int        needbuf;
+        int        convgrey;
+        int        alphaonly;
+
+        uchar        *bytey0s;                /* byteaddr(Pt(img->r.min.x, img->r.min.y)) */
+        uchar        *bytermin;        /* byteaddr(Pt(r.min.x, img->r.min.y)) */
+        uchar        *bytey0e;                /* byteaddr(Pt(img->r.max.x, img->r.min.y)) */
+        int                bwidth;
+
+        int        replcache;        /* if set, cache buffers */
+        Buffer        bcache[MAXBCACHE];
+        u32int        bfilled;
+        uchar        *bufbase;
+        int        bufoff;
+        int        bufdelta;
+
+        int        dir;
+
+        int        convbufoff;
+        uchar        *convbuf;
+        Param        *convdpar;
+        int        convdx;
+};
+
+static uchar *drawbuf;
+static int        ndrawbuf;
+static int        mdrawbuf;
+static Param spar, mpar, dpar;        /* easier on the stacks */
+static Readfn        greymaskread, replread, readptr;
+static Writefn        nullwrite;
+static Calcfn        alphacalc0, alphacalc14, alphacalc2810, alphacalc3679, alphacalc5, alphacalc11, alphacalcS;
+static Calcfn        boolcalc14, boolcalc236789, boolcalc1011;
+
+static Readfn*        readfn(Memimage*);
+static Readfn*        readalphafn(Memimage*);
+static Writefn*        writefn(Memimage*);
+
+static Calcfn*        boolcopyfn(Memimage*, Memimage*);
+static Readfn*        convfn(Memimage*, Param*, Memimage*, Param*);
+
+static Calcfn *alphacalc[Ncomp] = 
+{
+        alphacalc0,                /* Clear */
+        alphacalc14,                /* DoutS */
+        alphacalc2810,                /* SoutD */
+        alphacalc3679,                /* DxorS */
+        alphacalc14,                /* DinS */
+        alphacalc5,                /* D */
+        alphacalc3679,                /* DatopS */
+        alphacalc3679,                /* DoverS */
+        alphacalc2810,                /* SinD */
+        alphacalc3679,                /* SatopD */
+        alphacalc2810,                /* S */
+        alphacalc11,                /* SoverD */
+};
+
+static Calcfn *boolcalc[Ncomp] =
+{
+        alphacalc0,                /* Clear */
+        boolcalc14,                /* DoutS */
+        boolcalc236789,                /* SoutD */
+        boolcalc236789,                /* DxorS */
+        boolcalc14,                /* DinS */
+        alphacalc5,                /* D */
+        boolcalc236789,                /* DatopS */
+        boolcalc236789,                /* DoverS */
+        boolcalc236789,                /* SinD */
+        boolcalc236789,                /* SatopD */
+        boolcalc1011,                /* S */
+        boolcalc1011,                /* SoverD */
+};
+
+static int
+allocdrawbuf(void)
+{
+        uchar *p;
+
+        if(ndrawbuf > mdrawbuf){
+                p = realloc(drawbuf, ndrawbuf);
+                if(p == nil){
+                        werrstr("memimagedraw out of memory");
+                        return -1;
+                }
+                drawbuf = p;
+                mdrawbuf = ndrawbuf;
+        }
+        return 0;
+}
+
+static Param
+getparam(Memimage *img, Rectangle r, int convgrey, int needbuf)
+{
+        Param p;
+        int nbuf;
+
+        memset(&p, 0, sizeof p);
+
+        p.img = img;
+        p.r = r;
+        p.dx = Dx(r);
+        p.needbuf = needbuf;
+        p.convgrey = convgrey;
+
+        assert(img->r.min.x <= r.min.x && r.min.x < img->r.max.x);
+
+        p.bytey0s = byteaddr(img, Pt(img->r.min.x, img->r.min.y));
+        p.bytermin = byteaddr(img, Pt(r.min.x, img->r.min.y));
+        p.bytey0e = byteaddr(img, Pt(img->r.max.x, img->r.min.y));
+        p.bwidth = sizeof(u32int)*img->width;
+
+        assert(p.bytey0s <= p.bytermin && p.bytermin <= p.bytey0e);
+
+        if(p.r.min.x == p.img->r.min.x)
+                assert(p.bytermin == p.bytey0s);
+
+        nbuf = 1;
+        if((img->flags&Frepl) && Dy(img->r) <= MAXBCACHE && Dy(img->r) < Dy(r)){
+                p.replcache = 1;
+                nbuf = Dy(img->r);
+        }
+        p.bufdelta = 4*p.dx;
+        p.bufoff = ndrawbuf;
+        ndrawbuf += p.bufdelta*nbuf;
+
+        return p;
+}
+
+static void
+clipy(Memimage *img, int *y)
+{
+        int dy;
+
+        dy = Dy(img->r);
+        if(*y == dy)
+                *y = 0;
+        else if(*y == -1)
+                *y = dy-1;
+        assert(0 <= *y && *y < dy);
+}
+
+static void
+dumpbuf(char *s, Buffer b, int n)
+{
+        int i;
+        uchar *p;
+        
+        print("%s", s);
+        for(i=0; ir;
+        dx = Dx(r);
+        dy = Dy(r);
+
+        ndrawbuf = 0;
+
+        src = par->src;
+        mask = par->mask;        
+        dst = par->dst;
+        sr = par->sr;
+        mr = par->mr;
+        op = par->op;
+
+        isgrey = dst->flags&Fgrey;
+
+        /*
+         * Buffering when src and dst are the same bitmap is sufficient but not 
+         * necessary.  There are stronger conditions we could use.  We could
+         * check to see if the rectangles intersect, and if simply moving in the
+         * correct y direction can avoid the need to buffer.
+         */
+        needbuf = (src->data == dst->data);
+
+        spar = getparam(src, sr, isgrey, needbuf);
+        dpar = getparam(dst, r, isgrey, needbuf);
+        mpar = getparam(mask, mr, 0, needbuf);
+
+        dir = (needbuf && byteaddr(dst, r.min) > byteaddr(src, sr.min)) ? -1 : 1;
+        spar.dir = mpar.dir = dpar.dir = dir;
+
+        /*
+         * If the mask is purely boolean, we can convert from src to dst format
+         * when we read src, and then just copy it to dst where the mask tells us to.
+         * This requires a boolean (1-bit grey) mask and lack of a source alpha channel.
+         *
+         * The computation is accomplished by assigning the function pointers as follows:
+         *        rdsrc - read and convert source into dst format in a buffer
+         *         rdmask - convert mask to bytes, set pointer to it
+         *         rddst - fill with pointer to real dst data, but do no reads
+         *        calc - copy src onto dst when mask says to.
+         *        wrdst - do nothing
+         * This is slightly sleazy, since things aren't doing exactly what their names say,
+         * but it avoids a fair amount of code duplication to make this a case here
+         * rather than have a separate booldraw.
+         */
+//if(drawdebug) iprint("flag %lud mchan %lux=?%x dd %d\n", src->flags&Falpha, mask->chan, GREY1, dst->depth);
+        if(!(src->flags&Falpha) && mask->chan == GREY1 && dst->depth >= 8 && op == SoverD){
+//if(drawdebug) iprint("boolcopy...");
+                rdsrc = convfn(dst, &dpar, src, &spar);
+                rddst = readptr;
+                rdmask = readfn(mask);
+                calc = boolcopyfn(dst, mask);
+                wrdst = nullwrite;
+        }else{
+                /* usual alphadraw parameter fetching */
+                rdsrc = readfn(src);
+                rddst = readfn(dst);
+                wrdst = writefn(dst);
+                calc = alphacalc[op];
+
+                /*
+                 * If there is no alpha channel, we'll ask for a grey channel
+                 * and pretend it is the alpha.
+                 */
+                if(mask->flags&Falpha){
+                        rdmask = readalphafn(mask);
+                        mpar.alphaonly = 1;
+                }else{
+                        mpar.greymaskcall = readfn(mask);
+                        mpar.convgrey = 1;
+                        rdmask = greymaskread;
+
+                        /*
+                         * Should really be above, but then boolcopyfns would have
+                         * to deal with bit alignment, and I haven't written that.
+                         *
+                         * This is a common case for things like ellipse drawing.
+                         * When there's no alpha involved and the mask is boolean,
+                         * we can avoid all the division and multiplication.
+                         */
+                        if(mask->chan == GREY1 && !(src->flags&Falpha))
+                                calc = boolcalc[op];
+                        else if(op == SoverD && !(src->flags&Falpha))
+                                calc = alphacalcS;
+                }
+        }
+
+        /*
+         * If the image has a small enough repl rectangle,
+         * we can just read each line once and cache them.
+         */
+        if(spar.replcache){
+                spar.replcall = rdsrc;
+                rdsrc = replread;
+        }
+        if(mpar.replcache){
+                mpar.replcall = rdmask;
+                rdmask = replread;
+        }
+
+        if(allocdrawbuf() < 0)
+                return 0;
+
+        /*
+         * Before we were saving only offsets from drawbuf in the parameter
+         * structures; now that drawbuf has been grown to accomodate us,
+         * we can fill in the pointers.
+         */
+        spar.bufbase = drawbuf+spar.bufoff;
+        mpar.bufbase = drawbuf+mpar.bufoff;
+        dpar.bufbase = drawbuf+dpar.bufoff;
+        spar.convbuf = drawbuf+spar.convbufoff;
+
+        if(dir == 1){
+                starty = 0;
+                endy = dy;
+        }else{
+                starty = dy-1;
+                endy = -1;
+        }
+
+        /*
+         * srcy, masky, and dsty are offsets from the top of their
+         * respective Rectangles.  they need to be contained within
+         * the rectangles, so clipy can keep them there without division.
+          */
+        srcy = (starty + sr.min.y - src->r.min.y)%Dy(src->r);
+        masky = (starty + mr.min.y - mask->r.min.y)%Dy(mask->r);
+        dsty = starty + r.min.y - dst->r.min.y;
+
+        assert(0 <= srcy && srcy < Dy(src->r));
+        assert(0 <= masky && masky < Dy(mask->r));
+        assert(0 <= dsty && dsty < Dy(dst->r));
+
+        for(y=starty; y!=endy; y+=dir, srcy+=dir, masky+=dir, dsty+=dir){
+                clipy(src, &srcy);
+                clipy(dst, &dsty);
+                clipy(mask, &masky);
+
+                bsrc = rdsrc(&spar, spar.bufbase, srcy);
+DBG print("[");
+                bmask = rdmask(&mpar, mpar.bufbase, masky);
+DBG print("]\n");
+                bdst = rddst(&dpar, dpar.bufbase, dsty);
+DBG                dumpbuf("src", bsrc, dx);
+DBG                dumpbuf("mask", bmask, dx);
+DBG                dumpbuf("dst", bdst, dx);
+                bdst = calc(bdst, bsrc, bmask, dx, isgrey, op);
+                wrdst(&dpar, dpar.bytermin+dsty*dpar.bwidth, bdst);
+        }
+
+        return 1;
+}
+#undef DBG
+
+static Buffer
+alphacalc0(Buffer bdst, Buffer b1, Buffer b2, int dx, int grey, int op)
+{
+        USED(grey);
+        USED(op);
+        memset(bdst.rgba, 0, dx*bdst.delta);
+        return bdst;
+}
+
+static Buffer
+alphacalc14(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+        Buffer obdst;
+        int fd, sadelta;
+        int i, sa, ma, q;
+        u32int s, t;
+
+        obdst = bdst;
+        sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+        q = bsrc.delta == 4 && bdst.delta == 4;
+
+        for(i=0; ibcache[y];
+        if((p->bfilled & (1<bfilled |= 1<replcall(p, p->bufbase+y*p->bufdelta, y);
+        }
+        return *b;
+}
+
+/*
+ * Alpha reading function that simply relabels the grey pointer.
+ */
+static Buffer
+greymaskread(Param *p, uchar *buf, int y)
+{
+        Buffer b;
+
+        b = p->greymaskcall(p, buf, y);
+        b.alpha = b.grey;
+        return b;
+}
+
+#define DBG if(0)
+static Buffer
+readnbit(Param *p, uchar *buf, int y)
+{
+        Buffer b;
+        Memimage *img;
+        uchar *repl, *r, *w, *ow, bits;
+        int i, n, sh, depth, x, dx, npack, nbits;
+
+        b.rgba = (u32int*)buf;
+        b.grey = w = buf;
+        b.red = b.blu = b.grn = w;
+        b.alpha = &ones;
+        b.delta = 1;
+
+        dx = p->dx;
+        img = p->img;
+        depth = img->depth;
+        repl = &replbit[depth][0];
+        npack = 8/depth;
+        sh = 8-depth;
+
+        /* copy from p->r.min.x until end of repl rectangle */
+        x = p->r.min.x;
+        n = dx;
+        if(n > p->img->r.max.x - x)
+                n = p->img->r.max.x - x;
+
+        r = p->bytermin + y*p->bwidth;
+DBG print("readnbit dx %d %p=%p+%d*%d, *r=%d fetch %d ", dx, r, p->bytermin, y, p->bwidth, *r, n);
+        bits = *r++;
+        nbits = 8;
+        if(i=x&(npack-1)){
+DBG print("throwaway %d...", i);
+                bits <<= depth*i;
+                nbits -= depth*i;
+        }
+        for(i=0; i>sh];
+DBG print("bit %x...", repl[bits>>sh]);
+                bits <<= depth;
+                nbits -= depth;
+        }
+        dx -= n;
+        if(dx == 0)
+                return b;
+
+        assert(x+i == p->img->r.max.x);
+
+        /* copy from beginning of repl rectangle until where we were before. */
+        x = p->img->r.min.x;
+        n = dx;
+        if(n > p->r.min.x - x)
+                n = p->r.min.x - x;
+
+        r = p->bytey0s + y*p->bwidth;
+DBG print("x=%d r=%p...", x, r);
+        bits = *r++;
+        nbits = 8;
+        if(i=x&(npack-1)){
+                bits <<= depth*i;
+                nbits -= depth*i;
+        }
+DBG print("nbits=%d...", nbits);
+        for(i=0; i>sh];
+DBG print("bit %x...", repl[bits>>sh]);
+                bits <<= depth;
+                nbits -= depth;
+DBG print("bits %x nbits %d...", bits, nbits);
+        }
+        dx -= n;
+        if(dx == 0)
+                return b;
+
+        assert(dx > 0);
+        /* now we have exactly one full scan line: just replicate the buffer itself until we are done */
+        ow = buf;
+        while(dx--)
+                *w++ = *ow++;
+
+        return b;
+}
+#undef DBG
+
+#define DBG if(0)
+static void
+writenbit(Param *p, uchar *w, Buffer src)
+{
+        uchar *r;
+        u32int 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> sh);
+                nbits += depth;
+                if(nbits == 8){
+                        *w++ = bits;
+                        nbits = 0;
+                }
+        }
+
+        if(nbits){
+                sh = 8-nbits;
+                bits <<= sh;
+                bits |= *w & ((1<bytey0s + y*p->bwidth;
+        r = p->bytermin + y*p->bwidth;
+        end = p->bytey0e + y*p->bwidth;
+        cmap = p->img->cmap->cmap2rgb;
+        convgrey = p->convgrey;
+        copyalpha = (p->img->flags&Falpha) ? 1 : 0;
+
+        w = buf;
+        dx = p->dx;
+        if(copyalpha){
+                b.alpha = buf++;
+                a = p->img->shift[CAlpha]/8;
+                m = p->img->shift[CMap]/8;
+                for(i=0; iimg->cmap->rgb2cmap;
+        
+        delta = src.delta;
+        red= src.red;
+        grn = src.grn;
+        blu = src.blu;
+
+        dx = p->dx;
+        for(i=0; i>4)*256+(*grn>>4)*16+(*blu>>4)];
+}
+
+#define DBG if(0)
+static Buffer
+readbyte(Param *p, uchar *buf, int y)
+{
+        Buffer b;
+        Memimage *img;
+        int dx, isgrey, convgrey, alphaonly, copyalpha, i, nb;
+        uchar *begin, *end, *r, *w, *rrepl, *grepl, *brepl, *arepl, *krepl;
+        uchar ured, ugrn, ublu;
+        u32int u;
+
+        img = p->img;
+        begin = p->bytey0s + y*p->bwidth;
+        r = p->bytermin + y*p->bwidth;
+        end = p->bytey0e + y*p->bwidth;
+
+        w = buf;
+        dx = p->dx;
+        nb = img->depth/8;
+
+        convgrey = p->convgrey;        /* convert rgb to grey */
+        isgrey = img->flags&Fgrey;
+        alphaonly = p->alphaonly;
+        copyalpha = (img->flags&Falpha) ? 1 : 0;
+
+DBG print("copyalpha %d alphaonly %d convgrey %d isgrey %d\n", copyalpha, alphaonly, convgrey, isgrey);
+        /* if we can, avoid processing everything */
+        if(!(img->flags&Frepl) && !convgrey && (img->flags&Fbytes)){
+                memset(&b, 0, sizeof b);
+                if(p->needbuf){
+                        memmove(buf, r, dx*nb);
+                        r = buf;
+                }
+                b.rgba = (u32int*)r;
+                if(copyalpha)
+                        b.alpha = r+img->shift[CAlpha]/8;
+                else
+                        b.alpha = &ones;
+                if(isgrey){
+                        b.grey = r+img->shift[CGrey]/8;
+                        b.red = b.grn = b.blu = b.grey;
+                }else{
+                        b.red = r+img->shift[CRed]/8;
+                        b.grn = r+img->shift[CGreen]/8;
+                        b.blu = r+img->shift[CBlue]/8;
+                }
+                b.delta = nb;
+                return b;
+        }
+
+DBG print("2\n");
+        rrepl = replbit[img->nbits[CRed]];
+        grepl = replbit[img->nbits[CGreen]];
+        brepl = replbit[img->nbits[CBlue]];
+        arepl = replbit[img->nbits[CAlpha]];
+        krepl = replbit[img->nbits[CGrey]];
+
+        for(i=0; i>img->shift[CAlpha]) & img->mask[CAlpha]];
+DBG print("a %x\n", w[-1]);
+                }
+
+                if(isgrey)
+                        *w++ = krepl[(u >> img->shift[CGrey]) & img->mask[CGrey]];
+                else if(!alphaonly){
+                        ured = rrepl[(u >> img->shift[CRed]) & img->mask[CRed]];
+                        ugrn = grepl[(u >> img->shift[CGreen]) & img->mask[CGreen]];
+                        ublu = brepl[(u >> img->shift[CBlue]) & img->mask[CBlue]];
+                        if(convgrey){
+DBG print("g %x %x %x\n", ured, ugrn, ublu);
+                                *w++ = RGB2K(ured, ugrn, ublu);
+DBG print("%x\n", w[-1]);
+                        }else{
+                                *w++ = brepl[(u >> img->shift[CBlue]) & img->mask[CBlue]];
+                                *w++ = grepl[(u >> img->shift[CGreen]) & img->mask[CGreen]];
+                                *w++ = rrepl[(u >> img->shift[CRed]) & img->mask[CRed]];
+                        }
+                }
+                r += nb;
+                if(r == end)
+                        r = begin;
+        }
+        
+        b.alpha = copyalpha ? buf : &ones;
+        b.rgba = (u32int*)buf;
+        if(alphaonly){
+                b.red = b.grn = b.blu = b.grey = nil;
+                if(!copyalpha)
+                        b.rgba = nil;
+                b.delta = 1;
+        }else if(isgrey || convgrey){
+                b.grey = buf+copyalpha;
+                b.red = b.grn = b.blu = buf+copyalpha;
+                b.delta = copyalpha+1;
+DBG print("alpha %x grey %x\n", b.alpha ? *b.alpha : 0xFF, *b.grey);
+        }else{
+                b.blu = buf+copyalpha;
+                b.grn = buf+copyalpha+1;
+                b.grey = nil;
+                b.red = buf+copyalpha+2;
+                b.delta = copyalpha+3;
+        }
+        return b;
+}
+#undef DBG
+
+#define DBG if(0)
+static void
+writebyte(Param *p, uchar *w, Buffer src)
+{
+        Memimage *img;
+        int i, isalpha, isgrey, nb, delta, dx, adelta;
+        uchar ff, *red, *grn, *blu, *grey, *alpha;
+        u32int u, mask;
+
+        img = p->img;
+
+        red = src.red;
+        grn = src.grn;
+        blu = src.blu;
+        alpha = src.alpha;
+        delta = src.delta;
+        grey = src.grey;
+        dx = p->dx;
+
+        nb = img->depth/8;
+        mask = (nb==4) ? 0 : ~((1<depth)-1);
+
+        isalpha = img->flags&Falpha;
+        isgrey = img->flags&Fgrey;
+        adelta = src.delta;
+
+        if(isalpha && (alpha == nil || alpha == &ones)){
+                ff = 0xFF;
+                alpha = &ff;
+                adelta = 0;
+        }
+
+        for(i=0; i> (8-img->nbits[CGrey])) & img->mask[CGrey]) << img->shift[CGrey];
+DBG print("|grey %.8lux...", u);
+                        grey += delta;
+                }else{
+                        u |= ((*red >> (8-img->nbits[CRed])) & img->mask[CRed]) << img->shift[CRed];
+                        u |= ((*grn >> (8-img->nbits[CGreen])) & img->mask[CGreen]) << img->shift[CGreen];
+                        u |= ((*blu >> (8-img->nbits[CBlue])) & img->mask[CBlue]) << img->shift[CBlue];
+                        red += delta;
+                        grn += delta;
+                        blu += delta;
+DBG print("|rgb %.8lux...", u);
+                }
+
+                if(isalpha){
+                        u |= ((*alpha >> (8-img->nbits[CAlpha])) & img->mask[CAlpha]) << img->shift[CAlpha];
+                        alpha += adelta;
+DBG print("|alpha %.8lux...", u);
+                }
+
+                w[0] = u;
+                w[1] = u>>8;
+                w[2] = u>>16;
+                w[3] = u>>24;
+                w += nb;
+        }
+}
+#undef DBG
+
+static Readfn*
+readfn(Memimage *img)
+{
+        if(img->depth < 8)
+                return readnbit;
+        if(img->nbits[CMap] == 8)
+                return readcmap;
+        return readbyte;
+}
+
+static Readfn*
+readalphafn(Memimage *m)
+{
+        USED(m);
+        return readbyte;
+}
+
+static Writefn*
+writefn(Memimage *img)
+{
+        if(img->depth < 8)
+                return writenbit;
+        if(img->chan == CMAP8)
+                return writecmap;
+        return writebyte;
+}
+
+static void
+nullwrite(Param *p, uchar *s, Buffer b)
+{
+        USED(p);
+        USED(s);
+}
+
+static Buffer
+readptr(Param *p, uchar *s, int y)
+{
+        Buffer b;
+        uchar *q;
+
+        USED(s);
+        q = p->bytermin + y*p->bwidth;
+        b.red = q;        /* ptr to data */
+        b.grn = b.blu = b.grey = b.alpha = nil;
+        b.rgba = (u32int*)q;
+        b.delta = p->img->depth/8;
+        return b;
+}
+
+static Buffer
+boolmemmove(Buffer bdst, Buffer bsrc, Buffer b1, int dx, int i, int o)
+{
+        USED(i);
+        USED(o);
+        memmove(bdst.red, bsrc.red, dx*bdst.delta);
+        return bdst;
+}
+
+static Buffer
+boolcopy8(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+        uchar *m, *r, *w, *ew;
+
+        USED(i);
+        USED(o);
+        m = bmask.grey;
+        w = bdst.red;
+        r = bsrc.red;
+        ew = w+dx;
+        for(; w < ew; w++,r++)
+                if(*m++)
+                        *w = *r;
+        return bdst;        /* not used */
+}
+
+static Buffer
+boolcopy16(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+        uchar *m;
+        ushort *r, *w, *ew;
+
+        USED(i);
+        USED(o);
+        m = bmask.grey;
+        w = (ushort*)bdst.red;
+        r = (ushort*)bsrc.red;
+        ew = w+dx;
+        for(; w < ew; w++,r++)
+                if(*m++)
+                        *w = *r;
+        return bdst;        /* not used */
+}
+
+static Buffer
+boolcopy24(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+        uchar *m;
+        uchar *r, *w, *ew;
+
+        USED(i);
+        USED(o);
+        m = bmask.grey;
+        w = bdst.red;
+        r = bsrc.red;
+        ew = w+dx*3;
+        while(w < ew){
+                if(*m++){
+                        *w++ = *r++;
+                        *w++ = *r++;
+                        *w++ = *r++;
+                }else{
+                        w += 3;
+                        r += 3;
+                }
+        }
+        return bdst;        /* not used */
+}
+
+static Buffer
+boolcopy32(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int i, int o)
+{
+        uchar *m;
+        u32int *r, *w, *ew;
+
+        USED(i);
+        USED(o);
+        m = bmask.grey;
+        w = (u32int*)bdst.red;
+        r = (u32int*)bsrc.red;
+        ew = w+dx;
+        for(; w < ew; w++,r++)
+                if(*m++)
+                        *w = *r;
+        return bdst;        /* not used */
+}
+
+static Buffer
+genconv(Param *p, uchar *buf, int y)
+{
+        Buffer b;
+        int nb;
+        uchar *r, *w, *ew;
+
+        /* read from source into RGB format in convbuf */
+        b = p->convreadcall(p, p->convbuf, y);
+
+        /* write RGB format into dst format in buf */
+        p->convwritecall(p->convdpar, buf, b);
+
+        if(p->convdx){
+                nb = p->convdpar->img->depth/8;
+                r = buf;
+                w = buf+nb*p->dx;
+                ew = buf+nb*p->convdx;
+                while(wchan == src->chan && !(src->flags&Frepl)){
+//if(drawdebug) iprint("readptr...");
+                return readptr;
+        }
+
+        if(dst->chan==CMAP8 && (src->chan==GREY1||src->chan==GREY2||src->chan==GREY4)){
+                /* cheat because we know the replicated value is exactly the color map entry. */
+//if(drawdebug) iprint("Readnbit...");
+                return readnbit;
+        }
+
+        spar->convreadcall = readfn(src);
+        spar->convwritecall = writefn(dst);
+        spar->convdpar = dpar;
+
+        /* allocate a conversion buffer */
+        spar->convbufoff = ndrawbuf;
+        ndrawbuf += spar->dx*4;
+
+        if(spar->dx > Dx(spar->img->r)){
+                spar->convdx = spar->dx;
+                spar->dx = Dx(spar->img->r);
+        }
+
+//if(drawdebug) iprint("genconv...");
+        return genconv;
+}
+
+/*
+ * Do NOT call this directly.  pixelbits is a wrapper
+ * around this that fetches the bits from the X server
+ * when necessary.
+ */
+u32int
+_pixelbits(Memimage *i, Point pt)
+{
+        uchar *p;
+        u32int 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<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;
+
+        p = vp;
+        ep = p+n;
+        while(p>8;
+        c = val>>16;
+        while(pchan; chan; chan>>=8){
+                nb = NBITS(chan);
+                ov = v = val&((1<>= nb;
+
+                while(nb < 8){
+                        v |= v<>= (nb-8);
+
+                switch(TYPE(chan)){
+                case CRed:
+                        r = v;
+                        break;
+                case CGreen:
+                        g = v;
+                        break;
+                case CBlue:
+                        b = v;
+                        break;
+                case CAlpha:
+                        a = v;
+                        break;
+                case CGrey:
+                        r = g = b = v;
+                        break;
+                case CMap:
+                        p = img->cmap->cmap2rgb+3*ov;
+                        r = *p++;
+                        g = *p++;        
+                        b = *p;
+                        break;
+                }
+        }
+        return (r<<24)|(g<<16)|(b<<8)|a;        
+}
+
+u32int
+_rgbatoimg(Memimage *img, u32int rgba)
+{
+        u32int chan;
+        int d, nb;
+        u32int 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))<>(8-nb))<>(8-nb))<>(8-nb))<cmap->rgb2cmap;
+                        m = p[(r>>4)*256+(g>>4)*16+(b>>4)];
+                        v |= (m>>(8-nb))<>(8-nb))<r);
+        dy = Dy(par->r);
+        src = par->src;
+        dst = par->dst;
+        op = par->op;
+
+DBG print("state %lux mval %lux dd %d\n", par->state, par->mval, dst->depth);
+        /*
+         * If we have an opaque mask and source is one opaque pixel we can convert to the
+         * destination format and just replicate with memset.
+         */
+        m = Simplesrc|Simplemask|Fullmask;
+        if((par->state&m)==m && (par->srgba&0xFF) == 0xFF && (op ==S || op == SoverD)){
+                uchar *dp, p[4];
+                int d, dwid, ppb, np, nb;
+                uchar lm, rm;
+
+DBG print("memopt, dst %p, dst->data->bdata %p\n", dst, dst->data->bdata);
+                dwid = dst->width*sizeof(u32int);
+                dp = byteaddr(dst, par->r.min);
+                v = par->sdval;
+DBG print("sdval %lud, depth %d\n", v, dst->depth);
+                switch(dst->depth){
+                case 1:
+                case 2:
+                case 4:
+                        for(d=dst->depth; d<8; d*=2)
+                                v |= (v<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<r.min.x, nb, lm, ppb, m);        
+
+                        /* right edge */
+                        np = par->r.max.x&m;        /* no. pixels used on left side of word */
+                        dx -= np;
+                        nb = 8 - np * dst->depth;                /* no. bits unused on right side of word */
+                        rm = ~((1<r.max.x, nb, rm, ppb, m);        
+
+DBG print("dx %d Dx %d\n", dx, Dx(par->r));
+                        /* lm, rm are masks that are 1 where we should touch the bits */
+                        if(dx < 0){        /* just one byte */
+                                lm &= rm;
+                                for(y=0; y>8;
+                        v = *(ushort*)p;
+DBG print("dp=%p; dx=%d; for(y=0; y<%d; y++, dp+=%d)\nmemsets(dp, v, dx);\n",
+        dp, dx, dy, dwid);
+                        for(y=0; y>8;
+                        p[2] = v>>16;
+                        p[3] = v>>24;
+                        v = *(u32int*)p;
+                        for(y=0; ystate&(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(u32int);
+                dwid = dst->width*sizeof(u32int);
+                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; ystate&(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(u32int);
+                dwid = dst->width*sizeof(u32int);
+                mwid = par->mask->width*sizeof(u32int);
+
+                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; ymask->flags, par->mask->depth, par->src->flags, 
+                Dx(par->src->r), Dy(par->src->r), par->dst->depth, par->dst->data, par->src->data);
+
+        mask = par->mask;
+        src = par->src;
+        dst = par->dst;
+        r = par->r;
+        mr = par->mr;
+        op = par->op;
+
+        if((par->state&(Replsrc|Simplesrc|Replmask)) != (Replsrc|Simplesrc)
+        || mask->depth != 1 || src->flags&Falpha || dst->depth<8 || dst->data==src->data
+        || op != SoverD)
+                return 0;
+
+//if(drawdebug) iprint("chardraw...");
+
+        depth = mask->depth;
+        maskwid = mask->width*sizeof(u32int);
+        rp = byteaddr(mask, mr.min);
+        npack = 8/depth;
+        bsh = (mr.min.x % npack) * depth;
+
+        wp = byteaddr(dst, r.min);
+        dstwid = dst->width*sizeof(u32int);
+DBG print("bsh %d\n", bsh);
+        dy = Dy(r);
+        dx = Dx(r);
+
+        ddepth = dst->depth;
+
+        /*
+         * for loop counts from bsh to bsh+dx
+         *
+         * we want the bottom bits to be the amount
+         * to shift the pixels down, so for n≡0 (mod 8) we want 
+         * bottom bits 7.  for n≡1, 6, etc.
+         * the bits come from -n-1.
+         */
+
+        bx = -bsh-1;
+        ex = -bsh-1-dx;
+        SET(bits);
+        v = par->sdval;
+
+        /* make little endian */
+        sp[0] = v;
+        sp[1] = v>>8;
+        sp[2] = v>>16;
+        sp[3] = v>>24;
+
+//print("sp %x %x %x %x\n", sp[0], sp[1], sp[2], sp[3]);
+        for(y=0; yex; x--, wc++){
+                                i = x&7;
+                                if(i == 8-1)
+                                        bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+                                if((bits>>i)&1)
+                                        *wc = v;
+                        }
+                        break;
+                case 16:
+                        ws = (ushort*)wp;
+                        v = *(ushort*)sp;
+                        for(x=bx; x>ex; x--, ws++){
+                                i = x&7;
+                                if(i == 8-1)
+                                        bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+                                if((bits>>i)&1)
+                                        *ws = v;
+                        }
+                        break;
+                case 24:
+                        wc = wp;
+                        for(x=bx; x>ex; x--, wc+=3){
+                                i = x&7;
+                                if(i == 8-1)
+                                        bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+                                if((bits>>i)&1){
+                                        wc[0] = sp[0];
+                                        wc[1] = sp[1];
+                                        wc[2] = sp[2];
+                                }
+                        }
+                        break;
+                case 32:
+                        wl = (u32int*)wp;
+                        v = *(u32int*)sp;
+                        for(x=bx; x>ex; x--, wl++){
+                                i = x&7;
+                                if(i == 8-1)
+                                        bits = *q++;
+DBG iprint("bits %lux sh %d...", bits, i);
+                                if((bits>>i)&1)
+                                        *wl = v;
+                        }
+                        break;
+                }
+        }
+
+DBG print("\n");        
+        return 1;        
+}
+#undef DBG
+
+
+/*
+ * Fill entire byte with replicated (if necessary) copy of source pixel,
+ * assuming destination ldepth is >= source ldepth.
+ *
+ * This code is just plain wrong for >8bpp.
+ *
+u32int
+membyteval(Memimage *src)
+{
+        int i, val, bpp;
+        uchar uc;
+
+        unloadmemimage(src, src->r, &uc, 1);
+        bpp = src->depth;
+        uc <<= (src->r.min.x&(7/src->depth))*src->depth;
+        uc &= ~(0xFF>>bpp);
+        * pixel value is now in high part of byte. repeat throughout byte 
+        val = uc;
+        for(i=bpp; i<8; i<<=1)
+                val |= val>>i;
+        return val;
+}
+ * 
+ */
+
+void
+_memfillcolor(Memimage *i, u32int val)
+{
+        u32int bits;
+        int d, y;
+        uchar p[4];
+
+        if(val == DNofill)
+                return;
+
+        bits = _rgbatoimg(i, val);
+        switch(i->depth){
+        case 24:        /* 24-bit images suck */
+                for(y=i->r.min.y; yr.max.y; y++)
+                        memset24(byteaddr(i, Pt(i->r.min.x, y)), bits, Dx(i->r));
+                break;
+        default:        /* 1, 2, 4, 8, 16, 32 */
+                for(d=i->depth; d<32; d*=2)
+                        bits = (bits << d) | bits;
+                p[0] = bits;                /* make little endian */
+                p[1] = bits>>8;
+                p[2] = bits>>16;
+                p[3] = bits>>24;
+                bits = *(u32int*)p;
+                memsetl(wordaddr(i, i->r.min), bits, i->width*Dy(i->r));
+                break;
+        }
+}
+
diff --git a/src/libdraw/md-drawtest.c b/src/libdraw/md-drawtest.c
t@@ -0,0 +1,1004 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define DBG if(0)
+#define RGB2K(r,g,b)        ((299*((u32int)(r))+587*((u32int)(g))+114*((u32int)(b)))/1000)
+
+/*
+ * This program tests the 'memimagedraw' primitive stochastically.
+ * It tests the combination aspects of it thoroughly, but since the
+ * three images it uses are disjoint, it makes no check of the
+ * correct behavior when images overlap.  That is, however, much
+ * easier to get right and to test.
+ */
+
+void        drawonepixel(Memimage*, Point, Memimage*, Point, Memimage*, Point);
+void        verifyone(void);
+void        verifyline(void);
+void        verifyrect(void);
+void        verifyrectrepl(int, int);
+void putpixel(Memimage *img, Point pt, u32int nv);
+u32int rgbatopix(uchar, uchar, uchar, uchar);
+
+char *dchan, *schan, *mchan;
+int dbpp, sbpp, mbpp;
+
+int drawdebug=0;
+int        seed;
+int        niters = 100;
+int        dbpp;        /* bits per pixel in destination */
+int        sbpp;        /* bits per pixel in src */
+int        mbpp;        /* bits per pixel in mask */
+int        dpm;        /* pixel mask at high part of byte, in destination */
+int        nbytes;        /* in destination */
+
+int        Xrange        = 64;
+int        Yrange        = 8;
+
+Memimage        *dst;
+Memimage        *src;
+Memimage        *mask;
+Memimage        *stmp;
+Memimage        *mtmp;
+Memimage        *ones;
+uchar        *dstbits;
+uchar        *srcbits;
+uchar        *maskbits;
+u32int        *savedstbits;
+
+void
+rdb(void)
+{
+}
+
+int
+iprint(char *fmt, ...)
+{
+        int n;        
+        va_list va;
+        char buf[1024];
+
+        va_start(va, fmt);
+        n = doprint(buf, buf+sizeof buf, fmt, va) - buf;
+        va_end(va);
+
+        write(1,buf,n);
+        return 1;
+}
+
+void
+main(int argc, char *argv[])
+{
+        memimageinit();
+        seed = time(0);
+
+        ARGBEGIN{
+        case 'x':
+                Xrange = atoi(ARGF());
+                break;
+        case 'y':
+                Yrange = atoi(ARGF());
+                break;
+        case 'n':
+                niters = atoi(ARGF());
+                break;
+        case 's':
+                seed = atoi(ARGF());
+                break;
+        }ARGEND
+
+        dchan = "r8g8b8";
+        schan = "r8g8b8";
+        mchan = "r8g8b8";
+        switch(argc){
+        case 3:        mchan = argv[2];
+        case 2:        schan = argv[1];
+        case 1:        dchan = argv[0];
+        case 0:        break;
+        default:        goto Usage;
+        Usage:
+                fprint(2, "usage: dtest [dchan [schan [mchan]]]\n");
+                exits("usage");
+        }
+
+        fmtinstall('b', numbconv);        /* binary! */
+
+        fprint(2, "%s -x %d -y %d -s 0x%x %s %s %s\n", argv0, Xrange, Yrange, seed, dchan, schan, mchan);
+        srand(seed);
+
+        dst = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(dchan));
+        src = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(schan));
+        mask = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
+        stmp = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(schan));
+        mtmp = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
+        ones = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
+//        print("chan %lux %lux %lux %lux %lux %lux\n", dst->chan, src->chan, mask->chan, stmp->chan, mtmp->chan, ones->chan);
+        if(dst==0 || src==0 || mask==0 || mtmp==0 || ones==0) {
+        Alloc:
+                fprint(2, "dtest: allocation failed: %r\n");
+                exits("alloc");
+        }
+        nbytes = (4*Xrange+4)*Yrange;
+        srcbits = malloc(nbytes);
+        dstbits = malloc(nbytes);
+        maskbits = malloc(nbytes);
+        savedstbits = malloc(nbytes);
+        if(dstbits==0 || srcbits==0 || maskbits==0 || savedstbits==0)
+                goto Alloc;
+        dbpp = dst->depth;
+        sbpp = src->depth;
+        mbpp = mask->depth;
+        dpm = 0xFF ^ (0xFF>>dbpp);
+        memset(ones->data->bdata, 0xFF, ones->width*sizeof(u32int)*Yrange);
+
+
+        fprint(2, "dtest: verify single pixel operation\n");
+        verifyone();
+
+        fprint(2, "dtest: verify full line non-replicated\n");
+        verifyline();
+
+        fprint(2, "dtest: verify full rectangle non-replicated\n");
+        verifyrect();
+
+        fprint(2, "dtest: verify full rectangle source replicated\n");
+        verifyrectrepl(1, 0);
+
+        fprint(2, "dtest: verify full rectangle mask replicated\n");
+        verifyrectrepl(0, 1);
+
+        fprint(2, "dtest: verify full rectangle source and mask replicated\n");
+        verifyrectrepl(1, 1);
+
+        exits(0);
+}
+
+/*
+ * Dump out an ASCII representation of an image.  The label specifies
+ * a list of characters to put at various points in the picture.
+ */
+static void
+Bprintr5g6b5(Biobuf *bio, char*, u32int v)
+{
+        int r,g,b;
+        r = (v>>11)&31;
+        g = (v>>5)&63;
+        b = v&31;
+        Bprint(bio, "%.2x%.2x%.2x", r,g,b);
+}
+
+static void
+Bprintr5g5b5a1(Biobuf *bio, char*, u32int v)
+{
+        int r,g,b,a;
+        r = (v>>11)&31;
+        g = (v>>6)&31;
+        b = (v>>1)&31;
+        a = v&1;
+        Bprint(bio, "%.2x%.2x%.2x%.2x", r,g,b,a);
+}
+
+void
+dumpimage(char *name, Memimage *img, void *vdata, Point labelpt)
+{
+        Biobuf b;
+        uchar *data;
+        uchar *p;
+        char *arg;
+        void (*fmt)(Biobuf*, char*, u32int);
+        int npr, x, y, nb, bpp;
+        u32int v, mask;
+        Rectangle r;
+
+        fmt = nil;
+        arg = nil;
+        switch(img->depth){
+        case 1:
+        case 2:
+        case 4:
+                fmt = (void(*)(Biobuf*,char*,u32int))Bprint;
+                arg = "%.1ux";
+                break;
+        case 8:
+                fmt = (void(*)(Biobuf*,char*,u32int))Bprint;
+                arg = "%.2ux";
+                break;
+        case 16:
+                arg = nil;
+                if(img->chan == RGB16)
+                        fmt = Bprintr5g6b5;
+                else{
+                        fmt = (void(*)(Biobuf*,char*,u32int))Bprint;
+                        arg = "%.4ux";
+                }
+                break;
+        case 24:
+                fmt = (void(*)(Biobuf*,char*,u32int))Bprint;
+                arg = "%.6lux";
+                break;
+        case 32:
+                fmt = (void(*)(Biobuf*,char*,u32int))Bprint;
+                arg = "%.8lux";
+                break;
+        }
+        if(fmt == nil){
+                fprint(2, "bad format\n");
+                abort();
+        }
+
+        r  = img->r;
+        Binit(&b, 2, OWRITE);
+        data = vdata;
+        bpp = img->depth;
+        Bprint(&b, "%s\t%d\tr %R clipr %R repl %d data %p *%P\n", name, r.min.x, r, img->clipr, (img->flags&Frepl) ? 1 : 0, vdata, labelpt);
+        mask = (1ULL<data->bdata);
+                Bprint(&b, "%-4d\t", y);
+//                for(x=r.min.x; x>nb)&mask);
+                }
+                Bprint(&b, "\n");
+        }
+        Bterm(&b);
+}
+
+/*
+ * Verify that the destination pixel has the specified value.
+ * The value is in the high bits of v, suitably masked, but must
+ * be extracted from the destination Memimage.
+ */
+void
+checkone(Point p, Point sp, Point mp)
+{
+        int delta;
+        uchar *dp, *sdp;
+
+        delta = (uchar*)byteaddr(dst, p)-(uchar*)dst->data->bdata;
+        dp = (uchar*)dst->data->bdata+delta;
+        sdp = (uchar*)savedstbits+delta;
+
+        if(memcmp(dp, sdp, (dst->depth+7)/8) != 0) {
+                fprint(2, "dtest: one bad pixel drawing at dst %P from source %P mask %P\n", p, sp, mp);
+                fprint(2, " %.2ux %.2ux %.2ux %.2ux should be %.2ux %.2ux %.2ux %.2ux\n",
+                        dp[0], dp[1], dp[2], dp[3], sdp[0], sdp[1], sdp[2], sdp[3]);
+                fprint(2, "addresses dst %p src %p mask %p\n", dp, byteaddr(src, sp), byteaddr(mask, mp));
+                dumpimage("src", src, src->data->bdata, sp);
+                dumpimage("mask", mask, mask->data->bdata, mp);
+                dumpimage("origdst", dst, dstbits, p);
+                dumpimage("dst", dst, dst->data->bdata, p);
+                dumpimage("gooddst", dst, savedstbits, p);
+                abort();
+        }
+}
+
+/*
+ * Verify that the destination line has the same value as the saved line.
+ */
+#define RECTPTS(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
+void
+checkline(Rectangle r, Point sp, Point mp, int y, Memimage *stmp, Memimage *mtmp)
+{
+        u32int *dp;
+        int nb;
+        u32int *saved;
+
+        dp = wordaddr(dst, Pt(0, y));
+        saved = savedstbits + y*dst->width;
+        if(dst->depth < 8)
+                nb = Xrange/(8/dst->depth);
+        else
+                nb = Xrange*(dst->depth/8);
+        if(memcmp(dp, saved, nb) != 0){
+                fprint(2, "dtest: bad line at y=%d; saved %p dp %p\n", y, saved, dp);
+                fprint(2, "draw dst %R src %P mask %P\n", r, sp, mp);
+                dumpimage("src", src, src->data->bdata, sp);
+                if(stmp) dumpimage("stmp", stmp, stmp->data->bdata, sp);
+                dumpimage("mask", mask, mask->data->bdata, mp);
+                if(mtmp) dumpimage("mtmp", mtmp, mtmp->data->bdata, mp);
+                dumpimage("origdst", dst, dstbits, r.min);
+                dumpimage("dst", dst, dst->data->bdata, r.min);
+                dumpimage("gooddst", dst, savedstbits, r.min);
+                abort();
+        }
+}
+
+/*
+ * Fill the bits of an image with random data.
+ * The Memimage parameter is used only to make sure
+ * the data is well formatted: only ucbits is written.
+ */
+void
+fill(Memimage *img, uchar *ucbits)
+{
+        int i, x, y;
+        ushort *up;
+        uchar alpha, r, g, b;
+        void *data;
+
+        if((img->flags&Falpha) == 0){
+                up = (ushort*)ucbits;
+                for(i=0; i> 7;
+                if(i+i != nbytes)
+                        *(uchar*)up = lrand() >> 7;
+        }else{
+                data = img->data->bdata;
+                img->data->bdata = ucbits;
+
+                for(x=img->r.min.x; xr.max.x; x++)
+                for(y=img->r.min.y; yr.max.y; y++){
+                        alpha = rand() >> 4;
+                        r = rand()%(alpha+1);
+                        g = rand()%(alpha+1);
+                        b = rand()%(alpha+1);
+                        putpixel(img, Pt(x,y), rgbatopix(r,g,b,alpha));
+                }
+                img->data->bdata = data;
+        }
+                
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyonemask(void)
+{
+        Point dp, sp, mp;
+
+        fill(dst, dstbits);
+        fill(src, srcbits);
+        memmove(dst->data->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+        memmove(src->data->bdata, srcbits, src->width*sizeof(u32int)*Yrange);
+        memmove(mask->data->bdata, maskbits, mask->width*sizeof(u32int)*Yrange);
+
+        dp.x = nrand(Xrange);
+        dp.y = nrand(Yrange);
+
+        sp.x = nrand(Xrange);
+        sp.y = nrand(Yrange);
+
+        mp.x = nrand(Xrange);
+        mp.y = nrand(Yrange);
+
+        drawonepixel(dst, dp, src, sp, mask, mp);
+        memmove(mask->data->bdata, maskbits, mask->width*sizeof(u32int)*Yrange);
+        memmove(savedstbits, dst->data->bdata, dst->width*sizeof(u32int)*Yrange);
+        
+        memmove(dst->data->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+        memimagedraw(dst, Rect(dp.x, dp.y, dp.x+1, dp.y+1), src, sp, mask, mp, SoverD);
+        memmove(mask->data->bdata, maskbits, mask->width*sizeof(u32int)*Yrange);
+
+        checkone(dp, sp, mp);
+}
+
+void
+verifyone(void)
+{
+        int i;
+
+        /* mask all zeros */
+        memset(maskbits, 0, nbytes);
+        for(i=0; idata->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+        memmove(src->data->bdata, srcbits, src->width*sizeof(u32int)*Yrange);
+        memmove(mask->data->bdata, maskbits, mask->width*sizeof(u32int)*Yrange);
+
+        dr.min.x = nrand(Xrange-1);
+        dr.min.y = nrand(Yrange-1);
+        dr.max.x = dr.min.x + 1 + nrand(Xrange-1-dr.min.x);
+        dr.max.y = dr.min.y + 1;
+
+        sp.x = nrand(Xrange);
+        sp.y = nrand(Yrange);
+
+        mp.x = nrand(Xrange);
+        mp.y = nrand(Yrange);
+
+        tp = sp;
+        up = mp;
+        for(x=dr.min.x; xdata->bdata, dst->width*sizeof(u32int)*Yrange);
+
+        memmove(dst->data->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+
+        memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
+        checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), dr.min.y, nil, nil);
+}
+
+void
+verifyline(void)
+{
+        int i;
+
+        /* mask all ones */
+        memset(maskbits, 0xFF, nbytes);
+        for(i=0; idata->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+        memmove(src->data->bdata, srcbits, src->width*sizeof(u32int)*Yrange);
+        memmove(mask->data->bdata, maskbits, mask->width*sizeof(u32int)*Yrange);
+
+        dr.min.x = nrand(Xrange-1);
+        dr.min.y = nrand(Yrange-1);
+        dr.max.x = dr.min.x + 1 + nrand(Xrange-1-dr.min.x);
+        dr.max.y = dr.min.y + 1 + nrand(Yrange-1-dr.min.y);
+
+        sp.x = nrand(Xrange);
+        sp.y = nrand(Yrange);
+
+        mp.x = nrand(Xrange);
+        mp.y = nrand(Yrange);
+
+        tp = sp;
+        up = mp;
+        for(y=dr.min.y; ydata->bdata, dst->width*sizeof(u32int)*Yrange);
+
+        memmove(dst->data->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+
+        memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
+        for(y=0; yr, sp), drawrepl(mask->r, mp), y, nil, nil);
+}
+
+void
+verifyrect(void)
+{
+        int i;
+
+        /* mask all zeros */
+        memset(maskbits, 0, nbytes);
+        for(i=0; ir) */
+        r.min.x = nrand(Xrange-1);
+        r.min.y = nrand(Yrange-1);
+        /* make it trivial more often than pure chance allows */
+        switch(lrand()&0){
+        case 1:
+                r.max.x = r.min.x + 2;
+                r.max.y = r.min.y + 2;
+                if(r.max.x < Xrange && r.max.y < Yrange)
+                        break;
+                /* fall through */
+        case 0:
+                r.max.x = r.min.x + 1;
+                r.max.y = r.min.y + 1;
+                break;
+        default:
+                if(r.min.x+3 >= Xrange)
+                        r.max.x = Xrange;
+                else
+                        r.max.x = r.min.x+3 + nrand(Xrange-(r.min.x+3));
+
+                if(r.min.y+3 >= Yrange)
+                        r.max.y = Yrange;
+                else
+                        r.max.y = r.min.y+3 + nrand(Yrange-(r.min.y+3));
+        }
+        assert(r.min.x >= 0);        
+        assert(r.max.x <= Xrange);
+        assert(r.min.y >= 0);
+        assert(r.max.y <= Yrange);
+        /* copy from i to tmp so we have just the replicated bits */
+        nb = tmp->width*sizeof(u32int)*Yrange;
+        memset(tmp->data->bdata, 0, nb);
+        memimagedraw(tmp, r, i, r.min, ones, r.min, SoverD);
+        memmove(i->data->bdata, tmp->data->bdata, nb);
+        /* i is now a non-replicated instance of the replication */
+        /* replicate it by hand through tmp */
+        memset(tmp->data->bdata, 0, nb);
+        x = -(tilexy(r.min.x, r.max.x, 0)-r.min.x);
+        for(; xflags |= Frepl;
+        i->r = r;
+        i->clipr = randrect();
+//        fprint(2, "replicate [[%d %d] [%d %d]] [[%d %d][%d %d]]\n", r.min.x, r.min.y, r.max.x, r.max.y,
+//                i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
+        tmp->clipr = i->clipr;
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyrectmaskrepl(int srcrepl, int maskrepl)
+{
+        Point sp, mp, tp, up;
+        Rectangle dr;
+        int x, y;
+        Memimage *s, *m;
+
+//        print("verfrect %d %d\n", srcrepl, maskrepl);
+        src->flags &= ~Frepl;
+        src->r = Rect(0, 0, Xrange, Yrange);
+        src->clipr = src->r;
+        stmp->flags &= ~Frepl;
+        stmp->r = Rect(0, 0, Xrange, Yrange);
+        stmp->clipr = src->r;
+        mask->flags &= ~Frepl;
+        mask->r = Rect(0, 0, Xrange, Yrange);
+        mask->clipr = mask->r;
+        mtmp->flags &= ~Frepl;
+        mtmp->r = Rect(0, 0, Xrange, Yrange);
+        mtmp->clipr = mask->r;
+
+        fill(dst, dstbits);
+        fill(src, srcbits);
+
+        memmove(dst->data->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+        memmove(src->data->bdata, srcbits, src->width*sizeof(u32int)*Yrange);
+        memmove(mask->data->bdata, maskbits, mask->width*sizeof(u32int)*Yrange);
+
+        if(srcrepl){
+                replicate(src, stmp);
+                s = stmp;
+        }else
+                s = src;
+        if(maskrepl){
+                replicate(mask, mtmp);
+                m = mtmp;
+        }else
+                m = mask;
+
+        dr = randrect();
+
+        sp.x = nrand(Xrange);
+        sp.y = nrand(Yrange);
+
+        mp.x = nrand(Xrange);
+        mp.y = nrand(Yrange);
+
+DBG        print("smalldraws\n");
+        for(tp.y=sp.y,up.y=mp.y,y=dr.min.y; ydata->bdata, dst->width*sizeof(u32int)*Yrange);
+
+        memmove(dst->data->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+
+DBG        print("bigdraw\n");
+        memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
+        for(y=0; yr, sp), drawrepl(mask->r, mp), y, srcrepl?stmp:nil, maskrepl?mtmp:nil);
+}
+
+void
+verifyrectrepl(int srcrepl, int maskrepl)
+{
+        int i;
+
+        /* mask all ones */
+        memset(maskbits, 0xFF, nbytes);
+        for(i=0; i>= (nhave-nwant);
+        return v & ((1<>24;
+        *r = v>>16;
+        *g = v>>8;
+        *b = v;
+}
+
+/*
+ * Convert uchar channels into u32int pixel.
+ */
+u32int
+rgbatopix(uchar r, uchar g, uchar b, uchar a)
+{
+        return (a<<24)|(r<<16)|(g<<8)|b;
+}
+
+/*
+ * Retrieve the pixel value at pt in the image.
+ */
+u32int
+getpixel(Memimage *img, Point pt)
+{
+        uchar r, g, b, a, *p;
+        int nbits, npack, bpp;
+        u32int v, c, rbits, bits;
+
+        r = g = b = 0;
+        a = ~0;        /* default alpha is full */
+
+        p = byteaddr(img, pt);
+        v = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
+        bpp = img->depth;
+        if(bpp<8){
+                /*
+                 * Sub-byte greyscale pixels.
+                 *
+                 * We want to throw away the top pt.x%npack pixels and then use the next bpp bits
+                 * in the bottom byte of v.  This madness is due to having big endian bits
+                 * but little endian bytes.
+                 */
+                npack = 8/bpp;
+                v >>= 8 - bpp*(pt.x%npack+1);
+                v &= (1<chan; c; c>>=8){
+                        nbits = NBITS(c);
+                        bits = v & ((1<>= nbits;
+                        switch(TYPE(c)){
+                        case CRed:
+                                r = rbits;
+                                break;
+                        case CGreen:
+                                g = rbits;
+                                break;
+                        case CBlue:
+                                b = rbits;
+                                break;
+                        case CGrey:
+                                r = g = b = rbits;
+                                break;
+                        case CAlpha:
+                                a = rbits;
+                                break;
+                        case CMap:
+                                p = img->cmap->cmap2rgb + 3*bits;
+                                r = p[0];
+                                g = p[1];
+                                b = p[2];
+                                break;
+                        case CIgnore:
+                                break;
+                        default:
+                                fprint(2, "unknown channel type %lud\n", TYPE(c));
+                                abort();
+                        }
+                }
+        }
+        return rgbatopix(r, g, b, a);
+}
+
+/*
+ * Return the greyscale equivalent of a pixel.
+ */
+uchar
+getgrey(Memimage *img, Point pt)
+{
+        uchar r, g, b, a;
+        pixtorgba(getpixel(img, pt), &r, &g, &b, &a);
+        return RGB2K(r, g, b);
+}
+
+/*
+ * Return the value at pt in image, if image is interpreted
+ * as a mask.  This means the alpha channel if present, else
+ * the greyscale or its computed equivalent.
+ */
+uchar
+getmask(Memimage *img, Point pt)
+{
+        if(img->flags&Falpha)
+                return getpixel(img, pt)>>24;
+        else
+                return getgrey(img, pt);
+}
+#undef DBG
+
+#define DBG if(0)
+/*
+ * Write a pixel to img at point pt.
+ * 
+ * We do this by reading a 32-bit little endian
+ * value from p and then writing it back
+ * after tweaking the appropriate bits.  Because
+ * the data is little endian, we don't have to worry
+ * about what the actual depth is, as long as it is
+ * less than 32 bits.
+ */
+void
+putpixel(Memimage *img, Point pt, u32int nv)
+{
+        uchar r, g, b, a, *p, *q;
+        u32int c, mask, bits, v;
+        int bpp, sh, npack, nbits;
+
+        pixtorgba(nv, &r, &g, &b, &a);
+
+        p = byteaddr(img, pt);
+        v = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
+        bpp = img->depth;
+DBG print("v %.8lux...", v);
+        if(bpp < 8){
+                /*
+                 * Sub-byte greyscale pixels.  We need to skip the leftmost pt.x%npack pixels,
+                 * which is equivalent to skipping the rightmost npack - pt.x%npack - 1 pixels.
+                 */        
+                npack = 8/bpp;
+                sh = bpp*(npack - pt.x%npack - 1);
+                bits = RGB2K(r,g,b);
+DBG print("repl %lux 8 %d = %lux...", bits, bpp, replbits(bits, 8, bpp));
+                bits = replbits(bits, 8, bpp);
+                mask = (1<chan; c; c>>=8){
+                        nbits = NBITS(c);
+                        switch(TYPE(c)){
+                        case CRed:
+                                bits = r;
+                                break;
+                        case CGreen:
+                                bits = g;
+                                break;
+                        case CBlue:
+                                bits = b;
+                                break;
+                        case CGrey:
+                                bits = RGB2K(r, g, b);
+                                break;
+                        case CAlpha:
+                                bits = a;
+                                break;
+                        case CIgnore:
+                                bits = 0;
+                                break;
+                        case CMap:
+                                q = img->cmap->rgb2cmap;
+                                bits = q[(r>>4)*16*16+(g>>4)*16+(b>>4)];
+                                break;
+                        default:
+                                SET(bits);
+                                fprint(2, "unknown channel type %lud\n", TYPE(c));
+                                abort();
+                        }
+
+DBG print("repl %lux 8 %d = %lux...", bits, nbits, replbits(bits, 8, nbits));
+                        if(TYPE(c) != CMap)
+                                bits = replbits(bits, 8, nbits);
+                        mask = (1<>8;
+        p[2] = v>>16;
+        p[3] = v>>24;        
+}
+#undef DBG
+
+#define DBG if(0)
+void
+drawonepixel(Memimage *dst, Point dp, Memimage *src, Point sp, Memimage *mask, Point mp)
+{
+        uchar m, M, sr, sg, sb, sa, sk, dr, dg, db, da, dk;
+
+        pixtorgba(getpixel(dst, dp), &dr, &dg, &db, &da);
+        pixtorgba(getpixel(src, sp), &sr, &sg, &sb, &sa);
+        m = getmask(mask, mp);
+        M = 255-(sa*m)/255;
+
+DBG print("dst %x %x %x %x src %x %x %x %x m %x = ", dr,dg,db,da, sr,sg,sb,sa, m);
+        if(dst->flags&Fgrey){
+                /*
+                 * We need to do the conversion to grey before the alpha calculation
+                 * because the draw operator does this, and we need to be operating
+                 * at the same precision so we get exactly the same answers.
+                 */
+                sk = RGB2K(sr, sg, sb);
+                dk = RGB2K(dr, dg, db);
+                dk = (sk*m + dk*M)/255;
+                dr = dg = db = dk;
+                da = (sa*m + da*M)/255;
+        }else{
+                /*
+                 * True color alpha calculation treats all channels (including alpha)
+                 * the same.  It might have been nice to use an array, but oh well.
+                 */
+                dr = (sr*m + dr*M)/255;
+                dg = (sg*m + dg*M)/255;
+                db = (sb*m + db*M)/255;
+                da = (sa*m + da*M)/255;
+        }
+
+DBG print("%x %x %x %x\n", dr,dg,db,da);
+        putpixel(dst, dp, rgbatopix(dr, dg, db, da));
+}
diff --git a/src/libdraw/md-ellipse.c b/src/libdraw/md-ellipse.c
t@@ -0,0 +1,247 @@
+#include 
+#include 
+#include 
+#include 
+
+/*
+ * 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 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(bb*b || aa*a) {
+/*        if(bb*b/a || aa*a/b)        # very thick */
+                bellipse(b, newstate(&in, a, b), &p);
+                return;
+        }
+
+        if(t < 0) {
+                inb = -1;
+                newstate(&out, a, y = b);
+        } else {        
+                inb = b - t;
+                newstate(&out, a+t, y = b+t);
+        }
+        if(t > 0)
+                newstate(&in, a-t, inb);
+        inx = 0;
+        for( ; y>=0; y--) {
+                outx = step(&out);
+                if(y > inb) {
+                        erect(-outx, y, outx, y, &p);
+                        if(y != 0)
+                                erect(-outx, -y, outx, -y, &p);
+                        continue;
+                }
+                if(t > 0) {
+                        inx = step(&in);
+                        if(y == inb)
+                                inx = 0;
+                } else if(inx > outx)
+                        inx = outx;
+                erect(inx, y, outx, y, &p);
+                if(y != 0)
+                        erect(inx, -y, outx, -y, &p);
+                erect(-outx, y, -inx, y, &p);
+                if(y != 0)
+                        erect(-outx, -y, -inx, -y, &p);
+                inx = outx + 1;
+        }
+}
+
+static Point p00 = {0, 0};
+
+/*
+ * a brushed ellipse
+ */
+static
+void
+bellipse(int y, State *s, Param *p)
+{
+        int t, ox, oy, x, nx;
+
+        t = p->t;
+        p->disc = allocmemimage(Rect(-t,-t,t+1,t+1), GREY1);
+        if(p->disc == nil)
+                return;
+        memfillcolor(p->disc, DTransparent);
+        memellipse(p->disc, p00, t, t, -1, memopaque, p00, p->op);
+        oy = y;
+        ox = 0;
+        nx = x = step(s);
+        do {
+                while(nx==x && y-->0)
+                        nx = step(s);
+                y++;
+                eline(-x,-oy,-ox, -y, p);
+                eline(ox,-oy,  x, -y, p);
+                eline(-x,  y,-ox, oy, p);
+                eline(ox,  y,  x, oy, p);
+                ox = x+1;
+                x = nx;
+                y--;
+                oy = y;
+        } while(oy > 0);
+}
+
+/*
+ * a rectangle with closed (not half-open) coordinates expressed
+ * relative to the center of the ellipse
+ */
+static
+void
+erect(int x0, int y0, int x1, int y1, Param *p)
+{
+        Rectangle r;
+
+/*        print("R %d,%d %d,%d\n", x0, y0, x1, y1); */
+        r = Rect(p->c.x+x0, p->c.y+y0, p->c.x+x1+1, p->c.y+y1+1);
+        memdraw(p->dst, r, p->src, addpt(p->sp, r.min), memopaque, p00, p->op);
+}
+
+/*
+ * a brushed point similarly specified
+ */
+static
+void
+epoint(int x, int y, Param *p)
+{
+        Point p0;
+        Rectangle r;
+
+/*        print("P%d %d,%d\n", p->t, x, y);        */
+        p0 = Pt(p->c.x+x, p->c.y+y);
+        r = Rpt(addpt(p0, p->disc->r.min), addpt(p0, p->disc->r.max));
+        memdraw(p->dst, r, p->src, addpt(p->sp, r.min), p->disc, p->disc->r.min, p->op);
+}
+
+/* 
+ * a brushed horizontal or vertical line similarly specified
+ */
+static
+void
+eline(int x0, int y0, int x1, int y1, Param *p)
+{
+/*        print("L%d %d,%d %d,%d\n", p->t, x0, y0, x1, y1); */
+        if(x1 > x0+1)
+                erect(x0+1, y0-p->t, x1-1, y1+p->t, p);
+        else if(y1 > y0+1)
+                erect(x0-p->t, y0+1, x1+p->t, y1-1, p);
+        epoint(x0, y0, p);
+        if(x1-x0 || y1-y0)
+                epoint(x1, y1, p);
+}
diff --git a/src/libdraw/md-fillpoly.c b/src/libdraw/md-fillpoly.c
t@@ -0,0 +1,524 @@
+#include 
+#include 
+#include 
+#include 
+
+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);
+
+#if 0
+static void
+fillcolor(Memimage *dst, int left, int right, int y, Memimage *src, Point p)
+{
+        int srcval;
+
+        USED(src);
+        srcval = p.x;
+        p.x = left;
+        p.y = y;
+        memset(byteaddr(dst, p), srcval, right-left);
+}
+#endif
+
+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)(((u32int)z)^((u32int)y)) > 0 || z == 0)
+                return z;
+        return z + y;
+}
+
+static long
+sdiv(long x, long y)
+{
+        if((long)(((u32int)x)^((u32int)y)) >= 0 || x == 0)
+                return x/y;
+
+        return (x+((y>>30)|1))/y-1;
+}
+
+static long
+smuldivmod(long x, long y, long z, long *mod)
+{
+        vlong vx;
+
+        if(x == 0 || y == 0){
+                *mod = 0;
+                return 0;
+        }
+        vx = x;
+        vx *= y;
+        *mod = vx % z;
+        if(*mod < 0)
+                *mod += z;        /* z is always >0 */
+        if((vx < 0) == (z < 0))
+                return vx/z;
+        return -((-vx)/z);
+}
+
+static void
+xscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int detail, int fixshift, int clipped, int op)
+{
+        long y, maxy, x, x2, xerr, xden, onehalf;
+        Seg **ep, **next, **p, **q, *s;
+        long n, i, iy, cnt, ix, ix2, minx, maxx;
+        Point pt;
+        void        (*fill)(Memimage*, int, int, int, Memimage*, Point, int);
+
+        fill = fillline;
+/*
+ * This can only work on 8-bit destinations, since fillcolor is
+ * just using memset on sp.x.
+ *
+ * I'd rather not even enable it then, since then if the general
+ * code is too slow, someone will come up with a better improvement
+ * than this sleazy hack.        -rsc
+ *
+        if(clipped && (src->flags&Frepl) && src->depth==8 && Dx(src->r)==1 && Dy(src->r)==1) {
+                fill = fillcolor;
+                sp.x = membyteval(src);
+        }
+ *
+ */
+        USED(clipped);
+
+
+        for(i=0, s=segtab, p=seg; ip0.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(yp1.y < y)
+                                continue;
+                        s->z += s->dz;
+                        s->zerr += s->dzrem;
+                        if(s->zerr >= s->den) {
+                                s->z++;
+                                s->zerr -= s->den;
+                                if(s->zerr < 0 || s->zerr >= s->den)
+                                        print("bad ratzerr1: %ld den %ld dzrem %ld\n", s->zerr, s->den, s->dzrem);
+                        }
+                        *q++ = s;
+                }
+
+                for(p = next; *p; p++) {
+                        s = *p;
+                        if(s->p0.y >= y)
+                                break;
+                        if(s->p1.y < y)
+                                continue;
+                        s->z = s->p0.x;
+                        s->z += smuldivmod(y - s->p0.y, s->num, s->den, &s->zerr);
+                        if(s->zerr < 0 || s->zerr >= s->den)
+                                print("bad ratzerr2: %ld den %ld ratdzrem %ld\n", s->zerr, s->den, s->dzrem);
+                        *q++ = s;
+                }
+                ep = q;
+                next = p;
+
+                if(ep == seg) {
+                        if(*next == 0)
+                                break;
+                        iy = (next[0]->p0.y + onehalf) >> fixshift;
+                        y = (iy << fixshift) + onehalf;
+                        continue;
+                }
+
+                zsort(seg, ep);
+
+                for(p = seg; p < ep; p++) {
+                        cnt = 0;
+                        x = p[0]->z;
+                        xerr = p[0]->zerr;
+                        xden = p[0]->den;
+                        ix = (x + onehalf) >> fixshift;
+                        if(ix >= maxx)
+                                break;
+                        if(ix < minx)
+                                ix = minx;
+                        cnt += p[0]->d;
+                        p++;
+                        for(;;) {
+                                if(p == ep) {
+                                        print("xscan: fill to infinity");
+                                        return;
+                                }
+                                cnt += p[0]->d;
+                                if((cnt&wind) == 0)
+                                        break;
+                                p++;
+                        }
+                        x2 = p[0]->z;
+                        ix2 = (x2 + onehalf) >> fixshift;
+                        if(ix2 <= minx)
+                                continue;
+                        if(ix2 > maxx)
+                                ix2 = maxx;
+                        if(ix == ix2 && detail) {
+                                if(xerr*p[0]->den + p[0]->zerr*xden > p[0]->den*xden)
+                                        x++;
+                                ix = (x + x2) >> (fixshift+1);
+                                ix2 = ix+1;
+                        }
+                        (*fill)(dst, ix, ix2, iy, src, sp, op);
+                }
+                y += (1<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(xp1.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<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;
+}
diff --git a/src/libdraw/md-hwdraw.c b/src/libdraw/md-hwdraw.c
t@@ -0,0 +1,12 @@
+#include 
+#include 
+#include 
+#include 
+
+int
+hwdraw(Memdrawparam *p)
+{
+        USED(p);
+        return 0;        /* could not satisfy request */
+}
+
diff --git a/src/libdraw/md-iprint.c b/src/libdraw/md-iprint.c
t@@ -0,0 +1,12 @@
+#include 
+#include 
+#include 
+#include 
+
+int
+iprint(char *fmt,...)
+{
+        USED(fmt);
+        return -1;
+}
+
diff --git a/src/libdraw/md-line.c b/src/libdraw/md-line.c
t@@ -0,0 +1,484 @@
+#include 
+#include 
+#include 
+#include 
+
+enum
+{
+        Arrow1 = 8,
+        Arrow2 = 10,
+        Arrow3 = 3,
+};
+
+static
+int
+lmin(int a, int b)
+{
+        if(a < b)
+                return a;
+        return b;
+}
+
+static
+int
+lmax(int a, int b)
+{
+        if(a > b)
+                return a;
+        return b;
+}
+
+#ifdef NOTUSED
+/*
+ * Rather than line clip, we run the Bresenham loop over the full line,
+ * and clip on each pixel.  This is more expensive but means that
+ * lines look the same regardless of how the windowing has tiled them.
+ * For speed, we check for clipping outside the loop and make the
+ * test easy when possible.
+ */
+
+static
+void
+horline1(Memimage *dst, Point p0, Point p1, int srcval, Rectangle clipr)
+{
+        int x, y, dy, deltay, deltax, maxx;
+        int dd, easy, e, bpp, m, m0;
+        uchar *d;
+
+        deltax = p1.x - p0.x;
+        deltay = p1.y - p0.y;
+        dd = dst->width*sizeof(u32int);
+        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 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 0){
+                        x += dd;
+                        d += dd;
+                        e += deltay;
+                }else
+                        e += deltax;
+                d += dst->width*sizeof(u32int);
+                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 && yr.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> (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>= 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> (x&(7/dst->depth))*bpp;
+                        d = byteaddr(dst, Pt(x, y));
+                        s = byteaddr(src, addpt(dsrc, Pt(x, y)));
+                        *d ^= (*d^*s) & m;
+                }
+        }
+}
+#endif /* NOTUSED */
+
+static Memimage*
+membrush(int radius)
+{
+        static Memimage *brush;
+        static int brushradius;
+
+        if(brush==nil || brushradius!=radius){
+                freememimage(brush);
+                brush = allocmemimage(Rect(0, 0, 2*radius+1, 2*radius+1), memopaque->chan);
+                if(brush != nil){
+                        memfillcolor(brush, DTransparent);        /* zeros */
+                        memellipse(brush, Pt(radius, radius), radius, radius, -1, memopaque, Pt(radius, radius), S);
+                }
+                brushradius = radius;
+        }
+        return brush;
+}
+
+static
+void
+discend(Point p, int radius, Memimage *dst, Memimage *src, Point dsrc, int op)
+{
+        Memimage *disc;
+        Rectangle r;
+
+        disc = membrush(radius);
+        if(disc != nil){
+                r.min.x = p.x - radius;
+                r.min.y = p.y - radius;
+                r.max.x = p.x + radius+1;
+                r.max.y = p.y + radius+1;
+                memdraw(dst, r, src, addpt(r.min, dsrc), disc, Pt(0,0), op);
+        }
+}
+
+static
+void
+arrowend(Point tip, Point *pp, int end, int sin, int cos, int radius)
+{
+        int x1, x2, x3;
+
+        /* before rotation */
+        if(end == Endarrow){
+                x1 = Arrow1;
+                x2 = Arrow2;
+                x3 = Arrow3;
+        }else{
+                x1 = (end>>5) & 0x1FF;        /* distance along line from end of line to tip */
+                x2 = (end>>14) & 0x1FF;        /* distance along line from barb to tip */
+                x3 = (end>>23) & 0x1FF;        /* distance perpendicular from edge of line to barb */
+        }
+
+        /* comments follow track of right-facing arrowhead */
+        pp->x = tip.x+((2*radius+1)*sin/2-x1*cos);                /* upper side of shaft */
+        pp->y = tip.y-((2*radius+1)*cos/2+x1*sin);
+        pp++;
+        pp->x = tip.x+((2*radius+2*x3+1)*sin/2-x2*cos);                /* upper barb */
+        pp->y = tip.y-((2*radius+2*x3+1)*cos/2+x2*sin);
+        pp++;
+        pp->x = tip.x;
+        pp->y = tip.y;
+        pp++;
+        pp->x = tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos);        /* lower barb */
+        pp->y = tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin);
+        pp++;
+        pp->x = tip.x+(-(2*radius+1)*sin/2-x1*cos);                /* lower side of shaft */
+        pp->y = tip.y+((2*radius+1)*cos/2-x1*sin);
+}
+
+void
+_memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, Rectangle clipr, int op)
+{
+        /*
+         * BUG: We should really really pick off purely horizontal and purely
+         * vertical lines and handle them separately with calls to memimagedraw
+         * on rectangles.
+         */
+
+        int hor;
+        int sin, cos, dx, dy, t;
+        Rectangle oclipr, r;
+        Point q, pts[10], *pp, d;
+
+        if(radius < 0)
+                return;
+        if(rectclip(&clipr, dst->r) == 0)
+                return;
+        if(rectclip(&clipr, dst->clipr) == 0)
+                return;
+        d = subpt(sp, p0);
+        if(rectclip(&clipr, rectsubpt(src->clipr, d)) == 0)
+                return;
+        if((src->flags&Frepl)==0 && rectclip(&clipr, rectsubpt(src->r, d))==0)
+                return;
+        /* this means that only verline() handles degenerate lines (p0==p1) */
+        hor = (abs(p1.x-p0.x) > abs(p1.y-p0.y));
+        /*
+         * Clipping is a little peculiar.  We can't use Sutherland-Cohen
+         * clipping because lines are wide.  But this is probably just fine:
+         * we do all math with the original p0 and p1, but clip when deciding
+         * what pixels to draw.  This means the layer code can call this routine,
+         * using clipr to define the region being written, and get the same set
+         * of pixels regardless of the dicing.
+         */
+        if((hor && p0.x>p1.x) || (!hor && p0.y>p1.y)){
+                q = p0;
+                p0 = p1;
+                p1 = q;
+                t = end0;
+                end0 = end1;
+                end1 = t;
+        }
+
+        if((p0.x == p1.x || p0.y == p1.y) && (end0&0x1F) == Endsquare && (end1&0x1F) == Endsquare){
+                r.min = p0;
+                r.max = p1;
+                if(p0.x == p1.x){
+                        r.min.x -= radius;
+                        r.max.x += radius+1;
+                }
+                else{
+                        r.min.y -= radius;
+                        r.max.y += radius+1;
+                }
+                oclipr = dst->clipr;
+                dst->clipr = clipr;
+                memimagedraw(dst, r, src, sp, memopaque, sp, op);
+                dst->clipr = oclipr;
+                return;
+        }
+
+/*    Hard: */
+        /* draw thick line using polygon fill */
+        icossin2(p1.x-p0.x, p1.y-p0.y, &cos, &sin);
+        dx = (sin*(2*radius+1))/2;
+        dy = (cos*(2*radius+1))/2;
+        pp = pts;
+        oclipr = dst->clipr;
+        dst->clipr = clipr;
+        q.x = ICOSSCALE*p0.x+ICOSSCALE/2-cos/2;
+        q.y = ICOSSCALE*p0.y+ICOSSCALE/2-sin/2;
+        switch(end0 & 0x1F){
+        case Enddisc:
+                discend(p0, radius, dst, src, d, op);
+                /* fall through */
+        case Endsquare:
+        default:
+                pp->x = q.x-dx;
+                pp->y = q.y+dy;
+                pp++;
+                pp->x = q.x+dx;
+                pp->y = q.y-dy;
+                pp++;
+                break;
+        case Endarrow:
+                arrowend(q, pp, end0, -sin, -cos, radius);
+                _memfillpolysc(dst, pts, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
+                pp[1] = pp[4];
+                pp += 2;
+        }
+        q.x = ICOSSCALE*p1.x+ICOSSCALE/2+cos/2;
+        q.y = ICOSSCALE*p1.y+ICOSSCALE/2+sin/2;
+        switch(end1 & 0x1F){
+        case Enddisc:
+                discend(p1, radius, dst, src, d, op);
+                /* fall through */
+        case Endsquare:
+        default:
+                pp->x = q.x+dx;
+                pp->y = q.y-dy;
+                pp++;
+                pp->x = q.x-dx;
+                pp->y = q.y+dy;
+                pp++;
+                break;
+        case Endarrow:
+                arrowend(q, pp, end1, sin, cos, radius);
+                _memfillpolysc(dst, pp, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
+                pp[1] = pp[4];
+                pp += 2;
+        }
+        _memfillpolysc(dst, pts, pp-pts, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 0, 10, 1, op);
+        dst->clipr = oclipr;
+        return;
+}
+
+void
+memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+        _memimageline(dst, p0, p1, end0, end1, radius, src, sp, dst->clipr, op);
+}
+
+/*
+ * Simple-minded conservative code to compute bounding box of line.
+ * Result is probably a little larger than it needs to be.
+ */
+static
+void
+addbbox(Rectangle *r, Point p)
+{
+        if(r->min.x > p.x)
+                r->min.x = p.x;
+        if(r->min.y > p.y)
+                r->min.y = p.y;
+        if(r->max.x < p.x+1)
+                r->max.x = p.x+1;
+        if(r->max.y < p.y+1)
+                r->max.y = p.y+1;
+}
+
+int
+memlineendsize(int end)
+{
+        int x3;
+
+        if((end&0x3F) != Endarrow)
+                return 0;
+        if(end == Endarrow)
+                x3 = Arrow3;
+        else
+                x3 = (end>>23) & 0x1FF;
+        return x3;
+}
+
+Rectangle
+memlinebbox(Point p0, Point p1, int end0, int end1, int radius)
+{
+        Rectangle r, r1;
+        int extra;
+
+        r.min.x = 10000000;
+        r.min.y = 10000000;
+        r.max.x = -10000000;
+        r.max.y = -10000000;
+        extra = lmax(memlineendsize(end0), memlineendsize(end1));
+        r1 = insetrect(canonrect(Rpt(p0, p1)), -(radius+extra));
+        addbbox(&r, r1.min);
+        addbbox(&r, r1.max);
+        return r;
+}
diff --git a/src/libdraw/md-load.c b/src/libdraw/md-load.c
t@@ -0,0 +1,72 @@
+#include 
+#include 
+#include 
+#include 
+
+int
+_loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+        int y, l, lpart, rpart, mx, m, mr;
+        uchar *q;
+
+        if(!rectinrect(r, i->r))
+                return -1;
+        l = bytesperline(r, i->depth);
+        if(ndata < l*Dy(r))
+                return -1;
+        ndata = l*Dy(r);
+        q = byteaddr(i, r.min);
+        mx = 7/i->depth;
+        lpart = (r.min.x & mx) * i->depth;
+        rpart = (r.max.x & mx) * i->depth;
+        m = 0xFF >> lpart;
+        /* may need to do bit insertion on edges */
+        if(l == 1){        /* all in one byte */
+                if(rpart)
+                        m ^= 0xFF >> rpart;
+                for(y=r.min.y; ywidth*sizeof(u32int);
+                        data++;
+                }
+                return ndata;
+        }
+        if(lpart==0 && rpart==0){        /* easy case */
+                for(y=r.min.y; ywidth*sizeof(u32int);
+                        data += l;
+                }
+                return ndata;
+        }
+        mr = 0xFF ^ (0xFF >> rpart);
+        if(lpart!=0 && rpart==0){
+                for(y=r.min.y; y 1)
+                                memmove(q+1, data+1, l-1);
+                        q += i->width*sizeof(u32int);
+                        data += l;
+                }
+                return ndata;
+        }
+        if(lpart==0 && rpart!=0){
+                for(y=r.min.y; y 1)
+                                memmove(q, data, l-1);
+                        q[l-1] ^= (data[l-1]^q[l-1]) & mr;
+                        q += i->width*sizeof(u32int);
+                        data += l;
+                }
+                return ndata;
+        }
+        for(y=r.min.y; y 2)
+                        memmove(q+1, data+1, l-2);
+                q[l-1] ^= (data[l-1]^q[l-1]) & mr;
+                q += i->width*sizeof(u32int);
+                data += l;
+        }
+        return ndata;
+}
diff --git a/src/libdraw/md-mkcmap.c b/src/libdraw/md-mkcmap.c
t@@ -0,0 +1,79 @@
+#include 
+#include 
+#include 
+#include 
+
+/*
+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 \n#include \n");
+        else
+                print("#include \"lib9.h\"\n");
+        print("#include \n");
+        print("#include \n\n");
+        print("static Memcmap def = {\n");
+        print("/* cmap2rgb */ {\n");
+        for(i=0; icmap2rgb); ){
+                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; irgb2cmap);){
+                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);
+}
diff --git a/src/libdraw/md-openmemsubfont.c b/src/libdraw/md-openmemsubfont.c
t@@ -0,0 +1,53 @@
+#include 
+#include 
+#include 
+#include 
+
+Memsubfont*
+openmemsubfont(char *name)
+{
+        Memsubfont *sf;
+        Memimage *i;
+        Fontchar *fc;
+        int fd, n;
+        char hdr[3*12+4+1];
+        uchar *p;
+
+        fd = open(name, OREAD);
+        if(fd < 0)
+                return nil;
+        p = nil;
+        i = readmemimage(fd);
+        if(i == nil)
+                goto Err;
+        if(read(fd, hdr, 3*12) != 3*12){
+                werrstr("openmemsubfont: header read error: %r");
+                goto Err;
+        }
+        n = atoi(hdr);
+        p = malloc(6*(n+1));
+        if(p == nil)
+                goto Err;
+        if(read(fd, p, 6*(n+1)) != 6*(n+1)){
+                werrstr("openmemsubfont: fontchar read error: %r");
+                goto Err;
+        }
+        fc = malloc(sizeof(Fontchar)*(n+1));
+        if(fc == nil)
+                goto Err;
+        _unpackinfo(fc, p, n);
+        sf = allocmemsubfont(name, n, atoi(hdr+12), atoi(hdr+24), fc, i);
+        if(sf == nil){
+                free(fc);
+                goto Err;
+        }
+        free(p);
+        return sf;
+Err:
+        close(fd);
+        if (i != nil)
+                freememimage(i);
+        if (p != nil)
+                free(p);
+        return nil;
+}
diff --git a/src/libdraw/md-poly.c b/src/libdraw/md-poly.c
t@@ -0,0 +1,23 @@
+#include 
+#include 
+#include 
+#include 
+
+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
diff --git a/src/libdraw/md-read.c b/src/libdraw/md-read.c
t@@ -0,0 +1,111 @@
+#include 
+#include 
+#include 
+#include 
+
+Memimage*
+readmemimage(int fd)
+{
+        char hdr[5*12+1];
+        int dy;
+        u32int chan;
+        uint l, n;
+        int m, j;
+        int new, miny, maxy;
+        Rectangle r;
+        uchar *tmp;
+        int ldepth, chunk;
+        Memimage *i;
+
+        if(readn(fd, hdr, 11) != 11){
+                werrstr("readimage: short header");
+                return nil;
+        }
+        if(memcmp(hdr, "compressed\n", 11) == 0)
+                return creadmemimage(fd);
+        if(readn(fd, hdr+11, 5*12-11) != 5*12-11){
+                werrstr("readimage: short header (2)");
+                return nil;
+        }
+
+        /*
+         * distinguish new channel descriptor from old ldepth.
+         * channel descriptors have letters as well as numbers,
+         * while ldepths are a single digit formatted as %-11d.
+         */
+        new = 0;
+        for(m=0; m<10; m++){
+                if(hdr[m] != ' '){
+                        new = 1;
+                        break;
+                }
+        }
+        if(hdr[11] != ' '){
+                werrstr("readimage: bad format");
+                return nil;
+        }
+        if(new){
+                hdr[11] = '\0';
+                if((chan = strtochan(hdr)) == 0){
+                        werrstr("readimage: bad channel string %s", hdr);
+                        return nil;
+                }
+        }else{
+                ldepth = ((int)hdr[10])-'0';
+                if(ldepth<0 || ldepth>3){
+                        werrstr("readimage: bad ldepth %d", ldepth);
+                        return nil;
+                }
+                chan = drawld2chan[ldepth];
+        }
+
+        r.min.x = atoi(hdr+1*12);
+        r.min.y = atoi(hdr+2*12);
+        r.max.x = atoi(hdr+3*12);
+        r.max.y = atoi(hdr+4*12);
+        if(r.min.x>r.max.x || r.min.y>r.max.y){
+                werrstr("readimage: bad rectangle");
+                return nil;
+        }
+
+        miny = r.min.y;
+        maxy = r.max.y;
+
+        l = bytesperline(r, chantodepth(chan));
+        i = allocmemimage(r, chan);
+        if(i == nil)
+                return nil;
+        chunk = 32*1024;
+        if(chunk < l)
+                chunk = l;
+        tmp = malloc(chunk);
+        if(tmp == nil)
+                goto Err;
+        while(maxy > miny){
+                dy = maxy - miny;
+                if(dy*l > chunk)
+                        dy = chunk/l;
+                if(dy <= 0){
+                        werrstr("readmemimage: image too wide for buffer");
+                        goto Err;
+                }
+                n = dy*l;
+                m = readn(fd, tmp, n);
+                if(m != n){
+                        werrstr("readmemimage: read count %d not %d: %r", m, n);
+   Err:
+                         freememimage(i);
+                        free(tmp);
+                        return nil;
+                }
+                if(!new)        /* an old image: must flip all the bits */
+                        for(j=0; j
diff --git a/src/libdraw/md-string.c b/src/libdraw/md-string.c
t@@ -0,0 +1,66 @@
+#include 
+#include 
+#include 
+#include 
+
+Point
+memimagestring(Memimage *b, Point p, Memimage *color, Point cp, Memsubfont *f, char *cs)
+{
+        int w, width;
+        uchar *s;
+        Rune c;
+        Fontchar *i;
+
+        s = (uchar*)cs;
+        for(; c=*s; p.x+=width, cp.x+=width){
+                width = 0;
+                if(c < Runeself)
+                        s++;
+                else{
+                        w = chartorune(&c, (char*)s);
+                        if(w == 0){
+                                s++;
+                                continue;
+                        }
+                        s += w;
+                }
+                if(c >= f->n)
+                        continue;
+                i = f->info+c;
+                width = i->width;
+                memdraw(b, Rect(p.x+i->left, p.y+i->top, p.x+i->left+(i[1].x-i[0].x), p.y+i->bottom),
+                        color, cp, f->bits, Pt(i->x, i->top), SoverD);
+        }
+        return p;
+}
+
+Point
+memsubfontwidth(Memsubfont *f, char *cs)
+{
+        Rune c;
+        Point p;
+        uchar *s;
+        Fontchar *i;
+        int w, width;
+
+        p = Pt(0, f->height);
+        s = (uchar*)cs;
+        for(; c=*s; p.x+=width){
+                width = 0;
+                if(c < Runeself)
+                        s++;
+                else{
+                        w = chartorune(&c, (char*)s);
+                        if(w == 0){
+                                s++;
+                                continue;
+                        }
+                        s += w;
+                }
+                if(c >= f->n)
+                        continue;
+                i = f->info+c;
+                width = i->width;
+        }
+        return p;
+}
diff --git a/src/libdraw/md-subfont.c b/src/libdraw/md-subfont.c
t@@ -0,0 +1,34 @@
+#include 
+#include 
+#include 
+#include 
+
+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);
+}
diff --git a/src/libdraw/md-unload.c b/src/libdraw/md-unload.c
t@@ -0,0 +1,25 @@
+#include 
+#include 
+#include 
+#include 
+
+int
+_unloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+        int y, l;
+        uchar *q;
+
+        if(!rectinrect(r, i->r))
+                return -1;
+        l = bytesperline(r, i->depth);
+        if(ndata < l*Dy(r))
+                return -1;
+        ndata = l*Dy(r);
+        q = byteaddr(i, r.min);
+        for(y=r.min.y; ywidth*sizeof(u32int);
+                data += l;
+        }
+        return ndata;
+}
diff --git a/src/libdraw/md-write.c b/src/libdraw/md-write.c
t@@ -0,0 +1,183 @@
+#include 
+#include 
+#include 
+#include 
+
+#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)<r;
+        bpl = bytesperline(r, i->depth);
+        n = Dy(r)*bpl;
+        data = malloc(n);
+        ncblock = _compblocksize(r, i->depth);
+        outbuf = malloc(ncblock);
+        hash = malloc(NHASH*sizeof(Hlist));
+        chain = malloc(NMEM*sizeof(Hlist));
+        if(data == 0 || outbuf == 0 || hash == 0 || chain == 0){
+        ErrOut:
+                free(data);
+                free(outbuf);
+                free(hash);
+                free(chain);
+                return -1;
+        }
+        for(miny = r.min.y; miny != r.max.y; miny += dy){
+                dy = r.max.y-miny;
+                if(dy*bpl > CHUNK)
+                        dy = CHUNK/bpl;
+                nb = unloadmemimage(i, Rect(r.min.x, miny, r.max.x, miny+dy),
+                        data+(miny-r.min.y)*bpl, dy*bpl);
+                if(nb != dy*bpl)
+                        goto ErrOut;
+        }
+        sprint(hdr, "compressed\n%11s %11d %11d %11d %11d ",
+                chantostr(cbuf, i->chan), r.min.x, r.min.y, r.max.x, r.max.y);
+        if(write(fd, hdr, 11+5*12) != 11+5*12)
+                goto ErrOut;
+        edata = data+n;
+        eout = outbuf+ncblock;
+        line = data;
+        r.max.y = r.min.y;
+        while(line != edata){
+                memset(hash, 0, NHASH*sizeof(Hlist));
+                memset(chain, 0, NMEM*sizeof(Hlist));
+                cp = chain;
+                h = 0;
+                outp = outbuf;
+                for(n = 0; n != NMATCH; n++)
+                        h = hupdate(h, line[n]);
+                loutp = outbuf;
+                while(line != edata){
+                        ndump = 0;
+                        eline = line+bpl;
+                        for(p = line; p != eline; ){
+                                if(eline-p < NRUN)
+                                        es = eline;
+                                else
+                                        es = p+NRUN;
+                                q = 0;
+                                runlen = 0;
+                                for(hp = hash[h].next; hp; hp = hp->next){
+                                        s = p + runlen;
+                                        if(s >= es)
+                                                continue;
+                                        t = hp->s + runlen;
+                                        for(; s >= p; s--)
+                                                if(*s != *t--)
+                                                        goto matchloop;
+                                        t += runlen+2;
+                                        s += runlen+2;
+                                        for(; s < es; s++)
+                                                if(*s != *t++)
+                                                        break;
+                                        n = s-p;
+                                        if(n > runlen){
+                                                runlen = n;
+                                                q = hp->s;
+                                                if(n == NRUN)
+                                                        break;
+                                        }
+                        matchloop: ;
+                                }
+                                if(runlen < NMATCH){
+                                        if(ndump == NDUMP){
+                                                if(eout-outp < ndump+1)
+                                                        goto Bfull;
+                                                *outp++ = ndump-1+128;
+                                                memmove(outp, dumpbuf, ndump);
+                                                outp += ndump;
+                                                ndump = 0;
+                                        }
+                                        dumpbuf[ndump++] = *p;
+                                        runlen = 1;
+                                }
+                                else{
+                                        if(ndump != 0){
+                                                if(eout-outp < ndump+1)
+                                                        goto Bfull;
+                                                *outp++ = ndump-1+128;
+                                                memmove(outp, dumpbuf, ndump);
+                                                outp += ndump;
+                                                ndump = 0;
+                                        }
+                                        offs = p-q-1;
+                                        if(eout-outp < 2)
+                                                goto Bfull;
+                                        *outp++ = ((runlen-NMATCH)<<2) + (offs>>8);
+                                        *outp++ = offs&255;
+                                }
+                                for(q = p+runlen; p != q; p++){
+                                        if(cp->prev)
+                                                cp->prev->next = 0;
+                                        cp->next = hash[h].next;
+                                        cp->prev = &hash[h];
+                                        if(cp->next)
+                                                cp->next->prev = cp;
+                                        cp->prev->next = cp;
+                                        cp->s = p;
+                                        if(++cp == &chain[NMEM])
+                                                cp = chain;
+                                        if(edata-p > NMATCH)
+                                                h = hupdate(h, p[NMATCH]);
+                                }
+                        }
+                        if(ndump != 0){
+                                if(eout-outp < ndump+1)
+                                        goto Bfull;
+                                *outp++ = ndump-1+128;
+                                memmove(outp, dumpbuf, ndump);
+                                outp += ndump;
+                        }
+                        line = eline;
+                        loutp = outp;
+                        r.max.y++;
+                }
+        Bfull:
+                if(loutp == outbuf)
+                        goto ErrOut;
+                n = loutp-outbuf;
+                sprint(hdr, "%11d %11ld ", r.max.y, n);
+                write(fd, hdr, 2*12);
+                write(fd, outbuf, n);
+                r.min.y = r.max.y;
+        }
+        free(data);
+        free(outbuf);
+        free(hash);
+        free(chain);
+        return 0;
+}
diff --git a/src/libdraw/memdraw.h b/src/libdraw/memdraw.h
t@@ -0,0 +1,209 @@
+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
+{
+        u32int        *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 */
+        u32int                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) */
+        u32int                width;        /* width in words of a single scan line */
+        Memlayer        *layer;        /* nil if not a layer*/
+        u32int                flags;
+        void                *X;
+
+        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;
+
+        u32int state;
+        u32int mval;        /* if Simplemask, the mask pixel in mask format */
+        u32int mrgba;        /* mval in rgba */
+        u32int sval;        /* if Simplesrc, the source pixel in src format */
+        u32int srgba;        /* sval in rgba */
+        u32int sdval;        /* sval in dst format */
+};
+
+/*
+ * Memimage management
+ */
+
+extern Memimage*        allocmemimage(Rectangle, u32int);
+extern Memimage*        allocmemimaged(Rectangle, u32int, Memdata*, void*);
+extern Memimage*        readmemimage(int);
+extern Memimage*        creadmemimage(int);
+extern int                writememimage(int, Memimage*);
+extern void                freememimage(Memimage*);
+extern int                loadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int                cloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int                unloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern u32int*                wordaddr(Memimage*, Point);
+extern uchar*                byteaddr(Memimage*, Point);
+extern int                drawclip(Memimage*, Rectangle*, Memimage*, Point*,
+                                Memimage*, Point*, Rectangle*, Rectangle*);
+extern void                memfillcolor(Memimage*, u32int);
+extern int                memsetchan(Memimage*, u32int);
+extern u32int                pixelbits(Memimage*, Point);
+
+/*
+ * 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 void        memimageinit(void);
+
+/*
+ * Subfont management
+ */
+extern Memsubfont*        allocmemsubfont(char*, int, int, int, Fontchar*, Memimage*);
+extern Memsubfont*        openmemsubfont(char*);
+extern void                freememsubfont(Memsubfont*);
+extern Point                memsubfontwidth(Memsubfont*, char*);
+extern Memsubfont*        getmemdefont(void);
+
+/*
+ * Predefined 
+ */
+extern        Memimage*        memwhite;
+extern        Memimage*        memblack;
+extern        Memimage*        memopaque;
+extern        Memimage*        memtransparent;
+extern        Memcmap*        memdefcmap;
+
+/*
+ * Kernel interface
+ */
+void                        memimagemove(void*, void*);
+
+/*
+ * Kernel cruft
+ */
+extern void                rdb(void);
+extern int                iprint(char*, ...);
+extern int                drawdebug;
+
+/*
+ * For other implementations, like x11.
+ */
+extern void                _memfillcolor(Memimage*, u32int);
+extern Memimage*        _allocmemimage(Rectangle, u32int);
+extern int                _cloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern int                _loadmemimage(Memimage*, Rectangle, uchar*, int);
+extern void                _freememimage(Memimage*);
+extern u32int                _rgbatoimg(Memimage*, u32int);
+extern u32int                _imgtorgba(Memimage*, u32int);
+extern u32int                _pixelbits(Memimage*, Point);
+extern int                _unloadmemimage(Memimage*, Rectangle, uchar*, int);
+extern Memdrawparam*        _memimagedrawsetup(Memimage*,
+                                Rectangle, Memimage*, Point, Memimage*,
+                                Point, int);
+extern void                _memimagedraw(Memdrawparam*);
+extern void                _drawreplacescreenimage(Memimage*);
diff --git a/src/libdraw/memlayer.h b/src/libdraw/memlayer.h
t@@ -0,0 +1,48 @@
+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*, u32int);
+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*);
diff --git a/src/libdraw/menuhit.c b/src/libdraw/menuhit.c
t@@ -0,0 +1,277 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+enum
+{
+        Margin = 4,                /* outside to text */
+        Border = 2,                /* outside to selection boxes */
+        Blackborder = 2,        /* width of outlining border */
+        Vspacing = 2,                /* extra spacing between lines of text */
+        Maxunscroll = 25,        /* maximum #entries before scrolling turns on */
+        Nscroll = 20,                /* number entries in scrolling part */
+        Scrollwid = 14,                /* width of scroll bar */
+        Gap = 4,                        /* between text and scroll bar */
+};
+
+static        Image        *menutxt;
+static        Image        *back;
+static        Image        *high;
+static        Image        *bord;
+static        Image        *text;
+static        Image        *htext;
+
+static
+void
+menucolors(void)
+{
+        /* Main tone is greenish, with negative selection */
+        back = allocimagemix(display, DPalegreen, DWhite);
+        high = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkgreen);        /* dark green */
+        bord = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedgreen);        /* not as dark green */
+        if(back==nil || high==nil || bord==nil)
+                goto Error;
+        text = display->black;
+        htext = back;
+        return;
+
+    Error:
+        freeimage(back);
+        freeimage(high);
+        freeimage(bord);
+        back = display->white;
+        high = display->black;
+        bord = display->black;
+        text = display->black;
+        htext = display->white;
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the rectangle, including its black edge, holding element i.
+ */
+static Rectangle
+menurect(Rectangle r, int i)
+{
+        if(i < 0)
+                return Rect(0, 0, 0, 0);
+        r.min.y += (font->height+Vspacing)*i;
+        r.max.y = r.min.y+font->height+Vspacing;
+        return insetrect(r, Border-Margin);
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the element number containing p.
+ */
+static int
+menusel(Rectangle r, Point p)
+{
+        r = insetrect(r, Margin);
+        if(!ptinrect(p, r))
+                return -1;
+        return (p.y-r.min.y)/(font->height+Vspacing);
+}
+
+static
+void
+paintitem(Image *m, Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
+{
+        char *item;
+        Rectangle r;
+        Point pt;
+
+        if(i < 0)
+                return;
+        r = menurect(textr, i);
+        if(restore){
+                draw(m, r, restore, nil, restore->r.min);
+                return;
+        }
+        if(save)
+                draw(save, save->r, m, nil, r.min);
+        item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
+        pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
+        pt.y = textr.min.y+i*(font->height+Vspacing);
+        draw(m, r, highlight? high : back, nil, pt);
+        string(m, pt, highlight? htext : text, pt, font, item);
+}
+
+/*
+ * menur is a rectangle holding all the highlightable text elements.
+ * track mouse while inside the box, return what's selected when button
+ * is raised, -1 as soon as it leaves box.
+ * invariant: nothing is highlighted on entry or exit.
+ */
+static int
+menuscan(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save)
+{
+        int i;
+
+        paintitem(m, menu, textr, off, lasti, 1, save, nil);
+        for(readmouse(mc); mc->m.buttons & (1<<(but-1)); readmouse(mc)){
+                i = menusel(textr, mc->m.xy);
+                if(i != -1 && i == lasti)
+                        continue;
+                paintitem(m, menu, textr, off, lasti, 0, nil, save);
+                if(i == -1)
+                        return i;
+                lasti = i;
+                paintitem(m, menu, textr, off, lasti, 1, save, nil);
+        }
+        return lasti;
+}
+
+static void
+menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn)
+{
+        int i;
+
+        draw(m, insetrect(textr, Border-Margin), back, nil, ZP);
+        for(i = 0; ichan, 1, DDarkgreen);        /* border color; BUG? */
+        if(menutxt)
+                draw(m, insetrect(r, 1), menutxt, nil, ZP);
+}
+
+int
+menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr)
+{
+        int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
+        int scrolling;
+        Rectangle r, menur, sc, textr, scrollr;
+        Image *b, *save, *backup;
+        Point pt;
+        char *item;
+
+        if(back == nil)
+                menucolors();
+        sc = screen->clipr;
+        replclipr(screen, 0, screen->r);
+        maxwid = 0;
+        for(nitem = 0;
+            item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
+            nitem++){
+                i = stringwidth(font, item);
+                if(i > maxwid)
+                        maxwid = i;
+        }
+        if(menu->lasthit<0 || menu->lasthit>=nitem)
+                menu->lasthit = 0;
+        screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
+        if(nitem>Maxunscroll || nitem>screenitem){
+                scrolling = 1;
+                nitemdrawn = Nscroll;
+                if(nitemdrawn > screenitem)
+                        nitemdrawn = screenitem;
+                wid = maxwid + Gap + Scrollwid;
+                off = menu->lasthit - nitemdrawn/2;
+                if(off < 0)
+                        off = 0;
+                if(off > nitem-nitemdrawn)
+                        off = nitem-nitemdrawn;
+                lasti = menu->lasthit-off;
+        }else{
+                scrolling = 0;
+                nitemdrawn = nitem;
+                wid = maxwid;
+                off = 0;
+                lasti = menu->lasthit;
+        }
+        r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
+        r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
+        r = rectaddpt(r, mc->m.xy);
+        pt = ZP;
+        if(r.max.x>screen->r.max.x)
+                pt.x = screen->r.max.x-r.max.x;
+        if(r.max.y>screen->r.max.y)
+                pt.y = screen->r.max.y-r.max.y;
+        if(r.min.xr.min.x)
+                pt.x = screen->r.min.x-r.min.x;
+        if(r.min.yr.min.y)
+                pt.y = screen->r.min.y-r.min.y;
+        menur = rectaddpt(r, pt);
+        textr.max.x = menur.max.x-Margin;
+        textr.min.x = textr.max.x-maxwid;
+        textr.min.y = menur.min.y+Margin;
+        textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
+        if(scrolling){
+                scrollr = insetrect(menur, Border);
+                scrollr.max.x = scrollr.min.x+Scrollwid;
+        }else
+                scrollr = Rect(0, 0, 0, 0);
+
+        if(scr){
+                b = allocwindow(scr, menur, Refbackup, DWhite);
+                if(b == nil)
+                        b = screen;
+                backup = nil;
+        }else{
+                b = screen;
+                backup = allocimage(display, menur, screen->chan, 0, -1);
+                if(backup)
+                        draw(backup, menur, screen, nil, menur.min);
+        }
+        draw(b, menur, back, nil, ZP);
+        border(b, menur, Blackborder, bord, ZP);
+        save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
+        r = menurect(textr, lasti);
+        moveto(mc, divpt(addpt(r.min, r.max), 2));
+        menupaint(b, menu, textr, off, nitemdrawn);
+        if(scrolling)
+                menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+        while(mc->m.buttons & (1<<(but-1))){
+                lasti = menuscan(b, menu, but, mc, textr, off, lasti, save);
+                if(lasti >= 0)
+                        break;
+                while(!ptinrect(mc->m.xy, textr) && (mc->m.buttons & (1<<(but-1)))){
+                        if(scrolling && ptinrect(mc->m.xy, scrollr)){
+                                noff = ((mc->m.xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
+                                noff -= nitemdrawn/2;
+                                if(noff < 0)
+                                        noff = 0;
+                                if(noff > nitem-nitemdrawn)
+                                        noff = nitem-nitemdrawn;
+                                if(noff != off){
+                                        off = noff;
+                                        menupaint(b, menu, textr, off, nitemdrawn);
+                                        menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+                                }
+                        }
+                        readmouse(mc);
+                }
+        }
+        if(b != screen)
+                freeimage(b);
+        if(backup){
+                draw(screen, menur, backup, nil, menur.min);
+                freeimage(backup);
+        }
+        freeimage(save);
+        replclipr(screen, 0, sc);
+        flushimage(display, 1);
+        if(lasti >= 0){
+                menu->lasthit = lasti+off;
+                return menu->lasthit;
+        }
+        return -1;
+}
diff --git a/src/libdraw/mkfile b/src/libdraw/mkfile
t@@ -0,0 +1 @@
+<../libutf/mkfile
diff --git a/src/libdraw/ml-draw.c b/src/libdraw/ml-draw.c
t@@ -0,0 +1,192 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+struct Draw
+{
+        Point        deltas;
+        Point        deltam;
+        Memlayer                *dstlayer;
+        Memimage        *src;
+        Memimage        *mask;
+        int        op;
+};
+
+static
+void
+ldrawop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+        struct Draw *d;
+        Point p0, p1;
+        Rectangle oclipr, srcr, r, mr;
+        int ok;
+
+        d = etc;
+        if(insave && d->dstlayer->save==nil)
+                return;
+
+        p0 = addpt(screenr.min, d->deltas);
+        p1 = addpt(screenr.min, d->deltam);
+
+        if(insave){
+                r = rectsubpt(screenr, d->dstlayer->delta);
+                clipr = rectsubpt(clipr, d->dstlayer->delta);
+        }else
+                r = screenr;
+
+        /* now in logical coordinates */
+
+        /* clipr may have narrowed what we should draw on, so clip if necessary */
+        if(!rectinrect(r, clipr)){
+                oclipr = dst->clipr;
+                dst->clipr = clipr;
+                ok = drawclip(dst, &r, d->src, &p0, d->mask, &p1, &srcr, &mr);
+                dst->clipr = oclipr;
+                if(!ok)
+                        return;
+        }
+        memdraw(dst, r, d->src, p0, d->mask, p1, d->op);
+}
+
+void
+memdraw(Memimage *dst, Rectangle r, Memimage *src, Point p0, Memimage *mask, Point p1, int op)
+{
+        struct Draw d;
+        Rectangle srcr, tr, mr;
+        Memlayer *dl, *sl;
+
+        if(drawdebug)
+                iprint("memdraw %p %R %p %P %p %P\n", dst, r, src, p0, mask, p1);
+
+        if(mask == nil)
+                mask = memopaque;
+
+        if(mask->layer){
+if(drawdebug)        iprint("mask->layer != nil\n");
+                return;        /* too hard, at least for now */
+        }
+
+    Top:
+        if(dst->layer==nil && src->layer==nil){
+                memimagedraw(dst, r, src, p0, mask, p1, op);
+                return;
+        }
+
+        if(drawclip(dst, &r, src, &p0, mask, &p1, &srcr, &mr) == 0){
+if(drawdebug)        iprint("drawclip dstcr %R srccr %R maskcr %R\n", dst->clipr, src->clipr, mask->clipr);
+                return;
+        }
+
+        /*
+          * Convert to screen coordinates.
+         */
+        dl = dst->layer;
+        if(dl != nil){
+                r.min.x += dl->delta.x;
+                r.min.y += dl->delta.y;
+                r.max.x += dl->delta.x;
+                r.max.y += dl->delta.y;
+        }
+    Clearlayer:
+        if(dl!=nil && dl->clear){
+                if(src == dst){
+                        p0.x += dl->delta.x;
+                        p0.y += dl->delta.y;
+                        src = dl->screen->image;
+                }
+                dst = dl->screen->image;
+                goto Top;
+        }
+
+        sl = src->layer;
+        if(sl != nil){
+                p0.x += sl->delta.x;
+                p0.y += sl->delta.y;
+                srcr.min.x += sl->delta.x;
+                srcr.min.y += sl->delta.y;
+                srcr.max.x += sl->delta.x;
+                srcr.max.y += sl->delta.y;
+        }
+
+        /*
+         * Now everything is in screen coordinates.
+         * mask is an image.  dst and src are images or obscured layers.
+         */
+
+        /*
+         * if dst and src are the same layer, just draw in save area and expose.
+         */
+        if(dl!=nil && dst==src){
+                if(dl->save == nil)
+                        return;        /* refresh function makes this case unworkable */
+                if(rectXrect(r, srcr)){
+                        tr = r;
+                        if(srcr.min.x < tr.min.x){
+                                p1.x += tr.min.x - srcr.min.x;
+                                tr.min.x = srcr.min.x;
+                        }
+                        if(srcr.min.y < tr.min.y){
+                                p1.y += tr.min.x - srcr.min.x;
+                                tr.min.y = srcr.min.y;
+                        }
+                        if(srcr.max.x > tr.max.x)
+                                tr.max.x = srcr.max.x;
+                        if(srcr.max.y > tr.max.y)
+                                tr.max.y = srcr.max.y;
+                        memlhide(dst, tr);
+                }else{
+                        memlhide(dst, r);
+                        memlhide(dst, srcr);
+                }
+                memdraw(dl->save, rectsubpt(r, dl->delta), dl->save,
+                        subpt(srcr.min, src->layer->delta), mask, p1, op);
+                memlexpose(dst, r);
+                return;
+        }
+
+        if(sl){
+                if(sl->clear){
+                        src = sl->screen->image;
+                        if(dl != nil){
+                                r.min.x -= dl->delta.x;
+                                r.min.y -= dl->delta.y;
+                                r.max.x -= dl->delta.x;
+                                r.max.y -= dl->delta.y;
+                        }
+                        goto Top;
+                }
+                /* relatively rare case; use save area */
+                if(sl->save == nil)
+                        return;        /* refresh function makes this case unworkable */
+                memlhide(src, srcr);
+                /* convert back to logical coordinates */
+                p0.x -= sl->delta.x;
+                p0.y -= sl->delta.y;
+                srcr.min.x -= sl->delta.x;
+                srcr.min.y -= sl->delta.y;
+                srcr.max.x -= sl->delta.x;
+                srcr.max.y -= sl->delta.y;
+                src = src->layer->save;
+        }
+
+        /*
+         * src is now an image.  dst may be an image or a clear layer
+         */
+        if(dst->layer==nil)
+                goto Top;
+        if(dst->layer->clear)
+                goto Clearlayer;
+
+        /*
+         * dst is an obscured layer
+         */
+        d.deltas = subpt(p0, r.min);
+        d.deltam = subpt(p1, r.min);
+        d.dstlayer = dl;
+        d.src = src;
+        d.op = op;
+        d.mask = mask;
+        _memlayerop(ldrawop, dst, r, r, &d);
+}
diff --git a/src/libdraw/ml-lalloc.c b/src/libdraw/ml-lalloc.c
t@@ -0,0 +1,79 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+Memimage*
+memlalloc(Memscreen *s, Rectangle screenr, Refreshfn refreshfn, void *refreshptr, u32int 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, nil);
+        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;
+}
diff --git a/src/libdraw/ml-layerop.c b/src/libdraw/ml-layerop.c
t@@ -0,0 +1,112 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#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);
+        }
+}
diff --git a/src/libdraw/ml-ldelete.c b/src/libdraw/ml-ldelete.c
t@@ -0,0 +1,67 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+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;
+                                }
+        }
+}
diff --git a/src/libdraw/ml-lhide.c b/src/libdraw/ml-lhide.c
t@@ -0,0 +1,67 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*
+ * 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);
+}
diff --git a/src/libdraw/ml-line.c b/src/libdraw/ml-line.c
t@@ -0,0 +1,122 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+struct Lline
+{
+        Point                        p0;
+        Point                        p1;
+        Point                        delta;
+        int                        end0;
+        int                        end1;
+        int                        radius;
+        Point                        sp;
+        Memlayer                *dstlayer;
+        Memimage        *src;
+        int                        op;
+};
+
+static void llineop(Memimage*, Rectangle, Rectangle, void*, int);
+
+static
+void
+_memline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, Rectangle clipr, int op)
+{
+        Rectangle r;
+        struct Lline ll;
+        Point d;
+        int srcclipped;
+        Memlayer *dl;
+
+        if(radius < 0)
+                return;
+        if(src->layer)        /* can't draw line with layered source */
+                return;
+        srcclipped = 0;
+
+   Top:
+        dl = dst->layer;
+        if(dl == nil){
+                _memimageline(dst, p0, p1, end0, end1, radius, src, sp, clipr, op);
+                return;
+        }
+        if(!srcclipped){
+                d = subpt(sp, p0);
+                if(rectclip(&clipr, rectsubpt(src->clipr, d)) == 0)
+                        return;
+                if((src->flags&Frepl)==0 && rectclip(&clipr, rectsubpt(src->r, d))==0)
+                        return;
+                srcclipped = 1;
+        }
+
+        /* dst is known to be a layer */
+        p0.x += dl->delta.x;
+        p0.y += dl->delta.y;
+        p1.x += dl->delta.x;
+        p1.y += dl->delta.y;
+        clipr.min.x += dl->delta.x;
+        clipr.min.y += dl->delta.y;
+        clipr.max.x += dl->delta.x;
+        clipr.max.y += dl->delta.y;
+        if(dl->clear){
+                dst = dst->layer->screen->image;
+                goto Top;
+        }
+
+        /* XXX */
+        /* this is not the correct set of tests */
+//        if(log2[dst->depth] != log2[src->depth] || log2[dst->depth]!=3)
+//                return;
+
+        /* can't use sutherland-cohen clipping because lines are wide */
+        r = memlinebbox(p0, p1, end0, end1, radius);
+        /*
+         * r is now a bounding box for the line;
+         * use it as a clipping rectangle for subdivision
+         */
+        if(rectclip(&r, clipr) == 0)
+                return;
+        ll.p0 = p0;
+        ll.p1 = p1;
+        ll.end0 = end0;
+        ll.end1 = end1;
+        ll.sp = sp;
+        ll.dstlayer = dst->layer;
+        ll.src = src;
+        ll.radius = radius;
+        ll.delta = dl->delta;
+        ll.op = op;
+        _memlayerop(llineop, dst, r, r, &ll);
+}
+
+static
+void
+llineop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+        struct Lline *ll;
+        Point p0, p1;
+
+        USED(screenr.min.x);
+        ll = etc;
+        if(insave && ll->dstlayer->save==nil)
+                return;
+        if(!rectclip(&clipr, screenr))
+                return;
+        if(insave){
+                p0 = subpt(ll->p0, ll->delta);
+                p1 = subpt(ll->p1, ll->delta);
+                clipr = rectsubpt(clipr, ll->delta);
+        }else{
+                p0 = ll->p0;
+                p1 = ll->p1;
+        }
+        _memline(dst, p0, p1, ll->end0, ll->end1, ll->radius, ll->src, ll->sp, clipr, ll->op);
+}
+
+void
+memline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+        _memline(dst, p0, p1, end0, end1, radius, src, sp, dst->clipr, op);
+}
diff --git a/src/libdraw/ml-load.c b/src/libdraw/ml-load.c
t@@ -0,0 +1,55 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+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;
+}
diff --git a/src/libdraw/ml-lorigin.c b/src/libdraw/ml-lorigin.c
t@@ -0,0 +1,107 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*
+ * 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);
+}
diff --git a/src/libdraw/ml-lsetrefresh.c b/src/libdraw/ml-lsetrefresh.c
t@@ -0,0 +1,35 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+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;
+}
diff --git a/src/libdraw/ml-ltofront.c b/src/libdraw/ml-ltofront.c
t@@ -0,0 +1,80 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*
+ * 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);
+}
diff --git a/src/libdraw/ml-ltorear.c b/src/libdraw/ml-ltorear.c
t@@ -0,0 +1,69 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+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);
+}
diff --git a/src/libdraw/ml-unload.c b/src/libdraw/ml-unload.c
t@@ -0,0 +1,52 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+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;
+}
diff --git a/src/libdraw/mouse.c b/src/libdraw/mouse.c
t@@ -0,0 +1,139 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+void
+moveto(Mousectl *m, Point pt)
+{
+        fprint(m->mfd, "m%d %d", pt.x, pt.y);
+        m->xy = pt;
+}
+
+void
+closemouse(Mousectl *mc)
+{
+        if(mc == nil)
+                return;
+
+        postnote(PNPROC, mc->pid, "kill");
+
+        do; while(nbrecv(mc->c, &mc->Mouse) > 0);
+
+        close(mc->mfd);
+        close(mc->cfd);
+        free(mc->file);
+        free(mc->c);
+        free(mc->resizec);
+        free(mc);
+}
+
+int
+readmouse(Mousectl *mc)
+{
+        if(mc->image)
+                flushimage(mc->image->display, 1);
+        if(recv(mc->c, &mc->Mouse) < 0){
+                fprint(2, "readmouse: %r\n");
+                return -1;
+        }
+        return 0;
+}
+
+static
+void
+_ioproc(void *arg)
+{
+        int n, nerr, one;
+        char buf[1+5*12];
+        Mouse m;
+        Mousectl *mc;
+
+        mc = arg;
+        threadsetname("mouseproc");
+        one = 1;
+        memset(&m, 0, sizeof m);
+        mc->pid = getpid();
+        nerr = 0;
+        for(;;){
+                n = read(mc->mfd, buf, sizeof buf);
+                if(n != 1+4*12){
+                        yield();        /* if error is due to exiting, we'll exit here */
+                        fprint(2, "mouse: bad count %d not 49: %r\n", n);
+                        if(n<0 || ++nerr>10)
+                                threadexits("read error");
+                        continue;
+                }
+                nerr = 0;
+                switch(buf[0]){
+                case 'r':
+                        send(mc->resizec, &one);
+                        /* fall through */
+                case 'm':
+                        m.xy.x = atoi(buf+1+0*12);
+                        m.xy.y = atoi(buf+1+1*12);
+                        m.buttons = atoi(buf+1+2*12);
+                        m.msec = atoi(buf+1+3*12);
+                        send(mc->c, &m);
+                        /*
+                         * mc->Mouse is updated after send so it doesn't have wrong value if we block during send.
+                         * This means that programs should receive into mc->Mouse (see readmouse() above) if
+                         * they want full synchrony.
+                         */
+                        mc->Mouse = m;
+                        break;
+                }
+        }
+}
+
+Mousectl*
+initmouse(char *file, Image *i)
+{
+        Mousectl *mc;
+        char *t, *sl;
+
+        mc = mallocz(sizeof(Mousectl), 1);
+        if(file == nil)
+                file = "/dev/mouse";
+        mc->file = strdup(file);
+        mc->mfd = open(file, ORDWR|OCEXEC);
+        if(mc->mfd<0 && strcmp(file, "/dev/mouse")==0){
+                bind("#m", "/dev", MAFTER);
+                mc->mfd = open(file, ORDWR|OCEXEC);
+        }
+        if(mc->mfd < 0){
+                free(mc);
+                return nil;
+        }
+        t = malloc(strlen(file)+16);
+        strcpy(t, file);
+        sl = utfrrune(t, '/');
+        if(sl)
+                strcpy(sl, "/cursor");
+        else
+                strcpy(t, "/dev/cursor");
+        mc->cfd = open(t, ORDWR|OCEXEC);
+        free(t);
+        mc->image = i;
+        mc->c = chancreate(sizeof(Mouse), 0);
+        mc->resizec = chancreate(sizeof(int), 2);
+        proccreate(_ioproc, mc, 4096);
+        return mc;
+}
+
+void
+setcursor(Mousectl *mc, Cursor *c)
+{
+        char curs[2*4+2*2*16];
+
+        if(c == nil)
+                write(mc->cfd, curs, 0);
+        else{
+                BPLONG(curs+0*4, c->offset.x);
+                BPLONG(curs+1*4, c->offset.y);
+                memmove(curs+2*4, c->clr, 2*2*16);
+                write(mc->cfd, curs, sizeof curs);
+        }
+}
diff --git a/src/libdraw/mouse.h b/src/libdraw/mouse.h
t@@ -0,0 +1,44 @@
+typedef struct        Menu Menu;
+typedef struct         Mousectl Mousectl;
+
+struct        Mouse
+{
+        int        buttons;        /* bit array: LMR=124 */
+        Point        xy;
+        ulong        msec;
+};
+
+struct Mousectl
+{
+        Mouse        m;
+        struct Channel        *c;        /* chan(Mouse) */
+        struct Channel        *resizec;        /* chan(int)[2] */
+                        /* buffered in case client is waiting for a mouse action before handling resize */
+
+        char                *file;
+        int                mfd;                /* to mouse file */
+        int                cfd;                /* to cursor file */
+        int                pid;                /* of slave proc */
+        Display                *display;
+        /*Image*        image;        / * of associated window/display */
+};
+
+struct Menu
+{
+        char        **item;
+        char        *(*gen)(int);
+        int        lasthit;
+};
+
+/*
+ * Mouse
+ */
+extern Mousectl*        initmouse(char*, Image*);
+extern void                moveto(Mousectl*, Point);
+extern int                        readmouse(Mousectl*);
+extern void                closemouse(Mousectl*);
+struct Cursor;
+extern void                setcursor(Mousectl*, struct Cursor*);
+extern void                drawgetrect(Rectangle, int);
+extern Rectangle        getrect(int, Mousectl*);
+extern int                         menuhit(int, Mousectl*, Menu*, Screen*);
diff --git a/src/libdraw/openfont.c b/src/libdraw/openfont.c
t@@ -0,0 +1,32 @@
+#include 
+#include 
+#include 
+
+Font*
+openfont(Display *d, char *name)
+{
+        Font *fnt;
+        int fd, i, n;
+        char *buf;
+
+        fd = open(name, OREAD);
+        if(fd < 0)
+                return 0;
+
+        n = flength(fd);
+        buf = malloc(n+1);
+        if(buf == 0){
+                close(fd);
+                return 0;
+        }
+        buf[n] = 0;
+        i = read(fd, buf, n);
+        close(fd);
+        if(i != n){
+                free(buf);
+                return 0;
+        }
+        fnt = buildfont(d, buf, name);
+        free(buf);
+        return fnt;
+}
diff --git a/src/libdraw/readcolmap.c b/src/libdraw/readcolmap.c
t@@ -0,0 +1,49 @@
+#include 
+#include 
+#include 
+#include 
+
+static ulong
+getval(char **p)
+{
+        ulong v;
+        char *q;
+
+        v = strtoul(*p, &q, 0);
+        v |= v<<8;
+        v |= v<<16;
+        *p = q;
+        return v;
+}
+
+void
+readcolmap(Display *d, RGB *colmap)
+{
+        int i;
+        char *p, *q;
+        Biobuf *b;
+        char buf[128];
+
+        USED(screen);
+
+        sprint(buf, "/dev/draw/%d/colormap", d->dirno);
+        b = Bopen(buf, OREAD);
+        if(b == 0)
+                drawerror(d, "rdcolmap: can't open colormap device");
+
+        for(;;) {
+                p = Brdline(b, '\n');
+                if(p == 0)
+                        break;
+                i = strtoul(p, &q, 0);
+                if(i < 0 || i > 255) {
+                        fprint(2, "rdcolmap: bad index\n");
+                        exits("bad");
+                }
+                p = q;
+                colmap[255-i].red = getval(&p);
+                colmap[255-i].green = getval(&p);
+                colmap[255-i].blue = getval(&p);
+        }
+        Bterm(b);
+}
diff --git a/src/libdraw/readimage.c b/src/libdraw/readimage.c
t@@ -0,0 +1,118 @@
+#include 
+#include 
+#include 
+
+Image*
+readimage(Display *d, int fd, int dolock)
+{
+        char hdr[5*12+1];
+        int dy;
+        int new;
+        uint l, n;
+        int m, j, chunk;
+        int miny, maxy;
+        Rectangle r;
+        int ldepth;
+        u32int chan;
+        uchar *tmp;
+        Image *i;
+
+        if(readn(fd, hdr, 11) != 11)
+                return nil;
+        if(memcmp(hdr, "compressed\n", 11) == 0)
+                return creadimage(d, fd, dolock);
+        if(readn(fd, hdr+11, 5*12-11) != 5*12-11)
+                return nil;
+        chunk = d->bufsize - 32;        /* a little room for header */
+
+        /*
+         * 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));
+        if(dolock)
+                lockdisplay(d);
+        i = allocimage(d, r, chan, 0, -1);
+        if(dolock)
+                unlockdisplay(d);
+        if(i == nil)
+                return nil;
+        tmp = malloc(chunk);
+        if(tmp == nil)
+                goto Err;
+        while(maxy > miny){
+                dy = maxy - miny;
+                if(dy*l > chunk)
+                        dy = chunk/l;
+                if(dy <= 0){
+                        werrstr("readimage: image too wide for buffer");
+                        goto Err;
+                }
+                n = dy*l;
+                m = readn(fd, tmp, n);
+                if(m != n){
+                        werrstr("readimage: read count %d not %d: %r", m, n);
+   Err:
+                        if(dolock)
+                                lockdisplay(d);
+   Err1:
+                         freeimage(i);
+                        if(dolock)
+                                unlockdisplay(d);
+                        free(tmp);
+                        return nil;
+                }
+                if(!new)        /* an old image: must flip all the bits */
+                        for(j=0; j
diff --git a/src/libdraw/readsubfont.c b/src/libdraw/readsubfont.c
t@@ -0,0 +1,58 @@
+#include 
+#include 
+#include 
+
+Subfont*
+readsubfonti(Display*d, char *name, int fd, Image *ai, int dolock)
+{
+        char hdr[3*12+4+1];
+        int n;
+        uchar *p;
+        Fontchar *fc;
+        Subfont *f;
+        Image *i;
+
+        i = ai;
+        if(i == nil){
+                i = readimage(d, fd, dolock);
+                if(i == nil)
+                        return nil;
+        }
+        if(read(fd, hdr, 3*12) != 3*12){
+                if(ai == nil)
+                        freeimage(i);
+                werrstr("rdsubfonfile: header read error: %r");
+                return nil;
+        }
+        n = atoi(hdr);
+        p = malloc(6*(n+1));
+        if(p == nil)
+                return nil;
+        if(read(fd, p, 6*(n+1)) != 6*(n+1)){
+                werrstr("rdsubfonfile: fontchar read error: %r");
+    Err:
+                free(p);
+                return nil;
+        }
+        fc = malloc(sizeof(Fontchar)*(n+1));
+        if(fc == nil)
+                goto Err;
+        _unpackinfo(fc, p, n);
+        if(dolock)
+                lockdisplay(d);
+        f = allocsubfont(name, n, atoi(hdr+12), atoi(hdr+24), fc, i);
+        if(dolock)
+                unlockdisplay(d);
+        if(f == nil){
+                free(fc);
+                goto Err;
+        }
+        free(p);
+        return f;
+}
+
+Subfont*
+readsubfont(Display*d, char *name, int fd, int dolock)
+{
+        return readsubfonti(d, name, fd, nil, dolock);
+}
diff --git a/src/libdraw/string.c b/src/libdraw/string.c
t@@ -0,0 +1,137 @@
+#include 
+#include 
+#include 
+
+enum
+{
+        Max = 100
+};
+
+Point
+string(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s)
+{
+        return _string(dst, pt, src, sp, f, s, nil, 1<<24, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+stringop(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, Drawop op)
+{
+        return _string(dst, pt, src, sp, f, s, nil, 1<<24, dst->clipr, nil, ZP, op);
+}
+
+Point
+stringn(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, int len)
+{
+        return _string(dst, pt, src, sp, f, s, nil, len, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+stringnop(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, int len, Drawop op)
+{
+        return _string(dst, pt, src, sp, f, s, nil, len, dst->clipr, nil, ZP, op);
+}
+
+Point
+runestring(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r)
+{
+        return _string(dst, pt, src, sp, f, nil, r, 1<<24, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+runestringop(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, Drawop op)
+{
+        return _string(dst, pt, src, sp, f, nil, r, 1<<24, dst->clipr, nil, ZP, op);
+}
+
+Point
+runestringn(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, int len)
+{
+        return _string(dst, pt, src, sp, f, nil, r, len, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+runestringnop(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, int len, Drawop op)
+{
+        return _string(dst, pt, src, sp, f, nil, r, len, dst->clipr, nil, ZP, op);
+}
+
+Point
+_string(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, Rune *r, int len, Rectangle clipr, Image *bg, Point bgp, Drawop op)
+{
+        int m, n, wid, max;
+        ushort cbuf[Max], *c, *ec;
+        uchar *b;
+        char *subfontname;
+        char **sptr;
+        Rune **rptr;
+        Font *def;
+
+        if(s == nil){
+                s = "";
+                sptr = nil;
+        }else
+                sptr = &s;
+        if(r == nil){
+                r = (Rune*) L"";
+                rptr = nil;
+        }else
+                rptr = &r;
+        while((*s || *r) && len){
+                max = Max;
+                if(len < max)
+                        max = len;
+                n = cachechars(f, sptr, rptr, cbuf, max, &wid, &subfontname);
+                if(n > 0){
+                        _setdrawop(dst->display, op);
+
+                        m = 47+2*n;
+                        if(bg)
+                                m += 4+2*4;
+                        b = bufimage(dst->display, m);
+                        if(b == 0){
+                                fprint(2, "string: %r\n");
+                                break;
+                        }
+                        if(bg)
+                                b[0] = 'x';
+                        else
+                                b[0] = 's';
+                        BPLONG(b+1, dst->id);
+                        BPLONG(b+5, src->id);
+                        BPLONG(b+9, f->cacheimage->id);
+                        BPLONG(b+13, pt.x);
+                        BPLONG(b+17, pt.y+f->ascent);
+                        BPLONG(b+21, clipr.min.x);
+                        BPLONG(b+25, clipr.min.y);
+                        BPLONG(b+29, clipr.max.x);
+                        BPLONG(b+33, clipr.max.y);
+                        BPLONG(b+37, sp.x);
+                        BPLONG(b+41, sp.y);
+                        BPSHORT(b+45, n);
+                        b += 47;
+                        if(bg){
+                                BPLONG(b, bg->id);
+                                BPLONG(b+4, bgp.x);
+                                BPLONG(b+8, bgp.y);
+                                b += 12;
+                        }
+                        ec = &cbuf[n];
+                        for(c=cbuf; cdisplay, subfontname) == 0){
+                                def = f->display->defaultfont;
+                                if(def && f!=def)
+                                        f = def;
+                                else
+                                        break;
+                        }
+                }
+        }
+        return pt;
+}
diff --git a/src/libdraw/stringwidth.c b/src/libdraw/stringwidth.c
t@@ -0,0 +1,97 @@
+#include 
+#include 
+#include 
+
+static Rune empty[] = { 0 };
+int
+_stringnwidth(Font *f, char *s, Rune *r, int len)
+{
+        int wid, twid, n, max, l;
+        char *name;
+        enum { Max = 64 };
+        ushort cbuf[Max];
+        Rune rune, **rptr;
+        char *subfontname, **sptr;
+        Font *def;
+
+        if(s == nil){
+                s = "";
+                sptr = nil;
+        }else
+                sptr = &s;
+        if(r == nil){
+                r = empty;
+                rptr = nil;
+        }else
+                rptr = &r;
+        twid = 0;
+        while((*s || *r) && len){
+                max = Max;
+                if(len < max)
+                        max = len;
+                n = 0;
+                while((l = cachechars(f, sptr, rptr, cbuf, max, &wid, &subfontname)) <= 0){
+                        if(++n > 10){
+                                if(*r)
+                                        rune = *r;
+                                else
+                                        chartorune(&rune, s);
+                                if(f->name != nil)
+                                        name = f->name;
+                                else
+                                        name = "unnamed font";
+                                fprint(2, "stringwidth: bad character set for rune 0x%.4ux in %s\n", rune, name);
+                                return twid;
+                        }
+                        if(subfontname){
+                                if(_getsubfont(f->display, subfontname) == 0){
+                                        def = f->display->defaultfont;
+                                        if(def && f!=def)
+                                                f = def;
+                                        else
+                                                break;
+                                }
+                        }
+                }
+                agefont(f);
+                twid += wid;
+                len -= l;
+        }
+        return twid;
+}
+
+int
+stringnwidth(Font *f, char *s, int len)
+{
+        return _stringnwidth(f, s, nil, len);
+}
+
+int
+stringwidth(Font *f, char *s)
+{
+        return _stringnwidth(f, s, nil, 1<<24);
+}
+
+Point
+stringsize(Font *f, char *s)
+{
+        return Pt(_stringnwidth(f, s, nil, 1<<24), f->height);
+}
+
+int
+runestringnwidth(Font *f, Rune *r, int len)
+{
+        return _stringnwidth(f, nil, r, len);
+}
+
+int
+runestringwidth(Font *f, Rune *r)
+{
+        return _stringnwidth(f, nil, r, 1<<24);
+}
+
+Point
+runestringsize(Font *f, Rune *r)
+{
+        return Pt(_stringnwidth(f, nil, r, 1<<24), f->height);
+}
diff --git a/src/libdraw/subfont.c b/src/libdraw/subfont.c
t@@ -0,0 +1,28 @@
+#include 
+#include 
+#include 
+
+Subfont*
+allocsubfont(char *name, int n, int height, int ascent, Fontchar *info, Image *i)
+{
+        Subfont *f;
+
+        assert(height != 0 /* allocsubfont */);
+
+        f = malloc(sizeof(Subfont));
+        if(f == 0)
+                return 0;
+        f->n = n;
+        f->height = height;
+        f->ascent = ascent;
+        f->info = info;
+        f->bits = i;
+        f->ref = 1;
+        if(name){
+                f->name = strdup(name);
+                if(lookupsubfont(i->display, name) == 0)
+                        installsubfont(name, f);
+        }else
+                f->name = 0;
+        return f;
+}
diff --git a/src/libdraw/subfontcache.c b/src/libdraw/subfontcache.c
t@@ -0,0 +1,39 @@
+#include 
+#include 
+#include 
+
+/*
+ * Easy versions of the cache routines; may be substituted by fancier ones for other purposes
+ */
+
+static char        *lastname;
+Subfont        *lastsubfont;
+
+Subfont*
+lookupsubfont(Display *d, char *name)
+{
+        if(strcmp(name, "*default*") == 0)
+                return d->defaultsubfont;
+        if(lastname && strcmp(name, lastname)==0 && d==lastsubfont->bits->display){
+                lastsubfont->ref++;
+                return lastsubfont;
+        }
+        return 0;
+}
+
+void
+installsubfont(char *name, Subfont *subfont)
+{
+        free(lastname);
+        lastname = strdup(name);
+        lastsubfont = subfont;        /* notice we don't free the old one; that's your business */
+}
+
+void
+uninstallsubfont(Subfont *subfont)
+{
+        if(subfont == lastsubfont){
+                lastname = 0;
+                lastsubfont = 0;
+        }
+}
diff --git a/src/libdraw/subfontname.c b/src/libdraw/subfontname.c
t@@ -0,0 +1,44 @@
+#include 
+#include 
+#include 
+
+/*
+ * Default version: convert to file name
+ */
+
+char*
+subfontname(char *cfname, char *fname, int maxdepth)
+{
+        char *t, *u, tmp1[64], tmp2[64];
+        int i;
+
+        if(strcmp(cfname, "*default*") == 0)
+                return strdup(cfname);
+        t = cfname;
+        if(t[0] != '/'){
+                snprint(tmp2, sizeof tmp2, "%s", fname);
+                u = utfrrune(tmp2, '/');
+                if(u)
+                        u[0] = 0;
+                else
+                        strcpy(tmp2, ".");
+                snprint(tmp1, sizeof tmp1, "%s/%s", tmp2, t);
+                t = tmp1;
+        }
+
+        if(maxdepth > 8)
+                maxdepth = 8;
+
+        for(i=log2[maxdepth]; i>=0; i--){
+                /* try i-bit grey */
+                snprint(tmp2, sizeof tmp2, "%s.%d", t, i);
+                if(access(tmp2, AREAD) == 0)
+                        return strdup(tmp2);
+        }
+
+        /* try default */
+        if(access(t, AREAD) == 0)
+                return strdup(t);
+
+        return nil;
+}
diff --git a/src/libdraw/unix.c b/src/libdraw/unix.c
t@@ -0,0 +1,16 @@
+#include 
+
+#include 
+#include 
+#include 
+
+vlong
+flength(int fd)
+{
+        struct stat s;
+
+        if(fstat(fd, &s) < 0)
+                return -1;
+        return s.st_size;
+}
+
diff --git a/src/libdraw/unloadimage.c b/src/libdraw/unloadimage.c
t@@ -0,0 +1,53 @@
+#include 
+#include 
+#include 
+
+int
+unloadimage(Image *i, Rectangle r, uchar *data, int ndata)
+{
+        int bpl, n, ntot, dy;
+        uchar *a;
+        Display *d;
+
+        if(!rectinrect(r, i->r)){
+                werrstr("unloadimage: bad rectangle");
+                return -1;
+        }
+        bpl = bytesperline(r, i->depth);
+        if(ndata < bpl*Dy(r)){
+                werrstr("unloadimage: buffer too small");
+                return -1;
+        }
+
+        d = i->display;
+        flushimage(d, 0);        /* make sure subsequent flush is for us only */
+        ntot = 0;
+        while(r.min.y < r.max.y){
+                a = bufimage(d, 1+4+4*4);
+                if(a == 0){
+                        werrstr("unloadimage: %r");
+                        return -1;
+                }
+                dy = 8000/bpl;
+                if(dy <= 0){
+                        werrstr("unloadimage: image too wide");
+                        return -1;
+                }
+                if(dy > Dy(r))
+                        dy = Dy(r);
+                a[0] = 'r';
+                BPLONG(a+1, i->id);
+                BPLONG(a+5, r.min.x);
+                BPLONG(a+9, r.min.y);
+                BPLONG(a+13, r.max.x);
+                BPLONG(a+17, r.min.y+dy);
+                if(flushimage(d, 0) < 0)
+                        return -1;
+                n = _drawmsgread(d, data+ntot, ndata-ntot);
+                if(n < 0)
+                        return n;
+                ntot += n;
+                r.min.y += dy;
+        }
+        return ntot;
+}
diff --git a/src/libdraw/writecolmap.c b/src/libdraw/writecolmap.c
t@@ -0,0 +1,35 @@
+#include 
+#include 
+#include 
+
+/*
+ * This code (and the devdraw interface) will have to change
+ * if we ever get bitmaps with ldepth > 3, because the
+ * colormap will have to be written in chunks
+ */
+
+void
+writecolmap(Display *d, RGB *m)
+{
+        int i, n, fd;
+        char buf[64], *t;
+        ulong r, g, b;
+
+        sprint(buf, "/dev/draw/%d/colormap", d->dirno);
+        fd = open(buf, OWRITE);
+        if(fd < 0)
+                drawerror(d, "wrcolmap: open colormap failed");
+        t = malloc(8192);
+        n = 0;
+        for(i = 0; i < 256; i++) {
+                r = m[i].red>>24;
+                g = m[i].green>>24;
+                b = m[i].blue>>24;
+                n += sprint(t+n, "%d %lud %lud %lud\n", 255-i, r, g, b);
+        }
+        i = write(fd, t, n);
+        free(t);
+        close(fd);
+        if(i != n)
+                drawerror(d, "wrcolmap: bad write");
+}
diff --git a/src/libdraw/x11-alloc.c b/src/libdraw/x11-alloc.c
t@@ -0,0 +1,120 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+/*
+ * Allocate a Memimage with an optional pixmap backing on the X server.
+ */
+Memimage*
+xallocmemimage(Rectangle r, u32int chan, int pixmap)
+{
+        int d, offset;
+        Memimage *m;
+        Xmem *xm;
+        XImage *xi;
+
+        m = _allocmemimage(r, chan);
+        if(chan != GREY1 && chan != _x.chan)
+                return m;
+
+        /*
+         * For bootstrapping, don't bother storing 1x1 images
+         * on the X server.  Memimageinit needs to allocate these
+         * and we memimageinit before we do the rest of the X stuff.
+         * Of course, 1x1 images on the server are useless anyway.
+         */
+        if(Dx(r)==1 && Dy(r)==1)
+                return m;
+
+        xm = mallocz(sizeof(Xmem), 1);
+        if(xm == nil){
+                freememimage(m);
+                return nil;
+        }
+
+        /*
+         * Allocate backing store.  What we call a 32-bit image
+         * the X server calls a 24-bit image.
+         */
+        d = m->depth;
+        if(pixmap != PMundef)
+                xm->pixmap = pixmap;
+        else
+                xm->pixmap = XCreatePixmap(_x.display, _x.drawable,
+                        Dx(r), Dy(r), d==32 ? 24 : d);
+
+        /*
+         * We want to align pixels on word boundaries.
+         */
+        if(d == 24)
+                offset = r.min.x&3;
+        else
+                offset = r.min.x&(31/m->depth);
+        r.min.x -= offset;
+        assert(wordsperline(r, m->depth) <= m->width);
+
+        /*
+         * Wrap our data in an XImage structure.
+         */
+        xi = XCreateImage(_x.display, _x.vis, d==32 ? 24 : d,
+                ZPixmap, 0, (char*)m->data->bdata, Dx(r), Dy(r),
+                32, m->width*sizeof(u32int));
+        if(xi == nil){
+                freememimage(m);
+                if(xm->pixmap != pixmap)
+                        XFreePixmap(_x.display, xm->pixmap);
+                return nil;
+        }
+
+        xm->xi = xi;
+        xm->r = r;
+
+        /*
+         * Set the XImage parameters so that it looks exactly like
+         * a Memimage -- we're using the same data.
+         */
+        if(m->depth < 8 || m->depth == 24)
+                xi->bitmap_unit = 8;
+        else
+                xi->bitmap_unit = m->depth;
+        xi->byte_order = LSBFirst;
+        xi->bitmap_bit_order = MSBFirst;
+        xi->bitmap_pad = 32;
+        XInitImage(xi);
+        XFlush(_x.display);
+
+        m->X = xm;
+        return m;
+}
+
+Memimage*
+allocmemimage(Rectangle r, u32int chan)
+{
+        return xallocmemimage(r, chan, PMundef);
+}
+
+void
+freememimage(Memimage *m)
+{
+        Xmem *xm;
+
+        if(m == nil)
+                return;
+
+        xm = m->X;
+        if(xm && m->data->ref == 1){
+                if(xm->xi){
+                        xm->xi->data = nil;
+                        XFree(xm->xi);
+                }
+                XFreePixmap(_x.display, xm->pixmap);
+                free(xm);
+                m->X = nil;
+        }
+        _freememimage(m);
+}
+
diff --git a/src/libdraw/x11-cload.c b/src/libdraw/x11-cload.c
t@@ -0,0 +1,19 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+int
+cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+        int n;
+
+        n = _cloadmemimage(i, r, data, ndata);
+        if(n > 0 && i->X)
+                xputxdata(i, r);
+        return n;
+}
+
diff --git a/src/libdraw/x11-draw.c b/src/libdraw/x11-draw.c
t@@ -0,0 +1,143 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+static int xdraw(Memdrawparam*);
+
+/*
+ * The X acceleration doesn't fit into the standard hwaccel
+ * model because we have the extra steps of pulling the image
+ * data off the server and putting it back when we're done.
+ */
+void
+memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point sp,
+        Memimage *mask, Point mp, int op)
+{
+        Memdrawparam *par;
+
+        if((par = _memimagedrawsetup(dst, r, src, sp, mask, mp, op)) == nil)
+                return;
+
+        if(xdraw(par))
+                return;
+
+        /* only fetch dst data if we need it */
+        if((par->state&(Simplemask|Fullmask)) != (Simplemask|Fullmask))
+                xgetxdata(dst, par->r);
+
+        /* always fetch source and mask */
+        xgetxdata(src, par->sr);
+        xgetxdata(mask, par->mr);
+
+        /* now can run memimagedraw on the in-memory bits */
+        _memimagedraw(par);
+
+        /* put bits back on x server */
+        xputxdata(dst, par->r);
+}
+
+static int
+xdraw(Memdrawparam *par)
+{
+        u32int sdval;
+        uint m, state;
+        Memimage *src, *dst, *mask;
+        Point dp, mp, sp;
+        Rectangle r;
+        Xmem *xdst, *xmask, *xsrc;
+        XGC gc;
+
+        if(par->dst->X == nil)
+                return 0;
+
+        dst   = par->dst;
+        mask  = par->mask;
+        r     = par->r;
+        src   = par->src;
+        state = par->state;
+
+        /*
+         * If we have an opaque mask and source is one opaque pixel,
+         * we can convert to the destination format and just XFillRectangle.
+         */
+        m = Simplesrc|Simplemask|Fullmask;
+        if((state&m) == m){
+                xfillcolor(dst, r, par->sdval);
+                xdirtyxdata(dst, r);
+                return 1;
+        }
+
+        /*
+         * If no source alpha and an opaque mask, we can just copy
+         * the source onto the destination.  If the channels are the
+         * same and the source is not replicated, XCopyArea works.
+         */
+        m = Simplemask|Fullmask;
+        if((state&(m|Replsrc))==m && src->chan==dst->chan && src->X){
+                xdst = dst->X;
+                xsrc = src->X;
+                dp = subpt(r.min,       dst->r.min);
+                sp = subpt(par->sr.min, src->r.min);
+                gc = dst->chan==GREY1 ?  _x.gccopy0 : _x.gccopy;
+
+                XCopyArea(_x.display, xsrc->pixmap, xdst->pixmap, gc,
+                        sp.x, sp.y, Dx(r), Dy(r), dp.x, dp.y);
+                xdirtyxdata(dst, r);
+                return 1;
+        }
+
+        /*
+         * If no source alpha, a 1-bit mask, and a simple source,
+         * we can copy through the mask onto the destination.
+         */
+        if(dst->X && mask->X && !(mask->flags&Frepl)
+        && mask->chan==GREY1 && (state&Simplesrc)){
+                xdst = dst->X;
+                xmask = mask->X;
+                sdval = par->sdval;
+
+                dp = subpt(r.min, dst->r.min);
+                mp = subpt(r.min, subpt(par->mr.min, mask->r.min));
+
+                if(dst->chan == GREY1){
+                        gc = _x.gcsimplesrc0;
+                        if(_x.gcsimplesrc0color != sdval){
+                                XSetForeground(_x.display, gc, sdval);
+                                _x.gcsimplesrc0color = sdval;
+                        }
+                        if(_x.gcsimplesrc0pixmap != xmask->pixmap){
+                                XSetStipple(_x.display, gc, xmask->pixmap);
+                                _x.gcsimplesrc0pixmap = xmask->pixmap;
+                        }
+                }else{
+                        /* this doesn't work on rob's mac?  */
+                        gc = _x.gcsimplesrc;
+                        if(dst->chan == CMAP8 && _x.usetable)
+                                sdval = _x.tox11[sdval];
+
+                        if(_x.gcsimplesrccolor != sdval){
+                                XSetForeground(_x.display, gc, sdval);
+                                _x.gcsimplesrccolor = sdval;
+                        }
+                        if(_x.gcsimplesrcpixmap != xmask->pixmap){
+                                XSetStipple(_x.display, gc, xmask->pixmap);
+                                _x.gcsimplesrcpixmap = xmask->pixmap;
+                        }
+                }
+                XSetTSOrigin(_x.display, gc, mp.x, mp.y);
+                XFillRectangle(_x.display, xdst->pixmap, gc, dp.x, dp.y,
+                        Dx(r), Dy(r));
+                xdirtyxdata(dst, r);
+                return 1;
+        }
+
+        /*
+         * Can't accelerate.
+         */
+        return 0;
+}
+
diff --git a/src/libdraw/x11-event.c b/src/libdraw/x11-event.c
t@@ -0,0 +1,136 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include "x11-memdraw.h"
+
+ulong
+event(Event *e)
+{
+        return eread(~0UL, e);
+}
+
+ulong
+eread(ulong keys, Event *e)
+{
+        ulong xmask;
+        XEvent xevent;
+
+        xmask = ExposureMask;
+
+        if(keys&Emouse)
+                xmask |= MouseMask|StructureNotifyMask;
+        if(keys&Ekeyboard)
+                xmask |= KeyPressMask;
+
+        XSelectInput(_x.display, _x.drawable, xmask);
+again:
+        XWindowEvent(_x.display, _x.drawable, xmask, &xevent);
+
+        switch(xevent.type){
+        case Expose:
+                xexpose(&xevent, _x.display);
+                goto again;
+        case ConfigureNotify:
+                if(xconfigure(&xevent, _x.display))
+                        eresized(1);
+                goto again;
+        case ButtonPress:
+        case ButtonRelease:
+        case MotionNotify:
+                if(xtoplan9mouse(&xevent, &e->mouse) < 0)
+                        goto again;
+                return Emouse;
+        case KeyPress:
+                e->kbdc = xtoplan9kbd(&xevent);
+                if(e->kbdc == -1)
+                        goto again;
+                return Ekeyboard;
+        default:
+                return 0;
+        }
+}
+
+void
+einit(ulong keys)
+{
+        keys &= ~(Emouse|Ekeyboard);
+        if(keys){
+                fprint(2, "unknown keys in einit\n");
+                abort();
+        }
+}
+
+int
+ekbd(void)
+{
+        Event e;
+
+        eread(Ekeyboard, &e);
+        return e.kbdc;
+}
+
+Mouse
+emouse(void)
+{
+        Event e;
+
+        eread(Emouse, &e);
+        return e.mouse;
+}
+
+int
+ecanread(ulong keys)
+{
+        int can;
+
+        can = 0;
+        if(keys&Emouse)
+                can |= ecanmouse();
+        if(keys&Ekeyboard)
+                can |= ecankbd();
+        return can;
+}
+
+int
+ecanmouse(void)
+{
+        XEvent xe;
+        Mouse m;
+
+again:
+        if(XCheckWindowEvent(_x.display, _x.drawable, MouseMask, &xe)){
+                if(xtoplan9mouse(&xe, &m) < 0)
+                        goto again;
+                XPutBackEvent(_x.display, &xe);
+                return 1;
+        }
+        return 0;
+}
+
+int
+ecankbd(void)
+{
+        XEvent xe;
+
+again:
+        if(XCheckWindowEvent(_x.display, _x.drawable, KeyPressMask, &xe)){
+                if(xtoplan9kbd(&xe) == -1)
+                        goto again;
+                XPutBackEvent(_x.display, &xe);
+                return 1;
+        }
+        return 0;
+}
+
+void
+emoveto(Point p)
+{
+        xmoveto(p);
+}
+
diff --git a/src/libdraw/x11-fill.c b/src/libdraw/x11-fill.c
t@@ -0,0 +1,57 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+void
+memfillcolor(Memimage *m, u32int val)
+{
+        _memfillcolor(m, val);
+        if(m->X == nil)
+                return;
+        if((val & 0xFF) == 0xFF)        /* full alpha */
+                xfillcolor(m, m->r, _rgbatoimg(m, val));
+        else
+                xputxdata(m, m->r);
+}
+
+void
+xfillcolor(Memimage *m, Rectangle r, u32int v)
+{
+        Point p;
+        Xmem *xm;
+        XGC gc;
+        
+        xm = m->X;
+        assert(xm != nil);
+
+        /*
+         * Set up fill context appropriately.
+         */
+        if(m->chan == GREY1){
+                gc = _x.gcfill0;
+                if(_x.gcfill0color != v){
+                        XSetForeground(_x.display, gc, v);
+                        _x.gcfill0color = v;
+                }
+        }else{
+                if(m->chan == CMAP8 && _x.usetable)
+                        v = _x.tox11[v];
+                gc = _x.gcfill;
+                if(_x.gcfillcolor != v){
+                        XSetForeground(_x.display, gc, v);
+                        _x.gcfillcolor = v;
+                }
+        }
+
+        /*
+         * XFillRectangle takes coordinates relative to image rectangle.
+         */
+        p = subpt(r.min, m->r.min);
+        XFillRectangle(_x.display, xm->pixmap, gc, p.x, p.y, Dx(r), Dy(r));
+}
+
+
diff --git a/src/libdraw/x11-get.c b/src/libdraw/x11-get.c
t@@ -0,0 +1,110 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+static void
+addrect(Rectangle *rp, Rectangle r)
+{
+        if(rp->min.x >= rp->max.x)
+                *rp = r;
+        else
+                combinerect(rp, r);
+}
+
+XImage*
+xgetxdata(Memimage *m, Rectangle r)
+{
+        int x, y;
+        uchar *p;
+        Point tp, xdelta, delta;
+        Xmem *xm;
+        
+        xm = m->X;
+        if(xm == nil)
+                return nil;
+
+        if(xm->dirty == 0)
+                return xm->xi;
+
+        r = xm->dirtyr;
+        if(Dx(r)==0 || Dy(r)==0)
+                return xm->xi;
+
+        delta = subpt(r.min, m->r.min);
+
+        tp = xm->r.min;        /* need temp for Digital UNIX */
+        xdelta = subpt(r.min, tp);
+
+        XGetSubImage(_x.display, xm->pixmap, delta.x, delta.y, Dx(r), Dy(r),
+                AllPlanes, ZPixmap, xm->xi, xdelta.x, delta.y);
+
+        if(_x.usetable && m->chan==CMAP8){
+                for(y=r.min.y; ydirty = 0;
+        xm->dirtyr = Rect(0,0,0,0);
+        return xm->xi;
+}
+
+void
+xputxdata(Memimage *m, Rectangle r)
+{
+        int offset, x, y;
+        uchar *p;
+        Point tp, xdelta, delta;
+        Xmem *xm;
+        XGC gc;
+        XImage *xi;
+
+        xm = m->X;
+        if(xm == nil)
+                return;
+
+        xi = xm->xi;
+        gc = m->chan==GREY1 ? _x.gccopy0 : _x.gccopy;
+        if(m->depth == 24)
+                offset = r.min.x & 3;
+        else
+                offset = r.min.x & (31/m->depth);
+
+        delta = subpt(r.min, m->r.min);
+
+        tp = xm->r.min;        /* need temporary on Digital UNIX */
+        xdelta = subpt(r.min, tp);
+
+        if(_x.usetable && m->chan==CMAP8){
+                for(y=r.min.y; ypixmap, gc, xi, xdelta.x, xdelta.y, delta.x, delta.y,
+                Dx(r), Dy(r));
+        
+        if(_x.usetable && m->chan==CMAP8){
+                for(y=r.min.y; yX;
+        if(xm == nil)
+                return;
+        xm->dirty = 1;
+        addrect(&xm->dirtyr, r);
+}
+
+
+
diff --git a/src/libdraw/x11-inc.h b/src/libdraw/x11-inc.h
t@@ -0,0 +1,31 @@
+#define Colormap        XColormap
+#define Cursor                XCursor
+#define Display                XDisplay
+#define Drawable        XDrawable
+#define Font                XFont
+#define GC                XGC
+#define Point                XPoint
+#define Rectangle        XRectangle
+#define Screen                XScreen
+#define Visual                XVisual
+#define Window                XWindow
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#undef Colormap
+#undef Cursor
+#undef Display
+#undef Drawable
+#undef Font
+#undef GC
+#undef Point
+#undef Rectangle
+#undef Screen
+#undef Visual
+#undef Window
+
diff --git a/src/libdraw/x11-init.c b/src/libdraw/x11-init.c
t@@ -0,0 +1,584 @@
+/*
+ * Some of the stuff in this file is not X-dependent and should be elsewhere.
+ */
+#include "x11-inc.h"
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+static Memimage        *xattach(char*);
+static void        plan9cmap(void);
+static int        setupcmap(XWindow);
+static XGC        xgc(XDrawable, int, int);
+static Image        *getimage0(Display*);
+
+Xprivate _x;
+
+Display*
+_initdisplay(void (*error)(Display*, char*), char *label)
+{
+        Display *d;
+        Memimage *m;
+
+        memimageinit();
+
+        d = mallocz(sizeof(Display), 1);
+        if(d == nil)
+                return nil;
+
+        d->buf = malloc(16000+5);
+        d->obuf = malloc(16000);
+        if(d->buf == nil || d->obuf == nil){
+                free(d->buf);
+                free(d->obuf);
+                free(d);
+                return nil;
+        }
+        d->bufsize = 16000;
+        d->obufsize = 16000;
+        d->bufp = d->buf;
+        d->obufp = d->obuf;
+
+        m = xattach(label);
+        if(m == nil){
+                free(d);
+                return nil;
+        }
+
+        d->error = error;
+        _initdisplaymemimage(d, m);
+        d->screenimage = getimage0(d);
+        return d;
+}
+
+static Image*
+getimage0(Display *d)
+{
+        char *a, info[12*12+1];
+        int n;
+        Image *image;
+
+        a = bufimage(d, 2);
+        a[0] = 'J';
+        a[1] = 'I';
+        if(flushimage(d, 0) < 0){
+                fprint(2, "cannot read screen info: %r\n");
+                abort();
+        }
+        
+        n = _drawmsgread(d, info, sizeof info);
+        if(n != 12*12){
+                fprint(2, "short screen info\n");
+                abort();
+        }
+
+        image = mallocz(sizeof(Image), 1);
+        image->display = d;
+        image->id = 0;
+        image->chan = strtochan(info+2*12);
+        image->depth = chantodepth(image->chan);
+        image->repl = atoi(info+3*12);
+        image->r.min.x = atoi(info+4*12);
+        image->r.min.y = atoi(info+5*12);
+        image->r.max.x = atoi(info+6*12);
+        image->r.max.y = atoi(info+7*12);
+        image->clipr.min.x = atoi(info+8*12);
+        image->clipr.min.y = atoi(info+9*12);
+        image->clipr.max.x = atoi(info+10*12);
+        image->clipr.max.y = atoi(info+11*12);
+        return image;
+}
+
+int
+getwindow(Display *d, int ref)
+{
+        Image *i;
+
+        freeimage(d->screenimage);
+        i = getimage0(d);
+        screen = d->screenimage = d->image = i;
+        return 0;
+}
+
+static int
+xerror(XDisplay *d, XErrorEvent *e)
+{
+        char buf[200];
+
+        print("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);
+        print("%s\n", buf);
+        return 0;
+}
+
+static int
+xioerror(XDisplay *d)
+{
+        print("X I/O error\n");
+        exit(1);
+        return -1;
+}
+
+
+static Memimage*
+xattach(char *label)
+{
+        char *argv[2], *disp;
+        int i, n, xrootid;
+        Rectangle r;
+        XClassHint classhint;
+        XDrawable pmid;
+        XPixmapFormatValues *pfmt;
+        XScreen *xscreen;
+        XSetWindowAttributes attr;
+        XSizeHints normalhint;
+        XTextProperty name;
+        XVisualInfo xvi;
+        XWindow xrootwin;
+        XWMHints hint;
+
+        /*
+         * Connect to X server.
+         */
+        _x.display = XOpenDisplay(NULL);
+        if(_x.display == nil){
+                disp = getenv("DISPLAY");
+                werrstr("XOpenDisplay %s: %r", disp ? disp : ":0");
+                free(disp);
+                return nil;
+        }
+        XSetErrorHandler(xerror);
+        XSetIOErrorHandler(xioerror);
+        xrootid = DefaultScreen(_x.display);
+        xrootwin = DefaultRootWindow(_x.display);
+
+        /* 
+         * Figure out underlying screen format.
+         */
+        _x.depth = DefaultDepth(_x.display, xrootid);
+        if(XMatchVisualInfo(_x.display, xrootid, 16, TrueColor, &xvi)
+        || XMatchVisualInfo(_x.display, xrootid, 16, DirectColor, &xvi)){
+                _x.vis = xvi.visual;
+                _x.depth = 16;
+                _x.usetable = 1;
+        }
+        else
+        if(XMatchVisualInfo(_x.display, xrootid, 24, TrueColor, &xvi)
+        || XMatchVisualInfo(_x.display, xrootid, 24, DirectColor, &xvi)){
+                _x.vis = xvi.visual;
+                _x.depth = 24;
+                _x.usetable = 1;
+        }
+        else
+        if(XMatchVisualInfo(_x.display, xrootid, 8, PseudoColor, &xvi)
+        || XMatchVisualInfo(_x.display, xrootid, 8, StaticColor, &xvi)){
+                if(_x.depth > 8){
+                        werrstr("can't deal with colormapped depth %d screens",
+                                _x.depth);
+                        goto err0;
+                }
+                _x.vis = xvi.visual;
+                _x.depth = 8;
+        }
+        else{
+                if(_x.depth != 8){
+                        werrstr("can't understand depth %d screen", _x.depth);
+                        goto err0;
+                }
+                _x.vis = DefaultVisual(_x.display, xrootid);
+        }
+
+        /*
+         * _x.depth is only the number of significant pixel bits,
+         * not the total number of pixel bits.  We need to walk the
+         * display list to find how many actual bits are used
+         * per pixel.
+         */
+        _x.chan = 0;
+        pfmt = XListPixmapFormats(_x.display, &n);
+        for(i=0; iclass != StaticColor){
+                plan9cmap();
+                setupcmap(xrootwin);
+        }
+
+        /*
+         * We get to choose the initial rectangle size.
+         * This is arbitrary.  In theory we should read the
+         * command line and allow the traditional X options.
+         */
+        r = Rect(0, 0, WidthOfScreen(xscreen)*3/4,
+                        HeightOfScreen(xscreen)*3/4);
+
+        memset(&attr, 0, sizeof attr);
+        attr.colormap = _x.cmap;
+        attr.background_pixel = 0;
+        attr.border_pixel = 0;
+        _x.drawable = XCreateWindow(
+                _x.display,        /* display */
+                xrootwin,        /* parent */
+                0,                /* x */
+                0,                /* y */
+                Dx(r),                /* width */
+                 Dy(r),                /* height */
+                0,                /* border width */
+                _x.depth,        /* depth */
+                InputOutput,        /* class */
+                _x.vis,                /* visual */
+                                /* valuemask */
+                CWBackPixel|CWBorderPixel|CWColormap,
+                &attr                /* attributes (the above aren't?!) */
+        );
+
+        /*
+         * Label and other properties required by ICCCCM.
+         */
+        memset(&name, 0, sizeof name);
+        if(label == nil)
+                label = "pjw-face-here";
+        name.value = (uchar*)label;
+        name.encoding = XA_STRING;
+        name.format = 8;
+        name.nitems = strlen(name.value);
+
+        memset(&normalhint, 0, sizeof normalhint);
+        normalhint.flags = USSize|PMaxSize;
+        normalhint.max_width = WidthOfScreen(xscreen);
+        normalhint.max_height = HeightOfScreen(xscreen);
+
+        memset(&hint, 0, sizeof hint);
+        hint.flags = InputHint|StateHint;
+        hint.input = 1;
+        hint.initial_state = NormalState;
+
+        memset(&classhint, 0, sizeof classhint);
+        classhint.res_name = label;
+        classhint.res_class = label;
+
+        argv[0] = label;
+        argv[1] = nil;
+
+        XSetWMProperties(
+                _x.display,        /* display */
+                _x.drawable,        /* window */
+                &name,                /* XA_WM_NAME property */
+                &name,                /* XA_WM_ICON_NAME property */
+                argv,                /* XA_WM_COMMAND */
+                1,                /* argc */
+                &normalhint,        /* XA_WM_NORMAL_HINTS */
+                &hint,                /* XA_WM_HINTS */
+                &classhint        /* XA_WM_CLASSHINTS */
+        );
+        XFlush(_x.display);
+
+        /*
+         * Allocate our local backing store.
+         */
+        _x.screenr = r;
+        _x.screenpm = XCreatePixmap(_x.display, _x.drawable, Dx(r), Dy(r), _x.depth);
+        _x.screenimage = xallocmemimage(r, _x.chan, _x.screenpm);
+
+        /*
+         * Allocate some useful graphics contexts for the future.
+         */
+        _x.gcfill        = xgc(_x.screenpm, FillSolid, -1);
+        _x.gccopy        = xgc(_x.screenpm, -1, -1);
+        _x.gcsimplesrc         = xgc(_x.screenpm, FillStippled, -1);
+        _x.gczero        = xgc(_x.screenpm, -1, -1);
+        _x.gcreplsrc        = xgc(_x.screenpm, FillTiled, -1);
+
+        pmid = XCreatePixmap(_x.display, _x.drawable, 1, 1, 1);
+        _x.gcfill0        = xgc(pmid, FillSolid, 0);
+        _x.gccopy0        = xgc(pmid, -1, -1);
+        _x.gcsimplesrc0        = xgc(pmid, FillStippled, -1);
+        _x.gczero0        = xgc(pmid, -1, -1);
+        _x.gcreplsrc0        = xgc(pmid, FillTiled, -1);
+        XFreePixmap(_x.display, pmid);
+
+        /*
+         * Put the window on the screen.
+         */
+        XMapWindow(_x.display, _x.drawable);
+        XFlush(_x.display);
+
+        /*
+         * Lots of display connections for various threads.
+         */
+        _x.kbdcon        = XOpenDisplay(NULL);
+        _x.mousecon        = XOpenDisplay(NULL);
+        _x.snarfcon        = XOpenDisplay(NULL);
+
+        _x.black        = xscreen->black_pixel;
+        _x.white        = xscreen->white_pixel;
+
+        return _x.screenimage;
+
+err0:
+        /*
+         * Should do a better job of cleaning up here.
+         */
+        XCloseDisplay(_x.display);
+        return nil;
+}
+
+/*
+ * Create a GC with a particular fill style and XXX.
+ * Disable generation of GraphicsExpose/NoExpose events in the GC.
+ */
+static XGC
+xgc(XDrawable d, int fillstyle, int foreground)
+{
+        XGC gc;
+        XGCValues v;
+
+        memset(&v, 0, sizeof v);
+        v.function = GXcopy;
+        v.graphics_exposures = False;
+        gc = XCreateGC(_x.display, d, GCFunction|GCGraphicsExposures, &v);
+        if(fillstyle != -1)
+                XSetFillStyle(_x.display, gc, fillstyle);
+        if(foreground != -1)
+                XSetForeground(_x.display, gc, 0);
+        return gc;
+}
+
+
+/*
+ * Initialize map with the Plan 9 rgbv color map.
+ */
+static void
+plan9cmap(void)
+{
+        int r, g, b, cr, cg, cb, v, num, den, idx, v7, idx7;
+        static int once;
+
+        if(once)
+                return;
+        once = 1;
+
+        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);
+                _x.map[idx].red = cr*0x0101;
+                _x.map[idx].green = cg*0x0101;
+                _x.map[idx].blue = cb*0x0101;
+                _x.map[idx].pixel = idx;
+                _x.map[idx].flags = DoRed|DoGreen|DoBlue;
+
+                v7 = v >> 1;
+                idx7 = r*32 + v7*16 + g*4 + b;
+                if((v & 1) == v7){
+                        _x.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;
+                        }
+                        _x.map7[idx7].red = cr*0x0101;
+                        _x.map7[idx7].green = cg*0x0101;
+                        _x.map7[idx7].blue = cb*0x0101;
+                        _x.map7[idx7].pixel = idx7;
+                        _x.map7[idx7].flags = DoRed|DoGreen|DoBlue;
+                }
+                else
+                        _x.map7to8[idx7][1] = idx;
+        }
+}
+
+/*
+ * Initialize and install the rgbv color map as a private color map
+ * for this application.  It gets the best colors when it has the
+ * cursor focus.
+ */
+static int 
+setupcmap(XWindow w)
+{
+        char buf[30];
+        int i;
+        u32int p, pp;
+        XColor c;
+
+        if(_x.depth <= 1)
+                return 0;
+
+        if(_x.depth >= 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 _x.vis->byte_order but 
+                 * some displays say MSB even though they run on LSB.
+                 * Besides, this is more anal.
+                 */
+
+                c = _x.map[19];        /* known to have different R, G, B values */
+                if(!XAllocColor(_x.display, _x.cmap, &c)){
+                        werrstr("XAllocColor: %r");
+                        return -1;
+                }
+                p  = c.pixel;
+                pp = rgb2cmap((p>>16)&0xff,(p>>8)&0xff,p&0xff);
+                if(pp != _x.map[19].pixel) {
+                        /* check if endian is other way */
+                        pp = rgb2cmap(p&0xff,(p>>8)&0xff,(p>>16)&0xff);
+                        if(pp != _x.map[19].pixel){
+                                werrstr("cannot detect X server byte order");
+                                return -1;
+                        }
+
+                        switch(_x.chan){
+                        case RGB24:
+                                _x.chan = BGR24;
+                                break;
+                        case XRGB32:
+                                _x.chan = XBGR32;
+                                break;
+                        default:
+                                werrstr("cannot byteswap channel %s",
+                                        chantostr(buf, _x.chan));
+                                break;
+                        }
+                }
+        }else if(_x.vis->class == TrueColor || _x.vis->class == DirectColor){
+                /*
+                 * Do nothing.  We have no way to express a
+                 * mixed-endian 16-bit screen, so pretend they don't exist.
+                 */
+        }else if(_x.vis->class == PseudoColor){
+                if(_x.usetable == 0){
+                        _x.cmap = XCreateColormap(_x.display, w, _x.vis, AllocAll); 
+                        XStoreColors(_x.display, _x.cmap, _x.map, 256);
+                        for(i = 0; i < 256; i++){
+                                _x.tox11[i] = i;
+                                _x.toplan9[i] = i;
+                        }
+                }else{
+                        for(i = 0; i < 128; i++){
+                                c = _x.map7[i];
+                                if(!XAllocColor(_x.display, _x.cmap, &c)){
+                                        werrstr("can't allocate colors in 7-bit map");
+                                        return -1;
+                                }
+                                _x.tox11[_x.map7to8[i][0]] = c.pixel;
+                                _x.tox11[_x.map7to8[i][1]] = c.pixel;
+                                _x.toplan9[c.pixel] = _x.map7to8[i][0];
+                        }
+                }
+        }else{
+                werrstr("unsupported visual class %d", _x.vis->class);
+                return -1;
+        }
+        return 0;
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+        if(r.min.x >= r.max.x || r.min.y >= r.max.y)
+                return;
+        XCopyArea(_x.display, _x.screenpm, _x.drawable, _x.gccopy, r.min.x, r.min.y,
+                Dx(r), Dy(r), r.min.x, r.min.y);
+        XFlush(_x.display);
+}
+
+void
+xexpose(XEvent *e, XDisplay *xd)
+{
+        XExposeEvent *xe;
+        Rectangle r;
+
+        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;
+        XCopyArea(xd, _x.screenpm, _x.drawable, _x.gccopy, r.min.x, r.min.y,
+                Dx(r), Dy(r), r.min.x, r.min.y);
+        XFlush(xd);
+}
+
+int
+xconfigure(XEvent *e, XDisplay *xd)
+{
+        Memimage *m;
+        XConfigureEvent *xe = (XConfigureEvent*)e;
+        XDrawable pixmap;
+
+        if(xe->width == Dx(_x.screenr) && xe->height == Dy(_x.screenr))
+                return 0;
+        
+        pixmap = XCreatePixmap(xd, _x.drawable, xe->width, xe->height, _x.depth);
+        m = xallocmemimage(Rect(0, 0, xe->width, xe->height), _x.chan, pixmap);
+        _x.screenpm = pixmap;
+        _x.screenr = Rect(0, 0, xe->width, xe->height);
+        _drawreplacescreenimage(m);
+        return 1;
+}
+
diff --git a/src/libdraw/x11-itrans.c b/src/libdraw/x11-itrans.c
t@@ -0,0 +1,258 @@
+/* input event and data structure translation */
+
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+int
+xtoplan9kbd(XEvent *e)
+{
+        int ind, k, md;
+
+        md = e->xkey.state;
+        ind = 0;
+        if(md & ShiftMask)
+                ind = 1;
+
+        k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, ind);
+        if(k == XK_Multi_key || k == NoSymbol)
+                return -1;
+
+        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;
+                default:                /* not ISO-1 or tty control */
+                        return -1;
+                }
+        }
+
+        /* Compensate for servers that call a minus a hyphen */
+        if(k == XK_hyphen)
+                k = XK_minus;
+        /* Do control mapping ourselves if translator doesn't */
+        if(e->xkey.state&ControlMask)
+                k &= 0x9f;
+        if(k == NoSymbol) {
+                return -1;
+        }
+
+        /* BUG: could/should do Alt translation here! */
+        return k;
+}
+
+int
+xtoplan9mouse(XEvent *e, Mouse *m)
+{
+        int s;
+        XButtonEvent *be;
+        XMotionEvent *me;
+
+        switch(e->type){
+        case ButtonPress:
+                be = (XButtonEvent*)e;
+                /* BUG? on mac need to inherit these from elsewhere? */
+                m->xy.x = be->x;
+                m->xy.y = be->y;
+                s = be->state;
+                m->msec = be->time;
+                switch(be->button){
+                case 1:
+                        s |= Button1Mask;
+                        break;
+                case 2:
+                        s |= Button2Mask;
+                        break;
+                case 3:
+                        s |= Button3Mask;
+                        break;
+                }
+                break;
+        case ButtonRelease:
+                be = (XButtonEvent*)e;
+                m->xy.x = be->x;
+                m->xy.y = be->y;
+                s = be->state;
+                m->msec = be->time;
+                switch(be->button){
+                case 1:
+                        s &= ~Button1Mask;
+                        break;
+                case 2:
+                        s &= ~Button2Mask;
+                        break;
+                case 3:
+                        s &= ~Button3Mask;
+                        break;
+                }
+                break;
+
+        case MotionNotify:
+                me = (XMotionEvent*)e;
+                s = me->state;
+                m->xy.x = me->x;
+                m->xy.y = me->y;
+                m->msec = me->time;
+                break;
+
+        default:
+                return -1;
+        }
+
+        m->buttons = 0;
+        if(s & Button1Mask)
+                m->buttons |= 1;
+        if(s & Button2Mask)
+                m->buttons |= 2;
+        if(s & Button3Mask)
+                m->buttons |= 4;
+
+        return 0;
+}
+
+void
+xmoveto(Point p)
+{
+        XWarpPointer(_x.display, None, _x.drawable, 0, 0, 0, 0, p.x, p.y);
+        XFlush(_x.display);
+}
+
+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;
+}
+
+static void
+xcursorarrow(void)
+{
+        if(_x.cursor != 0){
+                XFreeCursor(_x.display, _x.cursor);
+                _x.cursor = 0;
+        }
+        XUndefineCursor(_x.display, _x.drawable);
+        XFlush(_x.display);
+}
+
+
+void
+xsetcursor(Cursor *c)
+{
+        XColor fg, bg;
+        XCursor xc;
+        Pixmap xsrc, xmask;
+        int i;
+        uchar src[2*16], mask[2*16];
+
+        if(c == nil){
+                xcursorarrow();
+                return;
+        }
+        for(i=0; i<2*16; i++){
+                src[i] = revbyte(c->set[i]);
+                mask[i] = revbyte(c->set[i] | c->clr[i]);
+        }
+
+        fg = _x.map[0];
+        bg = _x.map[255];
+        xsrc = XCreateBitmapFromData(_x.display, _x.drawable, src, 16, 16);
+        xmask = XCreateBitmapFromData(_x.display, _x.drawable, mask, 16, 16);
+        xc = XCreatePixmapCursor(_x.display, xsrc, xmask, &fg, &bg, -c->offset.x, -c->offset.y);
+        if(xc != 0) {
+                XDefineCursor(_x.display, _x.drawable, xc);
+                if(_x.cursor != 0)
+                        XFreeCursor(_x.display, _x.cursor);
+                _x.cursor = xc;
+        }
+        XFreePixmap(_x.display, xsrc);
+        XFreePixmap(_x.display, xmask);
+        XFlush(_x.display);
+}
+
diff --git a/src/libdraw/x11-keyboard.c b/src/libdraw/x11-keyboard.c
t@@ -0,0 +1,71 @@
+#include "x11-inc.h"
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+void
+closekeyboard(Keyboardctl *kc)
+{
+        if(kc == nil)
+                return;
+
+/*        postnote(PNPROC, kc->pid, "kill");
+*/
+
+#ifdef BUG
+        /* Drain the channel */
+        while(?kc->c)
+                <-kc->c;
+#endif
+
+        close(kc->ctlfd);
+        close(kc->consfd);
+        free(kc->file);
+        free(kc->c);
+        free(kc);
+}
+
+static
+void
+_ioproc(void *arg)
+{
+        int i;
+        Keyboardctl *kc;
+        Rune r;
+        XEvent xevent;
+
+        kc = arg;
+        threadsetname("kbdproc");
+        kc->pid = getpid();
+        for(;;){
+                XSelectInput(_x.kbdcon, _x.drawable, KeyPressMask);
+                XWindowEvent(_x.kbdcon, _x.drawable, KeyPressMask, &xevent);
+                switch(xevent.type){
+                case KeyPress:
+                        i = xtoplan9kbd(&xevent);
+                        if(i == -1)
+                                continue;
+                        r = i;
+                        send(kc->c, &r);
+                        break;
+                }
+        }
+}
+
+Keyboardctl*
+initkeyboard(char *file)
+{
+        Keyboardctl *kc;
+
+        kc = mallocz(sizeof(Keyboardctl), 1);
+        if(kc == nil)
+                return nil;
+        kc->c = chancreate(sizeof(Rune), 20);
+        proccreate(_ioproc, kc, 4096);
+        return kc;
+}
+
diff --git a/src/libdraw/x11-load.c b/src/libdraw/x11-load.c
t@@ -0,0 +1,19 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+int
+loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+        int n;
+
+        n = _loadmemimage(i, r, data, ndata);
+        if(n > 0 && i->X)
+                xputxdata(i, r);
+        return n;
+}
+
diff --git a/src/libdraw/x11-memdraw.h b/src/libdraw/x11-memdraw.h
t@@ -0,0 +1,93 @@
+/*
+ * Structure pointed to by X field of Memimage
+ */
+
+typedef struct Xmem Xmem;
+typedef struct Xprivate Xprivate;
+
+enum
+{
+        PMundef = ~0
+};
+
+struct Xmem
+{
+        int                pixmap;        /* pixmap id */
+        XImage                *xi;        /* local image */
+        int                dirty;        /* is the X server ahead of us?  */
+        Rectangle        dirtyr;        /* which pixels? */
+        Rectangle        r;        /* size of image */
+};
+
+struct Xprivate {
+        u32int                black;
+        u32int                chan;
+        XColormap        cmap;
+        XCursor                cursor;
+        XDisplay        *display;
+        int                depth;                                /* of screen */
+        XDrawable        drawable;
+        XColor                map[256];
+        XColor                map7[128];
+        uchar                map7to8[128][2];
+        XGC                gccopy;
+        XGC                gccopy0;
+        XGC                gcfill;
+        u32int                gcfillcolor;
+        XGC                gcfill0;
+        u32int                gcfill0color;
+        XGC                gcreplsrc;
+        u32int                gcreplsrctile;
+        XGC                gcreplsrc0;
+        u32int                gcreplsrc0tile;
+        XGC                gcsimplesrc;
+        u32int                gcsimplesrccolor;
+        u32int                gcsimplesrcpixmap;
+        XGC                gcsimplesrc0;
+        u32int                gcsimplesrc0color;
+        u32int                gcsimplesrc0pixmap;
+        XGC                gczero;
+        u32int                gczeropixmap;
+        XGC                gczero0;
+        u32int                gczero0pixmap;
+        XDisplay        *kbdcon;
+        XDisplay        *mousecon;
+        Memimage*        screenimage;
+        XDrawable        screenpm;
+        Rectangle        screenr;
+        XDisplay        *snarfcon;
+        int                toplan9[256];
+        int                tox11[256];
+        int                usetable;
+        XVisual                *vis;
+        u32int                white;
+};
+
+extern Xprivate _x;
+
+extern Memimage *xallocmemimage(Rectangle, u32int, int);
+extern XImage        *xallocxdata(Memimage*, Rectangle);
+extern void        xdirtyxdata(Memimage*, Rectangle);
+extern void        xfillcolor(Memimage*, Rectangle, u32int);
+extern void        xfreexdata(Memimage*);
+extern XImage        *xgetxdata(Memimage*, Rectangle);
+extern void        xputxdata(Memimage*, Rectangle);
+
+struct Mouse;
+extern int        xtoplan9mouse(XEvent*, struct Mouse*);
+extern int        xtoplan9kbd(XEvent*);
+extern void        xexpose(XEvent*, XDisplay*);
+extern int        xconfigure(XEvent*, XDisplay*);
+extern void        flushmemscreen(Rectangle);
+extern void        xmoveto(Point);
+struct Cursor;
+extern void        xsetcursor(struct Cursor*);
+
+#define MouseMask (\
+        ButtonPressMask|\
+        ButtonReleaseMask|\
+        PointerMotionMask|\
+        Button1MotionMask|\
+        Button2MotionMask|\
+        Button3MotionMask)
+
diff --git a/src/libdraw/x11-mouse.c b/src/libdraw/x11-mouse.c
t@@ -0,0 +1,107 @@
+#include "x11-inc.h"
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+void
+moveto(Mousectl *m, Point pt)
+{
+        xmoveto(pt);
+}
+
+void
+closemouse(Mousectl *mc)
+{
+        if(mc == nil)
+                return;
+
+/*        postnote(PNPROC, mc->pid, "kill");
+*/
+        do; while(nbrecv(mc->c, &mc->m) > 0);
+        close(mc->mfd);
+        close(mc->cfd);
+        free(mc->file);
+        chanfree(mc->c);
+        chanfree(mc->resizec);
+        free(mc);
+}
+
+int
+readmouse(Mousectl *mc)
+{
+        if(mc->display)
+                flushimage(mc->display, 1);
+        if(recv(mc->c, &mc->m) < 0){
+                fprint(2, "readmouse: %r\n");
+                return -1;
+        }
+        return 0;
+}
+
+static
+void
+_ioproc(void *arg)
+{
+        int one;
+        Mouse m;
+        Mousectl *mc;
+        XEvent xevent;
+
+        one = 1;
+        mc = arg;
+        threadsetname("mouseproc");
+        memset(&m, 0, sizeof m);
+        mc->pid = getpid();
+        for(;;){
+                XSelectInput(_x.mousecon, _x.drawable, MouseMask|ExposureMask|StructureNotifyMask);
+                XWindowEvent(_x.mousecon, _x.drawable, MouseMask|ExposureMask|StructureNotifyMask, &xevent);
+                switch(xevent.type){
+                case Expose:
+                        xexpose(&xevent, _x.mousecon);
+                        continue;
+                case ConfigureNotify:
+                        if(xconfigure(&xevent, _x.mousecon))
+                                nbsend(mc->resizec, &one);
+                        continue;
+                case ButtonPress:
+                case ButtonRelease:
+                case MotionNotify:
+                        if(xtoplan9mouse(&xevent, &m) < 0)
+                                continue;
+                        send(mc->c, &m);
+                        /*
+                         * mc->Mouse is updated after send so it doesn't have wrong value if we block during send.
+                         * This means that programs should receive into mc->Mouse (see readmouse() above) if
+                         * they want full synchrony.
+                         */
+                        mc->m = m;
+                        break;
+                }
+        }
+}
+
+Mousectl*
+initmouse(char *file, Image *i)
+{
+        Mousectl *mc;
+
+        mc = mallocz(sizeof(Mousectl), 1);
+        if(i)
+                mc->display = i->display;
+        mc->c = chancreate(sizeof(Mouse), 0);
+        mc->resizec = chancreate(sizeof(int), 2);
+        proccreate(_ioproc, mc, 16384);
+        return mc;
+}
+
+void
+setcursor(Mousectl *mc, Cursor *c)
+{
+        xsetcursor(c);
+}
+
diff --git a/src/libdraw/x11-pixelbits.c b/src/libdraw/x11-pixelbits.c
t@@ -0,0 +1,17 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+u32int
+pixelbits(Memimage *m, Point p)
+{
+        if(m->X)
+                xgetxdata(m, Rect(p.x, p.y, p.x+1, p.y+1));
+        return _pixelbits(m, p);
+}
+
+
diff --git a/src/libdraw/x11-unload.c b/src/libdraw/x11-unload.c
t@@ -0,0 +1,16 @@
+#include "x11-inc.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "x11-memdraw.h"
+
+int
+unloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+        if(i->X)
+                xgetxdata(i, r);
+        return _unloadmemimage(i, r, data, ndata);
+}
+
diff --git a/src/libthread/386.c b/src/libthread/386.c
t@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static void
+launcher386(void (*f)(void *arg), void *arg)
+{
+        (*f)(arg);
+        threadexits(nil);
+}
+
+void
+_threadinitstack(Thread *t, void (*f)(void*), void *arg)
+{
+        ulong *tos;
+
+        tos = (ulong*)&t->stk[t->stksize&~7];
+        *--tos = (ulong)arg;
+        *--tos = (ulong)f;
+        t->sched.pc = (ulong)launcher386;
+        t->sched.sp = (ulong)tos - 8;                /* old PC and new PC */
+}
+
diff --git a/src/libthread/FreeBSD-386.s b/src/libthread/FreeBSD-386.s
t@@ -0,0 +1,18 @@
+
+.globl        _xinc
+_xinc:
+        movl 4(%esp), %eax
+        lock incl 0(%eax)
+        ret
+
+.globl        _xdec
+_xdec:
+        movl 4(%esp), %eax
+        lock decl 0(%eax)
+        jz iszero
+        movl %eax, 1
+        ret
+iszero:
+        movl %eax, 0
+        ret
+
diff --git a/src/libthread/LICENSE b/src/libthread/LICENSE
t@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+   to redistribute (other than with the Plan 9 Operating System)
+   the screen imprinter fonts identified in subdirectory
+   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+   covered by the Aladdin Free Public License, reproduced in the file
+   /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+     Program, and
+  b. in the case of each Contributor,
+
+     i. changes to the Program, and
+    ii. additions to the Program;
+
+    where such changes and/or additions to the Program were added to the
+    Program by such Contributor itself or anyone acting on such
+    Contributor's behalf, and the Contributor explicitly consents, in
+    accordance with Section 3C, to characterization of the changes and/or
+    additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free copyright
+    license to reproduce, prepare derivative works of, publicly display,
+    publicly perform, distribute and sublicense the Contribution of such
+    Contributor, if any, and such derivative works, in source code and
+    object code form.
+    
+ b. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free patent
+    license under Licensed Patents to make, use, sell, offer to sell,
+    import and otherwise transfer the Contribution of such Contributor, if
+    any, in source code and object code form. The patent license granted
+    by a Contributor shall also apply to the combination of the
+    Contribution of that Contributor and the Program if, at the time the
+    Contribution is added by the Contributor, such addition of the
+    Contribution causes such combination to be covered by the Licensed
+    Patents. The patent license granted by a Contributor shall not apply
+    to (i) any other combinations which include the Contribution, nor to
+    (ii) Contributions of other Contributors. No hardware per se is
+    licensed hereunder.
+    
+ c. Recipient understands that although each Contributor grants the
+    licenses to its Contributions set forth herein, no assurances are
+    provided by any Contributor that the Program does not infringe the
+    patent or other intellectual property rights of any other entity. Each
+    Contributor disclaims any liability to Recipient for claims brought by
+    any other entity based on infringement of intellectual property rights
+    or otherwise. As a condition to exercising the rights and licenses
+    granted hereunder, each Recipient hereby assumes sole responsibility
+    to secure any other intellectual property rights needed, if any. For
+    example, if a third party patent license is required to allow
+    Recipient to distribute the Program, it is Recipient's responsibility
+    to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+    copyright rights in its Contribution, if any, to grant the copyright
+    license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+    form, a copy of this Agreement or Distributor's own license agreement
+    is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+    license agreement:
+
+      i. effectively disclaims on behalf of all Contributors all warranties
+         and conditions, express and implied, including warranties or
+         conditions of title and non-infringement, and implied warranties or
+         conditions of merchantability and fitness for a particular purpose;
+     ii. effectively excludes on behalf of all Contributors all liability
+         for damages, including direct, indirect, special, incidental and
+         consequential damages, such as lost profits; and
+    iii. states that any provisions which differ from this Agreement are
+         offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+   location in the Program:
+
+   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+   Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), 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 OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/libthread/Make.Darwin-PowerMacintosh b/src/libthread/Make.Darwin-PowerMacintosh
t@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.FreeBSD-386 b/src/libthread/Make.FreeBSD-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.HP-UX-9000 b/src/libthread/Make.HP-UX-9000
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.Linux-386 b/src/libthread/Make.Linux-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.NetBSD-386 b/src/libthread/Make.NetBSD-386
t@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.OSF1-alpha b/src/libthread/Make.OSF1-alpha
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u b/src/libthread/Make.SunOS-sun4u
t@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u-cc b/src/libthread/Make.SunOS-sun4u-cc
t@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u-gcc b/src/libthread/Make.SunOS-sun4u-gcc
t@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Makefile b/src/libthread/Makefile
t@@ -0,0 +1,125 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+LIB=libthread.a
+VERSION=2.0
+PORTPLACE=devel/libthread
+NAME=libthread
+
+OFILES=\
+        $(OBJTYPE).$O\
+        asm-$(SYSNAME)-$(OBJTYPE).$O\
+        channel.$O\
+        chanprint.$O\
+        create.$O\
+        debug.$O\
+        exec-unix.$O\
+        exit.$O\
+        getpid.$O\
+        id.$O\
+        iocall.$O\
+        ioclose.$O\
+        ioopen.$O\
+        ioproc.$O\
+        ioread.$O\
+        ioreadn.$O\
+        iowrite.$O\
+        kill.$O\
+        lib.$O\
+        main.$O\
+        memset.$O\
+        memsetd.$O\
+        note.$O\
+        proctab.$O\
+        ref.$O\
+        rendez.$O\
+        sched.$O\
+
+HFILES=\
+        thread.h\
+        label.h\
+        threadimpl.h\
+
+all: $(LIB)
+
+install: $(LIB)
+        test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+        install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
+        install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
+        install -m 0644 thread.h $(PREFIX)/include/thread.h
+        install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+tprimes: $(LIB) tprimes.$O
+        $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+texec: $(LIB) texec.$O
+        $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+$(LIB): $(OFILES)
+        $(AR) $(ARFLAGS) $(LIB) $(OFILES)
+
+NUKEFILES+=$(LIB)
+.c.$O:
+        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+        rm -rf $(NAME)-$(VERSION)
+        mkdir $(NAME)-$(VERSION)
+        cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+        tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+        rm -rf $(NAME)-$(VERSION)
+
+clean:
+        rm -f $(OFILES) $(LIB)
+
+nuke:
+        rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+        make tgz
+        cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+        rpm -ba rpm.spec
+        cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+        cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+        scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+        make tgz
+        rm -rf $(PORTDIR)
+        mkdir $(PORTDIR)
+        cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+        cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+        (cd $(PORTDIR); make makesum)
+        (cd $(PORTDIR); make)
+        (cd $(PORTDIR); /usr/local/bin/portlint)
+        rm -rf $(PORTDIR)/work
+        shar `find $(PORTDIR)` > ports.shar
+        (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+        scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/libthread/Makefile.MID b/src/libthread/Makefile.MID
t@@ -0,0 +1,54 @@
+LIB=libthread.a
+VERSION=2.0
+PORTPLACE=devel/libthread
+NAME=libthread
+
+OFILES=\
+        $(OBJTYPE).$O\
+        asm-$(SYSNAME)-$(OBJTYPE).$O\
+        channel.$O\
+        chanprint.$O\
+        create.$O\
+        debug.$O\
+        exec-unix.$O\
+        exit.$O\
+        getpid.$O\
+        id.$O\
+        iocall.$O\
+        ioclose.$O\
+        ioopen.$O\
+        ioproc.$O\
+        ioread.$O\
+        ioreadn.$O\
+        iowrite.$O\
+        kill.$O\
+        lib.$O\
+        main.$O\
+        memset.$O\
+        memsetd.$O\
+        note.$O\
+        proctab.$O\
+        ref.$O\
+        rendez.$O\
+        sched.$O\
+
+HFILES=\
+        thread.h\
+        label.h\
+        threadimpl.h\
+
+all: $(LIB)
+
+install: $(LIB)
+        test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+        install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
+        install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
+        install -m 0644 thread.h $(PREFIX)/include/thread.h
+        install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+tprimes: $(LIB) tprimes.$O
+        $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+texec: $(LIB) texec.$O
+        $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
diff --git a/src/libthread/NOTICE b/src/libthread/NOTICE
t@@ -0,0 +1,19 @@
+/*
+ * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
+ *                Copyright (c) 2003 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+This is a Unix port of the Plan 9 thread library.
+
+Please send comments about the packaging
+to Russ Cox .
+
diff --git a/src/libthread/README b/src/libthread/README
t@@ -0,0 +1,19 @@
+/*
+ * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
+ *                Copyright (c) 2003 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+This is a Unix port of the Plan 9 thread library.
+
+Please send comments about the packaging
+to Russ Cox .
+
diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s
t@@ -0,0 +1,49 @@
+.globl        _setlabel
+.type        _setlabel,@function
+
+_setlabel:
+        movl        4(%esp), %eax
+        movl        0(%esp), %edx
+        movl        %edx, 0(%eax)
+        movl        %ebx, 4(%eax)
+        movl        %esp, 8(%eax)
+        movl        %ebp, 12(%eax)
+        movl        %esi, 16(%eax)
+        movl        %edi, 20(%eax)
+        xorl        %eax, %eax
+        ret
+
+.globl        _gotolabel
+.type        _gotolabel,@function
+
+_gotolabel:
+        movl        4(%esp), %edx
+        movl        0(%edx), %ecx
+        movl        4(%edx), %ebx
+        movl        8(%edx), %esp
+        movl        12(%edx), %ebp
+        movl        16(%edx), %esi
+        movl        20(%edx), %edi
+        xorl        %eax, %eax
+        incl        %eax
+        movl        %ecx, 0(%esp)
+        ret
+
+
+.globl        _xinc
+_xinc:
+        movl 4(%esp), %eax
+        lock incl 0(%eax)
+        ret
+
+.globl        _xdec
+_xdec:
+        movl 4(%esp), %eax
+        lock decl 0(%eax)
+        jz iszero
+        movl %eax, 1
+        ret
+iszero:
+        movl %eax, 0
+        ret
+
diff --git a/src/libthread/asm-Linux-386.s b/src/libthread/asm-Linux-386.s
t@@ -0,0 +1 @@
+.include "asm-FreeBSD-386.s"
diff --git a/src/libthread/bundle.ports b/src/libthread/bundle.ports
t@@ -0,0 +1,42 @@
+--- Makefile ---
+# New ports collection makefile for: libthread
+# Date Created:                11 Feb 2003
+# Whom:                        rsc
+#
+
+PORTNAME=        libthread
+PORTVERSION=        1.0
+CATEGORIES=        devel
+MASTER_SITES=        http://pdos.lcs.mit.edu/~rsc/software/
+DISTNAME=        libthread
+EXTRACT_SUFX=        .tgz
+
+MAINTAINER=        rsc@post.harvard.edu
+
+MAN3=                print.3 fmtinstall.3
+MLINKS=                XXX
+USE_REINPLACE=        XXX (wkj says yes)
+
+.include 
+
+post-patch:
+        ${REINPLACE_CMD} -e 's,@@LOCAL@@,${PREFIX},g' ${WRKSRC}/Makefile
+
+.include 
+
+--- pkg-comment ---
+Plan 9 thread library
+--- pkg-descr ---
+Libthread is a port of Plan 9's thread library.
+
+WWW: http://pdos.lcs.mit.edu/~rsc/software/
+WWW: http://plan9.bell-labs.com/magic/man2html/2/thread
+
+Russ Cox
+rsc@post.harvard.edu
+--- pkg-plist ---
+lib/libthread.a
+include/thread.h
+--- /dev/null ---
+This is just a way to make sure blank lines don't
+creep into pkg-plist.
diff --git a/src/libthread/channel.c b/src/libthread/channel.c
t@@ -0,0 +1,485 @@
+#include "threadimpl.h"
+
+static Lock chanlock;                /* central channel access lock */
+
+static void enqueue(Alt*, Channel**);
+static void dequeue(Alt*);
+static int altexec(Alt*, int);
+
+int _threadhighnentry;
+int _threadnalt;
+
+static int
+canexec(Alt *a)
+{
+        int i, otherop;
+        Channel *c;
+
+        c = a->c;
+        /* are there senders or receivers blocked? */
+        otherop = (CHANSND+CHANRCV) - a->op;
+        for(i=0; inentry; i++)
+                if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil){
+                        _threaddebug(DBGCHAN, "can rendez alt %p chan %p", a, c);
+                        return 1;
+                }
+
+        /* is there room in the channel? */
+        if((a->op==CHANSND && c->n < c->s)
+        || (a->op==CHANRCV && c->n > 0)){
+                _threaddebug(DBGCHAN, "can buffer alt %p chan %p", a, c);
+                return 1;
+        }
+
+        return 0;
+}
+
+static void
+_chanfree(Channel *c)
+{
+        int i, inuse;
+
+        inuse = 0;
+        for(i = 0; i < c->nentry; i++)
+                if(c->qentry[i])
+                        inuse = 1;
+        if(inuse)
+                c->freed = 1;
+        else{
+                if(c->qentry)
+                        free(c->qentry);
+                free(c);
+        }
+}
+
+void
+chanfree(Channel *c)
+{
+        lock(&chanlock);
+        _chanfree(c);
+        unlock(&chanlock);
+}
+
+int
+chaninit(Channel *c, int elemsize, int elemcnt)
+{
+        if(elemcnt < 0 || elemsize <= 0 || c == nil)
+                return -1;
+        c->f = 0;
+        c->n = 0;
+        c->freed = 0;
+        c->e = elemsize;
+        c->s = elemcnt;
+        _threaddebug(DBGCHAN, "chaninit %p", c);
+        return 1;
+}
+
+Channel*
+chancreate(int elemsize, int elemcnt)
+{
+        Channel *c;
+
+        if(elemcnt < 0 || elemsize <= 0)
+                return nil;
+        c = _threadmalloc(sizeof(Channel)+elemsize*elemcnt, 1);
+        c->e = elemsize;
+        c->s = elemcnt;
+        _threaddebug(DBGCHAN, "chancreate %p", c);
+        return c;
+}
+
+int
+alt(Alt *alts)
+{
+        Alt *a, *xa;
+        Channel *volatile c;
+        int n, s;
+        ulong r;
+        Thread *t;
+
+        /*
+         * The point of going splhi here is that note handlers
+         * might reasonably want to use channel operations,
+         * but that will hang if the note comes while we hold the
+         * chanlock.  Instead, we delay the note until we've dropped
+         * the lock.
+         */
+        t = _threadgetproc()->thread;
+        if(t->moribund || _threadexitsallstatus)
+                yield();        /* won't return */
+        s = _procsplhi();
+        lock(&chanlock);
+        t->alt = alts;
+        t->chan = Chanalt;
+
+        /* test whether any channels can proceed */
+        n = 0;
+        a = nil;
+
+        for(xa=alts; xa->op!=CHANEND && xa->op!=CHANNOBLK; xa++){
+                xa->entryno = -1;
+                if(xa->op == CHANNOP)
+                        continue;
+                
+                c = xa->c;
+                if(c==nil){
+                        unlock(&chanlock);
+                        _procsplx(s);
+                        t->chan = Channone;
+                        return -1;
+                }
+                if(canexec(xa))
+                        if(nrand(++n) == 0)
+                                a = xa;
+        }
+
+        if(a==nil){
+                /* nothing can proceed */
+                if(xa->op == CHANNOBLK){
+                        unlock(&chanlock);
+                        _procsplx(s);
+                        t->chan = Channone;
+_threadnalt++;
+                        return xa - alts;
+                }
+
+                /* enqueue on all channels. */
+                c = nil;
+                for(xa=alts; xa->op!=CHANEND; xa++){
+                        if(xa->op==CHANNOP)
+                                continue;
+                        enqueue(xa, (Channel**)&c);
+                }
+
+                /*
+                 * wait for successful rendezvous.
+                 * we can't just give up if the rendezvous
+                 * is interrupted -- someone else might come
+                 * along and try to rendezvous with us, so
+                 * we need to be here.
+                 */
+            Again:
+                unlock(&chanlock);
+                _procsplx(s);
+                r = _threadrendezvous((ulong)&c, 0);
+                s = _procsplhi();
+                lock(&chanlock);
+
+                if(r==~0){                /* interrupted */
+                        if(c!=nil)                /* someone will meet us; go back */
+                                goto Again;
+                        c = (Channel*)~0;        /* so no one tries to meet us */
+                }
+
+                /* dequeue from channels, find selected one */
+                a = nil;
+                for(xa=alts; xa->op!=CHANEND; xa++){
+                        if(xa->op==CHANNOP)
+                                continue;
+                        if(xa->c == c)
+                                a = xa;
+                        dequeue(xa);
+                }
+                unlock(&chanlock);
+                _procsplx(s);
+                if(a == nil){        /* we were interrupted */
+                        assert(c==(Channel*)~0);
+                        return -1;
+                }
+        }else{
+                altexec(a, s);        /* unlocks chanlock, does splx */
+        }
+        _sched();
+        t->chan = Channone;
+_threadnalt++;
+        return a - alts;
+}
+
+static int
+runop(int op, Channel *c, void *v, int nb)
+{
+        int r;
+        Alt a[2];
+
+        /*
+         * we could do this without calling alt,
+         * but the only reason would be performance,
+         * and i'm not convinced it matters.
+         */
+        a[0].op = op;
+        a[0].c = c;
+        a[0].v = v;
+        a[1].op = CHANEND;
+        if(nb)
+                a[1].op = CHANNOBLK;
+        switch(r=alt(a)){
+        case -1:        /* interrupted */
+                return -1;
+        case 1:        /* nonblocking, didn't accomplish anything */
+                assert(nb);
+                return 0;
+        case 0:
+                return 1;
+        default:
+                fprint(2, "ERROR: channel alt returned %d\n", r);
+                abort();
+                return -1;
+        }
+}
+
+int
+recv(Channel *c, void *v)
+{
+        return runop(CHANRCV, c, v, 0);
+}
+
+int
+nbrecv(Channel *c, void *v)
+{
+        return runop(CHANRCV, c, v, 1);
+}
+
+int
+send(Channel *c, void *v)
+{
+        return runop(CHANSND, c, v, 0);
+}
+
+int
+nbsend(Channel *c, void *v)
+{
+        return runop(CHANSND, c, v, 1);
+}
+
+static void
+channelsize(Channel *c, int sz)
+{
+        if(c->e != sz){
+                fprint(2, "expected channel with elements of size %d, got size %d",
+                        sz, c->e);
+                abort();
+        }
+}
+
+int
+sendul(Channel *c, ulong v)
+{
+        channelsize(c, sizeof(ulong));
+        return send(c, &v);
+}
+
+ulong
+recvul(Channel *c)
+{
+        ulong v;
+
+        channelsize(c, sizeof(ulong));
+        if(recv(c, &v) < 0)
+                return ~0;
+        return v;
+}
+
+int
+sendp(Channel *c, void *v)
+{
+        channelsize(c, sizeof(void*));
+        return send(c, &v);
+}
+
+void*
+recvp(Channel *c)
+{
+        void *v;
+
+        channelsize(c, sizeof(void*));
+        if(recv(c, &v) < 0)
+                return nil;
+        return v;
+}
+
+int
+nbsendul(Channel *c, ulong v)
+{
+        channelsize(c, sizeof(ulong));
+        return nbsend(c, &v);
+}
+
+ulong
+nbrecvul(Channel *c)
+{
+        ulong v;
+
+        channelsize(c, sizeof(ulong));
+        if(nbrecv(c, &v) == 0)
+                return 0;
+        return v;
+}
+
+int
+nbsendp(Channel *c, void *v)
+{
+        channelsize(c, sizeof(void*));
+        return nbsend(c, &v);
+}
+
+void*
+nbrecvp(Channel *c)
+{
+        void *v;
+
+        channelsize(c, sizeof(void*));
+        if(nbrecv(c, &v) == 0)
+                return nil;
+        return v;
+}
+
+static int
+emptyentry(Channel *c)
+{
+        int i, extra;
+
+        assert((c->nentry==0 && c->qentry==nil) || (c->nentry && c->qentry));
+
+        for(i=0; inentry; i++)
+                if(c->qentry[i]==nil)
+                        return i;
+
+        extra = 16;
+        c->nentry += extra;
+if(c->nentry > _threadhighnentry) _threadhighnentry = c->nentry;
+        c->qentry = realloc((void*)c->qentry, c->nentry*sizeof(c->qentry[0]));
+        if(c->qentry == nil)
+                sysfatal("realloc channel entries: %r");
+        _threadmemset(&c->qentry[i], 0, extra*sizeof(c->qentry[0]));
+        return i;
+}
+
+static void
+enqueue(Alt *a, Channel **c)
+{
+        int i;
+
+        _threaddebug(DBGCHAN, "Queuing alt %p on channel %p", a, a->c);
+        a->tag = c;
+        i = emptyentry(a->c);
+        a->c->qentry[i] = a;
+}
+
+static void
+dequeue(Alt *a)
+{
+        int i;
+        Channel *c;
+
+        c = a->c;
+        for(i=0; inentry; i++)
+                if(c->qentry[i]==a){
+                        _threaddebug(DBGCHAN, "Dequeuing alt %p from channel %p", a, a->c);
+                        c->qentry[i] = nil;
+                        if(c->freed)
+                                _chanfree(c);
+                        return;
+                }
+}
+
+static void*
+altexecbuffered(Alt *a, int willreplace)
+{
+        uchar *v;
+        Channel *c;
+
+        c = a->c;
+        /* use buffered channel queue */
+        if(a->op==CHANRCV && c->n > 0){
+                _threaddebug(DBGCHAN, "buffer recv alt %p chan %p", a, c);
+                v = c->v + c->e*(c->f%c->s);
+                if(!willreplace)
+                        c->n--;
+                c->f++;
+                return v;
+        }
+        if(a->op==CHANSND && c->n < c->s){
+                _threaddebug(DBGCHAN, "buffer send alt %p chan %p", a, c);
+                v = c->v + c->e*((c->f+c->n)%c->s);
+                if(!willreplace)
+                        c->n++;
+                return v;
+        }
+        abort();
+        return nil;
+}
+
+static void
+altcopy(void *dst, void *src, int sz)
+{
+        if(dst){
+                if(src)
+                        memmove(dst, src, sz);
+                else
+                        _threadmemset(dst, 0, sz);
+        }
+}
+
+static int
+altexec(Alt *a, int spl)
+{
+        volatile Alt *b;
+        int i, n, otherop;
+        Channel *c;
+        void *me, *waiter, *buf;
+
+        c = a->c;
+
+        /* rendezvous with others */
+        otherop = (CHANSND+CHANRCV) - a->op;
+        n = 0;
+        b = nil;
+        me = a->v;
+        for(i=0; inentry; i++)
+                if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil)
+                        if(nrand(++n) == 0)
+                                b = c->qentry[i];
+        if(b != nil){
+                _threaddebug(DBGCHAN, "rendez %s alt %p chan %p alt %p", a->op==CHANRCV?"recv":"send", a, c, b);
+                waiter = b->v;
+                if(c->s && c->n){
+                        /*
+                         * if buffer is full and there are waiters
+                         * and we're meeting a waiter,
+                         * we must be receiving.
+                         *
+                         * we use the value in the channel buffer,
+                         * copy the waiter's value into the channel buffer
+                         * on behalf of the waiter, and then wake the waiter.
+                         */
+                        if(a->op!=CHANRCV)
+                                abort();
+                        buf = altexecbuffered(a, 1);
+                        altcopy(me, buf, c->e);
+                        altcopy(buf, waiter, c->e);
+                }else{
+                        if(a->op==CHANRCV)
+                                altcopy(me, waiter, c->e);
+                        else
+                                altcopy(waiter, me, c->e);
+                }
+                *b->tag = c;        /* commits us to rendezvous */
+                _threaddebug(DBGCHAN, "unlocking the chanlock");
+                unlock(&chanlock);
+                _procsplx(spl);
+                _threaddebug(DBGCHAN, "chanlock is %lud", *(ulong*)&chanlock);
+                while(_threadrendezvous((ulong)b->tag, 0) == ~0)
+                        ;
+                return 1;
+        }
+
+        buf = altexecbuffered(a, 0);
+        if(a->op==CHANRCV)
+                altcopy(me, buf, c->e);
+        else
+                altcopy(buf, me, c->e);
+
+        unlock(&chanlock);
+        _procsplx(spl);
+        return 1;
+}
diff --git a/src/libthread/chanprint.c b/src/libthread/chanprint.c
t@@ -0,0 +1,18 @@
+#include "threadimpl.h"
+
+int
+chanprint(Channel *c, char *fmt, ...)
+{
+        va_list arg;
+        char *p;
+        int n;
+
+        va_start(arg, fmt);
+        p = vsmprint(fmt, arg);
+        va_end(arg);
+        if(p == nil)
+                sysfatal("vsmprint failed: %r");
+        n = sendp(c, p);
+        yield();        /* let recipient handle message immediately */
+        return n;
+}
diff --git a/src/libthread/create.c b/src/libthread/create.c
t@@ -0,0 +1,182 @@
+#include "threadimpl.h"
+
+#define free
+Pqueue _threadpq;
+
+static int nextID(void);
+
+/*
+ * Create and initialize a new Thread structure attached to a given proc.
+ */
+
+typedef struct Stack Stack;
+struct Stack {
+        ulong magic;
+        Thread *thr;
+        Stack *next;
+        uchar buf[STKSIZE-12];
+};
+
+static Stack *stkfree;
+static Lock stklock;
+
+void
+_stackfree(void *v)
+{
+        Stack *s;
+
+        s = v;
+        lock(&stklock);
+        s->thr = nil;
+        s->magic = 0;
+        s->next = stkfree;
+        stkfree = s;
+        unlock(&stklock);
+}
+
+static Stack*
+stackalloc(void)
+{
+        char *buf;
+        Stack *s;
+        int i;
+
+        lock(&stklock);
+        while(stkfree == nil){
+                unlock(&stklock);
+                assert(STKSIZE == sizeof(Stack));
+                buf = malloc(STKSIZE+128*STKSIZE);
+                s = (Stack*)(((ulong)buf+STKSIZE)&~(STKSIZE-1));
+                for(i=0; i<128; i++)
+                        _stackfree(&s[i]);
+                lock(&stklock);
+        }
+        s = stkfree;
+        stkfree = stkfree->next;
+        unlock(&stklock);
+        s->magic = STKMAGIC;
+        return s;
+}
+
+static int
+newthread(Proc *p, void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp)
+{
+        int id;
+        Thread *t;
+        Stack *s;
+
+        if(stacksize < 32)
+                sysfatal("bad stacksize %d", stacksize);
+        t = _threadmalloc(sizeof(Thread), 1);
+        s = stackalloc();
+        s->thr = t;
+        t->stk = (char*)s;
+        t->stksize = STKSIZE;
+        _threaddebugmemset(s->buf, 0xFE, sizeof s->buf);
+        _threadinitstack(t, f, arg);
+        t->proc = p;
+        t->grp = grp;
+        if(name)
+                t->cmdname = strdup(name);
+        t->id = nextID();
+        id = t->id;
+        t->next = (Thread*)~0;
+        _threaddebug(DBGSCHED, "create thread %d.%d name %s", p->pid, t->id, name);
+        lock(&p->lock);
+        p->nthreads++;
+        if(p->threads.head == nil)
+                p->threads.head = t;
+        else{
+                t->prevt = p->threads.tail;
+                t->prevt->nextt = t;
+        }
+        p->threads.tail = t;
+        t->state = Ready;
+        _threadready(t);
+        unlock(&p->lock);
+        return id;
+}
+
+static int
+nextID(void)
+{
+        static Lock l;
+        static int id;
+        int i;
+
+        lock(&l);
+        i = ++id;
+        unlock(&l);
+        return i;
+}
+        
+int
+procrfork(void (*f)(void *), void *arg, uint stacksize, int rforkflag)
+{
+        Proc *p;
+        int id;
+
+        p = _threadgetproc();
+        assert(p->newproc == nil);
+        p->newproc = _newproc(f, arg, stacksize, nil, p->thread->grp, rforkflag);
+        id = p->newproc->threads.head->id;
+        _sched();
+        return id;
+}
+
+int
+proccreate(void (*f)(void*), void *arg, uint stacksize)
+{
+        return procrfork(f, arg, stacksize, 0);
+}
+
+void
+_freeproc(Proc *p)
+{
+        Thread *t, *nextt;
+
+        for(t = p->threads.head; t; t = nextt){
+                if(t->cmdname)
+                        free(t->cmdname);
+                assert(t->stk != nil);
+                _stackfree((Stack*)t->stk);
+                nextt = t->nextt;
+                free(t);
+        }
+        free(p);
+}
+
+/* 
+ * Create a new thread and schedule it to run.
+ * The thread grp is inherited from the currently running thread.
+ */
+int
+threadcreate(void (*f)(void *arg), void *arg, uint stacksize)
+{
+        return newthread(_threadgetproc(), f, arg, stacksize, nil, threadgetgrp());
+}
+
+/*
+ * Create and initialize a new Proc structure with a single Thread
+ * running inside it.  Add the Proc to the global process list.
+ */
+Proc*
+_newproc(void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp, int rforkflag)
+{
+        Proc *p;
+
+        p = _threadmalloc(sizeof *p, 1);
+        p->pid = -1;
+        p->rforkflag = rforkflag;
+        newthread(p, f, arg, stacksize, name, grp);
+
+        lock(&_threadpq.lock);
+        if(_threadpq.head == nil)
+                _threadpq.head = p;
+        else
+                *_threadpq.tail = p;
+        _threadpq.tail = &p->next;
+        unlock(&_threadpq.lock);
+        return p;
+}
+
diff --git a/src/libthread/debug.c b/src/libthread/debug.c
t@@ -0,0 +1,48 @@
+#include "threadimpl.h"
+
+int _threaddebuglevel;
+
+void
+__threaddebug(ulong flag, char *fmt, ...)
+{
+        char buf[128];
+        va_list arg;
+        Fmt f;
+        Proc *p;
+
+        if((_threaddebuglevel&flag) == 0)
+                return;
+
+        fmtfdinit(&f, 2, buf, sizeof buf);
+
+        p = _threadgetproc();
+        if(p==nil)
+                fmtprint(&f, "noproc ");
+        else if(p->thread)
+                fmtprint(&f, "%d.%d ", p->pid, p->thread->id);
+        else
+                fmtprint(&f, "%d._ ", p->pid);
+
+        va_start(arg, fmt);
+        fmtvprint(&f, fmt, arg);
+        va_end(arg);
+        fmtprint(&f, "\n");
+        fmtfdflush(&f);
+}
+
+void
+_threadassert(char *s)
+{
+        char buf[256];
+        int n;
+        Proc *p;
+
+        p = _threadgetproc();
+        if(p && p->thread)
+                n = sprint(buf, "%d.%d ", p->pid, p->thread->id);
+        else
+                n = 0;
+        snprint(buf+n, sizeof(buf)-n, "%s: assertion failed\n", s);
+        write(2, buf, strlen(buf));
+        abort();
+}
diff --git a/src/libthread/exec-unix.c b/src/libthread/exec-unix.c
t@@ -0,0 +1,124 @@
+#include 
+#include 
+#include "threadimpl.h"
+
+void
+procexec(Channel *pidc, char *prog, char *args[])
+{
+        int n;
+        Proc *p;
+        Thread *t;
+
+        _threaddebug(DBGEXEC, "procexec %s", prog);
+        /* must be only thread in proc */
+        p = _threadgetproc();
+        t = p->thread;
+        if(p->threads.head != t || p->threads.head->nextt != nil){
+                werrstr("not only thread in proc");
+        Bad:
+                if(pidc)
+                        sendul(pidc, ~0);
+                return;
+        }
+
+        /*
+         * We want procexec to behave like exec; if exec succeeds,
+         * never return, and if it fails, return with errstr set.
+         * Unfortunately, the exec happens in another proc since
+         * we have to wait for the exec'ed process to finish.
+         * To provide the semantics, we open a pipe with the 
+         * write end close-on-exec and hand it to the proc that
+         * is doing the exec.  If the exec succeeds, the pipe will
+         * close so that our read below fails.  If the exec fails,
+         * then the proc doing the exec sends the errstr down the
+         * pipe to us.
+         */
+        if(pipe(p->exec.fd) < 0)
+                goto Bad;
+        if(fcntl(p->exec.fd[1], F_SETFD, 1) < 0)
+                goto Bad;
+
+        /* exec in parallel via the scheduler */
+        assert(p->needexec==0);
+        p->exec.prog = prog;
+        p->exec.args = args;
+        p->needexec = 1;
+        _sched();
+
+        close(p->exec.fd[1]);
+        if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){        /* exec failed */
+                p->exitstr[n] = '\0';
+                errstr(p->exitstr, ERRMAX);
+                close(p->exec.fd[0]);
+                goto Bad;
+        }
+        close(p->exec.fd[0]);
+
+        if(pidc)
+                sendul(pidc, t->ret);
+
+        /* wait for exec'ed program, then exit */
+        _schedexecwait();
+}
+
+void
+procexecl(Channel *pidc, char *f, ...)
+{
+        procexec(pidc, f, &f+1);
+}
+
+void
+_schedexecwait(void)
+{
+        int pid;
+        Channel *c;
+        Proc *p;
+        Thread *t;
+        Waitmsg *w;
+
+        p = _threadgetproc();
+        t = p->thread;
+        pid = t->ret;
+        _threaddebug(DBGEXEC, "_schedexecwait %d", t->ret);
+
+        for(;;){
+                w = wait();
+                if(w == nil)
+                        break;
+                if(w->pid == pid)
+                        break;
+                free(w);
+        }
+        if(w != nil){
+                if((c = _threadwaitchan) != nil)
+                        sendp(c, w);
+                else
+                        free(w);
+        }
+        threadexits("procexec");
+}
+
+static void
+efork(void *ve)
+{
+        char buf[ERRMAX];
+        Execargs *e;
+
+        e = ve;
+        _threaddebug(DBGEXEC, "_schedexec %s", e->prog);
+        close(e->fd[0]);
+        execv(e->prog, e->args);
+        _threaddebug(DBGEXEC, "_schedexec failed: %r");
+        rerrstr(buf, sizeof buf);
+        if(buf[0]=='\0')
+                strcpy(buf, "exec failed");
+        write(e->fd[1], buf, strlen(buf));
+        close(e->fd[1]);
+        _exits(buf);
+}
+
+int
+_schedexec(Execargs *e)
+{
+        return ffork(RFFDG|RFPROC|RFMEM, efork, e);
+}
diff --git a/src/libthread/exec.c b/src/libthread/exec.c
t@@ -0,0 +1,77 @@
+#include "threadimpl.h"
+
+#define PIPEMNT        "/mnt/temp"
+
+void
+procexec(Channel *pidc, char *prog, char *args[])
+{
+        int n;
+        Proc *p;
+        Thread *t;
+
+        _threaddebug(DBGEXEC, "procexec %s", prog);
+        /* must be only thread in proc */
+        p = _threadgetproc();
+        t = p->thread;
+        if(p->threads.head != t || p->threads.head->nextt != nil){
+                werrstr("not only thread in proc");
+        Bad:
+                if(pidc)
+                        sendul(pidc, ~0);
+                return;
+        }
+
+        /*
+         * We want procexec to behave like exec; if exec succeeds,
+         * never return, and if it fails, return with errstr set.
+         * Unfortunately, the exec happens in another proc since
+         * we have to wait for the exec'ed process to finish.
+         * To provide the semantics, we open a pipe with the 
+         * write end close-on-exec and hand it to the proc that
+         * is doing the exec.  If the exec succeeds, the pipe will
+         * close so that our read below fails.  If the exec fails,
+         * then the proc doing the exec sends the errstr down the
+         * pipe to us.
+         */
+        if(bind("#|", PIPEMNT, MREPL) < 0)
+                goto Bad;
+        if((p->exec.fd[0] = open(PIPEMNT "/data", OREAD)) < 0){
+                unmount(nil, PIPEMNT);
+                goto Bad;
+        }
+        if((p->exec.fd[1] = open(PIPEMNT "/data1", OWRITE|OCEXEC)) < 0){
+                close(p->exec.fd[0]);
+                unmount(nil, PIPEMNT);
+                goto Bad;
+        }
+        unmount(nil, PIPEMNT);
+
+        /* exec in parallel via the scheduler */
+        assert(p->needexec==0);
+        p->exec.prog = prog;
+        p->exec.args = args;
+        p->needexec = 1;
+        _sched();
+
+        close(p->exec.fd[1]);
+        if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){        /* exec failed */
+                p->exitstr[n] = '\0';
+                errstr(p->exitstr, ERRMAX);
+                close(p->exec.fd[0]);
+                goto Bad;
+        }
+        close(p->exec.fd[0]);
+
+        if(pidc)
+                sendul(pidc, t->ret);
+
+        /* wait for exec'ed program, then exit */
+        _schedexecwait();
+}
+
+void
+procexecl(Channel *pidc, char *f, ...)
+{
+        procexec(pidc, f, &f+1);
+}
+
diff --git a/src/libthread/exit.c b/src/libthread/exit.c
t@@ -0,0 +1,63 @@
+#include "threadimpl.h"
+#include 
+
+char *_threadexitsallstatus;
+Channel *_threadwaitchan;
+
+void
+threadexits(char *exitstr)
+{
+        Proc *p;
+        Thread *t;
+
+        p = _threadgetproc();
+        t = p->thread;
+        t->moribund = 1;
+        if(exitstr==nil)
+                exitstr="";
+        utfecpy(p->exitstr, p->exitstr+ERRMAX, exitstr);
+        _sched();
+}
+
+void
+threadexitsall(char *exitstr)
+{
+        Proc *p;
+        int *pid;
+        int i, npid, mypid;
+
+        if(exitstr == nil)
+                exitstr = "";
+        _threadexitsallstatus = exitstr;
+        _threaddebug(DBGSCHED, "_threadexitsallstatus set to %p", _threadexitsallstatus);
+        mypid = _threadgetpid();
+
+        /*
+         * signal others.
+         * copying all the pids first avoids other threads
+         * teardown procedures getting in the way.
+         */
+        lock(&_threadpq.lock);
+        npid = 0;
+        for(p=_threadpq.head; p; p=p->next)
+                npid++;
+        pid = _threadmalloc(npid*sizeof(pid[0]), 0);
+        npid = 0;
+        for(p = _threadpq.head; p; p=p->next)
+                pid[npid++] = p->pid;
+        unlock(&_threadpq.lock);
+        for(i=0; i
diff --git a/src/libthread/getpid.c b/src/libthread/getpid.c
t@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include 
+
+int
+_threadgetpid(void)
+{
+        return getpid();
+}
diff --git a/src/libthread/id.c b/src/libthread/id.c
t@@ -0,0 +1,135 @@
+#include "threadimpl.h"
+
+int
+threadid(void)
+{
+        return _threadgetproc()->thread->id;
+}
+
+int
+threadpid(int id)
+{
+        int pid;
+        Proc *p;
+        Thread *t;
+
+        if (id < 0)
+                return -1;
+        if (id == 0)
+                return _threadgetproc()->pid;
+        lock(&_threadpq.lock);
+        for (p = _threadpq.head; p->next; p = p->next){
+                lock(&p->lock);
+                for (t = p->threads.head; t; t = t->nextt)
+                        if (t->id == id){
+                                pid = p->pid;
+                                unlock(&p->lock);
+                                unlock(&_threadpq.lock);
+                                return pid;
+                        }
+                unlock(&p->lock);
+        }
+        unlock(&_threadpq.lock);
+        return -1;
+}
+
+int
+threadsetgrp(int ng)
+{
+        int og;
+        Thread *t;
+
+        t = _threadgetproc()->thread;
+        og = t->grp;
+        t->grp = ng;
+        return og;
+}
+
+int
+threadgetgrp(void)
+{
+        return _threadgetproc()->thread->grp;
+}
+
+void
+threadsetname(char *name)
+{
+/*
+        int fd, n;
+        char buf[128], *s;
+*/
+        Proc *p;
+        Thread *t;
+
+        p = _threadgetproc();
+        t = p->thread;
+        if (t->cmdname)
+                free(t->cmdname);
+        t->cmdname = strdup(name);
+/* Plan 9 only 
+        if(p->nthreads == 1){
+                snprint(buf, sizeof buf, "#p/%d/args", getpid());
+                if((fd = open(buf, OWRITE)) >= 0){
+                        snprint(buf, sizeof buf, "%s [%s]", argv0, name);
+                        n = strlen(buf)+1;
+                        s = strchr(buf, ' ');
+                        if(s)
+                                *s = '\0';
+                        write(fd, buf, n);
+                        close(fd);
+                }
+        }
+*/
+}
+
+char*
+threadgetname(void)
+{
+        return _threadgetproc()->thread->cmdname;
+}
+
+void**
+threaddata(void)
+{
+        return &_threadgetproc()->thread->udata[0];
+}
+
+void**
+procdata(void)
+{
+        return &_threadgetproc()->udata;
+}
+
+static Lock privlock;
+static int privmask = 1;
+
+int
+tprivalloc(void)
+{
+        int i;
+
+        lock(&privlock);
+        for(i=0; i= NPRIV)
+                abort();
+        lock(&privlock);
+        privmask &= ~(1<thread->udata[i];
+}
diff --git a/src/libthread/iocall.c b/src/libthread/iocall.c
t@@ -0,0 +1,49 @@
+#include "threadimpl.h"
+
+long
+iocall(Ioproc *io, long (*op)(va_list*), ...)
+{
+        int ret, inted;
+        Ioproc *msg;
+
+        if(send(io->c, &io) == -1){
+                werrstr("interrupted");
+                return -1;
+        }
+        assert(!io->inuse);
+        io->inuse = 1;
+        io->op = op;
+        va_start(io->arg, op);
+        msg = io;
+        inted = 0;
+        while(send(io->creply, &msg) == -1){
+                msg = nil;
+                inted = 1;
+        }
+        if(inted){
+                werrstr("interrupted");
+                return -1;
+        }
+
+        /*
+         * If we get interrupted, we have stick around so that
+         * the IO proc has someone to talk to.  Send it an interrupt
+         * and try again.
+         */
+        inted = 0;
+        while(recv(io->creply, nil) == -1){
+                inted = 1;
+                iointerrupt(io);
+        }
+        USED(inted);
+        va_end(io->arg);
+        ret = io->ret;
+        if(ret < 0)
+                errstr(io->err, sizeof io->err);
+        io->inuse = 0;
+
+        /* release resources */
+        while(send(io->creply, &io) == -1)
+                ;
+        return ret;
+}
diff --git a/src/libthread/ioclose.c b/src/libthread/ioclose.c
t@@ -0,0 +1,16 @@
+#include "threadimpl.h"
+
+static long
+_ioclose(va_list *arg)
+{
+        int fd;
+
+        fd = va_arg(*arg, int);
+        return close(fd);
+}
+
+int
+ioclose(Ioproc *io, int fd)
+{
+        return iocall(io, _ioclose, fd);
+}
diff --git a/src/libthread/iodial.c b/src/libthread/iodial.c
t@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_iodial(va_list *arg)
+{
+        char *addr, *local, *dir;
+        int *cdfp;
+
+        addr = va_arg(*arg, char*);
+        local = va_arg(*arg, char*);
+        dir = va_arg(*arg, char*);
+        cdfp = va_arg(*arg, int*);
+
+        return dial(addr, local, dir, cdfp);
+}
+
+int
+iodial(Ioproc *io, char *addr, char *local, char *dir, int *cdfp)
+{
+        return iocall(io, _iodial, addr, local, dir, cdfp);
+}
diff --git a/src/libthread/ioopen.c b/src/libthread/ioopen.c
t@@ -0,0 +1,20 @@
+#include 
+#include 
+#include "threadimpl.h"
+
+static long
+_ioopen(va_list *arg)
+{
+        char *path;
+        int mode;
+
+        path = va_arg(*arg, char*);
+        mode = va_arg(*arg, int);
+        return open(path, mode);
+}
+
+int
+ioopen(Ioproc *io, char *path, int mode)
+{
+        return iocall(io, _ioopen, path, mode);
+}
diff --git a/src/libthread/ioproc.3 b/src/libthread/ioproc.3
t@@ -0,0 +1,179 @@
+.TH IOPROC 2
+.SH NAME
+closeioproc,
+iocall,
+ioclose,
+iointerrupt,
+iodial,
+ioopen,
+ioproc,
+ioread,
+ioreadn,
+iowrite \- slave I/O processes for threaded programs
+.SH SYNOPSIS
+.PP
+.de XX
+.ift .sp 0.5
+.ifn .sp
+..
+.EX
+.ta \w'Ioproc* 'u
+#include 
+#include 
+#include 
+.sp
+typedef struct Ioproc Ioproc;
+.sp
+Ioproc*        ioproc(void);
+.XX
+int        ioopen(Ioproc *io, char *file, int omode);
+int        ioclose(Ioproc *io, int fd);
+long        ioread(Ioproc *io, int fd, void *a, long n);
+long        ioreadn(Ioproc *io, int fd, void *a, long n);
+long        iowrite(Ioproc *io, int fd, void *a, long n);
+int        iodial(Ioproc *io, char *addr, char *local, char *dir, char *cdfp);
+.XX
+void        iointerrupt(Ioproc *io);
+void        closeioproc(Ioproc *io);
+.XX
+long        iocall(Ioproc *io, long (*op)(va_list *arg), ...);
+.EE
+.SH DESCRIPTION
+.PP
+These routines provide access to I/O in slave procs.
+Since the I/O itself is done in a slave proc, other threads
+in the calling proc can run while the calling thread
+waits for the I/O to complete.
+.PP
+.I Ioproc
+forks a new slave proc and returns a pointer to the
+.B Ioproc
+associated with it.
+.I Ioproc
+uses
+.I mallocz
+and
+.IR proccreate ;
+if either fails, it calls
+.I sysfatal
+rather than return an error.
+.PP
+.IR Ioopen ,
+.IR ioclose ,
+.IR ioread ,
+.IR ioreadn ,
+.IR iowrite ,
+and
+.IR iodial
+are execute the
+similarly named library or system calls
+(see
+.IR open (2),
+.IR read (2),
+and
+.IR dial (2))
+in the slave process associated with
+.IR io .
+It is an error to execute more than one call
+at a time in an I/O proc.
+.PP
+.I Iointerrupt
+interrupts the call currently executing in the I/O proc.
+If no call is executing,
+.IR iointerrupt
+is a no-op.
+.PP
+.I Closeioproc
+terminates the I/O proc and frees the associated
+.B Ioproc .
+.PP
+.I Iocall
+is a primitive that may be used to implement
+more slave I/O routines.
+.I Iocall
+arranges for
+.I op
+to be called in
+.IR io 's
+proc, with
+.I arg
+set to the variable parameter list,
+returning the value that
+.I op
+returns.
+.SH EXAMPLE
+Relay messages between two file descriptors,
+counting the total number of bytes seen:
+.IP
+.EX
+.ta +\w'xxxx'u +\w'xxxx'u +\w'xxxx'u
+int tot;
+
+void
+relaythread(void *v)
+{
+        int *fd, n;
+        char buf[1024];
+        Ioproc *io;
+
+        fd = v;
+        io = ioproc();
+        while((n = ioread(io, fd[0], buf, sizeof buf)) > 0){
+                if(iowrite(io, fd[1], buf, n) != n)
+                        sysfatal("iowrite: %r");
+                tot += n;
+        }
+        closeioproc(io);
+}
+
+void
+relay(int fd0, int fd1)
+{
+        int fd[4];
+
+        fd[0] = fd[3] = fd0;
+        fd[1] = fd[2] = fd1;
+        threadcreate(relaythread, fd, 8192);
+        threadcreate(relaythread, fd+2, 8192);
+}
+.EE
+.LP
+If the two
+.I relaythread
+instances were running in different procs, the
+common access to
+.I tot
+would be unsafe.
+.EE
+.PP
+Implement
+.IR ioread :
+.IP
+.EX
+static long
+_ioread(va_list *arg)
+{
+        int fd;
+        void *a;
+        long n;
+
+        fd = va_arg(*arg, int);
+        a = va_arg(*arg, void*);
+        n = va_arg(*arg, long);
+        return read(fd, a, n);
+}
+
+long
+ioread(Ioproc *io, int fd, void *a, long n)
+{
+        return iocall(io, _ioread, fd, a, n);
+}
+.EE
+.SH SOURCE
+.B /sys/src/libthread/io*.c
+.SH SEE ALSO
+.IR dial (2),
+.IR open (2),
+.IR read (2),
+.IR thread (2)
+
diff --git a/src/libthread/ioproc.c b/src/libthread/ioproc.c
t@@ -0,0 +1,74 @@
+#include "threadimpl.h"
+
+enum
+{
+        STACK = 8192,
+};
+
+void
+iointerrupt(Ioproc *io)
+{
+        if(!io->inuse)
+                return;
+        threadint(io->tid);
+}
+
+static void
+xioproc(void *a)
+{
+        Ioproc *io, *x;
+        io = a;
+        /*
+         * first recvp acquires the ioproc.
+         * second tells us that the data is ready.
+         */
+        for(;;){
+                while(recv(io->c, &x) == -1)
+                        ;
+                if(x == 0)        /* our cue to leave */
+                        break;
+                assert(x == io);
+
+                /* caller is now committed -- even if interrupted he'll return */
+                while(recv(io->creply, &x) == -1)
+                        ;
+                if(x == 0)        /* caller backed out */
+                        continue;
+                assert(x == io);
+
+                io->ret = io->op(&io->arg);
+                if(io->ret < 0)
+                        rerrstr(io->err, sizeof io->err);
+                while(send(io->creply, &io) == -1)
+                        ;
+                while(recv(io->creply, &x) == -1)
+                        ;
+        }
+}
+
+Ioproc*
+ioproc(void)
+{
+        Ioproc *io;
+
+        io = mallocz(sizeof(*io), 1);
+        if(io == nil)
+                sysfatal("ioproc malloc: %r");
+        io->c = chancreate(sizeof(void*), 0);
+        io->creply = chancreate(sizeof(void*), 0);
+        io->tid = proccreate(xioproc, io, STACK);
+        return io;
+}
+
+void
+closeioproc(Ioproc *io)
+{
+        if(io == nil)
+                return;
+        iointerrupt(io);
+        while(send(io->c, 0) == -1)
+                ;
+        chanfree(io->c);
+        chanfree(io->creply);
+        free(io);
+}
diff --git a/src/libthread/ioread.c b/src/libthread/ioread.c
t@@ -0,0 +1,20 @@
+#include "threadimpl.h"
+
+static long
+_ioread(va_list *arg)
+{
+        int fd;
+        void *a;
+        long n;
+
+        fd = va_arg(*arg, int);
+        a = va_arg(*arg, void*);
+        n = va_arg(*arg, long);
+        return read(fd, a, n);
+}
+
+long
+ioread(Ioproc *io, int fd, void *a, long n)
+{
+        return iocall(io, _ioread, fd, a, n);
+}
diff --git a/src/libthread/ioreadn.c b/src/libthread/ioreadn.c
t@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_ioreadn(va_list *arg)
+{
+        int fd;
+        void *a;
+        long n;
+
+        fd = va_arg(*arg, int);
+        a = va_arg(*arg, void*);
+        n = va_arg(*arg, long);
+        n = readn(fd, a, n);
+        return n;
+}
+
+long
+ioreadn(Ioproc *io, int fd, void *a, long n)
+{
+        return iocall(io, _ioreadn, fd, a, n);
+}
diff --git a/src/libthread/iosleep.c b/src/libthread/iosleep.c
t@@ -0,0 +1,16 @@
+#include "threadimpl.h"
+
+static long
+_iosleep(va_list *arg)
+{
+        long n;
+
+        n = va_arg(*arg, long);
+        return sleep(n);
+}
+
+int
+iosleep(Ioproc *io, long n)
+{
+        return iocall(io, _iosleep, n);
+}
diff --git a/src/libthread/iowrite.c b/src/libthread/iowrite.c
t@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_iowrite(va_list *arg)
+{
+        int fd;
+        void *a;
+        long n;
+
+        fd = va_arg(*arg, int);
+        a = va_arg(*arg, void*);
+        n = va_arg(*arg, long);
+        n = write(fd, a, n);
+        return n;
+}
+
+long
+iowrite(Ioproc *io, int fd, void *a, long n)
+{
+        return iocall(io, _iowrite, fd, a, n);
+}
diff --git a/src/libthread/kill.c b/src/libthread/kill.c
t@@ -0,0 +1,89 @@
+#include "threadimpl.h"
+#include 
+
+static void tinterrupt(Proc*, Thread*);
+
+static void
+threadxxxgrp(int grp, int dokill)
+{
+        Proc *p;
+        Thread *t;
+
+        lock(&_threadpq.lock);
+        for(p=_threadpq.head; p; p=p->next){
+                lock(&p->lock);
+                for(t=p->threads.head; t; t=t->nextt)
+                        if(t->grp == grp){
+                                if(dokill)
+                                        t->moribund = 1;
+                                tinterrupt(p, t);
+                        }
+                unlock(&p->lock);
+        }
+        unlock(&_threadpq.lock);
+        _threadbreakrendez();
+}
+
+static void
+threadxxx(int id, int dokill)
+{
+        Proc *p;
+        Thread *t;
+
+        lock(&_threadpq.lock);
+        for(p=_threadpq.head; p; p=p->next){
+                lock(&p->lock);
+                for(t=p->threads.head; t; t=t->nextt)
+                        if(t->id == id){
+                                if(dokill)
+                                        t->moribund = 1;
+                                tinterrupt(p, t);
+                                unlock(&p->lock);
+                                unlock(&_threadpq.lock);
+                                _threadbreakrendez();
+                                return;
+                        }
+                unlock(&p->lock);
+        }
+        unlock(&_threadpq.lock);
+        _threaddebug(DBGNOTE, "Can't find thread to kill");
+        return;
+}
+
+void
+threadkillgrp(int grp)
+{
+        threadxxxgrp(grp, 1);
+}
+
+void
+threadkill(int id)
+{
+        threadxxx(id, 1);
+}
+
+void
+threadintgrp(int grp)
+{
+        threadxxxgrp(grp, 0);
+}
+
+void
+threadint(int id)
+{
+        threadxxx(id, 0);
+}
+
+static void
+tinterrupt(Proc *p, Thread *t)
+{
+        switch(t->state){
+        case Running:
+                kill(p->pid, SIGINT);
+        //        postnote(PNPROC, p->pid, "threadint");
+                break;
+        case Rendezvous:
+                _threadflagrendez(t);
+                break;
+        }
+}
diff --git a/src/libthread/label.h b/src/libthread/label.h
t@@ -0,0 +1,24 @@
+/*
+ * setjmp and longjmp, but our own because some (stupid) c libraries
+ * assume longjmp is only used to move up the stack, and error out
+ * if you do otherwise.
+ */
+
+typedef struct Label Label;
+#define LABELDPC 0
+
+#if defined (__i386__) && (defined(__FreeBSD__) || defined(__linux__))
+struct Label
+{
+        ulong pc;
+        ulong bx;
+        ulong sp;
+        ulong bp;
+        ulong si;
+        ulong di;
+};
+#else
+#error "Unknown or unsupported architecture"
+#endif
+
+
diff --git a/src/libthread/lib.c b/src/libthread/lib.c
t@@ -0,0 +1,35 @@
+#include "threadimpl.h"
+
+static long totalmalloc;
+
+void*
+_threadmalloc(long size, int z)
+{
+        void *m;
+
+        m = malloc(size);
+        if (m == nil)
+                sysfatal("Malloc of size %ld failed: %r\n", size);
+        setmalloctag(m, getcallerpc(&size));
+        totalmalloc += size;
+        if (size > 1000000) {
+                fprint(2, "Malloc of size %ld, total %ld\n", size, totalmalloc);
+                abort();
+        }
+        if (z)
+                _threadmemset(m, 0, size);
+        return m;
+}
+
+void
+_threadsysfatal(char *fmt, va_list arg)
+{
+        char buf[1024];        /* size doesn't matter; we're about to exit */
+
+        vseprint(buf, buf+sizeof(buf), fmt, arg);
+        if(argv0)
+                fprint(2, "%s: %s\n", argv0, buf);
+        else
+                fprint(2, "%s\n", buf);
+        threadexitsall(buf);
+}
diff --git a/src/libthread/main.c b/src/libthread/main.c
t@@ -0,0 +1,124 @@
+#include "threadimpl.h"
+#include 
+
+typedef struct Mainarg Mainarg;
+struct Mainarg
+{
+        int        argc;
+        char        **argv;
+};
+
+int        mainstacksize;
+int        _threadnotefd;
+int        _threadpasserpid;
+static void mainlauncher(void*);
+extern void (*_sysfatal)(char*, va_list);
+
+void
+_threaddie(int x)
+{
+        extern char *_threadexitsallstatus;
+        USED(x);
+
+        if(_threadexitsallstatus)
+                exit(_threadexitsallstatus[0] ? 1 : 0);
+}
+
+int
+main(int argc, char **argv)
+{
+        Mainarg *a;
+        Proc *p;
+
+        signal(SIGTERM, _threaddie);
+//        rfork(RFREND);
+
+//_threaddebuglevel = (DBGSCHED|DBGCHAN|DBGREND)^~0;
+        _systhreadinit();
+        _qlockinit(_threadrendezvous);
+        _sysfatal = _threadsysfatal;
+//        notify(_threadnote);
+        if(mainstacksize == 0)
+                mainstacksize = 32*1024;
+
+        a = _threadmalloc(sizeof *a, 1);
+        a->argc = argc;
+        a->argv = argv;
+
+        p = _newproc(mainlauncher, a, mainstacksize, "threadmain", 0, 0);
+        _schedinit(p);
+        abort();        /* not reached */
+        return 0;
+}
+
+static void
+mainlauncher(void *arg)
+{
+        Mainarg *a;
+
+        a = arg;
+        threadmain(a->argc, a->argv);
+        threadexits("threadmain");
+}
+
+void
+_threadsignal(void)
+{
+}
+
+void
+_threadsignalpasser(void)
+{
+}
+
+int
+_schedfork(Proc *p)
+{
+        return ffork(RFMEM|RFNOWAIT, _schedinit, p);
+}
+
+void
+_schedexit(Proc *p)
+{
+        char ex[ERRMAX];
+        Proc **l;
+
+        lock(&_threadpq.lock);
+        for(l=&_threadpq.head; *l; l=&(*l)->next){
+                if(*l == p){
+                        *l = p->next;
+                        if(*l == nil)
+                                _threadpq.tail = l;
+                        break;
+                }
+        }
+        unlock(&_threadpq.lock);
+
+        strncpy(ex, p->exitstr, sizeof ex);
+        ex[sizeof ex-1] = '\0';
+        free(p);
+        _exit(ex[0]);
+}
+
+int
+nrand(int n)
+{
+        return random()%n;
+}
+
+void
+_systhreadinit(void)
+{
+}
+
+void
+threadstats(void)
+{
+        extern int _threadnrendez, _threadhighnrendez,
+                _threadnalt, _threadhighnentry;
+        fprint(2, "*** THREAD LIBRARY STATS ***\n");
+        fprint(2, "nrendez %d high simultaneous %d\n", 
+                _threadnrendez, _threadhighnrendez);
+        fprint(2, "nalt %d high simultaneous entry %d\n",
+                _threadnalt, _threadhighnentry);
+}
diff --git a/src/libthread/memset.c b/src/libthread/memset.c
t@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include 
+
+void
+_threadmemset(void *v, int c, int n)
+{
+        memset(v, c, n);
+}
diff --git a/src/libthread/memsetd.c b/src/libthread/memsetd.c
t@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include 
+
+void
+_threaddebugmemset(void *v, int c, int n)
+{
+        memset(v, c, n);
+}
diff --git a/src/libthread/mkfile b/src/libthread/mkfile
t@@ -0,0 +1,2 @@
+<../libutf/mkfile
+
diff --git a/src/libthread/note.c b/src/libthread/note.c
t@@ -0,0 +1,143 @@
+#include "threadimpl.h"
+
+int        _threadnopasser;
+
+#ifdef NOTDEF
+#define        NFN                33
+#define        ERRLEN        48
+typedef struct Note Note;
+struct Note
+{
+        Lock                inuse;
+        Proc                *proc;                /* recipient */
+        char                s[ERRMAX];        /* arg2 */
+};
+
+static Note        notes[128];
+static Note        *enotes = notes+nelem(notes);
+static int                (*onnote[NFN])(void*, char*);
+static int                onnotepid[NFN];
+static Lock        onnotelock;
+
+int
+threadnotify(int (*f)(void*, char*), int in)
+{
+        int i, topid;
+        int (*from)(void*, char*), (*to)(void*, char*);
+
+        if(in){
+                from = nil;
+                to = f;
+                topid = _threadgetproc()->pid;
+        }else{
+                from = f;
+                to = nil;
+                topid = 0;
+        }
+        lock(&onnotelock);
+        for(i=0; ipending)
+                return;
+
+        p->pending = 0;
+        for(n=notes; nproc == p){
+                        for(i=0; ipid || (fn = onnote[i])==nil)
+                                        continue;
+                                if((*fn)(v, n->s))
+                                        break;
+                        }
+                        if(i==NFN){
+                                _threaddebug(DBGNOTE, "Unhandled note %s, proc %p\n", n->s, p);
+                                if(v != nil)
+                                        noted(NDFLT);
+                                else if(strncmp(n->s, "sys:", 4)==0)
+                                        abort();
+                                threadexitsall(n->s);
+                        }
+                        n->proc = nil;
+                        unlock(&n->inuse);
+                }
+        }
+}
+
+void
+_threadnote(void *v, char *s)
+{
+        Proc *p;
+        Note *n;
+
+        _threaddebug(DBGNOTE, "Got note %s", s);
+        if(strncmp(s, "sys:", 4) == 0)
+                noted(NDFLT);
+
+//        if(_threadexitsallstatus){
+//                _threaddebug(DBGNOTE, "Threadexitsallstatus = '%s'\n", _threadexitsallstatus);
+//                _exits(_threadexitsallstatus);
+//        }
+
+        if(strcmp(s, "threadint")==0)
+                noted(NCONT);
+
+        p = _threadgetproc();
+        if(p == nil)
+                noted(NDFLT);
+
+        for(n=notes; ninuse))
+                        break;
+        if(n==enotes)
+                sysfatal("libthread: too many delayed notes");
+        utfecpy(n->s, n->s+ERRMAX, s);
+        n->proc = p;
+        p->pending = 1;
+        if(!p->splhi)
+                delayednotes(p, v);
+        noted(NCONT);
+}
+#endif
+
+int
+_procsplhi(void)
+{
+        int s;
+        Proc *p;
+
+        p = _threadgetproc();
+        s = p->splhi;
+        p->splhi = 1;
+        return s;
+}
+
+void
+_procsplx(int s)
+{
+        Proc *p;
+
+        p = _threadgetproc();
+        p->splhi = s;
+        if(s)
+                return;
+/*
+        if(p->pending)
+                delayednotes(p, nil);
+*/
+}
+
diff --git a/src/libthread/proctab.c b/src/libthread/proctab.c
t@@ -0,0 +1,64 @@
+#include "threadimpl.h"
+
+/* this will need work */
+enum
+{
+        PTABHASH = 257,
+};
+
+static Lock ptablock;
+Proc *ptab[PTABHASH];
+
+void
+_threadsetproc(Proc *p)
+{
+        int h;
+
+        lock(&ptablock);
+        h = ((unsigned)p->pid)%PTABHASH;
+        p->link = ptab[h];
+        unlock(&ptablock);
+        ptab[h] = p;
+}
+
+static Proc*
+__threadgetproc(int rm)
+{
+        Proc **l, *p;
+        int h, pid;
+        Thread *t;
+        ulong *s;
+
+        s = (ulong*)((ulong)&pid & ~(STKSIZE-1));
+        if(s[0] == STKMAGIC){
+                t = (Thread*)s[1];
+                return t->proc;
+        }
+
+        pid = _threadgetpid();
+
+        lock(&ptablock);
+        h = ((unsigned)pid)%PTABHASH;
+        for(l=&ptab[h]; p=*l; l=&p->link){
+                if(p->pid == pid){
+                        if(rm)
+                                *l = p->link;
+                        unlock(&ptablock);
+                        return p;
+                }
+        }
+        unlock(&ptablock);
+        return nil;
+}
+
+Proc*
+_threadgetproc(void)
+{
+        return __threadgetproc(0);
+}
+
+Proc*
+_threaddelproc(void)
+{
+        return __threadgetproc(1);
+}
diff --git a/src/libthread/ref.c b/src/libthread/ref.c
t@@ -0,0 +1,13 @@
+#include "threadimpl.h"
+
+void
+incref(Ref *r)
+{
+        _xinc(&r->ref);
+}
+
+long
+decref(Ref *r)
+{
+        return _xdec(&r->ref);
+}
diff --git a/src/libthread/rendez.c b/src/libthread/rendez.c
t@@ -0,0 +1,104 @@
+#include "threadimpl.h"
+
+Rgrp _threadrgrp;
+static int isdirty;
+int _threadhighnrendez;
+int _threadnrendez;
+static int nrendez;
+
+static ulong
+finish(Thread *t, ulong val)
+{
+        ulong ret;
+
+        ret = t->rendval;
+        t->rendval = val;
+        while(t->state == Running)
+                sleep(0);
+        lock(&t->proc->lock);
+        if(t->state == Rendezvous){        /* not always true: might be Dead */
+                t->state = Ready;
+                _threadready(t);
+        }
+        unlock(&t->proc->lock);
+        return ret;
+}
+
+ulong
+_threadrendezvous(ulong tag, ulong val)
+{
+        ulong ret;
+        Thread *t, **l;
+
+        lock(&_threadrgrp.lock);
+_threadnrendez++;
+        l = &_threadrgrp.hash[tag%nelem(_threadrgrp.hash)];
+        for(t=*l; t; l=&t->rendhash, t=*l){
+                if(t->rendtag==tag){
+                        _threaddebug(DBGREND, "Rendezvous with thread %d.%d", t->proc->pid, t->id);
+                        *l = t->rendhash;
+                        ret = finish(t, val);
+                        --nrendez;
+                        unlock(&_threadrgrp.lock);
+                        return ret;
+                }
+        }
+
+        /* Going to sleep here. */
+        t = _threadgetproc()->thread;
+        t->rendbreak = 0;
+        t->inrendez = 1;
+        t->rendtag = tag;
+        t->rendval = val;
+        t->rendhash = *l;
+        *l = t;
+        t->nextstate = Rendezvous;
+        ++nrendez;
+        if(nrendez > _threadhighnrendez)
+                _threadhighnrendez = nrendez;
+        _threaddebug(DBGREND, "Rendezvous for tag %lud", t->rendtag);
+        unlock(&_threadrgrp.lock);
+        _sched();
+        t->inrendez = 0;
+        _threaddebug(DBGREND, "Woke after rendezvous; val is %lud", t->rendval);
+        return t->rendval;
+}
+
+/*
+ * This is called while holding _threadpq.lock and p->lock,
+ * so we can't lock _threadrgrp.lock.  Instead our caller has 
+ * to call _threadbreakrendez after dropping those locks.
+ */
+void
+_threadflagrendez(Thread *t)
+{
+        t->rendbreak = 1;
+        isdirty = 1;
+}
+
+void
+_threadbreakrendez(void)
+{
+        int i;
+        Thread *t, **l;
+
+        if(isdirty == 0)
+                return;
+        lock(&_threadrgrp.lock);
+        if(isdirty == 0){
+                unlock(&_threadrgrp.lock);
+                return;
+        }
+        isdirty = 0;
+        for(i=0; irendbreak){
+                                *l = t->rendhash;
+                                finish(t, ~0);
+                        }else
+                                 l=&t->rendhash;
+                }
+        }
+        unlock(&_threadrgrp.lock);
+}
diff --git a/src/libthread/rpm.spec b/src/libthread/rpm.spec
t@@ -0,0 +1,26 @@
+Summary: Port of Plan 9's thread library
+Name: libthread
+Version: 2.0
+Release: 1
+Group: Development/C
+Copyright: BSD-like
+Packager: Russ Cox 
+Source: http://pdos.lcs.mit.edu/~rsc/software/libthread-2.0.tgz
+URL: http://pdos.lcs.mit.edu/~rsc/software/#libthread
+
+%description
+Libthread is a port of Plan 9's thread library
+%prep
+%setup
+
+%build
+make
+
+%install
+make install
+
+%files
+/usr/local/include/thread.h
+/usr/local/lib/libthread.a
+/usr/local/man/man3/thread.3
+/usr/local/man/man3/ioproc.3
diff --git a/src/libthread/sched.c b/src/libthread/sched.c
t@@ -0,0 +1,192 @@
+#include "threadimpl.h"
+#include 
+
+//static Thread        *runthread(Proc*);
+
+static char *_psstate[] = {
+        "Dead",
+        "Running",
+        "Ready",
+        "Rendezvous",
+};
+
+static char*
+psstate(int s)
+{
+        if(s < 0 || s >= nelem(_psstate))
+                return "unknown";
+        return _psstate[s];
+}
+
+void
+_schedinit(void *arg)
+{
+        Proc *p;
+        Thread *t;
+        extern void ignusr1(void), _threaddie(int);
+        ignusr1();
+        signal(SIGTERM, _threaddie);
+  
+
+
+        p = arg;
+        p->pid = _threadgetpid();
+        _threadsetproc(p);
+        while(_setlabel(&p->sched))
+                ;
+        _threaddebug(DBGSCHED, "top of schedinit, _threadexitsallstatus=%p", _threadexitsallstatus);
+        if(_threadexitsallstatus)
+                exits(_threadexitsallstatus);
+        lock(&p->lock);
+        if((t=p->thread) != nil){
+                p->thread = nil;
+                if(t->moribund){
+                        assert(t->moribund == 1);
+                        t->state = Dead;
+                        if(t->prevt)
+                                t->prevt->nextt = t->nextt;
+                        else
+                                p->threads.head = t->nextt;
+                        if(t->nextt)
+                                t->nextt->prevt = t->prevt;
+                        else
+                                p->threads.tail = t->prevt;
+                        unlock(&p->lock);
+                        if(t->inrendez){
+                                _threadflagrendez(t);
+                                _threadbreakrendez();
+                        }
+                        _stackfree(t->stk);
+                        free(t->cmdname);
+                        free(t);        /* XXX how do we know there are no references? */
+                        t = nil;
+                        _sched();
+                }
+                if(p->needexec){
+                        t->ret = _schedexec(&p->exec);
+                        p->needexec = 0;
+                }
+                if(p->newproc){
+                        t->ret = _schedfork(p->newproc);
+                        if(t->ret < 0){
+//fprint(2, "_schedfork: %r\n");
+                                abort();
+}
+                        p->newproc = nil;
+                }
+                t->state = t->nextstate;
+                if(t->state == Ready)
+                        _threadready(t);
+        }
+        unlock(&p->lock);
+        _sched();
+}
+
+static inline Thread*
+runthread(Proc *p)
+{
+        Thread *t;
+        Tqueue *q;
+
+        if(p->nthreads==0)
+                return nil;
+        q = &p->ready;
+        lock(&p->readylock);
+        if(q->head == nil){
+                q->asleep = 1;
+                _threaddebug(DBGSCHED, "sleeping for more work");
+                unlock(&p->readylock);
+                while(rendezvous((ulong)q, 0) == ~0){
+                        if(_threadexitsallstatus)
+                                exits(_threadexitsallstatus);
+                }
+                /* lock picked up from _threadready */
+        }
+        t = q->head;
+        q->head = t->next;
+        unlock(&p->readylock);
+        return t;
+}
+
+void
+_sched(void)
+{
+        Proc *p;
+        Thread *t;
+
+Resched:
+        p = _threadgetproc();
+//fprint(2, "p %p\n", p);
+        if((t = p->thread) != nil){
+                if((ulong)&p < (ulong)t->stk){        /* stack overflow */
+                        fprint(2, "stack overflow %lux %lux\n", (ulong)&p, (ulong)t->stk);
+                        abort();
+                }
+        //        _threaddebug(DBGSCHED, "pausing, state=%s set %p goto %p",
+        //                psstate(t->state), &t->sched, &p->sched);
+                if(_setlabel(&t->sched)==0)
+                        _gotolabel(&p->sched);
+                return;
+        }else{
+                t = runthread(p);
+                if(t == nil){
+                        _threaddebug(DBGSCHED, "all threads gone; exiting");
+                        _threaddelproc();
+                        _schedexit(p);
+                }
+        //        _threaddebug(DBGSCHED, "running %d.%d", t->proc->pid, t->id);
+                p->thread = t;
+                if(t->moribund){
+                        _threaddebug(DBGSCHED, "%d.%d marked to die");
+                        goto Resched;
+                }
+                t->state = Running;
+                t->nextstate = Ready;
+                _gotolabel(&t->sched);
+        }
+}
+
+long
+threadstack(void)
+{
+        Proc *p;
+        Thread *t;
+
+        p = _threadgetproc();
+        t = p->thread;
+        return (ulong)&p - (ulong)t->stk;
+}
+
+void
+_threadready(Thread *t)
+{
+        Tqueue *q;
+
+        assert(t->state == Ready);
+        _threaddebug(DBGSCHED, "readying %d.%d", t->proc->pid, t->id);
+        q = &t->proc->ready;
+        lock(&t->proc->readylock);
+        t->next = nil;
+        if(q->head==nil)
+                q->head = t;
+        else
+                q->tail->next = t;
+        q->tail = t;
+        if(q->asleep){
+                q->asleep = 0;
+                /* lock passes to runthread */
+                _threaddebug(DBGSCHED, "waking process %d", t->proc->pid);
+                while(rendezvous((ulong)q, 0) == ~0){
+                        if(_threadexitsallstatus)
+                                exits(_threadexitsallstatus);
+                }
+        }else
+                unlock(&t->proc->readylock);
+}
+
+void
+yield(void)
+{
+        _sched();
+}
+
diff --git a/src/libthread/texec.c b/src/libthread/texec.c
t@@ -0,0 +1,34 @@
+#include 
+#include 
+extern int _threaddebuglevel;
+
+void
+doexec(void *v)
+{
+        char **argv = v;
+
+        procexec(nil, argv[0], argv);
+        sendp(threadwaitchan(), nil);
+}
+
+void
+threadmain(int argc, char **argv)
+{
+        Channel *c;
+        Waitmsg *w;
+
+        ARGBEGIN{
+        case 'D':
+                _threaddebuglevel = ~0;
+                break;
+        }ARGEND
+
+        c = threadwaitchan();
+        proccreate(doexec, argv, 8192);
+        w = recvp(c);
+        if(w == nil)
+                print("exec failed\n");
+        else
+                print("%d %lu %lu %lu %s\n", w->pid, w->time[0], w->time[1], w->time[2], w->msg);
+        threadexits(nil);
+}
diff --git a/src/libthread/thread.3 b/src/libthread/thread.3
t@@ -0,0 +1,576 @@
+.TH THREAD 2
+.SH NAME
+alt,
+chancreate,
+chanfree,
+chaninit,
+chanprint,
+mainstacksize,
+proccreate,
+procdata,
+procexec,
+procexecl,
+procrfork,
+recv,
+recvp,
+recvul,
+send,
+sendp,
+sendul,
+nbrecv,
+nbrecvp,
+nbrecvul,
+nbsend,
+nbsendp,
+nbsendul,
+threadcreate,
+threaddata,
+threadexits,
+threadexitsall,
+threadgetgrp,
+threadgetname,
+threadint,
+threadintgrp,
+threadkill,
+threadkillgrp,
+threadmain,
+threadnotify,
+threadid,
+threadpid,
+threadsetgrp,
+threadsetname,
+threadwaitchan,
+yield \- thread and proc management
+.SH SYNOPSIS
+.PP
+.EX
+.ta 4n +4n +4n +4n +4n +4n +4n
+#include 
+#include 
+#include 
+.sp
+#define        CHANEND                0
+#define        CHANSND                1
+#define        CHANRCV                2
+#define        CHANNOP                3
+#define        CHANNOBLK        4
+.sp
+.ta \w'    'u +\w'Channel 'u
+typedef struct Alt Alt;
+struct Alt {
+        Channel        *c;
+        void        *v;
+        int        op;
+        Channel        **tag;
+        int        entryno;
+};
+.fi
+.de XX
+.ift .sp 0.5
+.ifn .sp
+..
+.PP
+.nf
+.ft L
+.ta \w'\fLChannel* 'u +4n +4n +4n +4n
+void        threadmain(int argc, char *argv[])
+int        mainstacksize
+int        proccreate(void (*fn)(void*), void *arg, uint stacksize)
+int        procrfork(void (*fn)(void*), void *arg, uint stacksize,
+                int rforkflag)
+int        threadcreate(void (*fn)(void*), void *arg, uint stacksize)
+void        threadexits(char *status)
+void        threadexitsall(char *status)
+void        yield(void)
+.XX
+int        threadid(void)
+int        threadgrp(void)
+int        threadsetgrp(int group)
+int        threadpid(int id)
+.XX
+int        threadint(int id)
+int        threadintgrp(int group)
+int        threadkill(int id)
+int        threadkillgrp(int group)
+.XX
+void        threadsetname(char *name)
+char*        threadgetname(void)
+.XX
+void**        threaddata(void)
+void**        procdata(void)
+.XX
+int        chaninit(Channel *c, int elsize, int nel)
+Channel*        chancreate(int elsize, int nel)
+void        chanfree(Channel *c)
+.XX
+int        alt(Alt *alts)
+int        recv(Channel *c, void *v)
+void*        recvp(Channel *c)
+ulong        recvul(Channel *c)
+int        nbrecv(Channel *c, void *v)
+void*        nbrecvp(Channel *c)
+ulong        nbrecvul(Channel *c)
+int        send(Channel *c, void *v)
+int        sendp(Channel *c, void *v)
+int        sendul(Channel *c, ulong v)
+int        nbsend(Channel *c, void *v)
+int        nbsendp(Channel *c, void *v)
+int        nbsendul(Channel *c, ulong v)
+int        chanprint(Channel *c, char *fmt, ...)
+.XX
+int        procexecl(Channel *cpid, char *file, ...)
+int        procexec(Channel *cpid, char *file, char *args[])
+Channel*        threadwaitchan(void)
+.XX
+int        threadnotify(int (*f)(void*, char*), int in)
+.EE
+.SH DESCRIPTION
+.PP
+The thread library provides parallel programming support similar to that
+of the languages
+Alef and Newsqueak.
+Threads
+and
+procs
+occupy a shared address space,
+communicating and synchronizing through
+.I channels
+and shared variables.
+.PP
+A
+.I proc
+is a Plan 9 process that contains one or more cooperatively scheduled
+.IR threads .
+Programs using threads must replace
+.I main
+by
+.IR threadmain .
+The thread library provides a
+.I main
+function that sets up a proc with a single thread executing
+.I threadmain
+on a stack of size
+.I mainstacksize
+(default eight kilobytes).
+To set
+.IR mainstacksize ,
+declare a global variable
+initialized to the desired value
+.RI ( e.g. ,
+.B int
+.B mainstacksize
+.B =
+.BR 1024 ).
+.PP
+.I Threadcreate
+creates a new thread in the calling proc, returning a unique integer
+identifying the thread; the thread
+executes
+.I fn(arg)
+on a stack of size
+.IR stacksize .
+Thread stacks are allocated in shared memory, making it valid to pass 
+pointers to stack variables between threads and procs.
+.I Procrfork
+creates a new proc, and inside that proc creates
+a single thread as
+.I threadcreate
+would,
+returning the id of the created thread.
+.I Procrfork
+creates the new proc by calling
+.B rfork
+(see
+.IR fork (2))
+with flags
+.BR RFPROC|RFMEM|RFNOWAIT| \fIrforkflag\fR.
+(The thread library depends on all its procs
+running in the same rendezvous group.
+Do not include
+.B RFREND
+in
+.IR rforkflag .)
+.I Proccreate
+is identical to 
+.I procrfork
+with
+.I rforkflag
+set to zero.
+Be aware that the calling thread may continue
+execution before
+the newly created proc and thread
+are scheduled.
+Because of this,
+.I arg
+should not point to data on the stack of a function that could
+return before the new process is scheduled.
+.PP
+.I Threadexits
+terminates the calling thread.
+If the thread is the last in its proc,
+.I threadexits
+also terminates the proc, using
+.I status
+as the exit status.
+.I Threadexitsall
+terminates all procs in the program,
+using
+.I status
+as the exit status.
+.PP
+The threads in a proc are coroutines, scheduled nonpreemptively
+in a round-robin fashion.
+A thread must explicitly relinquish control of the processor
+before another thread in the same proc is run.
+Calls that do this are
+.IR yield ,
+.IR proccreate ,
+.IR procexec ,
+.IR procexecl ,
+.IR threadexits ,
+.IR alt ,
+.IR send ,
+and
+.I recv
+(and the calls related to
+.I send
+and
+.IR recv \(emsee
+their descriptions further on).
+Procs are scheduled by the operating system.
+Therefore, threads in different procs can preempt one another
+in arbitrary ways and should synchronize their
+actions using
+.B qlocks
+(see
+.IR lock (2))
+or channel communication.
+System calls such as
+.IR read (2)
+block the entire proc;
+all threads in a proc block until the system call finishes.
+.PP
+As mentioned above, each thread has a unique integer thread id.
+Thread ids are not reused; they are unique across the life of the program.
+.I Threadid
+returns the id for the current thread.
+Each thread also has a thread group id.
+The initial thread has a group id of zero.
+Each new thread inherits the group id of
+the thread that created it.
+.I Threadgrp
+returns the group id for the current thread;
+.I threadsetgrp
+sets it.
+.I Threadpid
+returns the pid of the Plan 9 process containing
+the thread identified by
+.IR id ,
+or \-1
+if no such thread is found.
+.PP
+.I Threadint
+interrupts a thread that is blocked in a channel operation
+or system call.
+.I Threadintgrp
+interrupts all threads with the given group id.
+.I Threadkill
+marks a thread to die when it next relinquishes the processor
+(via one of the calls listed above).
+If the thread is blocked in a channel operation or system call,
+it is also interrupted.
+.I Threadkillgrp
+kills all threads with the given group id.
+Note that
+.I threadkill
+and
+.I threadkillgrp
+will not terminate a thread that never relinquishes
+the processor.
+.PP
+Primarily for debugging,
+threads can have string names associated with them.
+.I Threadgetname
+returns the current thread's name;
+.I threadsetname
+sets it.
+The pointer returned by
+.I threadgetname
+is only valid until the next call to
+.IR threadsetname .
+.PP
+.I Threaddata
+returns a pointer to a per-thread pointer
+that may be modified by threaded programs for
+per-thread storage.
+Similarly, 
+.I procdata
+returns a pointer to a per-proc pointer.
+.PP
+.I Procexecl
+and
+.I procexec
+are threaded analogues of
+.I exec
+and
+.I execl
+(see
+.IR exec (2));
+on success,
+they replace the calling thread (which must be the only thread in its proc)
+and invoke the external program, never returning.
+On error, they return \-1.
+If
+.I cpid
+is not null, the pid of the invoked program
+will be sent along
+.I cpid
+once the program has been started, or \-1 will be sent if an
+error occurs.
+.I Procexec
+and
+.I procexecl
+will not access their arguments after sending a result
+along
+.IR cpid .
+Thus, programs that malloc the
+.I argv
+passed to
+.I procexec
+can safely free it once they have
+received the
+.I cpid
+response.
+.I Threadwaitchan
+returns a channel of pointers to
+.B Waitmsg
+structures (see
+.IR wait (2)).
+When an exec'ed process exits, a pointer to a
+.B Waitmsg
+is sent to this channel.
+These
+.B Waitmsg
+structures have been allocated with
+.IR malloc (2)
+and should be freed after use.
+.PP
+A
+.B Channel
+is a buffered or unbuffered queue for fixed-size messages.
+Procs and threads
+.I send
+messages into the channel and
+.I recv
+messages from the channel.  If the channel is unbuffered, a
+.I send
+operation blocks until the corresponding
+.I recv
+operation occurs and
+.IR "vice versa" .
+.I Chaninit
+initializes a 
+.B Channel
+for messages of size
+.I elsize 
+and with a buffer holding
+.I nel
+messages.
+If
+.I nel
+is zero, the channel is unbuffered.
+.IR Chancreate
+allocates a new channel and initializes it.
+.I Chanfree
+frees a channel that is no longer used.
+.I Chanfree
+can be called by either sender or receiver after the last item has been
+sent or received.  Freeing the channel will be delayed if there is a thread
+blocked on it until that thread unblocks (but
+.I chanfree
+returns immediately).
+.PP
+.I Send
+sends the element pointed at by
+.I v
+to the channel
+.IR c .
+If
+.I v
+is null, zeros are sent.
+.I Recv
+receives an element from
+.I c
+and stores it in
+.IR v .
+If
+.I v
+is null,
+the received value is discarded.
+.I Send
+and
+.I recv
+return 1 on success, \-1 if interrupted.
+.I Nbsend
+and
+.I nbrecv
+behave similarly, but return 0 rather than blocking.
+.PP
+.IR Sendp ,
+.IR nbsendp ,
+.IR sendul ,
+and
+.I nbsendul
+send a pointer or an unsigned long; the channel must
+have been initialized with the appropriate
+.IR elsize .
+.IR Recvp ,
+.IR nbrecvp ,
+.IR recvul ,
+and
+.I nbrecvul
+receive a pointer or an unsigned long;
+they return zero when a zero is received,
+when interrupted, or
+(for
+.I nbrecvp
+and
+.IR nbrecvul )
+when the operation would have blocked.
+To distinguish between these three cases,
+use
+.I recv
+or
+.IR nbrecv .
+.PP
+.I Alt
+can be used to recv from or send to one of a number of channels,
+as directed by an array of
+.B Alt
+structures,
+each of which describes a potential send or receive operation.
+In an
+.B Alt
+structure,
+.B c
+is the channel;
+.B v
+the value pointer (which may be null); and
+.B op
+the operation:
+.B CHANSND
+for a send operation,
+.B CHANRECV
+for a recv operation;
+.B CHANNOP
+for no operation
+(useful
+when
+.I alt
+is called with a varying set of operations).
+The array of
+.B Alt
+structures is terminated by an entry with
+.I op
+.B CHANEND
+or
+.BR CHANNOBLK .
+If at least one
+.B Alt
+structure can proceed, one of them is
+chosen at random to be executed.
+.I Alt
+returns the index of the chosen structure.
+If no operations can proceed and the list is terminated with
+.BR CHANNOBLK ,
+.I alt
+returns the index of the terminating
+.B CHANNOBLK
+structure.
+Otherwise,
+.I alt
+blocks until one of the operations can proceed,
+eventually returning the index of the structure executes.
+.I Alt
+returns \-1 when interrupted.
+The
+.B tag
+and
+.B entryno
+fields in the
+.B Alt
+structure are used internally by
+.I alt
+and need not be initialized.
+They are not used between
+.I alt
+calls.
+.PP
+.I Chanprint
+formats its arguments in the manner of
+.IR print (2)
+and sends the result to the channel
+.IR c.
+The string delivered by
+.I chanprint
+is allocated with
+.IR malloc (2)
+and should be freed upon receipt.
+.PP
+Thread library functions do not return on failure;
+if errors occur, the entire program is aborted.
+.PP
+Threaded programs should use
+.I threadnotify
+in place of
+.I atnotify
+(see
+.IR notify (2)).
+.PP
+It is safe to use
+.B sysfatal
+(see
+.IR perror (2))
+in threaded programs.
+.I Sysfatal
+will print the error string and call
+.IR threadexitsall .
+.PP
+It is safe to use 
+.IR rfork
+(see
+.IR fork (2))
+to manage the namespace, file descriptors, note group, and environment of a
+single process.
+That is, it is safe to call
+.I rfork
+with the flags
+.BR RFNAMEG ,
+.BR RFFDG ,
+.BR RFCFDG ,
+.BR RFNOTEG ,
+.BR RFENVG ,
+and
+.BR RFCENVG.
+(To create new processes, use
+.I proccreate
+and 
+.IR procrfork .)
+As mentioned above,
+the thread library depends on all procs being in the
+same rendezvous group; do not change the rendezvous
+group with
+.IR rfork .
+.SH FILES
+.B /sys/lib/acid/thread
+contains useful
+.IR acid (1)
+functions for debugging threaded programs.
+.PP
+.B /sys/src/libthread/example.c
+contains a full example program.
+.SH SOURCE
+.B /sys/src/libthread
+.SH SEE ALSO
+.IR intro (2),
+.IR ioproc (2)
diff --git a/src/libthread/thread.h b/src/libthread/thread.h
t@@ -0,0 +1,132 @@
+#ifndef _THREADH_
+#define _THREADH_ 1
+
+/* avoid conflicts with socket library */
+#undef send
+#define send _threadsend
+#undef recv
+#define recv _threadrecv
+
+typedef struct Alt        Alt;
+typedef struct Channel        Channel;
+typedef struct Ref        Ref;
+
+/* Channel structure.  S is the size of the buffer.  For unbuffered channels
+ * s is zero.  v is an array of s values.  If s is zero, v is unused.
+ * f and n represent the state of the queue pointed to by v.
+ */
+
+enum {
+        Nqwds = 2,
+        Nqshift = 5,        // 2log #of bits in long
+        Nqmask =  - 1,
+        Nqbits = (1 << Nqshift) * 2,
+};
+
+struct Channel {
+        int                        s;                // Size of the channel (may be zero)
+        unsigned int        f;                // Extraction point (insertion pt: (f + n) % s)
+        unsigned int        n;                // Number of values in the channel
+        int                        e;                // Element size
+        int                        freed;        // Set when channel is being deleted
+        volatile Alt        **qentry;        // Receivers/senders waiting (malloc)
+        volatile int        nentry;        // # of entries malloc-ed
+        unsigned char                v[1];                // Array of s values in the channel
+};
+
+
+/* Channel operations for alt: */
+typedef enum {
+        CHANEND,
+        CHANSND,
+        CHANRCV,
+        CHANNOP,
+        CHANNOBLK,
+} ChanOp;
+
+struct Alt {
+        Channel        *c;                /* channel */
+        void                *v;                /* pointer to value */
+        ChanOp        op;                /* operation */
+
+        /* the next variables are used internally to alt
+         * they need not be initialized
+         */
+        Channel        **tag;        /* pointer to rendez-vous tag */
+        int                entryno;        /* entry number */
+};
+
+struct Ref {
+        long ref;
+};
+
+int                alt(Alt alts[]);
+Channel*        chancreate(int elemsize, int bufsize);
+int                chaninit(Channel *c, int elemsize, int elemcnt);
+void                chanfree(Channel *c);
+int                chanprint(Channel *, char *, ...);
+long                decref(Ref *r);                /* returns 0 iff value is now zero */
+void                incref(Ref *r);
+int                nbrecv(Channel *c, void *v);
+void*                nbrecvp(Channel *c);
+unsigned long                nbrecvul(Channel *c);
+int                nbsend(Channel *c, void *v);
+int                nbsendp(Channel *c, void *v);
+int                nbsendul(Channel *c, unsigned long v);
+int                proccreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
+int                procrfork(void (*f)(void *arg), void *arg, unsigned int stacksize, int flag);
+void**                procdata(void);
+void                procexec(Channel *, char *, char *[]);
+void                procexecl(Channel *, char *, ...);
+int                recv(Channel *c, void *v);
+void*                recvp(Channel *c);
+unsigned long                recvul(Channel *c);
+int                send(Channel *c, void *v);
+int                sendp(Channel *c, void *v);
+int                sendul(Channel *c, unsigned long v);
+int                threadcreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
+void**                threaddata(void);
+void                threadexits(char *);
+void                threadexitsall(char *);
+int                threadgetgrp(void);        /* return thread group of current thread */
+char*                threadgetname(void);
+void                threadint(int);        /* interrupt thread */
+void                threadintgrp(int);        /* interrupt threads in grp */
+void                threadkill(int);        /* kill thread */
+void                threadkillgrp(int);        /* kill threads in group */
+void                threadmain(int argc, char *argv[]);
+void                threadnonotes(void);
+int                threadnotify(int (*f)(void*, char*), int in);
+int                threadid(void);
+int                threadpid(int);
+int                threadsetgrp(int);        /* set thread group, return old */
+void                threadsetname(char *name);
+Channel*        threadwaitchan(void);
+int        tprivalloc(void);
+void        tprivfree(int);
+void        **tprivaddr(int);
+void                yield(void);
+
+long                threadstack(void);
+
+extern        int                mainstacksize;
+
+/* slave I/O processes */
+typedef struct Ioproc Ioproc;
+
+Ioproc*        ioproc(void);
+void                closeioproc(Ioproc*);
+void                iointerrupt(Ioproc*);
+
+int                ioclose(Ioproc*, int);
+int                iodial(Ioproc*, char*, char*, char*, int*);
+int                ioopen(Ioproc*, char*, int);
+long                ioread(Ioproc*, int, void*, long);
+long                ioreadn(Ioproc*, int, void*, long);
+long                iowrite(Ioproc*, int, void*, long);
+int                iosleep(Ioproc*, long);
+
+long                iocall(Ioproc*, long (*)(va_list*), ...);
+void                ioret(Ioproc*, int);
+
+#endif        /* _THREADH_ */
diff --git a/src/libthread/threadimpl.h b/src/libthread/threadimpl.h
t@@ -0,0 +1,219 @@
+/* 
+ * Some notes on locking:
+ *
+ *        All the locking woes come from implementing
+ *        threadinterrupt (and threadkill).
+ *
+ *        _threadgetproc()->thread is always a live pointer.
+ *        p->threads, p->ready, and _threadrgrp also contain
+ *         live thread pointers.  These may only be consulted
+ *        while holding p->lock or _threadrgrp.lock; in procs
+ *        other than p, the pointers are only guaranteed to be live
+ *        while the lock is still being held.
+ *
+ *        Thread structures can only be freed by the proc
+ *        they belong to.  Threads marked with t->inrendez
+ *         need to be extracted from the _threadrgrp before
+ *        being freed.
+ *
+ *        _threadrgrp.lock cannot be acquired while holding p->lock.
+ */
+
+#include 
+#include 
+#include 
+#include "label.h"
+
+enum{
+STKSIZE = 16384,
+STKMAGIC = 0xCAFEBEEF
+};
+
+typedef struct Thread        Thread;
+typedef struct Proc        Proc;
+typedef struct Tqueue        Tqueue;
+typedef struct Pqueue        Pqueue;
+typedef struct Rgrp                Rgrp;
+typedef struct Execargs        Execargs;
+
+/* must match list in sched.c */
+typedef enum
+{
+        Dead,
+        Running,
+        Ready,
+        Rendezvous,
+} State;
+        
+typedef enum
+{
+        Channone,
+        Chanalt,
+        Chansend,
+        Chanrecv,
+} Chanstate;
+
+enum
+{
+        RENDHASH = 10009,
+        Printsize = 2048,
+        NPRIV = 8,
+};
+
+struct Rgrp
+{
+        Lock                lock;
+        Thread        *hash[RENDHASH];
+};
+
+struct Tqueue                /* Thread queue */
+{
+        int                asleep;
+        Thread        *head;
+        Thread        *tail;
+};
+
+struct Thread
+{
+        Lock                lock;                        /* protects thread data structure */
+        Label        sched;                /* for context switches */
+        int                id;                        /* thread id */
+        int                 grp;                        /* thread group */
+        int                moribund;        /* thread needs to die */
+        State                state;                /* run state */
+        State                nextstate;                /* next run state */
+        uchar        *stk;                        /* top of stack (lowest address of stack) */
+        uint                stksize;                /* stack size */
+        Thread        *next;                /* next on ready queue */
+
+        Proc                *proc;                /* proc of this thread */
+        Thread        *nextt;                /* next on list of threads in this proc */
+        Thread        *prevt;                /* prev on list of threads in this proc */
+        int                ret;                        /* return value for Exec, Fork */
+
+        char                *cmdname;        /* ptr to name of thread */
+
+        int                inrendez;
+        Thread        *rendhash;        /* Trgrp linked list */
+        ulong        rendtag;                /* rendezvous tag */
+        ulong        rendval;                /* rendezvous value */
+        int                rendbreak;        /* rendezvous has been taken */
+
+        Chanstate        chan;                /* which channel operation is current */
+        Alt                *alt;                        /* pointer to current alt structure (debugging) */
+
+        void*        udata[NPRIV];        /* User per-thread data pointer */
+};
+
+struct Execargs
+{
+        char                *prog;
+        char                **args;
+        int                fd[2];
+};
+
+struct Proc
+{
+        Lock                lock;
+        Label        sched;                /* for context switches */
+        Proc                *link;                /* in proctab */
+        int                pid;                        /* process id */
+        int                splhi;                /* delay notes */
+        Thread        *thread;                /* running thread */
+
+        int                needexec;
+        Execargs        exec;                /* exec argument */
+        Proc                *newproc;        /* fork argument */
+        char                exitstr[ERRMAX];        /* exit status */
+
+        int                rforkflag;
+        int                nthreads;
+        Tqueue        threads;                /* All threads of this proc */
+        Tqueue        ready;                /* Runnable threads */
+        Lock                readylock;
+
+        char                printbuf[Printsize];
+        int                blocked;                /* In a rendezvous */
+        int                pending;                /* delayed note pending */
+        int                nonotes;                /*  delay notes */
+        uint                nextID;                /* ID of most recently created thread */
+        Proc                *next;                /* linked list of Procs */
+
+        void                *arg;                        /* passed between shared and unshared stk */
+        char                str[ERRMAX];        /* used by threadexits to avoid malloc */
+        char                errbuf[ERRMAX];        /* errstr */
+
+        void*        udata;                /* User per-proc data pointer */
+};
+
+struct Pqueue {                /* Proc queue */
+        Lock                lock;
+        Proc                *head;
+        Proc                **tail;
+};
+
+struct Ioproc
+{
+        int tid;
+        Channel *c, *creply;
+        int inuse;
+        long (*op)(va_list*);
+        va_list arg;
+        long ret;
+        char err[ERRMAX];
+        Ioproc *next;
+};
+
+void                _gotolabel(Label*);
+int                _setlabel(Label*);
+void                _freeproc(Proc*);
+Proc*        _newproc(void(*)(void*), void*, uint, char*, int, int);
+int                _procsplhi(void);
+void                _procsplx(int);
+void                _sched(void);
+int                _schedexec(Execargs*);
+void                _schedexecwait(void);
+void                _schedexit(Proc*);
+int                _schedfork(Proc*);
+void                _schedinit(void*);
+void                _systhreadinit(void);
+void                _threadassert(char*);
+void                _threadbreakrendez(void);
+void                __threaddebug(ulong, char*, ...);
+#define _threaddebug if(!_threaddebuglevel){}else __threaddebug
+void                _threadexitsall(char*);
+void                _threadflagrendez(Thread*);
+Proc*        _threadgetproc(void);
+Proc*        _threaddelproc(void);
+void                _threadsetproc(Proc*);
+void                _threadinitstack(Thread*, void(*)(void*), void*);
+void*        _threadmalloc(long, int);
+void                _threadnote(void*, char*);
+void                _threadready(Thread*);
+ulong        _threadrendezvous(ulong, ulong);
+void                _threadsignal(void);
+void                _threadsysfatal(char*, va_list);
+long                _xdec(long*);
+void                _xinc(long*);
+void                _threadremove(Proc*, Thread*);
+
+extern int                        _threaddebuglevel;
+extern char*                _threadexitsallstatus;
+extern Pqueue                _threadpq;
+extern Channel*        _threadwaitchan;
+extern Rgrp                _threadrgrp;
+extern void _stackfree(void*);
+
+#define DBGAPPL        (1 << 0)
+#define DBGSCHED        (1 << 16)
+#define DBGCHAN        (1 << 17)
+#define DBGREND        (1 << 18)
+/* #define DBGKILL        (1 << 19) */
+#define DBGNOTE        (1 << 20)
+#define DBGEXEC        (1 << 21)
+
+#define ioproc_arg(io, type)        (va_arg((io)->arg, type))
+extern int _threadgetpid(void);
+extern void _threadmemset(void*, int, int);
+extern void _threaddebugmemset(void*, int, int);
+
diff --git a/src/libthread/tprimes b/src/libthread/tprimes
Binary files differ.
diff --git a/src/libthread/tprimes.c b/src/libthread/tprimes.c
t@@ -0,0 +1,62 @@
+#include 
+#include 
+
+int quiet;
+int goal;
+int buffer;
+int (*fn)(void(*)(void*), void*, uint) = threadcreate;
+
+void
+primethread(void *arg)
+{
+        Channel *c, *nc;
+        int p, i;
+
+        c = arg;
+        p = recvul(c);
+        if(p > goal)
+                threadexitsall(nil);
+        if(!quiet)
+                print("%d\n", p);
+        nc = chancreate(sizeof(ulong), buffer);
+        (*fn)(primethread, nc, 8192);
+        for(;;){
+                i = recvul(c);
+                if(i%p)
+                        sendul(nc, i);
+        }
+}
+
+extern int _threaddebuglevel;
+
+void
+threadmain(int argc, char **argv)
+{
+        int i;
+        Channel *c;
+
+        ARGBEGIN{
+        case 'D':
+                _threaddebuglevel = atoi(ARGF());
+                break;
+        case 'q':
+                quiet = 1;
+                break;
+        case 'b':
+                buffer = atoi(ARGF());
+                break;
+        case 'p':
+                fn=proccreate;
+                break;
+        }ARGEND
+
+        if(argc>0)
+                goal = atoi(argv[0]);
+        else
+                goal = 100;
+
+        c = chancreate(sizeof(ulong), buffer);
+        (*fn)(primethread, c, 8192);
+        for(i=2;; i++)
+                sendul(c, i);
+}