Initial revision
diff --git a/man/man7/regexp9.7 b/man/man7/regexp9.7
new file mode 100644
index 0000000..14a90d0
--- /dev/null
+++ b/man/man7/regexp9.7
@@ -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
new file mode 100644
index 0000000..97b7b1e
--- /dev/null
+++ b/man/man7/utf.7
@@ -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
new file mode 100644
index 0000000..a5d7d87
--- /dev/null
+++ b/src/cmd/mk/LICENSE
@@ -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
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/cmd/mk/Make.FreeBSD-386
@@ -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
new file mode 100644
index 0000000..edbdc11
--- /dev/null
+++ b/src/cmd/mk/Make.HP-UX-9000
@@ -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
new file mode 100644
index 0000000..74b0252
--- /dev/null
+++ b/src/cmd/mk/Make.Linux-386
@@ -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
new file mode 100644
index 0000000..3d45279
--- /dev/null
+++ b/src/cmd/mk/Make.OSF1-alpha
@@ -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
new file mode 100644
index 0000000..c5fe67b
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u
@@ -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
new file mode 100644
index 0000000..829301d
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u-cc
@@ -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
new file mode 100644
index 0000000..5c41594
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u-gcc
@@ -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
new file mode 100644
index 0000000..df21b60
--- /dev/null
+++ b/src/cmd/mk/Makefile
@@ -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
new file mode 100644
index 0000000..f840b18
--- /dev/null
+++ b/src/cmd/mk/Makefile.MID
@@ -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
new file mode 100644
index 0000000..521ef7a
--- /dev/null
+++ b/src/cmd/mk/arc.c
@@ -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
new file mode 100644
index 0000000..9ba7a12
--- /dev/null
+++ b/src/cmd/mk/archive.c
@@ -0,0 +1,180 @@
+#include	"mk.h"
+#define	ARMAG	"!<arch>\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
new file mode 100644
index 0000000..4649831
--- /dev/null
+++ b/src/cmd/mk/bundle.ports
@@ -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 <bsd.port.pre.mk>
+
+post-patch:
+	${REINPLACE_CMD} -e 's,$$(PREFIX),${PREFIX},g' ${WRKSRC}/Makefile
+
+.include <bsd.port.post.mk>
+--- 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
new file mode 100644
index 0000000..c040db5
--- /dev/null
+++ b/src/cmd/mk/env.c
@@ -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&REGEXP)
+		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&REGEXP) && 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
new file mode 100644
index 0000000..2533343
--- /dev/null
+++ b/src/cmd/mk/file.c
@@ -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
new file mode 100644
index 0000000..2c9f46a
--- /dev/null
+++ b/src/cmd/mk/fns.h
@@ -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
new file mode 100644
index 0000000..5852925
--- /dev/null
+++ b/src/cmd/mk/graph.c
@@ -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&REGEXP){
+			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&REGEXP)
+					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
new file mode 100644
index 0000000..3ee244f
--- /dev/null
+++ b/src/cmd/mk/lex.c
@@ -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
new file mode 100644
index 0000000..267ed73
--- /dev/null
+++ b/src/cmd/mk/main.c
@@ -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
new file mode 100644
index 0000000..2a96394
--- /dev/null
+++ b/src/cmd/mk/match.c
@@ -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
new file mode 100644
index 0000000..c58a6df
--- /dev/null
+++ b/src/cmd/mk/mk.1
@@ -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
+</$objtype/mkfile
+
+prog:	a.$O b.$O c.$O
+	$LD $LDFLAGS -o $target $prereq
+
+%.$O:	%.c
+	$CC $CFLAGS $stem.c
+.EE
+.PP
+Override flag settings in the mkfile:
+.IP
+.EX
+% mk target 'CFLAGS=-S -w'
+.EE
+.PP
+Maintain a library:
+.IP
+.EX
+libc.a(%.$O):N:	%.$O
+libc.a:	libc.a(abs.$O) libc.a(access.$O) libc.a(alarm.$O) ...
+	ar r libc.a $newmember
+.EE
+.PP
+String expression variables to derive names from a master list:
+.IP
+.EX
+NAMES=alloc arc bquote builtins expand main match mk var word
+OBJ=${NAMES:%=%.$O}
+.EE
+.PP
+Regular expression meta-rules:
+.IP
+.EX
+([^/]*)/(.*)\e.$O:R:  \e1/\e2.c
+	cd $stem1; $CC $CFLAGS $stem2.c
+.EE
+.PP
+A correct way to deal with
+.IR yacc (1)
+grammars.
+The file
+.B lex.c
+includes the file
+.B x.tab.h
+rather than
+.B y.tab.h
+in order to reflect changes in content, not just modification time.
+.IP
+.EX
+lex.$O:	x.tab.h
+x.tab.h:	y.tab.h
+	cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
+y.tab.c y.tab.h:	gram.y
+	$YACC -d gram.y
+.EE
+.PP
+The above example could also use the
+.B P
+attribute for the
+.B x.tab.h
+rule:
+.IP
+.EX
+x.tab.h:Pcmp -s:	y.tab.h
+	cp y.tab.h x.tab.h
+.EE
+.SH SEE ALSO
+.IR sh (1),
+.IR regexp9 (7)
+.PP
+A. Hume,
+``Mk: a Successor to Make''
+(Tenth Edition Research Unix Manuals).
+.PP
+Andrew G. Hume and Bob Flandrena,
+``Maintaining Files on Plan 9 with Mk''.
+DOCPREFIX/doc/mk.pdf
+.SH HISTORY
+Andrew Hume wrote
+.I mk
+for Tenth Edition Research Unix.
+It was later ported to Plan 9.
+This software is a port of the Plan 9 version back to Unix.
+.SH BUGS
+Identical recipes for regular expression meta-rules only have one target.
+.br
+Seemingly appropriate input like
+.B CFLAGS=-DHZ=60
+is parsed as an erroneous attribute; correct it by inserting
+a space after the first 
+.LR = .
+.br
+The recipes printed by
+.I mk
+before being passed to
+.I sh
+for execution are sometimes erroneously expanded
+for printing.  Don't trust what's printed; rely
+on what
+.I sh
+does.
diff --git a/src/cmd/mk/mk.c b/src/cmd/mk/mk.c
new file mode 100644
index 0000000..26b8a97
--- /dev/null
+++ b/src/cmd/mk/mk.c
@@ -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
new file mode 100644
index 0000000..9b3f627
--- /dev/null
+++ b/src/cmd/mk/mk.h
@@ -0,0 +1,203 @@
+#include <utf.h>
+#include <fmt.h>
+#include <setjmp.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include	<bio.h>
+#include	<regexp9.h>
+#include <time.h>
+
+#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
new file mode 100644
index 0000000..1015135
--- /dev/null
+++ b/src/cmd/mk/mkfile
@@ -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
new file mode 100644
index 0000000..5c1c0ed
--- /dev/null
+++ b/src/cmd/mk/mkfile.test
@@ -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
new file mode 100644
index 0000000..b7737ef
--- /dev/null
+++ b/src/cmd/mk/parse.c
@@ -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&REGEXP)){
+		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
new file mode 100644
index 0000000..657ddf2
--- /dev/null
+++ b/src/cmd/mk/rc.c
@@ -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
new file mode 100644
index 0000000..144a490
--- /dev/null
+++ b/src/cmd/mk/recipe.c
@@ -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&REGEXP){
+		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
new file mode 100644
index 0000000..be75e6b
--- /dev/null
+++ b/src/cmd/mk/rpm.spec
@@ -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 <rsc@post.harvard.edu>
+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
new file mode 100644
index 0000000..662f067
--- /dev/null
+++ b/src/cmd/mk/rule.c
@@ -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&REGEXP) || charin(head, "%&")){
+		r->attr |= META;
+		if(reuse)
+			return;
+		if(attr&REGEXP){
+			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
new file mode 100644
index 0000000..3c0c7f3
--- /dev/null
+++ b/src/cmd/mk/run.c
@@ -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
new file mode 100644
index 0000000..524167a
--- /dev/null
+++ b/src/cmd/mk/sh.c
@@ -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
new file mode 100644
index 0000000..9e15bce
--- /dev/null
+++ b/src/cmd/mk/shprint.c
@@ -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
new file mode 100644
index 0000000..6f7b888
--- /dev/null
+++ b/src/cmd/mk/symtab.c
@@ -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
new file mode 100644
index 0000000..6022278
--- /dev/null
+++ b/src/cmd/mk/unix.c
@@ -0,0 +1,306 @@
+#include	"mk.h"
+#include	<sys/wait.h>
+#include	<signal.h>
+#include	<sys/stat.h>
+#include	<sys/time.h>
+
+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
new file mode 100644
index 0000000..8429918
--- /dev/null
+++ b/src/cmd/mk/var.c
@@ -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
new file mode 100644
index 0000000..2a9ad98
--- /dev/null
+++ b/src/cmd/mk/varsub.c
@@ -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
new file mode 100644
index 0000000..ac34c47
--- /dev/null
+++ b/src/cmd/mk/word.c
@@ -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
new file mode 100644
index 0000000..a5d7d87
--- /dev/null
+++ b/src/cmd/sam/LICENSE
@@ -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
new file mode 100644
index 0000000..f5e9146
--- /dev/null
+++ b/src/cmd/sam/Makefile
@@ -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
new file mode 100644
index 0000000..85cca17
--- /dev/null
+++ b/src/cmd/sam/address.c
@@ -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; i<file.nused; i++){
+		f = file.filepptr[i];
+		if(f == cmd)
+			continue;
+		if(filematch(f, r)){
+			if(match)
+				error(Emanyfiles);
+			match = f;
+		}
+	}
+	if(!match)
+		error(Efsearch);
+	return match;
+}
+
+int
+filematch(File *f, String *r)
+{
+	char *c, buf[STRSIZE+100];
+	String *t;
+
+	c = Strtoc(&f->name);
+	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<l; ){	/* always runs once */
+				if(p == 0){
+					if(++n != l)
+						error(Erange);
+				}else{
+					c = filereadc(f, p-1);
+					if(c != '\n' || ++n != l)
+						p--;
+				}
+			}
+			a.r.p2 = p;
+			if(p > 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
new file mode 100644
index 0000000..30493c3
--- /dev/null
+++ b/src/cmd/sam/buff.c
@@ -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 && q0<b->cq+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
new file mode 100644
index 0000000..8c152f9
--- /dev/null
+++ b/src/cmd/sam/cmd.c
@@ -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'<c)	/* no number defaults to 1 */
+		return sign;
+	while('0'<=(c=getch()) && c<='9')
+		n = n*10 + (c-'0');
+	ungetch();
+	return sign*n;
+}
+
+int
+skipbl(void)
+{
+	int c;
+	do
+		c = getch();
+	while(c==' ' || c=='\t');
+	if(c >= 0)
+		ungetch();
+	return c;
+}
+
+void
+termcommand(void)
+{
+	Posn p;
+
+	for(p=cmdpt; p<cmd->_.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
new file mode 100644
index 0000000..83b2553
--- /dev/null
+++ b/src/cmd/sam/disk.c
@@ -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
new file mode 100644
index 0000000..d19b962
--- /dev/null
+++ b/src/cmd/sam/error.c
@@ -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
new file mode 100644
index 0000000..7bf46ea
--- /dev/null
+++ b/src/cmd/sam/errors.h
@@ -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
new file mode 100644
index 0000000..0428379
--- /dev/null
+++ b/src/cmd/sam/file.c
@@ -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; i<f->ntext; 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<p1; i+=n){
+		n = p1 - i;
+		if(n > 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<u.n; i+=n){
+				n = u.n - i;
+				if(n > 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
new file mode 100644
index 0000000..236090a
--- /dev/null
+++ b/src/cmd/sam/io.c
@@ -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; i<file.nused; i++){
+		g = file.filepptr[i];
+		if(w == i)
+			continue;
+		if(f->dev==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->mtime<mtime)){
+		f->dev = 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
new file mode 100644
index 0000000..a810542
--- /dev/null
+++ b/src/cmd/sam/list.c
@@ -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
new file mode 100644
index 0000000..189c11a
--- /dev/null
+++ b/src/cmd/sam/mesg.c
@@ -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; i<file.nused; i++)
+		if(file.filepptr[i]->tag==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; l<snarfbuf.nc; l+=m){
+			m = snarfbuf.nc-l;
+			if(m>BLOCKSIZE)
+				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; l<genstr.n; l++){
+			i = genstr.s[l];
+			if(utfrune(".*+?(|)\\[]^$", i))
+				Strinsert(&genstr, tmpcstr("\\"), l++);
+		}
+		Straddc(&genstr, '\0');
+		nextmatch(f, &genstr, p1, 1);
+		moveto(f, sel.p[0]);
+		break;
+
+	case Tsearch:
+		termlocked++;
+		if(curfile == 0)
+			error(Enofile);
+		if(lastpat.s[0] == 0)
+			panic("Tsearch");
+		nextmatch(curfile, &lastpat, curfile->dot.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; i<m; i++)
+				c[i] = rcvchar();
+			c[m] = 0;
+			str = tmpcstr(c);
+			free(c);
+			bufreset(&snarfbuf);
+			bufinsert(&snarfbuf, (Posn)0, str->s, 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<f->_.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; l<p2; l+=i){
+		i = p2-l>BLOCKSIZE? 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
new file mode 100644
index 0000000..b88bf14
--- /dev/null
+++ b/src/cmd/sam/mesg.h
@@ -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
new file mode 100644
index 0000000..cb60497
--- /dev/null
+++ b/src/cmd/sam/mkfile
@@ -0,0 +1,40 @@
+</$objtype/mkfile
+
+TARG=sam
+OFILES=sam.$O\
+	address.$O\
+	buff.$O\
+	cmd.$O\
+	disk.$O\
+	error.$O\
+	file.$O\
+	io.$O\
+	list.$O\
+	mesg.$O\
+	moveto.$O\
+	multi.$O\
+	plan9.$O\
+	rasp.$O\
+	regexp.$O\
+	shell.$O\
+	string.$O\
+	sys.$O\
+	util.$O\
+	xec.$O\
+
+HFILES=sam.h\
+	errors.h\
+	mesg.h\
+
+BIN=/$objtype/bin
+</sys/src/cmd/mkone
+
+address.$O cmd.$O parse.$O xec.$O unix.$O:	parse.h
+
+safeinstall: $O.out
+	mv $BIN/$TARG $BIN/o$TARG
+	cp $prereq $BIN/$TARG
+
+safeinstallall:V:
+	for (objtype in $CPUS)
+		mk safeinstall
diff --git a/src/cmd/sam/moveto.c b/src/cmd/sam/moveto.c
new file mode 100644
index 0000000..a84578c
--- /dev/null
+++ b/src/cmd/sam/moveto.c
@@ -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<ls && nc<ls*CHARSHIFT; nc++)
+		if((c=filereadc(f, --p)) == '\n'){
+			nl++;
+			oldp0 = p0-nc;
+		}
+	if(c == -1)
+		p0 = 0;
+	else if(nl==0){
+		if(p0>=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<f->_.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
new file mode 100644
index 0000000..3df0dbe
--- /dev/null
+++ b/src/cmd/sam/multi.c
@@ -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; i<file.nused; i++)
+		if(file.filepptr[i]==f)
+			return i;
+	return -1;
+}
+
+void
+delfile(File *f)
+{
+	int w = whichmenu(f);
+
+	if(w < 0)	/* e.g. x/./D */
+		return;
+	if(downloaded)
+		outTs(Hdelname, f->tag);
+	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; i<file.nused; i++){
+			cmp = Strcmp(&f->name, &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; i<file.nused; i++)
+		if(Strcmp(&file.filepptr[i]->name, s) == 0)
+			return file.filepptr[i];
+	return 0;
+}
diff --git a/src/cmd/sam/parse.h b/src/cmd/sam/parse.h
new file mode 100644
index 0000000..12f293f
--- /dev/null
+++ b/src/cmd/sam/parse.h
@@ -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
new file mode 100644
index 0000000..6c3a60e
--- /dev/null
+++ b/src/cmd/sam/plan9.c
@@ -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
new file mode 100644
index 0000000..d8db33d
--- /dev/null
+++ b/src/cmd/sam/plumb.c
@@ -0,0 +1,9 @@
+#include <u.h>
+#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
new file mode 100644
index 0000000..45ac820
--- /dev/null
+++ b/src/cmd/sam/rasp.c
@@ -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 && p1<cmdpt){
+		if(p2 <= cmdpt)
+			cmdpt -= n;
+		else
+			cmdpt = p1;
+	}
+	if(toterm){
+		if(grown){
+			outTsll(Hgrow, f->tag, 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 && p1<cmdpt)
+		cmdpt += n;
+	if(toterm){
+		if(shrunk){
+			outTsll(Hcut, f->tag, 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; i<r->nused && 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(i<r->nused && 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 && i<r->nused && 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; i<r->nused && 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; i<r->nused && 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; i<r->nused && 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)<p1+n)
+		n = L(i)-(p1-p);
+	rg.p1 = p1;
+	rg.p2 = p1+n;
+	if(p!=p1){
+		inslist(r, i+1, L(i)-(p1-p));
+		P(i)=p1-p;
+		i++;
+	}
+	if(L(i)!=n){
+		inslist(r, i+1, L(i)-n);
+		P(i)=n;
+	}
+	P(i)|=M;
+	/* now i is set; can we merge? */
+	if(i<r->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
new file mode 100644
index 0000000..dee4377
--- /dev/null
+++ b/src/cmd/sam/regexp.c
@@ -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; i<nclass; i++)
+		free(class[i]);
+	nclass = 0;
+	progp = program;
+	backwards = FALSE;
+	startinst = realcompile(s->s);
+	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; ip<atorp; ip++)
+		dprint("0%o\n", *ip);
+	dprint("operands\n");
+	for(stk = andstack; stk<andp; stk++)
+		dprint("0%o\t0%o\n", stk->first->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->type<OPERATOR? startinst->type : 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<startp || startp==eof)){
+			/* Add first instruction to this list */
+			if(++ntl >= 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].p1<sel.p[0].p1 ||
+	   (sp->p[0].p1==sel.p[0].p1 && sp->p[0].p2>sel.p[0].p2))
+		for(i = 0; i<NSUBEXP; i++)
+			sel.p[i] = sp->p[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->type<OPERATOR? bstartinst->type : 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].p2<sel.p[0].p1))
+                for(i = 0; i<NSUBEXP; i++){       /* note the reversal; p1<=p2 */
+                        sel.p[i].p1 = sp->p[i].p2;
+                        sel.p[i].p2 = sp->p[i].p1;
+                }
+}
diff --git a/src/cmd/sam/sam b/src/cmd/sam/sam
new file mode 100755
index 0000000..733b555
--- /dev/null
+++ b/src/cmd/sam/sam
Binary files differ
diff --git a/src/cmd/sam/sam.c b/src/cmd/sam/sam.c
new file mode 100644
index 0000000..81ccaf7
--- /dev/null
+++ b/src/cmd/sam/sam.c
@@ -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<argc-1; i++){
+			if(!setjmp(mainloop)){
+				t = tmpcstr(argv[i]);
+				Straddc(t, '\0');
+				Strduplstr(&genstr, t);
+				freetmpstr(t);
+				fixname(&genstr);
+				logsetname(newfile(), &genstr);
+			}
+		}
+	}else if(!downloaded)
+		newfile();
+	seq++;
+	if(file.nused)
+		current(file.filepptr[0]);
+	setjmp(mainloop);
+	cmdloop();
+	trytoquit();	/* if we already q'ed, quitok will be TRUE */
+	exits(0);
+}
+
+void
+usage(void)
+{
+	dprint("usage: sam [-d] [-t samterm] [-s sam name] -r machine\n");
+	exits("usage");
+}
+
+void
+rescue(void)
+{
+	int i, nblank = 0;
+	File *f;
+	char *c;
+	char buf[256];
+
+	if(rescuing++)
+		return;
+	io = -1;
+	for(i=0; i<file.nused; i++){
+		f = file.filepptr[i];
+		if(f==cmd || f->_.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; i<file.nused; i++){
+		f = file.filepptr[i];
+		if(f==cmd)
+			continue;
+		if(f->seq==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; c<file.nused; c++){
+			f = file.filepptr[c];
+			if(f!=cmd && fileisdirty(f)){
+				quitok = TRUE;
+				eof = FALSE;
+				error(Echanges);
+			}
+		}
+	}
+}
+
+void
+load(File *f)
+{
+	Address saveaddr;
+
+	Strduplstr(&genstr, &f->name);
+	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; i<tempfile.nused; i++){
+		f = tempfile.filepptr[i];
+		if(f==cmd)	/* cmd gets done in main() */
+			continue;
+		if(f->deleted) {
+			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<tempfile.nused; i++){
+		f = tempfile.filepptr[i];
+		if(f!=cmd && undoseq(f, isundo)==max)
+			undostep(f, isundo);
+	}
+	return 1;
+}
+
+int
+readcmd(String *s)
+{
+	int retcode;
+
+	if(flist != 0)
+		fileclose(flist);
+	flist = fileopen();
+
+	addr.r.p1 = 0, addr.r.p2 = flist->_.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; i<tempfile.nused; i++){
+		f = tempfile.filepptr[i];
+		if(f!=cmd && f->name.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<addr.r.p2; p+=ni){
+		ni = addr.r.p2-p;
+		if(ni > 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
new file mode 100644
index 0000000..6a2708c
--- /dev/null
+++ b/src/cmd/sam/sam.h
@@ -0,0 +1,407 @@
+#include <u.h>
+#include <libc.h>
+#include <plumb.h>
+#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
new file mode 100644
index 0000000..2cac31b
--- /dev/null
+++ b/src/cmd/sam/shell.c
@@ -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; l<plan9buf.nc; l+=m){
+						m = plan9buf.nc-l;
+						if(m>BLOCKSIZE-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
new file mode 100644
index 0000000..cc15db4
--- /dev/null
+++ b/src/cmd/sam/unix.c
@@ -0,0 +1,272 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#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
new file mode 100644
index 0000000..42acab0
--- /dev/null
+++ b/src/cmd/sam/xec.c
@@ -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; i<file.nused; i++){
+		if(file.filepptr[i] == cmd)
+			continue;
+		f = file.filepptr[i];
+		Strduplstr(&genstr, &f->name);
+		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; i<cp->ctext->n; i++)
+			if((c = cp->ctext->s[i])=='\\' && i<cp->ctext->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; p = a3.r.p2){
+		a3.r.p1 = a3.r.p2;
+/*pjw		if(p!=r.p1 || (linesel = lineaddr((Posn)0, a3, 1)).r.p2==p)*/
+		if(p!=r.p1 || (a = lineaddr((Posn)0, a3, 1), linesel = a.r, linesel.p2==p)){
+			a = lineaddr((Posn)1, a3, 1);
+			linesel = a.r;
+		}
+		if(linesel.p1 >= 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; i<tempfile.nused; i++){
+		f = tempfile.filepptr[i];
+		if(f==cmd)
+			continue;
+		if(cp->re==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
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/libdraw/BOT
diff --git a/src/libdraw/Make.Darwin-PowerMacintosh b/src/libdraw/Make.Darwin-PowerMacintosh
new file mode 100644
index 0000000..14b8d4e
--- /dev/null
+++ b/src/libdraw/Make.Darwin-PowerMacintosh
@@ -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
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/libdraw/Make.FreeBSD-386
@@ -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
new file mode 100644
index 0000000..edbdc11
--- /dev/null
+++ b/src/libdraw/Make.HP-UX-9000
@@ -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
new file mode 100644
index 0000000..74b0252
--- /dev/null
+++ b/src/libdraw/Make.Linux-386
@@ -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
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/libdraw/Make.NetBSD-386
@@ -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
new file mode 100644
index 0000000..3d45279
--- /dev/null
+++ b/src/libdraw/Make.OSF1-alpha
@@ -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
new file mode 100644
index 0000000..c5fe67b
--- /dev/null
+++ b/src/libdraw/Make.SunOS-sun4u
@@ -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
new file mode 100644
index 0000000..829301d
--- /dev/null
+++ b/src/libdraw/Make.SunOS-sun4u-cc
@@ -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
new file mode 100644
index 0000000..5c41594
--- /dev/null
+++ b/src/libdraw/Make.SunOS-sun4u-gcc
@@ -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
new file mode 100644
index 0000000..0aa2cd2
--- /dev/null
+++ b/src/libdraw/Makefile
@@ -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
new file mode 100644
index 0000000..55cf7d4
--- /dev/null
+++ b/src/libdraw/Makefile.MID
@@ -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
new file mode 100644
index 0000000..50b96fd
--- /dev/null
+++ b/src/libdraw/alloc.c
@@ -0,0 +1,237 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..41b3062
--- /dev/null
+++ b/src/libdraw/arith.c
@@ -0,0 +1,206 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Point
+Pt(int x, int y)
+{
+	Point p;
+
+	p.x = x;
+	p.y = y;
+	return p;
+}
+
+Rectangle
+Rect(int x, int y, int bx, int by)
+{
+	Rectangle r;
+
+	r.min.x = x;
+	r.min.y = y;
+	r.max.x = bx;
+	r.max.y = by;
+	return r;
+}
+
+Rectangle
+Rpt(Point min, Point max)
+{
+	Rectangle r;
+
+	r.min = min;
+	r.max = max;
+	return r;
+}
+
+Point
+addpt(Point a, Point b)
+{
+	a.x += b.x;
+	a.y += b.y;
+	return a;
+}
+
+Point
+subpt(Point a, Point b)
+{
+	a.x -= b.x;
+	a.y -= b.y;
+	return a;
+}
+
+Rectangle
+insetrect(Rectangle r, int n)
+{
+	r.min.x += n;
+	r.min.y += n;
+	r.max.x -= n;
+	r.max.y -= n;
+	return r;
+}
+
+Point
+divpt(Point a, int b)
+{
+	a.x /= b;
+	a.y /= b;
+	return a;
+}
+
+Point
+mulpt(Point a, int b)
+{
+	a.x *= b;
+	a.y *= b;
+	return a;
+}
+
+Rectangle
+rectsubpt(Rectangle r, Point p)
+{
+	r.min.x -= p.x;
+	r.min.y -= p.y;
+	r.max.x -= p.x;
+	r.max.y -= p.y;
+	return r;
+}
+
+Rectangle
+rectaddpt(Rectangle r, Point p)
+{
+	r.min.x += p.x;
+	r.min.y += p.y;
+	r.max.x += p.x;
+	r.max.y += p.y;
+	return r;
+}
+
+int
+eqpt(Point p, Point q)
+{
+	return p.x==q.x && p.y==q.y;
+}
+
+int
+eqrect(Rectangle r, Rectangle s)
+{
+	return r.min.x==s.min.x && r.max.x==s.max.x &&
+	       r.min.y==s.min.y && r.max.y==s.max.y;
+}
+
+int
+rectXrect(Rectangle r, Rectangle s)
+{
+	return r.min.x<s.max.x && s.min.x<r.max.x &&
+	       r.min.y<s.max.y && s.min.y<r.max.y;
+}
+
+int
+rectinrect(Rectangle r, Rectangle s)
+{
+	return s.min.x<=r.min.x && r.max.x<=s.max.x && s.min.y<=r.min.y && r.max.y<=s.max.y;
+}
+
+int
+ptinrect(Point p, Rectangle r)
+{
+	return p.x>=r.min.x && p.x<r.max.x &&
+	       p.y>=r.min.y && p.y<r.max.y;
+}
+
+Rectangle
+canonrect(Rectangle r)
+{
+	int t;
+	if (r.max.x < r.min.x) {
+		t = r.min.x;
+		r.min.x = r.max.x;
+		r.max.x = t;
+	}
+	if (r.max.y < r.min.y) {
+		t = r.min.y;
+		r.min.y = r.max.y;
+		r.max.y = t;
+	}
+	return r;
+}
+
+void
+combinerect(Rectangle *r1, Rectangle r2)
+{
+	if(r1->min.x > r2.min.x)
+		r1->min.x = r2.min.x;
+	if(r1->min.y > r2.min.y)
+		r1->min.y = r2.min.y;
+	if(r1->max.x < r2.max.x)
+		r1->max.x = r2.max.x;
+	if(r1->max.y < r2.max.y)
+		r1->max.y = r2.max.y;
+}
+
+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
new file mode 100644
index 0000000..ba32e77
--- /dev/null
+++ b/src/libdraw/buildfont.c
@@ -0,0 +1,141 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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; i<f->nsub; i++){
+		c = f->sub[i];
+		free(c->subfontname);
+		free(c->name);
+		free(c);
+	}
+	for(i=0; i<f->nsubf; 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
new file mode 100644
index 0000000..08ff7d7
--- /dev/null
+++ b/src/libdraw/bytesperline.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static
+int
+unitsperline(Rectangle r, int d, int bitsperunit)
+{
+	ulong l, t;
+
+	if(d <= 0 || d > 32)	/* being called wrong.  d is image depth. */
+		abort();
+
+	if(r.min.x >= 0){
+		l = (r.max.x*d+bitsperunit-1)/bitsperunit;
+		l -= (r.min.x*d)/bitsperunit;
+	}else{			/* make positive before divide */
+		t = (-r.min.x*d+bitsperunit-1)/bitsperunit;
+		l = t+(r.max.x*d+bitsperunit-1)/bitsperunit;
+	}
+	return l;
+}
+
+int
+wordsperline(Rectangle r, int d)
+{
+	return unitsperline(r, d, 8*sizeof(ulong));
+}
+
+int
+bytesperline(Rectangle r, int d)
+{
+	return unitsperline(r, d, 8);
+}
diff --git a/src/libdraw/chan.c b/src/libdraw/chan.c
new file mode 100644
index 0000000..3b76a32
--- /dev/null
+++ b/src/libdraw/chan.c
@@ -0,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..99c4275
--- /dev/null
+++ b/src/libdraw/creadimage.c
@@ -0,0 +1,113 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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.y<maxy){
+			werrstr("creadimage: bad maxy %d", maxy);
+			goto Errout;
+		}
+		if(nb<=0 || ncblock<nb){
+			werrstr("creadimage: bad count %d", nb);
+			goto Errout;
+		}
+		if(readn(fd, buf, nb)!=nb)
+			goto Errout;
+		if(dolock)
+			lockdisplay(d);
+		a = bufimage(i->display, 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
new file mode 100644
index 0000000..105cd0e
--- /dev/null
+++ b/src/libdraw/cursor.h
@@ -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
new file mode 100644
index 0000000..b003638
--- /dev/null
+++ b/src/libdraw/devdraw.c
@@ -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 <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+#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*2<anbb || rectXrect(flushrect, r)){
+		flushrect = nbb;
+		return;
+	}
+	/* emit current state */
+	if(flushrect.min.x < flushrect.max.x)
+		flushmemscreen(flushrect);
+	flushrect = r;
+	waste = 0;
+}
+
+static
+void
+dstflush(int dstid, Memimage *dst, Rectangle r)
+{
+	Memlayer *l;
+
+	if(dstid == 0){
+		combinerect(&flushrect, r);
+		return;
+	}
+	/* how can this happen? -rsc, dec 12 2002 */
+	if(dst == 0){
+		print("nil dstflush\n");
+		return;
+	}
+	l = dst->layer;
+	if(l == nil)
+		return;
+	do{
+		if(l->screen->image->data != screenimage->data)
+			return;
+		r = rectaddpt(r, l->delta);
+		l = l->screen->image->layer;
+	}while(l);
+	addflush(r);
+}
+
+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(; name<ename; name++)
+		if(drawcmp(name->name, 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; i<sdraw.nname; )
+		if(sdraw.name[i].dimage == dimage)
+			drawdelname(sdraw.name+i);
+		else
+			i++;
+	if(dimage->fromname){	/* acquired by name; owned by someone else*/
+		drawfreedimage(dimage->fromname);
+		goto Return;
+	}
+	//if(dimage->image == screenimage)	/* don't free the display */
+	//	goto Return;
+	ds = dimage->dscreen;
+	if(ds){
+		l = dimage->image;
+		if(l->data == screenimage->data)
+			addflush(l->layer->screenr);
+		if(l->layer->refreshfn == drawrefresh)	/* else true owner will clean up */
+			free(l->layer->refreshptr);
+		l->layer->refreshptr = nil;
+		if(drawgoodname(dimage))
+			memldelete(l);
+		else
+			memlfree(l);
+		drawfreedscreen(ds);
+	}else
+		freememimage(dimage->image);
+    Return:
+	free(dimage->fchar);
+	free(dimage);
+}
+
+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(; name<ename; name++)
+		if(drawcmp(name->name, 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<ni; y++){
+				q = p;
+				oesize = esize;
+				u = drawcoord(u, a+n, ox, &p.x);
+				if(!u)
+					goto Eshortdraw;
+				u = drawcoord(u, a+n, oy, &p.y);
+				if(!u)
+					goto Eshortdraw;
+				ox = p.x;
+				oy = p.y;
+				if(doflush){
+					esize = j;
+					if(*a == 'p'){
+						if(y == 0){
+							c = memlineendsize(e0);
+							if(c > esize)
+								esize = c;
+						}
+						if(y == ni-1){
+							c = memlineendsize(e1);
+							if(c > esize)
+								esize = c;
+						}
+					}
+					if(*a=='P' && e0!=1 && e0 !=~0)
+						r = dst->clipr;
+					else if(y > 0){
+						r = Rect(q.x-oesize, q.y-oesize, q.x+oesize+1, q.y+oesize+1);
+						combinerect(&r, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+					}
+					if(rectclip(&r, dst->clipr))		/* should perhaps be an arg to dstflush */
+						dstflush(dstid, dst, r);
+				}
+				pp[y] = p;
+			}
+			if(y == 1)
+				dstflush(dstid, dst, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+			op = drawclientop(client);
+			if(*a == 'p')
+				mempoly(dst, pp, ni, e0, e1, j, src, sp, op);
+			else
+				memfillpoly(dst, pp, ni, e0, src, sp, op);
+			free(pp);
+			m = u-a;
+			continue;
+
+		/* read: 'r' id[4] R[4*4] */
+		case 'r':
+			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; j<nw; j++){
+				lp[j] = drawimage(client, a+1+1+2+j*4);
+				if(lp[j] == nil){
+					free(lp);
+					goto Enodrawimage;
+				}
+			}
+			if(lp[0]->layer == 0){
+				err = "images are not windows";
+				free(lp);
+				goto error;
+			}
+			for(j=1; j<nw; j++)
+				if(lp[j]->layer->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; j<nw; j++)
+					addflush(lp[j]->layer->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
new file mode 100644
index 0000000..0f9ba63
--- /dev/null
+++ b/src/libdraw/draw.h
@@ -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<<LOG2NFCACHE),	/* #chars cached */
+	NFLOOK =	5,			/* #chars to scan in cache */
+	NFSUBF =	2,			/* #subfonts to cache */
+	/* max value */
+	MAXFCACHE =	1024+NFLOOK,		/* upper limit */
+	MAXSUBF =	50,			/* generous upper limit */
+	/* deltas */
+	DSUBF = 	4,
+	/* expiry ages */
+	SUBFAGE	=	10000,
+	CACHEAGE =	10000
+};
+
+struct Cachefont
+{
+	Rune		min;	/* lowest rune value to be taken from subfont */
+	Rune		max;	/* highest rune value+1 to be taken from subfont */
+	int		offset;	/* position in subfont of character at min */
+	char		*name;			/* stored in font */
+	char		*subfontname;		/* to access subfont */
+};
+
+struct Cacheinfo
+{
+	ushort		x;		/* left edge of bits */
+	uchar		width;		/* width of baseline */
+	schar		left;		/* offset of baseline */
+	Rune		value;	/* value of character at this slot in cache */
+	ushort		age;
+};
+
+struct Cachesubf
+{
+	u32int		age;	/* for replacement */
+	Cachefont	*cf;	/* font info that owns us */
+	Subfont		*f;	/* attached subfont */
+};
+
+struct Font
+{
+	char		*name;
+	Display		*display;
+	short		height;	/* max height of image, interline spacing */
+	short		ascent;	/* top of image to baseline */
+	short		width;	/* widest so far; used in caching only */	
+	short		nsub;	/* number of subfonts */
+	u32int		age;	/* increasing counter; used for LRU */
+	int		maxdepth;	/* maximum depth of all loaded subfonts */
+	int		ncache;	/* size of cache */
+	int		nsubf;	/* size of subfont list */
+	Cacheinfo	*cache;
+	Cachesubf	*subf;
+	Cachefont	**sub;	/* as read from file */
+	Image		*cacheimage;
+};
+
+#define	Dx(r)	((r).max.x-(r).min.x)
+#define	Dy(r)	((r).max.y-(r).min.y)
+
+/*
+ * Image management
+ */
+extern Image*	_allocimage(Image*, Display*, Rectangle, u32int, int, u32int, int, int);
+extern Image*	allocimage(Display*, Rectangle, u32int, int, u32int);
+extern uchar*	bufimage(Display*, int);
+extern int	bytesperline(Rectangle, int);
+extern void	closedisplay(Display*);
+extern void	drawerror(Display*, char*);
+extern int	flushimage(Display*, int);
+extern int	freeimage(Image*);
+extern int	_freeimage1(Image*);
+extern int	geninitdraw(char*, void(*)(Display*, char*), char*, char*, char*, int);
+extern int	initdraw(void(*)(Display*, char*), char*, char*);
+extern int	newwindow(char*);
+extern int	loadimage(Image*, Rectangle, uchar*, int);
+extern int	cloadimage(Image*, Rectangle, uchar*, int);
+extern int	getwindow(Display*, int);
+extern int	gengetwindow(Display*, char*, Image**, Screen**, int);
+extern Image* readimage(Display*, int, int);
+extern Image* creadimage(Display*, int, int);
+extern int	unloadimage(Image*, Rectangle, uchar*, int);
+extern int	wordsperline(Rectangle, int);
+extern int	writeimage(int, Image*, int);
+extern Image*	namedimage(Display*, char*);
+extern int	nameimage(Image*, char*, int);
+extern Image* allocimagemix(Display*, u32int, u32int);
+
+/*
+ * Colors
+ */
+extern	void	readcolmap(Display*, RGB*);
+extern	void	writecolmap(Display*, RGB*);
+extern	u32int	setalpha(u32int, uchar);
+
+/*
+ * Windows
+ */
+extern Screen*	allocscreen(Image*, Image*, int);
+extern Image*	_allocwindow(Image*, Screen*, Rectangle, int, u32int);
+extern Image*	allocwindow(Screen*, Rectangle, int, u32int);
+extern void	bottomnwindows(Image**, int);
+extern void	bottomwindow(Image*);
+extern int	freescreen(Screen*);
+extern Screen*	publicscreen(Display*, int, u32int);
+extern void	topnwindows(Image**, int);
+extern void	topwindow(Image*);
+extern int	originwindow(Image*, Point, Point);
+
+/*
+ * Geometry
+ */
+extern Point		Pt(int, int);
+extern Rectangle	Rect(int, int, int, int);
+extern Rectangle	Rpt(Point, Point);
+extern Point		addpt(Point, Point);
+extern Point		subpt(Point, Point);
+extern Point		divpt(Point, int);
+extern Point		mulpt(Point, int);
+extern int		eqpt(Point, Point);
+extern int		eqrect(Rectangle, Rectangle);
+extern Rectangle	insetrect(Rectangle, int);
+extern Rectangle	rectaddpt(Rectangle, Point);
+extern Rectangle	rectsubpt(Rectangle, Point);
+extern Rectangle	canonrect(Rectangle);
+extern int		rectXrect(Rectangle, Rectangle);
+extern int		rectinrect(Rectangle, Rectangle);
+extern void		combinerect(Rectangle*, Rectangle);
+extern int		rectclip(Rectangle*, Rectangle);
+extern int		ptinrect(Point, Rectangle);
+extern void		replclipr(Image*, int, Rectangle);
+extern int		drawreplxy(int, int, int);	/* used to be drawsetxy */
+extern Point	drawrepl(Rectangle, Point);
+extern int		rgb2cmap(int, int, int);
+extern int		cmap2rgb(int);
+extern int		cmap2rgba(int);
+extern void		icossin(int, int*, int*);
+extern void		icossin2(int, int, int*, int*);
+
+/*
+ * Graphics
+ */
+extern void	draw(Image*, Rectangle, Image*, Image*, Point);
+extern void	drawop(Image*, Rectangle, Image*, Image*, Point, Drawop);
+extern void	gendraw(Image*, Rectangle, Image*, Point, Image*, Point);
+extern void	gendrawop(Image*, Rectangle, Image*, Point, Image*, Point, Drawop);
+extern void	line(Image*, Point, Point, int, int, int, Image*, Point);
+extern void	lineop(Image*, Point, Point, int, int, int, Image*, Point, Drawop);
+extern void	poly(Image*, Point*, int, int, int, int, Image*, Point);
+extern void	polyop(Image*, Point*, int, int, int, int, Image*, Point, Drawop);
+extern void	fillpoly(Image*, Point*, int, int, Image*, Point);
+extern void	fillpolyop(Image*, Point*, int, int, Image*, Point, Drawop);
+extern Point	string(Image*, Point, Image*, Point, Font*, char*);
+extern Point	stringop(Image*, Point, Image*, Point, Font*, char*, Drawop);
+extern Point	stringn(Image*, Point, Image*, Point, Font*, char*, int);
+extern Point	stringnop(Image*, Point, Image*, Point, Font*, char*, int, Drawop);
+extern Point	runestring(Image*, Point, Image*, Point, Font*, Rune*);
+extern Point	runestringop(Image*, Point, Image*, Point, Font*, Rune*, Drawop);
+extern Point	runestringn(Image*, Point, Image*, Point, Font*, Rune*, int);
+extern Point	runestringnop(Image*, Point, Image*, Point, Font*, Rune*, int, Drawop);
+extern Point	stringbg(Image*, Point, Image*, Point, Font*, char*, Image*, Point);
+extern Point	stringbgop(Image*, Point, Image*, Point, Font*, char*, Image*, Point, Drawop);
+extern Point	stringnbg(Image*, Point, Image*, Point, Font*, char*, int, Image*, Point);
+extern Point	stringnbgop(Image*, Point, Image*, Point, Font*, char*, int, Image*, Point, Drawop);
+extern Point	runestringbg(Image*, Point, Image*, Point, Font*, Rune*, Image*, Point);
+extern Point	runestringbgop(Image*, Point, Image*, Point, Font*, Rune*, Image*, Point, Drawop);
+extern Point	runestringnbg(Image*, Point, Image*, Point, Font*, Rune*, int, Image*, Point);
+extern Point	runestringnbgop(Image*, Point, Image*, Point, Font*, Rune*, int, Image*, Point, Drawop);
+extern Point	_string(Image*, Point, Image*, Point, Font*, char*, Rune*, int, Rectangle, Image*, Point, Drawop);
+extern Point	stringsubfont(Image*, Point, Image*, Subfont*, char*);
+extern int		bezier(Image*, Point, Point, Point, Point, int, int, int, Image*, Point);
+extern int		bezierop(Image*, Point, Point, Point, Point, int, int, int, Image*, Point, Drawop);
+extern int		bezspline(Image*, Point*, int, int, int, int, Image*, Point);
+extern int		bezsplineop(Image*, Point*, int, int, int, int, Image*, Point, Drawop);
+extern int		bezsplinepts(Point*, int, Point**);
+extern int		fillbezier(Image*, Point, Point, Point, Point, int, Image*, Point);
+extern int		fillbezierop(Image*, Point, Point, Point, Point, int, Image*, Point, Drawop);
+extern int		fillbezspline(Image*, Point*, int, int, Image*, Point);
+extern int		fillbezsplineop(Image*, Point*, int, int, Image*, Point, Drawop);
+extern void	ellipse(Image*, Point, int, int, int, Image*, Point);
+extern void	ellipseop(Image*, Point, int, int, int, Image*, Point, Drawop);
+extern void	fillellipse(Image*, Point, int, int, Image*, Point);
+extern void	fillellipseop(Image*, Point, int, int, Image*, Point, Drawop);
+extern void	arc(Image*, Point, int, int, int, Image*, Point, int, int);
+extern void	arcop(Image*, Point, int, int, int, Image*, Point, int, int, Drawop);
+extern void	fillarc(Image*, Point, int, int, Image*, Point, int, int);
+extern void	fillarcop(Image*, Point, int, int, Image*, Point, int, int, Drawop);
+extern void	border(Image*, Rectangle, int, Image*, Point);
+extern void	borderop(Image*, Rectangle, int, Image*, Point, Drawop);
+
+/*
+ * Font management
+ */
+extern Font*	openfont(Display*, char*);
+extern Font*	buildfont(Display*, char*, char*);
+extern void	freefont(Font*);
+extern Font*	mkfont(Subfont*, Rune);
+extern int	cachechars(Font*, char**, Rune**, ushort*, int, int*, char**);
+extern void	agefont(Font*);
+extern Subfont*	allocsubfont(char*, int, int, int, Fontchar*, Image*);
+extern Subfont*	lookupsubfont(Display*, char*);
+extern void	installsubfont(char*, Subfont*);
+extern void	uninstallsubfont(Subfont*);
+extern void	freesubfont(Subfont*);
+extern Subfont*	readsubfont(Display*, char*, int, int);
+extern Subfont*	readsubfonti(Display*, char*, int, Image*, int);
+extern int	writesubfont(int, Subfont*);
+extern void	_unpackinfo(Fontchar*, uchar*, int);
+extern Point	stringsize(Font*, char*);
+extern int	stringwidth(Font*, char*);
+extern int	stringnwidth(Font*, char*, int);
+extern Point	runestringsize(Font*, Rune*);
+extern int	runestringwidth(Font*, Rune*);
+extern int	runestringnwidth(Font*, Rune*, int);
+extern Point	strsubfontwidth(Subfont*, char*);
+extern int	loadchar(Font*, Rune, Cacheinfo*, int, int, char**);
+extern char*	subfontname(char*, char*, int);
+extern Subfont*	_getsubfont(Display*, char*);
+extern Subfont*	getdefont(Display*);
+extern void		lockdisplay(Display*);
+extern void	unlockdisplay(Display*);
+extern int		drawlsetrefresh(u32int, int, void*, void*);
+
+/*
+ * Predefined 
+ */
+extern	uchar	defontdata[];
+extern	int		sizeofdefont;
+extern	Point		ZP;
+extern	Rectangle	ZR;
+
+/*
+ * Set up by initdraw()
+ */
+extern	Display	*display;
+extern	Font		*font;
+extern	Image	*screen;
+extern	Screen	*_screen;
+extern	int	_cursorfd;
+extern	int	_drawdebug;	/* set to 1 to see errors from flushimage */
+extern	void	_setdrawop(Display*, Drawop);
+extern	Display	*_initdisplay(void(*)(Display*,char*), char*);
+
+#define	BGSHORT(p)		(((p)[0]<<0) | ((p)[1]<<8))
+#define	BGLONG(p)		((BGSHORT(p)<<0) | (BGSHORT(p+2)<<16))
+#define	BPSHORT(p, v)		((p)[0]=(v), (p)[1]=((v)>>8))
+#define	BPLONG(p, v)		(BPSHORT(p, (v)), BPSHORT(p+2, (v)>>16))
+
+/*
+ * Compressed image file parameters and helper routines
+ */
+#define	NMATCH	3		/* shortest match possible */
+#define	NRUN	(NMATCH+31)	/* longest match possible */
+#define	NMEM	1024		/* window size */
+#define	NDUMP	128		/* maximum length of dump */
+#define	NCBLOCK	6000		/* size of compressed blocks */
+extern	void	_twiddlecompressed(uchar*, int);
+extern	int	_compblocksize(Rectangle, int);
+
+/* XXX backwards helps; should go */
+extern	int		log2[];
+extern	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
new file mode 100644
index 0000000..7a063f1
--- /dev/null
+++ b/src/libdraw/ellipse.c
@@ -0,0 +1,82 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..f596d7a
--- /dev/null
+++ b/src/libdraw/emenuhit.c
@@ -0,0 +1,271 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+
+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; i<nitemdrawn; i++)
+		paintitem(menu, textr, off, i, 0, nil, nil);
+}
+
+static void
+menuscrollpaint(Rectangle scrollr, int off, int nitem, int nitemdrawn)
+{
+	Rectangle r;
+
+	draw(screen, scrollr, back, nil, ZP);
+	r.min.x = scrollr.min.x;
+	r.max.x = scrollr.max.x;
+	r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
+	r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
+	if(r.max.y < r.min.y+2)
+		r.max.y = r.min.y+2;
+	border(screen, r, 1, bord, ZP);
+	if(menutxt == 0)
+		menutxt = allocimage(display, Rect(0, 0, 1, 1), CMAP8, 1, DDarkgreen);
+	if(menutxt)
+		draw(screen, insetrect(r, 1), menutxt, nil, ZP);
+}
+
+int
+emenuhit(int but, Mouse *m, Menu *menu)
+{
+	int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
+	int scrolling;
+	Rectangle r, menur, sc, textr, scrollr;
+	Image *b, *save;
+	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, 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.x<screen->r.min.x)
+		pt.x = screen->r.min.x-r.min.x;
+	if(r.min.y<screen->r.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
new file mode 100644
index 0000000..2f67cde
--- /dev/null
+++ b/src/libdraw/event.c
@@ -0,0 +1,486 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <cursor.h>
+#include <event.h>
+
+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; i<nslave; i++)
+			if((keys & (1<<i)) && eslave[i].head){
+				id = 1<<i;
+				if(i == Smouse)
+					e->mouse = 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; i<nslave; i++)
+			if((keys & (1<<i)) && eslave[i].head)
+				return 1;
+		d = dirfstat(epipe[0]);
+		if(d == nil)
+			drawerror(display, "events: ecanread stat error");
+		l = d->length;
+		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<<i;
+	}
+	buf[0] = i - MAXSLAVE;
+	while((r = read(fd, buf+1, n))>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<<Stimer;
+	if(n <= 0)
+		n = 1000;
+	t[0] = t[1] = Stimer - MAXSLAVE;
+	do
+		sleep(n);
+	while(write(epipe[1], t, 2) == 2);
+	t[0] = MAXSLAVE;
+	write(epipe[1], t, 1);
+	_exits(0);
+	return 0;
+}
+
+static void
+ekeyslave(int fd)
+{
+	Rune r;
+	char t[3], k[10];
+	int kr, kn, w;
+
+	if(eforkslave(Ekeyboard) < MAXSLAVE)
+		return;
+	kn = 0;
+	t[0] = Skeyboard;
+	for(;;){
+		while(!fullrune(k, kn)){
+			kr = read(fd, k+kn, sizeof k - kn);
+			if(kr <= 0)
+				goto breakout;
+			kn += kr;
+		}
+		w = chartorune(&r, k);
+		kn -= w;
+		memmove(k, &k[w], kn);
+		t[1] = r;
+		t[2] = r>>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<<Skeyboard); Skeyboard++)
+			;
+		ekeyslave(fd);
+	}
+	if(keys&Emouse){
+		estart(Emouse, mousefd, 1+4*12);
+		for(Smouse=0; Emouse & ~(1<<Smouse); Smouse++)
+			;
+	}
+}
+
+static void
+extract(void)
+{
+	Slave *s;
+	Ebuf *eb;
+	int i, n;
+	uchar ebuf[EMAXMSG+1];
+
+	/* avoid generating a message if there's nothing to show. */
+	/* this test isn't perfect, though; could do flushimage(display, 0) then call extract */
+	/* also: make sure we don't interfere if we're multiprocessing the display */
+	if(display->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; i<MAXSLAVE; i++)
+		if((key & ~(1<<i)) == 0 && eslave[i].pid == 0){
+			if(nslave <= i)
+				nslave = i + 1;
+			/*
+			 * share the file descriptors so the last child
+			 * out closes all connections to the window server.
+			 */
+			switch(pid = rfork(RFPROC)){
+			case 0:
+				return MAXSLAVE+i;
+			case -1:
+				fprint(2, "events: fork error\n");
+				exits("fork");
+			}
+			eslave[i].pid = pid;
+			eslave[i].head = eslave[i].tail = 0;
+			return i;
+		}
+	drawerror(display, "events: bad slave assignment");
+	return 0;
+}
+
+static int
+enote(void *v, char *s)
+{
+	char t[1];
+	int i, pid;
+
+	USED(v, s);
+	pid = getpid();
+	if(pid != parentpid){
+		for(i=0; i<nslave; i++){
+			if(pid == eslave[i].pid){
+				t[0] = MAXSLAVE;
+				write(epipe[1], t, 1);
+				break;
+			}
+		}
+		return 0;
+	}
+	close(epipe[0]);
+	epipe[0] = -1;
+	close(epipe[1]);
+	epipe[1] = -1;
+	for(i=0; i<nslave; i++){
+		if(pid == eslave[i].pid)
+			continue;	/* don't kill myself */
+		postnote(PNPROC, eslave[i].pid, "die");
+	}
+	return 0;
+}
+
+static void
+ekill(void)
+{
+	enote(0, 0);
+}
+
+Mouse
+emouse(void)
+{
+	Mouse m;
+	Ebuf *eb;
+	static but[2];
+	int b;
+
+	if(Smouse < 0)
+		drawerror(display, "events: mouse not initialized");
+	eb = ebread(&eslave[Smouse]);
+	m.xy.x = atoi((char*)eb->buf+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
new file mode 100644
index 0000000..e74183d
--- /dev/null
+++ b/src/libdraw/event.h
@@ -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
new file mode 100644
index 0000000..6b14d3f
--- /dev/null
+++ b/src/libdraw/font.c
@@ -0,0 +1,401 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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) && i<max; sp+=w, rp+=rw){
+		if(ss){
+			r = *(uchar*)sp;
+			if(r < Runeself)
+				w = 1;
+			else{
+				w = chartorune(&vr, sp);
+				r = vr;
+			}
+			rw = 0;
+		}else{
+			r = *rp;
+			w = 0;
+			rw = 1;
+		}
+
+		sh = (17 * (uint)r) & (f->ncache-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->age<SUBFAGE && s->cf->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; i<f->nsub; 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; i<f->nsubf; 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; i<subf->f->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
new file mode 100644
index 0000000..b7a8e44
--- /dev/null
+++ b/src/libdraw/getsubfont.c
@@ -0,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * 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
new file mode 100644
index 0000000..0cf590d
--- /dev/null
+++ b/src/libdraw/init.c
@@ -0,0 +1,203 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..5ab911a
--- /dev/null
+++ b/src/libdraw/keyboard.c
@@ -0,0 +1,102 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.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 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
new file mode 100644
index 0000000..a6d99bf
--- /dev/null
+++ b/src/libdraw/keyboard.h
@@ -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
new file mode 100644
index 0000000..8b277f0
--- /dev/null
+++ b/src/libdraw/libdraw.x
@@ -0,0 +1 @@
+!<arch>
diff --git a/src/libdraw/md-alloc.c b/src/libdraw/md-alloc.c
new file mode 100644
index 0000000..801c393
--- /dev/null
+++ b/src/libdraw/md-alloc.c
@@ -0,0 +1,200 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+#define poolalloc(a, b) malloc(b)
+#define poolfree(a, b) free(b)
+
+void
+memimagemove(void *from, void *to)
+{
+	Memdata *md;
+
+	md = *(Memdata**)to;
+	if(md->base != from){
+		print("compacted data not right: #%p\n", md->base);
+		abort();
+	}
+	md->base = to;
+
+	/* if allocmemimage changes this must change too */
+	md->bdata = (uchar*)&md->base[2];
+}
+
+Memimage*
+allocmemimaged(Rectangle r, 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(cc))-1;
+		i->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
new file mode 100644
index 0000000..df836a0
--- /dev/null
+++ b/src/libdraw/md-arc.c
@@ -0,0 +1,116 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+/*
+ * elarc(dst,c,a,b,t,src,sp,alpha,phi)
+ *   draws the part of an ellipse between rays at angles alpha and alpha+phi
+ *   measured counterclockwise from the positive x axis. other
+ *   arguments are as for ellipse(dst,c,a,b,t,src,sp)
+ */
+
+enum
+{
+	R, T, L, B	/* right, top, left, bottom */
+};
+
+static
+Point corners[] = {
+	{1,1},
+	{-1,1},
+	{-1,-1},
+	{1,-1}
+};
+
+static
+Point p00;
+
+/*
+ * make a "wedge" mask covering the desired angle and contained in
+ * a surrounding square; draw a full ellipse; intersect that with the
+ * wedge to make a mask through which to copy src to dst.
+ */
+void
+memarc(Memimage *dst, Point c, int a, int b, int t, Memimage *src, Point sp, int alpha, int phi, int op)
+{
+	int i, w, beta, tmp, c1, c2, m, m1;
+	Rectangle rect;
+	Point p,	bnd[8];
+	Memimage *wedge, *figure, *mask;
+
+	if(a < 0)
+		a = -a;
+	if(b < 0)
+		b = -b;
+	w = t;
+	if(w < 0)
+		w = 0;
+	alpha = -alpha;		/* compensate for upside-down coords */
+	phi = -phi;
+	beta = alpha + phi;
+	if(phi < 0){
+		tmp = alpha;
+		alpha = beta;
+		beta = tmp;
+		phi = -phi;
+	}
+	if(phi >= 360){
+		memellipse(dst, c, a, b, t, src, sp, op);
+		return;
+	}
+	while(alpha < 0)
+		alpha += 360;
+	while(beta < 0)
+		beta += 360;
+	c1 = alpha/90 & 3;	/* number of nearest corner */
+	c2 = beta/90 & 3;
+		/*
+		 * icossin returns point at radius ICOSSCALE.
+		 * multiplying by m1 moves it outside the ellipse
+		*/
+	rect = Rect(-a-w, -b-w, a+w+1, b+w+1);
+	m = rect.max.x;	/* inradius of bounding square */
+	if(m < rect.max.y)
+		m = rect.max.y;
+	m1 = (m+ICOSSCALE-1) >> 10;
+	m = m1 << 10;		/* assure m1*cossin is inside */
+	i = 0;
+	bnd[i++] = Pt(0,0);
+	icossin(alpha, &p.x, &p.y);
+	bnd[i++] = mulpt(p, m1);
+	for(;;) {
+		bnd[i++] = mulpt(corners[c1], m);
+		if(c1==c2 && phi<180)
+			break;
+		c1 = (c1+1) & 3;
+		phi -= 90;
+	}
+	icossin(beta, &p.x, &p.y);
+	bnd[i++] = mulpt(p, m1);
+
+	figure = nil;
+	mask = nil;
+	wedge = allocmemimage(rect, GREY1);
+	if(wedge == nil)
+		goto Return;
+	memfillcolor(wedge, DTransparent);
+	memfillpoly(wedge, bnd, i, ~0, memopaque, p00, S);
+	figure = allocmemimage(rect, GREY1);
+	if(figure == nil)
+		goto Return;
+	memfillcolor(figure, DTransparent);
+	memellipse(figure, p00, a, b, t, memopaque, p00, S);
+	mask = allocmemimage(rect, GREY1);
+	if(mask == nil)
+		goto Return;
+	memfillcolor(mask, DTransparent);
+	memimagedraw(mask, rect, figure, rect.min, wedge, rect.min, S);
+	c = subpt(c, dst->r.min);
+	memdraw(dst, dst->r, src, subpt(sp, c), mask, subpt(p00, c), op);
+
+    Return:
+	freememimage(wedge);
+	freememimage(figure);
+	freememimage(mask);
+}
diff --git a/src/libdraw/md-arctest.c b/src/libdraw/md-arctest.c
new file mode 100644
index 0000000..b3a8c21
--- /dev/null
+++ b/src/libdraw/md-arctest.c
@@ -0,0 +1,61 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+extern int drawdebug;
+void
+main(int argc, char **argv)
+{
+	char cc;
+	Memimage *x;
+	Point c = {208,871};
+	int a = 441;
+	int b = 441;
+	int thick = 0;
+	Point sp = {0,0};
+	int alpha = 51;
+	int phi = 3;
+	vlong t0, t1;
+	int i, n;
+	vlong del;
+
+	memimageinit();
+
+	x = allocmemimage(Rect(0,0,1000,1000), CMAP8);
+	n = atoi(argv[1]);
+
+	t0 = nsec();
+	t0 = nsec();
+	t0 = nsec();
+	t1 = nsec();
+	del = t1-t0;
+	t0 = nsec();
+	for(i=0; i<n; i++)
+		memarc(x, c, a, b, thick, memblack, sp, alpha, phi, SoverD);
+	t1 = nsec();
+	print("%lld %lld\n", t1-t0-del, del);
+}
+
+int drawdebug = 0;
+
+void
+rdb(void)
+{
+}
+
+int
+iprint(char *fmt, ...)
+{
+	int n;	
+	va_list va;
+	char buf[1024];
+
+	va_start(va, fmt);
+	n = doprint(buf, buf+sizeof buf, fmt, va) - buf;
+	va_end(va);
+
+	write(1,buf,n);
+	return 1;
+}
+
diff --git a/src/libdraw/md-cload.c b/src/libdraw/md-cload.c
new file mode 100644
index 0000000..472caa6
--- /dev/null
+++ b/src/libdraw/md-cload.c
@@ -0,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+_cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int y, bpl, c, cnt, offs;
+	uchar mem[NMEM], *memp, *omemp, *emem, *linep, *elinep, *u, *eu;
+
+	if(!rectinrect(r, i->r))
+		return -1;
+	bpl = bytesperline(r, i->depth);
+	u = data;
+	eu = data+ndata;
+	memp = mem;
+	emem = mem+NMEM;
+	y = r.min.y;
+	linep = byteaddr(i, Pt(r.min.x, y));
+	elinep = linep+bpl;
+	for(;;){
+		if(linep == elinep){
+			if(++y == r.max.y)
+				break;
+			linep = byteaddr(i, Pt(r.min.x, y));
+			elinep = linep+bpl;
+		}
+		if(u == eu){	/* buffer too small */
+			return -1;
+		}
+		c = *u++;
+		if(c >= 128){
+			for(cnt=c-128+1; cnt!=0 ;--cnt){
+				if(u == eu){		/* buffer too small */
+					return -1;
+				}
+				if(linep == elinep){	/* phase error */
+					return -1;
+				}
+				*linep++ = *u;
+				*memp++ = *u++;
+				if(memp == emem)
+					memp = mem;
+			}
+		}
+		else{
+			if(u == eu)	/* short buffer */
+				return -1;
+			offs = *u++ + ((c&3)<<8)+1;
+			if(memp-mem < offs)
+				omemp = memp+(NMEM-offs);
+			else
+				omemp = memp-offs;
+			for(cnt=(c>>2)+NMATCH; cnt!=0; --cnt){
+				if(linep == elinep)	/* phase error */
+					return -1;
+				*linep++ = *omemp;
+				*memp++ = *omemp++;
+				if(omemp == emem)
+					omemp = mem;
+				if(memp == emem)
+					memp = mem;
+			}
+		}
+	}
+	return u-data;
+}
diff --git a/src/libdraw/md-cmap.c b/src/libdraw/md-cmap.c
new file mode 100644
index 0000000..2561c3f
--- /dev/null
+++ b/src/libdraw/md-cmap.c
@@ -0,0 +1,320 @@
+/*
+ * generated by mkcmap.c
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+static Memcmap def = {
+/* cmap2rgb */ {
+	0x00,0x00,0x00,0x00,0x00,0x44,0x00,0x00,0x88,0x00,0x00,0xcc,0x00,0x44,0x00,0x00,
+	0x44,0x44,0x00,0x44,0x88,0x00,0x44,0xcc,0x00,0x88,0x00,0x00,0x88,0x44,0x00,0x88,
+	0x88,0x00,0x88,0xcc,0x00,0xcc,0x00,0x00,0xcc,0x44,0x00,0xcc,0x88,0x00,0xcc,0xcc,
+	0x00,0xdd,0xdd,0x11,0x11,0x11,0x00,0x00,0x55,0x00,0x00,0x99,0x00,0x00,0xdd,0x00,
+	0x55,0x00,0x00,0x55,0x55,0x00,0x4c,0x99,0x00,0x49,0xdd,0x00,0x99,0x00,0x00,0x99,
+	0x4c,0x00,0x99,0x99,0x00,0x93,0xdd,0x00,0xdd,0x00,0x00,0xdd,0x49,0x00,0xdd,0x93,
+	0x00,0xee,0x9e,0x00,0xee,0xee,0x22,0x22,0x22,0x00,0x00,0x66,0x00,0x00,0xaa,0x00,
+	0x00,0xee,0x00,0x66,0x00,0x00,0x66,0x66,0x00,0x55,0xaa,0x00,0x4f,0xee,0x00,0xaa,
+	0x00,0x00,0xaa,0x55,0x00,0xaa,0xaa,0x00,0x9e,0xee,0x00,0xee,0x00,0x00,0xee,0x4f,
+	0x00,0xff,0x55,0x00,0xff,0xaa,0x00,0xff,0xff,0x33,0x33,0x33,0x00,0x00,0x77,0x00,
+	0x00,0xbb,0x00,0x00,0xff,0x00,0x77,0x00,0x00,0x77,0x77,0x00,0x5d,0xbb,0x00,0x55,
+	0xff,0x00,0xbb,0x00,0x00,0xbb,0x5d,0x00,0xbb,0xbb,0x00,0xaa,0xff,0x00,0xff,0x00,
+	0x44,0x00,0x44,0x44,0x00,0x88,0x44,0x00,0xcc,0x44,0x44,0x00,0x44,0x44,0x44,0x44,
+	0x44,0x88,0x44,0x44,0xcc,0x44,0x88,0x00,0x44,0x88,0x44,0x44,0x88,0x88,0x44,0x88,
+	0xcc,0x44,0xcc,0x00,0x44,0xcc,0x44,0x44,0xcc,0x88,0x44,0xcc,0xcc,0x44,0x00,0x00,
+	0x55,0x00,0x00,0x55,0x00,0x55,0x4c,0x00,0x99,0x49,0x00,0xdd,0x55,0x55,0x00,0x55,
+	0x55,0x55,0x4c,0x4c,0x99,0x49,0x49,0xdd,0x4c,0x99,0x00,0x4c,0x99,0x4c,0x4c,0x99,
+	0x99,0x49,0x93,0xdd,0x49,0xdd,0x00,0x49,0xdd,0x49,0x49,0xdd,0x93,0x49,0xdd,0xdd,
+	0x4f,0xee,0xee,0x66,0x00,0x00,0x66,0x00,0x66,0x55,0x00,0xaa,0x4f,0x00,0xee,0x66,
+	0x66,0x00,0x66,0x66,0x66,0x55,0x55,0xaa,0x4f,0x4f,0xee,0x55,0xaa,0x00,0x55,0xaa,
+	0x55,0x55,0xaa,0xaa,0x4f,0x9e,0xee,0x4f,0xee,0x00,0x4f,0xee,0x4f,0x4f,0xee,0x9e,
+	0x55,0xff,0xaa,0x55,0xff,0xff,0x77,0x00,0x00,0x77,0x00,0x77,0x5d,0x00,0xbb,0x55,
+	0x00,0xff,0x77,0x77,0x00,0x77,0x77,0x77,0x5d,0x5d,0xbb,0x55,0x55,0xff,0x5d,0xbb,
+	0x00,0x5d,0xbb,0x5d,0x5d,0xbb,0xbb,0x55,0xaa,0xff,0x55,0xff,0x00,0x55,0xff,0x55,
+	0x88,0x00,0x88,0x88,0x00,0xcc,0x88,0x44,0x00,0x88,0x44,0x44,0x88,0x44,0x88,0x88,
+	0x44,0xcc,0x88,0x88,0x00,0x88,0x88,0x44,0x88,0x88,0x88,0x88,0x88,0xcc,0x88,0xcc,
+	0x00,0x88,0xcc,0x44,0x88,0xcc,0x88,0x88,0xcc,0xcc,0x88,0x00,0x00,0x88,0x00,0x44,
+	0x99,0x00,0x4c,0x99,0x00,0x99,0x93,0x00,0xdd,0x99,0x4c,0x00,0x99,0x4c,0x4c,0x99,
+	0x4c,0x99,0x93,0x49,0xdd,0x99,0x99,0x00,0x99,0x99,0x4c,0x99,0x99,0x99,0x93,0x93,
+	0xdd,0x93,0xdd,0x00,0x93,0xdd,0x49,0x93,0xdd,0x93,0x93,0xdd,0xdd,0x99,0x00,0x00,
+	0xaa,0x00,0x00,0xaa,0x00,0x55,0xaa,0x00,0xaa,0x9e,0x00,0xee,0xaa,0x55,0x00,0xaa,
+	0x55,0x55,0xaa,0x55,0xaa,0x9e,0x4f,0xee,0xaa,0xaa,0x00,0xaa,0xaa,0x55,0xaa,0xaa,
+	0xaa,0x9e,0x9e,0xee,0x9e,0xee,0x00,0x9e,0xee,0x4f,0x9e,0xee,0x9e,0x9e,0xee,0xee,
+	0xaa,0xff,0xff,0xbb,0x00,0x00,0xbb,0x00,0x5d,0xbb,0x00,0xbb,0xaa,0x00,0xff,0xbb,
+	0x5d,0x00,0xbb,0x5d,0x5d,0xbb,0x5d,0xbb,0xaa,0x55,0xff,0xbb,0xbb,0x00,0xbb,0xbb,
+	0x5d,0xbb,0xbb,0xbb,0xaa,0xaa,0xff,0xaa,0xff,0x00,0xaa,0xff,0x55,0xaa,0xff,0xaa,
+	0xcc,0x00,0xcc,0xcc,0x44,0x00,0xcc,0x44,0x44,0xcc,0x44,0x88,0xcc,0x44,0xcc,0xcc,
+	0x88,0x00,0xcc,0x88,0x44,0xcc,0x88,0x88,0xcc,0x88,0xcc,0xcc,0xcc,0x00,0xcc,0xcc,
+	0x44,0xcc,0xcc,0x88,0xcc,0xcc,0xcc,0xcc,0x00,0x00,0xcc,0x00,0x44,0xcc,0x00,0x88,
+	0xdd,0x00,0x93,0xdd,0x00,0xdd,0xdd,0x49,0x00,0xdd,0x49,0x49,0xdd,0x49,0x93,0xdd,
+	0x49,0xdd,0xdd,0x93,0x00,0xdd,0x93,0x49,0xdd,0x93,0x93,0xdd,0x93,0xdd,0xdd,0xdd,
+	0x00,0xdd,0xdd,0x49,0xdd,0xdd,0x93,0xdd,0xdd,0xdd,0xdd,0x00,0x00,0xdd,0x00,0x49,
+	0xee,0x00,0x4f,0xee,0x00,0x9e,0xee,0x00,0xee,0xee,0x4f,0x00,0xee,0x4f,0x4f,0xee,
+	0x4f,0x9e,0xee,0x4f,0xee,0xee,0x9e,0x00,0xee,0x9e,0x4f,0xee,0x9e,0x9e,0xee,0x9e,
+	0xee,0xee,0xee,0x00,0xee,0xee,0x4f,0xee,0xee,0x9e,0xee,0xee,0xee,0xee,0x00,0x00,
+	0xff,0x00,0x00,0xff,0x00,0x55,0xff,0x00,0xaa,0xff,0x00,0xff,0xff,0x55,0x00,0xff,
+	0x55,0x55,0xff,0x55,0xaa,0xff,0x55,0xff,0xff,0xaa,0x00,0xff,0xaa,0x55,0xff,0xaa,
+	0xaa,0xff,0xaa,0xff,0xff,0xff,0x00,0xff,0xff,0x55,0xff,0xff,0xaa,0xff,0xff,0xff,
+},
+/* rgb2cmap */ {
+	0x00,0x00,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x00,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x04,0x04,0x04,0x05,0x05,0x05,0x05,0x06,0x06,0x06,0x17,0x07,0x07,0x18,0x18,0x29,
+	0x04,0x04,0x04,0x05,0x05,0x05,0x16,0x06,0x06,0x17,0x28,0x07,0x07,0x18,0x29,0x3a,
+	0x15,0x15,0x15,0x05,0x05,0x16,0x16,0x06,0x06,0x17,0x28,0x39,0x07,0x18,0x29,0x3a,
+	0x26,0x26,0x26,0x05,0x16,0x16,0x27,0x27,0x38,0x28,0x28,0x39,0x39,0x29,0x29,0x3a,
+	0x37,0x37,0x37,0x09,0x09,0x09,0x27,0x38,0x0a,0x0a,0x39,0x0b,0x0b,0x0b,0x1c,0x3a,
+	0x08,0x08,0x08,0x09,0x09,0x09,0x38,0x0a,0x0a,0x0a,0x1b,0x0b,0x0b,0x1c,0x1c,0x2d,
+	0x19,0x19,0x19,0x09,0x1a,0x1a,0x2b,0x0a,0x0a,0x1b,0x1b,0x0b,0x0b,0x1c,0x2d,0x3e,
+	0x2a,0x2a,0x2a,0x1a,0x2b,0x2b,0x2b,0x3c,0x1b,0x1b,0x2c,0x2c,0x3d,0x2d,0x2d,0x3e,
+	0x3b,0x3b,0x3b,0x0d,0x0d,0x3c,0x3c,0x0e,0x0e,0x0e,0x2c,0x3d,0x0f,0x0f,0x3e,0x3e,
+	0x0c,0x0c,0x0c,0x0d,0x0d,0x0d,0x3c,0x0e,0x0e,0x0e,0x3d,0x0f,0x0f,0x0f,0x10,0x3e,
+	0x1d,0x1d,0x1d,0x1e,0x1e,0x1e,0x2f,0x0e,0x1f,0x1f,0x20,0x0f,0x0f,0x10,0x10,0x21,
+	0x2e,0x2e,0x2e,0x1e,0x2f,0x2f,0x2f,0x1f,0x1f,0x20,0x20,0x31,0x10,0x10,0x21,0x21,
+	0x3f,0x3f,0x3f,0x2f,0x30,0x30,0x30,0x30,0x20,0x31,0x31,0x31,0x31,0x21,0x21,0x32,
+	0x00,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x22,0x22,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x04,0x04,0x22,0x05,0x05,0x05,0x05,0x06,0x06,0x06,0x17,0x07,0x07,0x18,0x18,0x29,
+	0x04,0x04,0x04,0x05,0x05,0x05,0x16,0x06,0x06,0x17,0x28,0x07,0x07,0x18,0x29,0x3a,
+	0x15,0x15,0x15,0x05,0x05,0x16,0x16,0x06,0x06,0x17,0x28,0x39,0x07,0x18,0x29,0x3a,
+	0x26,0x26,0x26,0x05,0x16,0x16,0x27,0x27,0x38,0x28,0x28,0x39,0x39,0x29,0x29,0x3a,
+	0x37,0x37,0x37,0x09,0x09,0x09,0x27,0x38,0x0a,0x0a,0x39,0x0b,0x0b,0x0b,0x1c,0x3a,
+	0x08,0x08,0x08,0x09,0x09,0x09,0x38,0x0a,0x0a,0x0a,0x1b,0x0b,0x0b,0x1c,0x1c,0x2d,
+	0x19,0x19,0x19,0x09,0x1a,0x1a,0x2b,0x0a,0x0a,0x1b,0x1b,0x0b,0x0b,0x1c,0x2d,0x3e,
+	0x2a,0x2a,0x2a,0x1a,0x2b,0x2b,0x2b,0x3c,0x1b,0x1b,0x2c,0x2c,0x3d,0x2d,0x2d,0x3e,
+	0x3b,0x3b,0x3b,0x0d,0x0d,0x3c,0x3c,0x0e,0x0e,0x0e,0x2c,0x3d,0x0f,0x0f,0x3e,0x3e,
+	0x0c,0x0c,0x0c,0x0d,0x0d,0x0d,0x3c,0x0e,0x0e,0x0e,0x3d,0x0f,0x0f,0x0f,0x10,0x3e,
+	0x1d,0x1d,0x1d,0x1e,0x1e,0x1e,0x2f,0x0e,0x1f,0x1f,0x20,0x0f,0x0f,0x10,0x10,0x21,
+	0x2e,0x2e,0x2e,0x1e,0x2f,0x2f,0x2f,0x1f,0x1f,0x20,0x20,0x31,0x10,0x10,0x21,0x21,
+	0x3f,0x3f,0x3f,0x2f,0x30,0x30,0x30,0x30,0x20,0x31,0x31,0x31,0x31,0x21,0x21,0x32,
+	0x11,0x11,0x11,0x01,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x11,0x22,0x22,0x01,0x12,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x11,0x22,0x22,0x22,0x33,0x33,0x23,0x34,0x02,0x13,0x24,0x35,0x03,0x14,0x25,0x36,
+	0x04,0x22,0x22,0x33,0x33,0x33,0x05,0x06,0x06,0x06,0x17,0x07,0x07,0x18,0x18,0x29,
+	0x04,0x04,0x33,0x33,0x33,0x05,0x16,0x06,0x06,0x17,0x28,0x07,0x07,0x18,0x29,0x3a,
+	0x15,0x15,0x33,0x33,0x05,0x16,0x16,0x06,0x06,0x17,0x28,0x39,0x07,0x18,0x29,0x3a,
+	0x26,0x26,0x26,0x05,0x16,0x16,0x27,0x27,0x38,0x28,0x28,0x39,0x39,0x29,0x29,0x3a,
+	0x37,0x37,0x37,0x09,0x09,0x09,0x27,0x38,0x0a,0x0a,0x39,0x0b,0x0b,0x0b,0x1c,0x3a,
+	0x08,0x08,0x08,0x09,0x09,0x09,0x38,0x0a,0x0a,0x0a,0x1b,0x0b,0x0b,0x1c,0x1c,0x2d,
+	0x19,0x19,0x19,0x09,0x1a,0x1a,0x2b,0x0a,0x0a,0x1b,0x1b,0x0b,0x0b,0x1c,0x2d,0x3e,
+	0x2a,0x2a,0x2a,0x1a,0x2b,0x2b,0x2b,0x3c,0x1b,0x1b,0x2c,0x2c,0x3d,0x2d,0x2d,0x3e,
+	0x3b,0x3b,0x3b,0x0d,0x0d,0x3c,0x3c,0x0e,0x0e,0x0e,0x2c,0x3d,0x0f,0x0f,0x3e,0x3e,
+	0x0c,0x0c,0x0c,0x0d,0x0d,0x0d,0x3c,0x0e,0x0e,0x0e,0x3d,0x0f,0x0f,0x0f,0x10,0x3e,
+	0x1d,0x1d,0x1d,0x1e,0x1e,0x1e,0x2f,0x0e,0x1f,0x1f,0x20,0x0f,0x0f,0x10,0x10,0x21,
+	0x2e,0x2e,0x2e,0x1e,0x2f,0x2f,0x2f,0x1f,0x1f,0x20,0x20,0x31,0x10,0x10,0x21,0x21,
+	0x3f,0x3f,0x3f,0x2f,0x30,0x30,0x30,0x30,0x20,0x31,0x31,0x31,0x31,0x21,0x21,0x32,
+	0x4f,0x4f,0x22,0x40,0x40,0x40,0x40,0x41,0x41,0x41,0x52,0x42,0x42,0x53,0x53,0x64,
+	0x4f,0x22,0x22,0x22,0x40,0x40,0x40,0x41,0x41,0x41,0x52,0x42,0x42,0x53,0x53,0x64,
+	0x22,0x22,0x22,0x33,0x33,0x33,0x40,0x41,0x41,0x41,0x52,0x42,0x42,0x53,0x53,0x64,
+	0x43,0x22,0x33,0x33,0x33,0x44,0x44,0x45,0x45,0x45,0x56,0x46,0x46,0x46,0x57,0x68,
+	0x43,0x43,0x33,0x33,0x44,0x44,0x44,0x45,0x45,0x45,0x56,0x46,0x46,0x57,0x57,0x68,
+	0x43,0x43,0x33,0x44,0x44,0x44,0x55,0x45,0x45,0x56,0x56,0x46,0x46,0x57,0x68,0x68,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x49,0x4a,0x4a,0x4a,0x5b,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x5a,0x4a,0x4a,0x4a,0x5b,0x6c,
+	0x47,0x47,0x47,0x48,0x48,0x59,0x59,0x49,0x49,0x5a,0x5a,0x4a,0x4a,0x5b,0x5b,0x6c,
+	0x58,0x58,0x58,0x59,0x59,0x59,0x6a,0x49,0x5a,0x5a,0x6b,0x6b,0x5b,0x5b,0x6c,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x4c,0x4d,0x4d,0x4d,0x6b,0x4e,0x4e,0x4e,0x6c,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x4c,0x4d,0x4d,0x4d,0x5e,0x4e,0x4e,0x4e,0x5f,0x5f,
+	0x5c,0x5c,0x5c,0x4c,0x5d,0x5d,0x5d,0x4d,0x4d,0x5e,0x5e,0x4e,0x4e,0x5f,0x5f,0x60,
+	0x5c,0x5c,0x5c,0x5d,0x5d,0x6e,0x6e,0x5e,0x5e,0x5e,0x6f,0x6f,0x5f,0x5f,0x60,0x60,
+	0x6d,0x6d,0x6d,0x6e,0x6e,0x6e,0x7f,0x7f,0x6f,0x6f,0x70,0x70,0x5f,0x60,0x60,0x71,
+	0x4f,0x4f,0x40,0x40,0x40,0x40,0x51,0x41,0x41,0x52,0x63,0x42,0x42,0x53,0x64,0x75,
+	0x4f,0x4f,0x22,0x40,0x40,0x40,0x51,0x41,0x41,0x52,0x63,0x42,0x42,0x53,0x64,0x75,
+	0x43,0x22,0x33,0x33,0x33,0x40,0x51,0x41,0x41,0x52,0x63,0x42,0x42,0x53,0x64,0x75,
+	0x43,0x43,0x33,0x33,0x44,0x44,0x44,0x45,0x45,0x45,0x56,0x46,0x46,0x57,0x57,0x68,
+	0x43,0x43,0x33,0x44,0x44,0x44,0x55,0x45,0x45,0x56,0x56,0x46,0x46,0x57,0x68,0x68,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x44,0x55,0x55,0x55,0x45,0x56,0x56,0x67,0x78,0x78,0x57,0x68,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x49,0x4a,0x4a,0x4a,0x5b,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x59,0x49,0x49,0x49,0x5a,0x4a,0x4a,0x5b,0x5b,0x6c,
+	0x58,0x58,0x58,0x48,0x59,0x59,0x59,0x49,0x49,0x5a,0x5a,0x4a,0x4a,0x5b,0x6c,0x6c,
+	0x69,0x69,0x69,0x59,0x59,0x6a,0x6a,0x49,0x5a,0x5a,0x6b,0x6b,0x5b,0x5b,0x6c,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x7b,0x4d,0x4d,0x4d,0x6b,0x4e,0x4e,0x4e,0x7d,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x7b,0x4d,0x4d,0x4d,0x5e,0x4e,0x4e,0x4e,0x5f,0x7d,
+	0x5c,0x5c,0x5c,0x5d,0x5d,0x5d,0x5d,0x4d,0x5e,0x5e,0x5e,0x4e,0x4e,0x5f,0x5f,0x60,
+	0x6d,0x6d,0x6d,0x5d,0x6e,0x6e,0x6e,0x5e,0x5e,0x6f,0x6f,0x70,0x5f,0x5f,0x60,0x60,
+	0x7e,0x7e,0x7e,0x6e,0x6e,0x7f,0x7f,0x7f,0x6f,0x6f,0x70,0x70,0x70,0x60,0x60,0x71,
+	0x50,0x50,0x50,0x40,0x40,0x51,0x51,0x41,0x41,0x52,0x63,0x74,0x42,0x53,0x64,0x75,
+	0x50,0x50,0x50,0x40,0x40,0x51,0x51,0x41,0x41,0x52,0x63,0x74,0x42,0x53,0x64,0x75,
+	0x50,0x50,0x33,0x33,0x40,0x51,0x51,0x41,0x41,0x52,0x63,0x74,0x42,0x53,0x64,0x75,
+	0x43,0x43,0x33,0x44,0x44,0x44,0x55,0x45,0x45,0x56,0x56,0x46,0x46,0x57,0x68,0x68,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x44,0x55,0x55,0x55,0x45,0x56,0x56,0x67,0x78,0x78,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x55,0x55,0x55,0x66,0x66,0x56,0x67,0x67,0x78,0x78,0x68,0x68,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x48,0x66,0x49,0x49,0x49,0x78,0x78,0x4a,0x4a,0x5b,0x79,
+	0x47,0x47,0x47,0x48,0x48,0x59,0x59,0x49,0x49,0x5a,0x5a,0x4a,0x4a,0x5b,0x6c,0x6c,
+	0x58,0x58,0x58,0x59,0x59,0x59,0x6a,0x49,0x5a,0x5a,0x6b,0x6b,0x5b,0x5b,0x6c,0x7d,
+	0x69,0x69,0x69,0x59,0x6a,0x6a,0x6a,0x7b,0x5a,0x6b,0x6b,0x6b,0x7c,0x6c,0x6c,0x7d,
+	0x7a,0x7a,0x7a,0x4c,0x4c,0x7b,0x7b,0x7b,0x4d,0x6b,0x6b,0x7c,0x7c,0x4e,0x7d,0x7d,
+	0x4b,0x4b,0x4b,0x4c,0x4c,0x7b,0x7b,0x4d,0x4d,0x5e,0x7c,0x7c,0x4e,0x5f,0x5f,0x7d,
+	0x5c,0x5c,0x5c,0x5d,0x5d,0x5d,0x6e,0x4d,0x5e,0x5e,0x6f,0x4e,0x5f,0x5f,0x60,0x60,
+	0x6d,0x6d,0x6d,0x6e,0x6e,0x6e,0x6e,0x5e,0x6f,0x6f,0x6f,0x70,0x5f,0x60,0x60,0x71,
+	0x7e,0x7e,0x7e,0x6e,0x7f,0x7f,0x7f,0x7f,0x6f,0x70,0x70,0x70,0x70,0x60,0x71,0x71,
+	0x61,0x61,0x61,0x40,0x51,0x51,0x62,0x62,0x73,0x63,0x63,0x74,0x74,0x64,0x64,0x75,
+	0x61,0x61,0x61,0x40,0x51,0x51,0x62,0x62,0x73,0x63,0x63,0x74,0x74,0x64,0x64,0x75,
+	0x61,0x61,0x61,0x40,0x51,0x51,0x62,0x62,0x73,0x63,0x63,0x74,0x74,0x64,0x64,0x75,
+	0x43,0x43,0x43,0x44,0x44,0x55,0x55,0x45,0x45,0x56,0x67,0x46,0x46,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x44,0x55,0x55,0x55,0x45,0x56,0x56,0x67,0x78,0x78,0x57,0x68,0x79,
+	0x54,0x54,0x54,0x55,0x55,0x55,0x66,0x66,0x56,0x67,0x67,0x78,0x78,0x68,0x68,0x79,
+	0x65,0x65,0x65,0x55,0x55,0x66,0x66,0x66,0x77,0x67,0x78,0x78,0x78,0x78,0x79,0x79,
+	0x65,0x65,0x65,0x48,0x48,0x66,0x66,0x77,0x77,0x77,0x78,0x78,0x78,0x5b,0x79,0x79,
+	0x76,0x76,0x76,0x48,0x59,0x59,0x77,0x77,0x77,0x5a,0x5a,0x4a,0x4a,0x5b,0x6c,0x6c,
+	0x69,0x69,0x69,0x59,0x59,0x6a,0x6a,0x77,0x5a,0x5a,0x6b,0x6b,0x5b,0x6c,0x6c,0x7d,
+	0x69,0x69,0x69,0x6a,0x6a,0x6a,0x7b,0x7b,0x5a,0x6b,0x6b,0x7c,0x7c,0x6c,0x7d,0x7d,
+	0x7a,0x7a,0x7a,0x4c,0x7b,0x7b,0x7b,0x7b,0x4d,0x6b,0x7c,0x7c,0x7c,0x7c,0x7d,0x7d,
+	0x7a,0x7a,0x7a,0x4c,0x7b,0x7b,0x7b,0x7b,0x4d,0x5e,0x7c,0x7c,0x7c,0x5f,0x5f,0x7d,
+	0x6d,0x6d,0x6d,0x5d,0x5d,0x6e,0x7b,0x5e,0x5e,0x6f,0x6f,0x7c,0x5f,0x5f,0x60,0x60,
+	0x6d,0x6d,0x6d,0x6e,0x6e,0x6e,0x7f,0x7f,0x6f,0x6f,0x70,0x70,0x5f,0x60,0x60,0x71,
+	0x7e,0x7e,0x7e,0x7f,0x7f,0x7f,0x7f,0x7f,0x6f,0x70,0x70,0x70,0x70,0x60,0x71,0x71,
+	0x72,0x72,0x72,0x8f,0x8f,0x62,0x62,0x73,0x73,0x80,0x74,0x81,0x81,0x81,0x92,0x75,
+	0x72,0x72,0x72,0x8f,0x8f,0x62,0x62,0x73,0x73,0x80,0x74,0x81,0x81,0x81,0x92,0x75,
+	0x72,0x72,0x72,0x83,0x83,0x62,0x62,0x73,0x73,0x80,0x74,0x81,0x81,0x81,0x92,0x75,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x83,0x84,0x84,0x84,0x84,0x85,0x85,0x85,0x96,0x79,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x66,0x84,0x84,0x84,0x67,0x85,0x85,0x85,0x96,0x79,
+	0x65,0x65,0x65,0x83,0x83,0x66,0x66,0x66,0x84,0x84,0x78,0x78,0x85,0x85,0x96,0x79,
+	0x65,0x65,0x65,0x83,0x66,0x66,0x66,0x77,0x77,0x77,0x78,0x78,0x78,0x96,0x79,0x79,
+	0x76,0x76,0x76,0x87,0x87,0x66,0x77,0x77,0x77,0x88,0x78,0x89,0x89,0x89,0x89,0x79,
+	0x76,0x76,0x76,0x87,0x87,0x87,0x77,0x77,0x88,0x88,0x88,0x89,0x89,0x89,0x9a,0x9a,
+	0x86,0x86,0x86,0x87,0x87,0x87,0x77,0x88,0x88,0x88,0x6b,0x89,0x89,0x9a,0x9a,0x7d,
+	0x7a,0x7a,0x7a,0x87,0x6a,0x7b,0x7b,0x7b,0x88,0x6b,0x6b,0x7c,0x7c,0x9a,0x7d,0x7d,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x7b,0x7b,0x8c,0x8c,0x8c,0x7c,0x7c,0x8d,0x8d,0x7d,0x7d,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x7b,0x8c,0x8c,0x8c,0x7c,0x8d,0x8d,0x8d,0x9e,0x9e,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x9c,0x8c,0x8c,0x9d,0x9d,0x8d,0x8d,0x9e,0x9e,0x9e,
+	0x9b,0x9b,0x9b,0x9c,0x9c,0x9c,0x7f,0x8c,0x9d,0x9d,0x70,0x70,0x9e,0x9e,0x9e,0x71,
+	0x7e,0x7e,0x7e,0x7f,0x7f,0x7f,0x7f,0x7f,0x9d,0x70,0x70,0x70,0x9e,0x9e,0x71,0x71,
+	0x8e,0x8e,0x8e,0x8f,0x8f,0x8f,0x73,0x73,0x80,0x80,0x91,0x81,0x81,0x92,0x92,0xa3,
+	0x8e,0x8e,0x8e,0x8f,0x8f,0x8f,0x73,0x73,0x80,0x80,0x91,0x81,0x81,0x92,0x92,0xa3,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x73,0x73,0x80,0x80,0x91,0x81,0x81,0x92,0x92,0xa3,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x83,0x84,0x84,0x84,0x95,0x85,0x85,0x85,0x96,0xa7,
+	0x82,0x82,0x82,0x83,0x83,0x83,0x94,0x84,0x84,0x84,0x95,0x85,0x85,0x96,0x96,0xa7,
+	0x82,0x82,0x82,0x83,0x83,0x94,0x94,0x84,0x84,0x95,0x95,0x85,0x85,0x96,0xa7,0xa7,
+	0x76,0x76,0x76,0x83,0x94,0x94,0x77,0x77,0x77,0x95,0x95,0x85,0x85,0x96,0xa7,0xa7,
+	0x76,0x76,0x76,0x87,0x87,0x87,0x77,0x77,0x88,0x88,0x88,0x89,0x89,0x89,0x9a,0x9a,
+	0x86,0x86,0x86,0x87,0x87,0x87,0x77,0x88,0x88,0x88,0x99,0x89,0x89,0x9a,0x9a,0xab,
+	0x86,0x86,0x86,0x87,0x87,0x98,0x98,0x88,0x88,0x99,0x99,0x89,0x89,0x9a,0x9a,0xab,
+	0x97,0x97,0x97,0x98,0x98,0x98,0x98,0x88,0x99,0x99,0x99,0x89,0x9a,0x9a,0xab,0xab,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8c,0x8c,0x8c,0x8d,0x8d,0x8d,0xab,0xbc,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8c,0x8c,0x9d,0x8d,0x8d,0x8d,0x9e,0x9e,
+	0x9b,0x9b,0x9b,0x8b,0x9c,0x9c,0x9c,0x8c,0x9d,0x9d,0x9d,0x8d,0x8d,0x9e,0x9e,0xaf,
+	0x9b,0x9b,0x9b,0x9c,0x9c,0xad,0xad,0x9d,0x9d,0x9d,0xae,0xae,0x9e,0x9e,0xaf,0xaf,
+	0xac,0xac,0xac,0xad,0xad,0xad,0xad,0x9d,0xae,0xae,0xae,0xbf,0x9e,0xaf,0xaf,0xaf,
+	0x9f,0x9f,0x9f,0x8f,0x90,0x90,0xa1,0x80,0x80,0x91,0x91,0x81,0x81,0x92,0xa3,0xb4,
+	0x9f,0x9f,0x9f,0x8f,0x90,0x90,0xa1,0x80,0x80,0x91,0x91,0x81,0x81,0x92,0xa3,0xb4,
+	0x9f,0x9f,0x9f,0x83,0x90,0x90,0xa1,0x80,0x80,0x91,0x91,0x81,0x81,0x92,0xa3,0xb4,
+	0x82,0x82,0x82,0x83,0x83,0x94,0x94,0x84,0x84,0x95,0x95,0x85,0x85,0x96,0x96,0xa7,
+	0x93,0x93,0x93,0x83,0x94,0x94,0x94,0x84,0x84,0x95,0x95,0x85,0x85,0x96,0xa7,0xa7,
+	0x93,0x93,0x93,0x94,0x94,0x94,0xa5,0x84,0x95,0x95,0xa6,0xa6,0x96,0x96,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0x94,0x94,0xa5,0xa5,0x77,0x95,0x95,0xa6,0xa6,0x96,0xa7,0xa7,0xb8,
+	0x86,0x86,0x86,0x87,0x87,0x87,0x77,0x88,0x88,0x88,0x99,0x89,0x89,0x9a,0x9a,0xb8,
+	0x86,0x86,0x86,0x87,0x87,0x98,0x98,0x88,0x88,0x99,0x99,0x89,0x89,0x9a,0x9a,0xab,
+	0x97,0x97,0x97,0x98,0x98,0x98,0x98,0x88,0x99,0x99,0x99,0x89,0x9a,0x9a,0xab,0xab,
+	0x97,0x97,0x97,0x98,0x98,0xa9,0xa9,0x99,0x99,0x99,0xaa,0xaa,0x9a,0xab,0xab,0xbc,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0xa9,0xa9,0x8c,0x8c,0x8c,0xaa,0x8d,0x8d,0x8d,0xab,0xbc,
+	0x8a,0x8a,0x8a,0x8b,0x8b,0x9c,0x9c,0x8c,0x8c,0x9d,0x9d,0x8d,0x8d,0x9e,0x9e,0xbc,
+	0x9b,0x9b,0x9b,0x9c,0x9c,0x9c,0xad,0x9d,0x9d,0x9d,0xae,0x8d,0x9e,0x9e,0xaf,0xaf,
+	0xac,0xac,0xac,0x9c,0xad,0xad,0xad,0x9d,0x9d,0xae,0xae,0xae,0x9e,0xaf,0xaf,0xaf,
+	0xbd,0xbd,0xbd,0xad,0xad,0xbe,0xbe,0xbe,0xae,0xae,0xbf,0xbf,0xbf,0xaf,0xaf,0xb0,
+	0xa0,0xa0,0xa0,0x90,0xa1,0xa1,0xa1,0xb2,0x91,0x91,0xa2,0xa2,0xb3,0xa3,0xa3,0xb4,
+	0xa0,0xa0,0xa0,0x90,0xa1,0xa1,0xa1,0xb2,0x91,0x91,0xa2,0xa2,0xb3,0xa3,0xa3,0xb4,
+	0xa0,0xa0,0xa0,0x90,0xa1,0xa1,0xa1,0xb2,0x91,0x91,0xa2,0xa2,0xb3,0xa3,0xa3,0xb4,
+	0x93,0x93,0x93,0x94,0x94,0x94,0xa5,0x84,0x95,0x95,0xa6,0xa6,0x96,0x96,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0x94,0x94,0xa5,0xa5,0x84,0x95,0x95,0xa6,0xa6,0x96,0x96,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0x94,0xa5,0xa5,0xa5,0xb6,0x95,0xa6,0xa6,0xa6,0xb7,0xa7,0xa7,0xb8,
+	0xa4,0xa4,0xa4,0xa5,0xa5,0xa5,0xb6,0xb6,0x95,0xa6,0xa6,0xb7,0xb7,0xa7,0xb8,0xb8,
+	0xb5,0xb5,0xb5,0x87,0x87,0xb6,0xb6,0xb6,0x88,0x99,0xa6,0xb7,0xb7,0x9a,0xb8,0xb8,
+	0x97,0x97,0x97,0x98,0x98,0x98,0x98,0x88,0x99,0x99,0x99,0x89,0x9a,0x9a,0xab,0xab,
+	0x97,0x97,0x97,0x98,0x98,0xa9,0xa9,0x99,0x99,0x99,0xaa,0xaa,0x9a,0xab,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xa9,0xa9,0xa9,0xa9,0xa9,0x99,0xaa,0xaa,0xaa,0xbb,0xab,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xa9,0xa9,0xa9,0xba,0xba,0x8c,0xaa,0xaa,0xbb,0xbb,0xab,0xbc,0xbc,
+	0xb9,0xb9,0xb9,0x9c,0x9c,0xba,0xba,0xba,0x9d,0x9d,0xbb,0xbb,0xbb,0x9e,0x9e,0xbc,
+	0xac,0xac,0xac,0x9c,0x9c,0xad,0xad,0x9d,0x9d,0xae,0xae,0xae,0x9e,0x9e,0xaf,0xaf,
+	0xac,0xac,0xac,0xad,0xad,0xad,0xbe,0xbe,0xae,0xae,0xae,0xbf,0x9e,0xaf,0xaf,0xb0,
+	0xbd,0xbd,0xbd,0xbe,0xbe,0xbe,0xbe,0xbe,0xae,0xbf,0xbf,0xbf,0xbf,0xaf,0xb0,0xb0,
+	0xb1,0xb1,0xb1,0xce,0xce,0xb2,0xb2,0xcf,0xcf,0xa2,0xa2,0xb3,0xb3,0xc0,0xb4,0xb4,
+	0xb1,0xb1,0xb1,0xce,0xce,0xb2,0xb2,0xcf,0xcf,0xa2,0xa2,0xb3,0xb3,0xc0,0xb4,0xb4,
+	0xb1,0xb1,0xb1,0xc2,0xc2,0xb2,0xb2,0xc3,0xc3,0xa2,0xa2,0xb3,0xb3,0xc0,0xb4,0xb4,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xa5,0xc3,0xc3,0xc3,0xa6,0xc4,0xc4,0xc4,0xa7,0xb8,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xa5,0xb6,0xc3,0xc3,0xc3,0xa6,0xc4,0xc4,0xc4,0xb8,0xb8,
+	0xb5,0xb5,0xb5,0xc2,0xa5,0xb6,0xb6,0xb6,0xc3,0xa6,0xa6,0xb7,0xb7,0xc4,0xb8,0xb8,
+	0xb5,0xb5,0xb5,0xa5,0xb6,0xb6,0xb6,0xb6,0xc3,0xa6,0xb7,0xb7,0xb7,0xb7,0xb8,0xb8,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xb6,0xb6,0xc7,0xc7,0xc7,0xb7,0xb7,0xc8,0xc8,0xb8,0xb8,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xc6,0xc7,0xc7,0xc7,0xaa,0xc8,0xc8,0xc8,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xc6,0xc6,0xa9,0xa9,0xc7,0xc7,0xaa,0xaa,0xaa,0xc8,0xc8,0xab,0xbc,
+	0xa8,0xa8,0xa8,0xa9,0xa9,0xa9,0xba,0xba,0xaa,0xaa,0xaa,0xbb,0xbb,0xab,0xbc,0xbc,
+	0xb9,0xb9,0xb9,0xca,0xca,0xba,0xba,0xba,0xcb,0xaa,0xbb,0xbb,0xbb,0xcc,0xbc,0xbc,
+	0xb9,0xb9,0xb9,0xca,0xca,0xba,0xba,0xcb,0xcb,0xcb,0xbb,0xbb,0xcc,0xcc,0xcc,0xbc,
+	0xc9,0xc9,0xc9,0xca,0xca,0xca,0xba,0xcb,0xcb,0xcb,0xae,0xcc,0xcc,0xcc,0xaf,0xaf,
+	0xbd,0xbd,0xbd,0xad,0xbe,0xbe,0xbe,0xbe,0xae,0xae,0xbf,0xbf,0xcc,0xaf,0xaf,0xb0,
+	0xbd,0xbd,0xbd,0xbe,0xbe,0xbe,0xbe,0xbe,0xbf,0xbf,0xbf,0xbf,0xbf,0xaf,0xb0,0xb0,
+	0xcd,0xcd,0xcd,0xce,0xce,0xce,0xb2,0xcf,0xcf,0xcf,0xb3,0xb3,0xc0,0xc0,0xd1,0xb4,
+	0xcd,0xcd,0xcd,0xce,0xce,0xce,0xb2,0xcf,0xcf,0xcf,0xb3,0xb3,0xc0,0xc0,0xd1,0xb4,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xb2,0xc3,0xc3,0xc3,0xb3,0xb3,0xc0,0xc0,0xd1,0xb4,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xc2,0xc3,0xc3,0xc3,0xd4,0xc4,0xc4,0xc4,0xd5,0xd5,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xb6,0xc3,0xc3,0xc3,0xd4,0xc4,0xc4,0xc4,0xd5,0xb8,
+	0xc1,0xc1,0xc1,0xc2,0xc2,0xb6,0xb6,0xc3,0xc3,0xd4,0xb7,0xb7,0xc4,0xd5,0xd5,0xb8,
+	0xb5,0xb5,0xb5,0xc2,0xb6,0xb6,0xb6,0xb6,0xc3,0xd4,0xb7,0xb7,0xb7,0xd5,0xd5,0xb8,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xb6,0xc7,0xc7,0xc7,0xb7,0xc8,0xc8,0xc8,0xd9,0xd9,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xc6,0xc7,0xc7,0xc7,0xd8,0xc8,0xc8,0xc8,0xd9,0xd9,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xd7,0xd7,0xc7,0xc7,0xd8,0xd8,0xc8,0xc8,0xd9,0xd9,0xbc,
+	0xb9,0xb9,0xb9,0xd7,0xd7,0xba,0xba,0xba,0xd8,0xd8,0xbb,0xbb,0xbb,0xd9,0xd9,0xbc,
+	0xb9,0xb9,0xb9,0xca,0xca,0xba,0xba,0xcb,0xcb,0xcb,0xbb,0xbb,0xcc,0xcc,0xcc,0xbc,
+	0xc9,0xc9,0xc9,0xca,0xca,0xca,0xba,0xcb,0xcb,0xcb,0xbb,0xcc,0xcc,0xcc,0xdd,0xdd,
+	0xc9,0xc9,0xc9,0xca,0xca,0xdb,0xdb,0xcb,0xcb,0xdc,0xdc,0xcc,0xcc,0xdd,0xdd,0xdd,
+	0xda,0xda,0xda,0xdb,0xdb,0xdb,0xdb,0xdc,0xdc,0xdc,0xdc,0xcc,0xdd,0xdd,0xdd,0xb0,
+	0xbd,0xbd,0xbd,0xdb,0xbe,0xbe,0xbe,0xdc,0xdc,0xbf,0xbf,0xbf,0xdd,0xdd,0xb0,0xb0,
+	0xde,0xde,0xde,0xdf,0xdf,0xdf,0xe0,0xcf,0xd0,0xd0,0xe1,0xc0,0xc0,0xd1,0xd1,0xe2,
+	0xde,0xde,0xde,0xdf,0xdf,0xdf,0xe0,0xcf,0xd0,0xd0,0xe1,0xc0,0xc0,0xd1,0xd1,0xe2,
+	0xde,0xde,0xde,0xdf,0xdf,0xdf,0xe0,0xc3,0xd0,0xd0,0xe1,0xc0,0xc0,0xd1,0xd1,0xe2,
+	0xd2,0xd2,0xd2,0xc2,0xd3,0xd3,0xd3,0xc3,0xc3,0xd4,0xd4,0xc4,0xc4,0xd5,0xd5,0xe6,
+	0xd2,0xd2,0xd2,0xd3,0xd3,0xd3,0xd3,0xc3,0xd4,0xd4,0xd4,0xc4,0xc4,0xd5,0xd5,0xe6,
+	0xd2,0xd2,0xd2,0xd3,0xd3,0xd3,0xe4,0xc3,0xd4,0xd4,0xe5,0xc4,0xd5,0xd5,0xe6,0xe6,
+	0xe3,0xe3,0xe3,0xd3,0xd3,0xe4,0xb6,0xd4,0xd4,0xe5,0xe5,0xb7,0xd5,0xd5,0xe6,0xe6,
+	0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xd7,0xc7,0xc7,0xd8,0xd8,0xc8,0xc8,0xd9,0xd9,0xd9,
+	0xd6,0xd6,0xd6,0xc6,0xd7,0xd7,0xd7,0xc7,0xd8,0xd8,0xd8,0xc8,0xc8,0xd9,0xd9,0xea,
+	0xd6,0xd6,0xd6,0xd7,0xd7,0xd7,0xe8,0xd8,0xd8,0xd8,0xe9,0xc8,0xd9,0xd9,0xea,0xea,
+	0xe7,0xe7,0xe7,0xd7,0xd7,0xe8,0xe8,0xd8,0xd8,0xe9,0xe9,0xe9,0xd9,0xd9,0xea,0xea,
+	0xc9,0xc9,0xc9,0xca,0xca,0xca,0xba,0xcb,0xcb,0xcb,0xe9,0xcc,0xcc,0xcc,0xea,0xea,
+	0xc9,0xc9,0xc9,0xca,0xca,0xdb,0xdb,0xcb,0xcb,0xdc,0xdc,0xcc,0xcc,0xdd,0xdd,0xdd,
+	0xda,0xda,0xda,0xdb,0xdb,0xdb,0xdb,0xdc,0xdc,0xdc,0xdc,0xcc,0xdd,0xdd,0xdd,0xee,
+	0xda,0xda,0xda,0xdb,0xdb,0xec,0xec,0xdc,0xdc,0xed,0xed,0xed,0xdd,0xdd,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xec,0xec,0xdc,0xed,0xed,0xed,0xed,0xdd,0xee,0xee,0xee,
+	0xef,0xef,0xef,0xdf,0xe0,0xe0,0xe0,0xd0,0xd0,0xe1,0xe1,0xf2,0xd1,0xd1,0xe2,0xe2,
+	0xef,0xef,0xef,0xdf,0xe0,0xe0,0xe0,0xd0,0xd0,0xe1,0xe1,0xf2,0xd1,0xd1,0xe2,0xe2,
+	0xef,0xef,0xef,0xdf,0xe0,0xe0,0xe0,0xd0,0xd0,0xe1,0xe1,0xf2,0xd1,0xd1,0xe2,0xe2,
+	0xd2,0xd2,0xd2,0xd3,0xd3,0xe4,0xe4,0xd4,0xd4,0xd4,0xe5,0xe5,0xd5,0xd5,0xe6,0xe6,
+	0xe3,0xe3,0xe3,0xd3,0xe4,0xe4,0xe4,0xd4,0xd4,0xe5,0xe5,0xf6,0xd5,0xd5,0xe6,0xe6,
+	0xe3,0xe3,0xe3,0xe4,0xe4,0xe4,0xe4,0xd4,0xe5,0xe5,0xe5,0xf6,0xd5,0xe6,0xe6,0xf7,
+	0xe3,0xe3,0xe3,0xe4,0xe4,0xe4,0xf5,0xf5,0xe5,0xe5,0xf6,0xf6,0xd5,0xe6,0xe6,0xf7,
+	0xd6,0xd6,0xd6,0xd7,0xd7,0xd7,0xf5,0xc7,0xd8,0xd8,0xf6,0xc8,0xd9,0xd9,0xd9,0xf7,
+	0xd6,0xd6,0xd6,0xd7,0xd7,0xe8,0xe8,0xd8,0xd8,0xd8,0xe9,0xe9,0xd9,0xd9,0xea,0xea,
+	0xe7,0xe7,0xe7,0xd7,0xe8,0xe8,0xe8,0xd8,0xd8,0xe9,0xe9,0xe9,0xd9,0xea,0xea,0xea,
+	0xe7,0xe7,0xe7,0xe8,0xe8,0xe8,0xf9,0xf9,0xe9,0xe9,0xe9,0xfa,0xd9,0xea,0xea,0xfb,
+	0xf8,0xf8,0xf8,0xe8,0xf9,0xf9,0xf9,0xcb,0xe9,0xe9,0xfa,0xfa,0xcc,0xea,0xea,0xfb,
+	0xda,0xda,0xda,0xdb,0xdb,0xdb,0xdb,0xdc,0xdc,0xdc,0xdc,0xcc,0xdd,0xdd,0xdd,0xee,
+	0xda,0xda,0xda,0xdb,0xdb,0xec,0xec,0xdc,0xdc,0xed,0xed,0xed,0xdd,0xdd,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xec,0xec,0xdc,0xed,0xed,0xed,0xed,0xdd,0xee,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xfd,0xfd,0xfd,0xed,0xed,0xfe,0xfe,0xee,0xee,0xee,0xff,
+	0xf0,0xf0,0xf0,0xe0,0xf1,0xf1,0xf1,0xf1,0xe1,0xf2,0xf2,0xf2,0xf2,0xe2,0xe2,0xf3,
+	0xf0,0xf0,0xf0,0xe0,0xf1,0xf1,0xf1,0xf1,0xe1,0xf2,0xf2,0xf2,0xf2,0xe2,0xe2,0xf3,
+	0xf0,0xf0,0xf0,0xe0,0xf1,0xf1,0xf1,0xf1,0xe1,0xf2,0xf2,0xf2,0xf2,0xe2,0xe2,0xf3,
+	0xe3,0xe3,0xe3,0xe4,0xe4,0xe4,0xf5,0xf5,0xe5,0xe5,0xf6,0xf6,0xd5,0xe6,0xe6,0xf7,
+	0xf4,0xf4,0xf4,0xe4,0xe4,0xf5,0xf5,0xf5,0xe5,0xe5,0xf6,0xf6,0xf6,0xe6,0xe6,0xf7,
+	0xf4,0xf4,0xf4,0xe4,0xf5,0xf5,0xf5,0xf5,0xe5,0xf6,0xf6,0xf6,0xf6,0xe6,0xf7,0xf7,
+	0xf4,0xf4,0xf4,0xf5,0xf5,0xf5,0xf5,0xf5,0xe5,0xf6,0xf6,0xf6,0xf6,0xe6,0xf7,0xf7,
+	0xf4,0xf4,0xf4,0xf5,0xf5,0xf5,0xf5,0xf5,0xd8,0xf6,0xf6,0xf6,0xd9,0xd9,0xf7,0xf7,
+	0xe7,0xe7,0xe7,0xe8,0xe8,0xe8,0xe8,0xd8,0xe9,0xe9,0xe9,0xfa,0xd9,0xea,0xea,0xea,
+	0xf8,0xf8,0xf8,0xe8,0xe8,0xf9,0xf9,0xf9,0xe9,0xe9,0xfa,0xfa,0xfa,0xea,0xea,0xfb,
+	0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xe9,0xfa,0xfa,0xfa,0xfa,0xea,0xfb,0xfb,
+	0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xfa,0xfa,0xfa,0xfa,0xfa,0xea,0xfb,0xfb,
+	0xf8,0xf8,0xf8,0xdb,0xf9,0xf9,0xf9,0xdc,0xdc,0xfa,0xfa,0xfa,0xdd,0xdd,0xee,0xfb,
+	0xeb,0xeb,0xeb,0xec,0xec,0xec,0xec,0xdc,0xed,0xed,0xed,0xed,0xdd,0xee,0xee,0xee,
+	0xeb,0xeb,0xeb,0xec,0xec,0xfd,0xfd,0xfd,0xed,0xed,0xfe,0xfe,0xee,0xee,0xee,0xff,
+	0xfc,0xfc,0xfc,0xfd,0xfd,0xfd,0xfd,0xfd,0xed,0xfe,0xfe,0xfe,0xfe,0xee,0xff,0xff,
+}
+};
+Memcmap *memdefcmap = &def;
+void _memmkcmap(void){}
diff --git a/src/libdraw/md-cread.c b/src/libdraw/md-cread.c
new file mode 100644
index 0000000..4fa5942
--- /dev/null
+++ b/src/libdraw/md-cread.c
@@ -0,0 +1,96 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memimage*
+creadmemimage(int fd)
+{
+	char hdr[5*12+1];
+	Rectangle r;
+	int m, nb, miny, maxy, new, ldepth, ncblock;
+	uchar *buf;
+	Memimage *i;
+	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<maxy){
+			werrstr("readimage: bad maxy %d", maxy);
+			goto Errout;
+		}
+		if(nb<=0 || ncblock<nb){
+			werrstr("readimage: bad count %d", nb);
+			goto Errout;
+		}
+		if(readn(fd, buf, nb)!=nb)
+			goto Shortread;
+		if(!new)	/* old image: flip the data bits */
+			_twiddlecompressed(buf, nb);
+		cloadmemimage(i, Rect(r.min.x, miny, r.max.x, maxy), buf, nb);
+		miny = maxy;
+	}
+	free(buf);
+	return i;
+}
diff --git a/src/libdraw/md-defont.c b/src/libdraw/md-defont.c
new file mode 100644
index 0000000..446c2e9
--- /dev/null
+++ b/src/libdraw/md-defont.c
@@ -0,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memsubfont*
+getmemdefont(void)
+{
+	char *hdr, *p;
+	int n;
+	Fontchar *fc;
+	Memsubfont *f;
+	int ld;
+	Rectangle r;
+	Memdata *md;
+	Memimage *i;
+
+	/*
+	 * make sure data is word-aligned.  this is true with Plan 9 compilers
+	 * but not in general.  the byte order is right because the data is
+	 * declared as char*, not 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
new file mode 100644
index 0000000..d0f2d4f
--- /dev/null
+++ b/src/libdraw/md-draw.c
@@ -0,0 +1,2487 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int drawdebug;
+static int	tablesbuilt;
+
+/* perfect approximation to NTSC = .299r+.587g+.114b when 0 ≤ r,g,b < 256 */
+#define RGB2K(r,g,b)	((156763*(r)+307758*(g)+59769*(b))>>19)
+
+/*
+ * for 0 ≤ x ≤ 255*255, (x*0x0101+0x100)>>16 is a perfect approximation.
+ * for 0 ≤ x < (1<<16), x/255 = ((x+1)*0x0101)>>16 is a perfect approximation.
+ * the last one is perfect for all up to 1<<16, avoids a multiply, but requires a rathole.
+ */
+/* #define DIV255(x) (((x)*257+256)>>16)  */
+#define DIV255(x) ((((x)+1)*257)>>16)
+/* #define DIV255(x) (tmp=(x)+1, (tmp+(tmp>>8))>>8) */
+
+#define MUL(x, y, t)	(t = (x)*(y)+128, (t+(t>>8))>>8)
+#define MASK13	0xFF00FF00
+#define MASK02	0x00FF00FF
+#define MUL13(a, x, t)		(t = (a)*(((x)&MASK13)>>8)+128, ((t+((t>>8)&MASK02))>>8)&MASK02)
+#define MUL02(a, x, t)		(t = (a)*(((x)&MASK02)>>0)+128, ((t+((t>>8)&MASK02))>>8)&MASK02)
+#define MUL0123(a, x, s, t)	((MUL13(a, x, s)<<8)|MUL02(a, x, t))
+
+#define MUL2(u, v, x, y)	(t = (u)*(v)+(x)*(y)+256, (t+(t>>8))>>8)
+
+static void mktables(void);
+typedef int Subdraw(Memdrawparam*);
+static Subdraw chardraw, alphadraw, memoptdraw;
+
+static Memimage*	memones;
+static Memimage*	memzeros;
+Memimage *memwhite;
+Memimage *memblack;
+Memimage *memtransparent;
+Memimage *memopaque;
+
+int	__ifmt(Fmt*);
+
+void
+memimageinit(void)
+{
+	static int didinit = 0;
+
+	if(didinit)
+		return;
+
+	didinit = 1;
+
+	mktables();
+	_memmkcmap();
+
+	fmtinstall('R', Rfmt); 
+	fmtinstall('P', Pfmt);
+	fmtinstall('b', __ifmt);
+
+	memones = allocmemimage(Rect(0,0,1,1), GREY1);
+	memones->flags |= Frepl;
+	memones->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+	*byteaddr(memones, ZP) = ~0;
+
+	memzeros = allocmemimage(Rect(0,0,1,1), GREY1);
+	memzeros->flags |= Frepl;
+	memzeros->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
+	*byteaddr(memzeros, ZP) = 0;
+
+	if(memones == nil || memzeros == nil)
+		assert(0 /*cannot initialize memimage library */);	/* RSC BUG */
+
+	memwhite = memones;
+	memblack = memzeros;
+	memopaque = memones;
+	memtransparent = memzeros;
+}
+
+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 &par;
+}
+
+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<<j)-1);
+			replbit[j][i] = (small*replmul[j])>>8;
+		}
+	}
+
+	/* bit unpacking up to 8 bits, only powers of 2 */
+	for(i=0; i<256; i++){
+		for(j=0, sh=7, mask=1; j<8; j++, sh--)
+			conv18[i][j] = replbit[1][(i>>sh)&mask];
+
+		for(j=0, sh=6, mask=3; j<4; j++, sh-=2)
+			conv28[i][j] = replbit[2][(i>>sh)&mask];
+
+		for(j=0, sh=4, mask=15; j<2; j++, sh-=4)
+			conv48[i][j] = replbit[4][(i>>sh)&mask];
+	}
+}
+
+static uchar ones = 0xff;
+
+/*
+ * General alpha drawing case.  Can handle anything.
+ */
+typedef struct	Buffer	Buffer;
+struct Buffer {
+	/* used by most routines */
+	uchar	*red;
+	uchar	*grn;
+	uchar	*blu;
+	uchar	*alpha;
+	uchar	*grey;
+	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; i<n; i++){
+		print(" ");
+		if(p=b.grey){
+			print(" k%.2uX", *p);
+			b.grey += b.delta;
+		}else{	
+			if(p=b.red){
+				print(" r%.2uX", *p);
+				b.red += b.delta;
+			}
+			if(p=b.grn){
+				print(" g%.2uX", *p);
+				b.grn += b.delta;
+			}
+			if(p=b.blu){
+				print(" b%.2uX", *p);
+				b.blu += b.delta;
+			}
+		}
+		if((p=b.alpha) != &ones){
+			print(" α%.2uX", *p);
+			b.alpha += b.delta;
+		}
+	}
+	print("\n");
+}
+
+/*
+ * For each scan line, we expand the pixels from source, mask, and destination
+ * into byte-aligned red, green, blue, alpha, and grey channels.  If buffering is not
+ * needed and the channels were already byte-aligned (grey8, rgb24, rgba32, rgb32),
+ * the readers need not copy the data: they can simply return pointers to the data.
+ * If the destination image is grey and the source is not, it is converted using the NTSC
+ * formula.
+ *
+ * Once we have all the channels, we call either rgbcalc or greycalc, depending on 
+ * whether the destination image is color.  This is allowed to overwrite the dst buffer (perhaps
+ * the actual data, perhaps a copy) with its result.  It should only overwrite the dst buffer
+ * with the same format (i.e. red bytes with red bytes, etc.)  A new buffer is returned from
+ * the calculator, and that buffer is passed to a function to write it to the destination.
+ * If the buffer is already pointing at the destination, the writing function is a no-op.
+ */
+#define DBG if(0)
+static int
+alphadraw(Memdrawparam *par)
+{
+	int isgrey, starty, endy, op;
+	int needbuf, dsty, srcy, masky;
+	int y, dir, dx, dy;
+	Buffer bsrc, bdst, bmask;
+	Readfn *rdsrc, *rdmask, *rddst;
+	Calcfn *calc;
+	Writefn *wrdst;
+	Memimage *src, *mask, *dst;
+	Rectangle r, sr, mr;
+
+	r = par->r;
+	dx = Dx(r);
+	dy = Dy(r);
+
+	ndrawbuf = 0;
+
+	src = par->src;
+	mask = par->mask;	
+	dst = par->dst;
+	sr = par->sr;
+	mr = par->mr;
+	op = par->op;
+
+	isgrey = dst->flags&Fgrey;
+
+	/*
+	 * Buffering when src and dst are the same bitmap is sufficient but not 
+	 * necessary.  There are stronger conditions we could use.  We could
+	 * check to see if the rectangles intersect, and if simply moving in the
+	 * correct y direction can avoid the need to buffer.
+	 */
+	needbuf = (src->data == dst->data);
+
+	spar = getparam(src, sr, isgrey, needbuf);
+	dpar = getparam(dst, r, isgrey, needbuf);
+	mpar = getparam(mask, mr, 0, needbuf);
+
+	dir = (needbuf && byteaddr(dst, r.min) > byteaddr(src, sr.min)) ? -1 : 1;
+	spar.dir = mpar.dir = dpar.dir = dir;
+
+	/*
+	 * If the mask is purely boolean, we can convert from src to dst format
+	 * when we read src, and then just copy it to dst where the mask tells us to.
+	 * This requires a boolean (1-bit grey) mask and lack of a source alpha channel.
+	 *
+	 * The computation is accomplished by assigning the function pointers as follows:
+	 *	rdsrc - read and convert source into dst format in a buffer
+	 * 	rdmask - convert mask to bytes, set pointer to it
+	 * 	rddst - fill with pointer to real dst data, but do no reads
+	 *	calc - copy src onto dst when mask says to.
+	 *	wrdst - do nothing
+	 * This is slightly sleazy, since things aren't doing exactly what their names say,
+	 * but it avoids a fair amount of code duplication to make this a case here
+	 * rather than have a separate booldraw.
+	 */
+//if(drawdebug) iprint("flag %lud mchan %lux=?%x dd %d\n", src->flags&Falpha, mask->chan, GREY1, dst->depth);
+	if(!(src->flags&Falpha) && mask->chan == GREY1 && dst->depth >= 8 && op == SoverD){
+//if(drawdebug) iprint("boolcopy...");
+		rdsrc = convfn(dst, &dpar, src, &spar);
+		rddst = readptr;
+		rdmask = readfn(mask);
+		calc = boolcopyfn(dst, mask);
+		wrdst = nullwrite;
+	}else{
+		/* usual alphadraw parameter fetching */
+		rdsrc = readfn(src);
+		rddst = readfn(dst);
+		wrdst = writefn(dst);
+		calc = alphacalc[op];
+
+		/*
+		 * If there is no alpha channel, we'll ask for a grey channel
+		 * and pretend it is the alpha.
+		 */
+		if(mask->flags&Falpha){
+			rdmask = readalphafn(mask);
+			mpar.alphaonly = 1;
+		}else{
+			mpar.greymaskcall = readfn(mask);
+			mpar.convgrey = 1;
+			rdmask = greymaskread;
+
+			/*
+			 * Should really be above, but then boolcopyfns would have
+			 * to deal with bit alignment, and I haven't written that.
+			 *
+			 * This is a common case for things like ellipse drawing.
+			 * When there's no alpha involved and the mask is boolean,
+			 * we can avoid all the division and multiplication.
+			 */
+			if(mask->chan == GREY1 && !(src->flags&Falpha))
+				calc = boolcalc[op];
+			else if(op == SoverD && !(src->flags&Falpha))
+				calc = alphacalcS;
+		}
+	}
+
+	/*
+	 * If the image has a small enough repl rectangle,
+	 * we can just read each line once and cache them.
+	 */
+	if(spar.replcache){
+		spar.replcall = rdsrc;
+		rdsrc = replread;
+	}
+	if(mpar.replcache){
+		mpar.replcall = rdmask;
+		rdmask = replread;
+	}
+
+	if(allocdrawbuf() < 0)
+		return 0;
+
+	/*
+	 * Before we were saving only offsets from drawbuf in the parameter
+	 * structures; now that drawbuf has been grown to accomodate us,
+	 * we can fill in the pointers.
+	 */
+	spar.bufbase = drawbuf+spar.bufoff;
+	mpar.bufbase = drawbuf+mpar.bufoff;
+	dpar.bufbase = drawbuf+dpar.bufoff;
+	spar.convbuf = drawbuf+spar.convbufoff;
+
+	if(dir == 1){
+		starty = 0;
+		endy = dy;
+	}else{
+		starty = dy-1;
+		endy = -1;
+	}
+
+	/*
+	 * srcy, masky, and dsty are offsets from the top of their
+	 * respective Rectangles.  they need to be contained within
+	 * the rectangles, so clipy can keep them there without division.
+ 	 */
+	srcy = (starty + sr.min.y - src->r.min.y)%Dy(src->r);
+	masky = (starty + mr.min.y - mask->r.min.y)%Dy(mask->r);
+	dsty = starty + r.min.y - dst->r.min.y;
+
+	assert(0 <= srcy && srcy < Dy(src->r));
+	assert(0 <= masky && masky < Dy(mask->r));
+	assert(0 <= dsty && dsty < Dy(dst->r));
+
+	for(y=starty; y!=endy; y+=dir, srcy+=dir, masky+=dir, dsty+=dir){
+		clipy(src, &srcy);
+		clipy(dst, &dsty);
+		clipy(mask, &masky);
+
+		bsrc = rdsrc(&spar, spar.bufbase, srcy);
+DBG print("[");
+		bmask = rdmask(&mpar, mpar.bufbase, masky);
+DBG print("]\n");
+		bdst = rddst(&dpar, dpar.bufbase, dsty);
+DBG		dumpbuf("src", bsrc, dx);
+DBG		dumpbuf("mask", bmask, dx);
+DBG		dumpbuf("dst", bdst, dx);
+		bdst = calc(bdst, bsrc, bmask, dx, isgrey, op);
+		wrdst(&dpar, dpar.bytermin+dsty*dpar.bwidth, bdst);
+	}
+
+	return 1;
+}
+#undef DBG
+
+static Buffer
+alphacalc0(Buffer bdst, Buffer b1, Buffer b2, int dx, int grey, int op)
+{
+	USED(grey);
+	USED(op);
+	memset(bdst.rgba, 0, dx*bdst.delta);
+	return bdst;
+}
+
+static Buffer
+alphacalc14(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fd, sadelta;
+	int i, sa, ma, q;
+	u32int s, t;
+
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		fd = MUL(sa, ma, t);
+		if(op == DoutS)
+			fd = 255-fd;
+
+		if(grey){
+			*bdst.grey = MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(fd, *bdst.rgba, s, t);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				continue;
+			}
+			*bdst.red = MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+static Buffer
+alphacalc2810(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fs, sadelta;
+	int i, ma, da, q;
+	u32int s, t;
+
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		if(op == SoutD)
+			da = 255-da;
+		fs = ma;
+		if(op != S)
+			fs = MUL(fs, da, t);
+
+		if(grey){
+			*bdst.grey = MUL(fs, *bsrc.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(fs, *bsrc.rgba, s, t);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bmask.alpha += bmask.delta;
+				bdst.alpha += bdst.delta;
+				continue;
+			}
+			*bdst.red = MUL(fs, *bsrc.red, t);
+			*bdst.grn = MUL(fs, *bsrc.grn, t);
+			*bdst.blu = MUL(fs, *bsrc.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(fs, *bsrc.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+static Buffer
+alphacalc3679(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fs, fd, sadelta;
+	int i, sa, ma, da, q;
+	u32int s, t, u, v;
+
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		if(op == SatopD)
+			fs = MUL(ma, da, t);
+		else
+			fs = MUL(ma, 255-da, t);
+		if(op == DoverS)
+			fd = 255;
+		else{
+			fd = MUL(sa, ma, t);
+			if(op != DatopS)
+				fd = 255-fd;
+		}
+
+		if(grey){
+			*bdst.grey = MUL(fs, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(fs, *bsrc.rgba, s, t)+MUL0123(fd, *bdst.rgba, u, v);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				bdst.alpha += bdst.delta;
+				continue;
+			}
+			*bdst.red = MUL(fs, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(fs, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(fs, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(fs, sa, s)+MUL(fd, da, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+static Buffer
+alphacalc5(Buffer bdst, Buffer b1, Buffer b2, int dx, int grey, int op)
+{
+	USED(dx);
+	USED(grey);
+	USED(op);
+	return bdst;
+}
+
+static Buffer
+alphacalc11(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fd, sadelta;
+	int i, sa, ma, q;
+	u32int s, t, u, v;
+
+	USED(op);
+	obdst = bdst;
+	sadelta = bsrc.alpha == &ones ? 0 : bsrc.delta;
+	q = bsrc.delta == 4 && bdst.delta == 4;
+
+	for(i=0; i<dx; i++){
+		sa = *bsrc.alpha;
+		ma = *bmask.alpha;
+		fd = 255-MUL(sa, ma, t);
+
+		if(grey){
+			*bdst.grey = MUL(ma, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(q){
+				*bdst.rgba = MUL0123(ma, *bsrc.rgba, s, t)+MUL0123(fd, *bdst.rgba, u, v);
+				bsrc.rgba++;
+				bdst.rgba++;
+				bsrc.alpha += sadelta;
+				bmask.alpha += bmask.delta;
+				continue;
+			}
+			*bdst.red = MUL(ma, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(ma, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(ma, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = MUL(ma, sa, s)+MUL(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		bsrc.alpha += sadelta;
+	}
+	return obdst;
+}
+
+/*
+not used yet
+source and mask alpha 1
+static Buffer
+alphacalcS0(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int i;
+
+	USED(op);
+	obdst = bdst;
+	if(bsrc.delta == bdst.delta){
+		memmove(bdst.rgba, bsrc.rgba, dx*bdst.delta);
+		return obdst;
+	}
+	for(i=0; i<dx; i++){
+		if(grey){
+			*bdst.grey = *bsrc.grey;
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			*bdst.red = *bsrc.red;
+			*bdst.grn = *bsrc.grn;
+			*bdst.blu = *bsrc.blu;
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = 255;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+*/
+
+/* source alpha 1 */
+static Buffer
+alphacalcS(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fd;
+	int i, ma;
+	u32int s, t;
+
+	USED(op);
+	obdst = bdst;
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		fd = 255-ma;
+
+		if(grey){
+			*bdst.grey = MUL(ma, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			*bdst.red = MUL(ma, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+			*bdst.grn = MUL(ma, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+			*bdst.blu = MUL(ma, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		if(bdst.alpha != &ones){
+			*bdst.alpha = ma+MUL(fd, *bdst.alpha, t);
+			bdst.alpha += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+	}
+	return obdst;
+}
+
+static Buffer
+boolcalc14(Buffer bdst, Buffer b1, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int i, ma, zero;
+
+	obdst = bdst;
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		zero = ma ? op == DoutS : op == DinS;
+
+		if(grey){
+			if(zero)
+				*bdst.grey = 0;
+			bdst.grey += bdst.delta;
+		}else{
+			if(zero)
+				*bdst.red = *bdst.grn = *bdst.blu = 0;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		if(bdst.alpha != &ones){
+			if(zero)
+				*bdst.alpha = 0;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+
+static Buffer
+boolcalc236789(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int fs, fd;
+	int i, ma, da, zero;
+	u32int s, t;
+
+	obdst = bdst;
+	zero = !(op&1);
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+		da = *bdst.alpha;
+		fs = da;
+		if(op&2)
+			fs = 255-da;
+		fd = 0;
+		if(op&4)
+			fd = 255;
+
+		if(grey){
+			if(ma)
+				*bdst.grey = MUL(fs, *bsrc.grey, s)+MUL(fd, *bdst.grey, t);
+			else if(zero)
+				*bdst.grey = 0;
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(ma){
+				*bdst.red = MUL(fs, *bsrc.red, s)+MUL(fd, *bdst.red, t);
+				*bdst.grn = MUL(fs, *bsrc.grn, s)+MUL(fd, *bdst.grn, t);
+				*bdst.blu = MUL(fs, *bsrc.blu, s)+MUL(fd, *bdst.blu, t);
+			}
+			else if(zero)
+				*bdst.red = *bdst.grn = *bdst.blu = 0;
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		if(bdst.alpha != &ones){
+			if(ma)
+				*bdst.alpha = fs+MUL(fd, da, t);
+			else if(zero)
+				*bdst.alpha = 0;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+
+static Buffer
+boolcalc1011(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op)
+{
+	Buffer obdst;
+	int i, ma, zero;
+
+	obdst = bdst;
+	zero = !(op&1);
+
+	for(i=0; i<dx; i++){
+		ma = *bmask.alpha;
+
+		if(grey){
+			if(ma)
+				*bdst.grey = *bsrc.grey;
+			else if(zero)
+				*bdst.grey = 0;
+			bsrc.grey += bsrc.delta;
+			bdst.grey += bdst.delta;
+		}else{
+			if(ma){
+				*bdst.red = *bsrc.red;
+				*bdst.grn = *bsrc.grn;
+				*bdst.blu = *bsrc.blu;
+			}
+			else if(zero)
+				*bdst.red = *bdst.grn = *bdst.blu = 0;
+			bsrc.red += bsrc.delta;
+			bsrc.blu += bsrc.delta;
+			bsrc.grn += bsrc.delta;
+			bdst.red += bdst.delta;
+			bdst.blu += bdst.delta;
+			bdst.grn += bdst.delta;
+		}
+		bmask.alpha += bmask.delta;
+		if(bdst.alpha != &ones){
+			if(ma)
+				*bdst.alpha = 255;
+			else if(zero)
+				*bdst.alpha = 0;
+			bdst.alpha += bdst.delta;
+		}
+	}
+	return obdst;
+}
+/*
+ * Replicated cached scan line read.  Call the function listed in the Param,
+ * but cache the result so that for replicated images we only do the work once.
+ */
+static Buffer
+replread(Param *p, uchar *s, int y)
+{
+	Buffer *b;
+
+	USED(s);
+	b = &p->bcache[y];
+	if((p->bfilled & (1<<y)) == 0){
+		p->bfilled |= 1<<y;
+		*b = p->replcall(p, p->bufbase+y*p->bufdelta, y);
+	}
+	return *b;
+}
+
+/*
+ * Alpha reading function that simply relabels the grey pointer.
+ */
+static Buffer
+greymaskread(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+
+	b = p->greymaskcall(p, buf, y);
+	b.alpha = b.grey;
+	return b;
+}
+
+#define DBG if(0)
+static Buffer
+readnbit(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+	Memimage *img;
+	uchar *repl, *r, *w, *ow, bits;
+	int i, n, sh, depth, x, dx, npack, nbits;
+
+	b.rgba = (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<n; i++){
+		if(nbits == 0){
+DBG print("(%.2ux)...", *r);
+			bits = *r++;
+			nbits = 8;
+		}
+		*w++ = repl[bits>>sh];
+DBG print("bit %x...", repl[bits>>sh]);
+		bits <<= depth;
+		nbits -= depth;
+	}
+	dx -= n;
+	if(dx == 0)
+		return b;
+
+	assert(x+i == p->img->r.max.x);
+
+	/* copy from beginning of repl rectangle until where we were before. */
+	x = p->img->r.min.x;
+	n = dx;
+	if(n > p->r.min.x - x)
+		n = p->r.min.x - x;
+
+	r = p->bytey0s + y*p->bwidth;
+DBG print("x=%d r=%p...", x, r);
+	bits = *r++;
+	nbits = 8;
+	if(i=x&(npack-1)){
+		bits <<= depth*i;
+		nbits -= depth*i;
+	}
+DBG print("nbits=%d...", nbits);
+	for(i=0; i<n; i++){
+		if(nbits == 0){
+			bits = *r++;
+			nbits = 8;
+		}
+		*w++ = repl[bits>>sh];
+DBG print("bit %x...", repl[bits>>sh]);
+		bits <<= depth;
+		nbits -= depth;
+DBG print("bits %x nbits %d...", bits, nbits);
+	}
+	dx -= n;
+	if(dx == 0)
+		return b;
+
+	assert(dx > 0);
+	/* now we have exactly one full scan line: just replicate the buffer itself until we are done */
+	ow = buf;
+	while(dx--)
+		*w++ = *ow++;
+
+	return b;
+}
+#undef DBG
+
+#define DBG if(0)
+static void
+writenbit(Param *p, uchar *w, Buffer src)
+{
+	uchar *r;
+	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<ex; x++){
+		bits <<= depth;
+DBG print(" %x", *r);
+		bits |= (*r++ >> sh);
+		nbits += depth;
+		if(nbits == 8){
+			*w++ = bits;
+			nbits = 0;
+		}
+	}
+
+	if(nbits){
+		sh = 8-nbits;
+		bits <<= sh;
+		bits |= *w & ((1<<sh)-1);
+		*w = bits;
+	}
+DBG print("\n");
+	return;
+}
+#undef DBG
+
+static Buffer
+readcmap(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+	int a, convgrey, copyalpha, dx, i, m;
+	uchar *q, *cmap, *begin, *end, *r, *w;
+
+	begin = p->bytey0s + y*p->bwidth;
+	r = p->bytermin + y*p->bwidth;
+	end = p->bytey0e + y*p->bwidth;
+	cmap = p->img->cmap->cmap2rgb;
+	convgrey = p->convgrey;
+	copyalpha = (p->img->flags&Falpha) ? 1 : 0;
+
+	w = buf;
+	dx = p->dx;
+	if(copyalpha){
+		b.alpha = buf++;
+		a = p->img->shift[CAlpha]/8;
+		m = p->img->shift[CMap]/8;
+		for(i=0; i<dx; i++){
+			*w++ = r[a];
+			q = cmap+r[m]*3;
+			r += 2;
+			if(r == end)
+				r = begin;
+			if(convgrey){
+				*w++ = RGB2K(q[0], q[1], q[2]);
+			}else{
+				*w++ = q[2];	/* blue */
+				*w++ = q[1];	/* green */
+				*w++ = q[0];	/* red */
+			}
+		}
+	}else{
+		b.alpha = &ones;
+		for(i=0; i<dx; i++){
+			q = cmap+*r++*3;
+			if(r == end)
+				r = begin;
+			if(convgrey){
+				*w++ = RGB2K(q[0], q[1], q[2]);
+			}else{
+				*w++ = q[2];	/* blue */
+				*w++ = q[1];	/* green */
+				*w++ = q[0];	/* red */
+			}
+		}
+	}
+
+	b.rgba = (u32int*)(buf-copyalpha);
+
+	if(convgrey){
+		b.grey = buf;
+		b.red = b.blu = b.grn = buf;
+		b.delta = 1+copyalpha;
+	}else{
+		b.blu = buf;
+		b.grn = buf+1;
+		b.red = buf+2;
+		b.grey = nil;
+		b.delta = 3+copyalpha;
+	}
+	return b;
+}
+
+static void
+writecmap(Param *p, uchar *w, Buffer src)
+{
+	uchar *cmap, *red, *grn, *blu;
+	int i, dx, delta;
+
+	cmap = p->img->cmap->rgb2cmap;
+	
+	delta = src.delta;
+	red= src.red;
+	grn = src.grn;
+	blu = src.blu;
+
+	dx = p->dx;
+	for(i=0; i<dx; i++, red+=delta, grn+=delta, blu+=delta)
+		*w++ = cmap[(*red>>4)*256+(*grn>>4)*16+(*blu>>4)];
+}
+
+#define DBG if(0)
+static Buffer
+readbyte(Param *p, uchar *buf, int y)
+{
+	Buffer b;
+	Memimage *img;
+	int dx, isgrey, convgrey, alphaonly, copyalpha, i, nb;
+	uchar *begin, *end, *r, *w, *rrepl, *grepl, *brepl, *arepl, *krepl;
+	uchar ured, ugrn, ublu;
+	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<dx; i++){
+		u = r[0] | (r[1]<<8) | (r[2]<<16) | (r[3]<<24);
+		if(copyalpha) {
+			*w++ = arepl[(u>>img->shift[CAlpha]) & img->mask[CAlpha]];
+DBG print("a %x\n", w[-1]);
+		}
+
+		if(isgrey)
+			*w++ = krepl[(u >> img->shift[CGrey]) & img->mask[CGrey]];
+		else if(!alphaonly){
+			ured = rrepl[(u >> img->shift[CRed]) & img->mask[CRed]];
+			ugrn = grepl[(u >> img->shift[CGreen]) & img->mask[CGreen]];
+			ublu = brepl[(u >> img->shift[CBlue]) & img->mask[CBlue]];
+			if(convgrey){
+DBG print("g %x %x %x\n", ured, ugrn, ublu);
+				*w++ = RGB2K(ured, ugrn, ublu);
+DBG print("%x\n", w[-1]);
+			}else{
+				*w++ = brepl[(u >> img->shift[CBlue]) & img->mask[CBlue]];
+				*w++ = grepl[(u >> img->shift[CGreen]) & img->mask[CGreen]];
+				*w++ = rrepl[(u >> img->shift[CRed]) & img->mask[CRed]];
+			}
+		}
+		r += nb;
+		if(r == end)
+			r = begin;
+	}
+	
+	b.alpha = copyalpha ? buf : &ones;
+	b.rgba = (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<<img->depth)-1);
+
+	isalpha = img->flags&Falpha;
+	isgrey = img->flags&Fgrey;
+	adelta = src.delta;
+
+	if(isalpha && (alpha == nil || alpha == &ones)){
+		ff = 0xFF;
+		alpha = &ff;
+		adelta = 0;
+	}
+
+	for(i=0; i<dx; i++){
+		u = w[0] | (w[1]<<8) | (w[2]<<16) | (w[3]<<24);
+DBG print("u %.8lux...", u);
+		u &= mask;
+DBG print("&mask %.8lux...", u);
+		if(isgrey){
+			u |= ((*grey >> (8-img->nbits[CGrey])) & img->mask[CGrey]) << img->shift[CGrey];
+DBG print("|grey %.8lux...", u);
+			grey += delta;
+		}else{
+			u |= ((*red >> (8-img->nbits[CRed])) & img->mask[CRed]) << img->shift[CRed];
+			u |= ((*grn >> (8-img->nbits[CGreen])) & img->mask[CGreen]) << img->shift[CGreen];
+			u |= ((*blu >> (8-img->nbits[CBlue])) & img->mask[CBlue]) << img->shift[CBlue];
+			red += delta;
+			grn += delta;
+			blu += delta;
+DBG print("|rgb %.8lux...", u);
+		}
+
+		if(isalpha){
+			u |= ((*alpha >> (8-img->nbits[CAlpha])) & img->mask[CAlpha]) << img->shift[CAlpha];
+			alpha += adelta;
+DBG print("|alpha %.8lux...", u);
+		}
+
+		w[0] = u;
+		w[1] = u>>8;
+		w[2] = u>>16;
+		w[3] = u>>24;
+		w += nb;
+	}
+}
+#undef DBG
+
+static Readfn*
+readfn(Memimage *img)
+{
+	if(img->depth < 8)
+		return readnbit;
+	if(img->nbits[CMap] == 8)
+		return readcmap;
+	return readbyte;
+}
+
+static Readfn*
+readalphafn(Memimage *m)
+{
+	USED(m);
+	return readbyte;
+}
+
+static Writefn*
+writefn(Memimage *img)
+{
+	if(img->depth < 8)
+		return writenbit;
+	if(img->chan == CMAP8)
+		return writecmap;
+	return writebyte;
+}
+
+static void
+nullwrite(Param *p, uchar *s, Buffer b)
+{
+	USED(p);
+	USED(s);
+}
+
+static Buffer
+readptr(Param *p, uchar *s, int y)
+{
+	Buffer b;
+	uchar *q;
+
+	USED(s);
+	q = p->bytermin + y*p->bwidth;
+	b.red = q;	/* ptr to data */
+	b.grn = b.blu = b.grey = b.alpha = nil;
+	b.rgba = (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(w<ew)
+			*w++ = *r++;
+	}
+
+	b.red = buf;
+	b.blu = b.grn = b.grey = b.alpha = nil;
+	b.rgba = (u32int*)buf;
+	b.delta = 0;
+	
+	return b;
+}
+
+static Readfn*
+convfn(Memimage *dst, Param *dpar, Memimage *src, Param *spar)
+{
+	if(dst->chan == src->chan && !(src->flags&Frepl)){
+//if(drawdebug) iprint("readptr...");
+		return readptr;
+	}
+
+	if(dst->chan==CMAP8 && (src->chan==GREY1||src->chan==GREY2||src->chan==GREY4)){
+		/* cheat because we know the replicated value is exactly the color map entry. */
+//if(drawdebug) iprint("Readnbit...");
+		return readnbit;
+	}
+
+	spar->convreadcall = readfn(src);
+	spar->convwritecall = writefn(dst);
+	spar->convdpar = dpar;
+
+	/* allocate a conversion buffer */
+	spar->convbufoff = ndrawbuf;
+	ndrawbuf += spar->dx*4;
+
+	if(spar->dx > Dx(spar->img->r)){
+		spar->convdx = spar->dx;
+		spar->dx = Dx(spar->img->r);
+	}
+
+//if(drawdebug) iprint("genconv...");
+	return genconv;
+}
+
+/*
+ * 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<<bpp)-1;
+		break;
+	case 8:
+		val = p[0];
+		break;
+	case 16:
+		val = p[0]|(p[1]<<8);
+		break;
+	case 24:
+		val = p[0]|(p[1]<<8)|(p[2]<<16);
+		break;
+	case 32:
+		val = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
+		break;
+	}
+	while(bpp<32){
+		val |= val<<bpp;
+		bpp *= 2;
+	}
+	return val;
+}
+
+static Calcfn*
+boolcopyfn(Memimage *img, Memimage *mask)
+{
+	if(mask->flags&Frepl && Dx(mask->r)==1 && Dy(mask->r)==1 && pixelbits(mask, mask->r.min)==~0)
+		return boolmemmove;
+
+	switch(img->depth){
+	case 8:
+		return boolcopy8;
+	case 16:
+		return boolcopy16;
+	case 24:
+		return boolcopy24;
+	case 32:
+		return boolcopy32;
+	default:
+		assert(0 /* boolcopyfn */);
+	}
+	return nil;
+}
+
+/*
+ * Optimized draw for filling and scrolling; uses memset and memmove.
+ */
+static void
+memsets(void *vp, ushort val, int n)
+{
+	ushort *p, *ep;
+
+	p = vp;
+	ep = p+n;
+	while(p<ep)
+		*p++ = val;
+}
+
+static void
+memsetl(void *vp, u32int val, int n)
+{
+	u32int *p, *ep;
+
+	p = vp;
+	ep = p+n;
+	while(p<ep)
+		*p++ = val;
+}
+
+static void
+memset24(void *vp, u32int val, int n)
+{
+	uchar *p, *ep;
+	uchar a,b,c;
+
+	p = vp;
+	ep = p+3*n;
+	a = val;
+	b = val>>8;
+	c = val>>16;
+	while(p<ep){
+		*p++ = a;
+		*p++ = b;
+		*p++ = c;
+	}
+}
+
+u32int
+_imgtorgba(Memimage *img, u32int val)
+{
+	uchar r, g, b, a;
+	int nb, ov, v;
+	u32int chan;
+	uchar *p;
+
+	a = 0xFF;
+	r = g = b = 0xAA;	/* garbage */
+	for(chan=img->chan; chan; chan>>=8){
+		nb = NBITS(chan);
+		ov = v = val&((1<<nb)-1);
+		val >>= nb;
+
+		while(nb < 8){
+			v |= v<<nb;
+			nb *= 2;
+		}
+		v >>= (nb-8);
+
+		switch(TYPE(chan)){
+		case CRed:
+			r = v;
+			break;
+		case CGreen:
+			g = v;
+			break;
+		case CBlue:
+			b = v;
+			break;
+		case CAlpha:
+			a = v;
+			break;
+		case CGrey:
+			r = g = b = v;
+			break;
+		case CMap:
+			p = img->cmap->cmap2rgb+3*ov;
+			r = *p++;
+			g = *p++;	
+			b = *p;
+			break;
+		}
+	}
+	return (r<<24)|(g<<16)|(b<<8)|a;	
+}
+
+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))<<d;
+			break;
+		case CGreen:
+			v |= (g>>(8-nb))<<d;
+			break;
+		case CBlue:
+			v |= (b>>(8-nb))<<d;
+			break;
+		case CAlpha:
+			v |= (a>>(8-nb))<<d;
+			break;
+		case CMap:
+			p = img->cmap->rgb2cmap;
+			m = p[(r>>4)*256+(g>>4)*16+(b>>4)];
+			v |= (m>>(8-nb))<<d;
+			break;
+		case CGrey:
+			m = RGB2K(r,g,b);
+			v |= (m>>(8-nb))<<d;
+			break;
+		}
+		d += nb;
+	}
+//	print("rgba2img %.8lux = %.*lux\n", rgba, 2*d/8, v);
+	return v;
+}
+
+#define DBG if(0)
+static int
+memoptdraw(Memdrawparam *par)
+{
+	int m, y, dy, dx, op;
+	u32int v;
+	Memimage *src;
+	Memimage *dst;
+
+	dx = Dx(par->r);
+	dy = Dy(par->r);
+	src = par->src;
+	dst = par->dst;
+	op = par->op;
+
+DBG print("state %lux mval %lux dd %d\n", par->state, par->mval, dst->depth);
+	/*
+	 * If we have an opaque mask and source is one opaque pixel we can convert to the
+	 * destination format and just replicate with memset.
+	 */
+	m = Simplesrc|Simplemask|Fullmask;
+	if((par->state&m)==m && (par->srgba&0xFF) == 0xFF && (op ==S || op == SoverD)){
+		uchar *dp, p[4];
+		int d, dwid, ppb, np, nb;
+		uchar lm, rm;
+
+DBG print("memopt, dst %p, dst->data->bdata %p\n", dst, dst->data->bdata);
+		dwid = dst->width*sizeof(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<<d);
+			ppb = 8/dst->depth;	/* pixels per byte */
+			m = ppb-1;
+			/* left edge */
+			np = par->r.min.x&m;		/* no. pixels unused on left side of word */
+			dx -= (ppb-np);
+			nb = 8 - np * dst->depth;		/* no. bits used on right side of word */
+			lm = (1<<nb)-1;
+DBG print("np %d x %d nb %d lm %ux ppb %d m %ux\n", np, par->r.min.x, nb, lm, ppb, m);	
+
+			/* right edge */
+			np = par->r.max.x&m;	/* no. pixels used on left side of word */
+			dx -= np;
+			nb = 8 - np * dst->depth;		/* no. bits unused on right side of word */
+			rm = ~((1<<nb)-1);
+DBG print("np %d x %d nb %d rm %ux ppb %d m %ux\n", np, par->r.max.x, nb, rm, ppb, m);	
+
+DBG print("dx %d Dx %d\n", dx, Dx(par->r));
+			/* lm, rm are masks that are 1 where we should touch the bits */
+			if(dx < 0){	/* just one byte */
+				lm &= rm;
+				for(y=0; y<dy; y++, dp+=dwid)
+					*dp ^= (v ^ *dp) & lm;
+			}else if(dx == 0){	/* no full bytes */
+				if(lm)
+					dwid--;
+
+				for(y=0; y<dy; y++, dp+=dwid){
+					if(lm){
+DBG print("dp %p v %lux lm %ux (v ^ *dp) & lm %lux\n", dp, v, lm, (v^*dp)&lm);
+						*dp ^= (v ^ *dp) & lm;
+						dp++;
+					}
+					*dp ^= (v ^ *dp) & rm;
+				}
+			}else{		/* full bytes in middle */
+				dx /= ppb;
+				if(lm)
+					dwid--;
+				dwid -= dx;
+
+				for(y=0; y<dy; y++, dp+=dwid){
+					if(lm){
+						*dp ^= (v ^ *dp) & lm;
+						dp++;
+					}
+					memset(dp, v, dx);
+					dp += dx;
+					*dp ^= (v ^ *dp) & rm;
+				}
+			}
+			return 1;
+		case 8:
+			for(y=0; y<dy; y++, dp+=dwid)
+				memset(dp, v, dx);
+			return 1;
+		case 16:
+			p[0] = v;		/* make little endian */
+			p[1] = v>>8;
+			v = *(ushort*)p;
+DBG print("dp=%p; dx=%d; for(y=0; y<%d; y++, dp+=%d)\nmemsets(dp, v, dx);\n",
+	dp, dx, dy, dwid);
+			for(y=0; y<dy; y++, dp+=dwid)
+				memsets(dp, v, dx);
+			return 1;
+		case 24:
+			for(y=0; y<dy; y++, dp+=dwid)
+				memset24(dp, v, dx);
+			return 1;
+		case 32:
+			p[0] = v;		/* make little endian */
+			p[1] = v>>8;
+			p[2] = v>>16;
+			p[3] = v>>24;
+			v = *(u32int*)p;
+			for(y=0; y<dy; y++, dp+=dwid)
+				memsetl(dp, v, dx);
+			return 1;
+		default:
+			assert(0 /* bad dest depth in memoptdraw */);
+		}
+	}
+
+	/*
+	 * If no source alpha, an opaque mask, we can just copy the
+	 * source onto the destination.  If the channels are the same and
+	 * the source is not replicated, memmove suffices.
+	 */
+	m = Simplemask|Fullmask;
+	if((par->state&(m|Replsrc))==m && src->depth >= 8 
+	&& src->chan == dst->chan && !(src->flags&Falpha) && (op == S || op == SoverD)){
+		uchar *sp, *dp;
+		long swid, dwid, nb;
+		int dir;
+
+		if(src->data == dst->data && byteaddr(dst, par->r.min) > byteaddr(src, par->sr.min))
+			dir = -1;
+		else
+			dir = 1;
+
+		swid = src->width*sizeof(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; y<dy; y++, sp+=swid, dp+=dwid)
+			memmove(dp, sp, nb);
+		return 1;
+	}
+
+	/*
+	 * If we have a 1-bit mask, 1-bit source, and 1-bit destination, and
+	 * they're all bit aligned, we can just use bit operators.  This happens
+	 * when we're manipulating boolean masks, e.g. in the arc code.
+	 */
+	if((par->state&(Simplemask|Simplesrc|Replmask|Replsrc))==0 
+	&& dst->chan==GREY1 && src->chan==GREY1 && par->mask->chan==GREY1 
+	&& (par->r.min.x&7)==(par->sr.min.x&7) && (par->r.min.x&7)==(par->mr.min.x&7)){
+		uchar *sp, *dp, *mp;
+		uchar lm, rm;
+		long swid, dwid, mwid;
+		int i, x, dir;
+
+		sp = byteaddr(src, par->sr.min);
+		dp = byteaddr(dst, par->r.min);
+		mp = byteaddr(par->mask, par->mr.min);
+		swid = src->width*sizeof(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; y<dy; y++){
+				*dp ^= (*dp ^ *sp) & *mp & lm;
+				dp += dwid;
+				sp += swid;
+				mp += mwid;
+			}
+			return 1;
+		}
+
+		dx /= 8;
+		if(dir == 1){
+			i = (lm!=0)+dx+(rm!=0);
+			mwid -= i;
+			swid -= i;
+			dwid -= i;
+			for(y=0; y<dy; y++, dp+=dwid, sp+=swid, mp+=mwid){
+				if(lm){
+					*dp ^= (*dp ^ *sp++) & *mp++ & lm;
+					dp++;
+				}
+				for(x=0; x<dx; x++){
+					*dp ^= (*dp ^ *sp++) & *mp++;
+					dp++;
+				}
+				if(rm){
+					*dp ^= (*dp ^ *sp++) & *mp++ & rm;
+					dp++;
+				}
+			}
+			return 1;
+		}else{
+		/* dir == -1 */
+			i = (lm!=0)+dx+(rm!=0);
+			dp += dwid*(dy-1)+i-1;
+			sp += swid*(dy-1)+i-1;
+			mp += mwid*(dy-1)+i-1;
+			dwid = -dwid+i;
+			swid = -swid+i;
+			mwid = -mwid+i;
+			for(y=0; y<dy; y++, dp+=dwid, sp+=swid, mp+=mwid){
+				if(rm){
+					*dp ^= (*dp ^ *sp--) & *mp-- & rm;
+					dp--;
+				}
+				for(x=0; x<dx; x++){
+					*dp ^= (*dp ^ *sp--) & *mp--;
+					dp--;
+				}
+				if(lm){
+					*dp ^= (*dp ^ *sp--) & *mp-- & lm;
+					dp--;
+				}
+			}
+		}
+		return 1;
+	}
+	return 0;	
+}
+#undef DBG
+
+/*
+ * Boolean character drawing.
+ * Solid opaque color through a 1-bit greyscale mask.
+ */
+#define DBG if(0)
+static int
+chardraw(Memdrawparam *par)
+{
+	u32int bits;
+	int i, ddepth, dy, dx, x, bx, ex, y, npack, bsh, depth, op;
+	u32int v, maskwid, dstwid;
+	uchar *wp, *rp, *q, *wc;
+	ushort *ws;
+	u32int *wl;
+	uchar sp[4];
+	Rectangle r, mr;
+	Memimage *mask, *src, *dst;
+
+if(0) if(drawdebug) iprint("chardraw? mf %lux md %d sf %lux dxs %d dys %d dd %d ddat %p sdat %p\n",
+		par->mask->flags, par->mask->depth, par->src->flags, 
+		Dx(par->src->r), Dy(par->src->r), par->dst->depth, par->dst->data, par->src->data);
+
+	mask = par->mask;
+	src = par->src;
+	dst = par->dst;
+	r = par->r;
+	mr = par->mr;
+	op = par->op;
+
+	if((par->state&(Replsrc|Simplesrc|Replmask)) != (Replsrc|Simplesrc)
+	|| mask->depth != 1 || src->flags&Falpha || dst->depth<8 || dst->data==src->data
+	|| op != SoverD)
+		return 0;
+
+//if(drawdebug) iprint("chardraw...");
+
+	depth = mask->depth;
+	maskwid = mask->width*sizeof(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; y<dy; y++, rp+=maskwid, wp+=dstwid){
+		q = rp;
+		if(bsh)
+			bits = *q++;
+		switch(ddepth){
+		case 8:
+//if(drawdebug) iprint("8loop...");
+			wc = wp;
+			for(x=bx; x>ex; x--, wc++){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+				if((bits>>i)&1)
+					*wc = v;
+			}
+			break;
+		case 16:
+			ws = (ushort*)wp;
+			v = *(ushort*)sp;
+			for(x=bx; x>ex; x--, ws++){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+				if((bits>>i)&1)
+					*ws = v;
+			}
+			break;
+		case 24:
+			wc = wp;
+			for(x=bx; x>ex; x--, wc+=3){
+				i = x&7;
+				if(i == 8-1)
+					bits = *q++;
+DBG print("bits %lux sh %d...", bits, i);
+				if((bits>>i)&1){
+					wc[0] = sp[0];
+					wc[1] = sp[1];
+					wc[2] = sp[2];
+				}
+			}
+			break;
+		case 32:
+			wl = (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; y<i->r.max.y; y++)
+			memset24(byteaddr(i, Pt(i->r.min.x, y)), bits, Dx(i->r));
+		break;
+	default:	/* 1, 2, 4, 8, 16, 32 */
+		for(d=i->depth; d<32; d*=2)
+			bits = (bits << d) | bits;
+		p[0] = bits;		/* make little endian */
+		p[1] = bits>>8;
+		p[2] = bits>>16;
+		p[3] = bits>>24;
+		bits = *(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
new file mode 100644
index 0000000..26eb54d
--- /dev/null
+++ b/src/libdraw/md-drawtest.c
@@ -0,0 +1,1004 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+
+#define DBG if(0)
+#define RGB2K(r,g,b)	((299*((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<<bpp)-1;
+//	for(y=r.min.y; y<r.max.y; y++){
+	for(y=0; y<Yrange; y++){
+		nb = 0;
+		v = 0;
+		p = data+(byteaddr(img, Pt(0,y))-(uchar*)img->data->bdata);
+		Bprint(&b, "%-4d\t", y);
+//		for(x=r.min.x; x<r.max.x; x++){
+		for(x=0; x<Xrange; x++){
+			if(x==0)
+				Bprint(&b, "\t");
+
+			if(x != 0 && (x%8)==0)
+				Bprint(&b, " ");
+
+			npr = 0;
+			if(x==labelpt.x && y==labelpt.y){
+				Bprint(&b, "*");
+				npr++;
+			}
+			if(npr == 0)
+				Bprint(&b, " ");
+
+			while(nb < bpp){
+				v &= (1<<nb)-1;
+				v |= (u32int)(*p++) << nb;
+				nb += 8;
+			}
+			nb -= bpp;
+//			print("bpp %d v %.8lux mask %.8lux nb %d\n", bpp, v, mask, nb);
+			fmt(&b, arg, (v>>nb)&mask);
+		}
+		Bprint(&b, "\n");
+	}
+	Bterm(&b);
+}
+
+/*
+ * Verify that the destination pixel has the specified value.
+ * The value is in the high bits of v, suitably masked, but must
+ * be extracted from the destination Memimage.
+ */
+void
+checkone(Point p, Point sp, Point mp)
+{
+	int delta;
+	uchar *dp, *sdp;
+
+	delta = (uchar*)byteaddr(dst, p)-(uchar*)dst->data->bdata;
+	dp = (uchar*)dst->data->bdata+delta;
+	sdp = (uchar*)savedstbits+delta;
+
+	if(memcmp(dp, sdp, (dst->depth+7)/8) != 0) {
+		fprint(2, "dtest: one bad pixel drawing at dst %P from source %P mask %P\n", p, sp, mp);
+		fprint(2, " %.2ux %.2ux %.2ux %.2ux should be %.2ux %.2ux %.2ux %.2ux\n",
+			dp[0], dp[1], dp[2], dp[3], sdp[0], sdp[1], sdp[2], sdp[3]);
+		fprint(2, "addresses dst %p src %p mask %p\n", dp, byteaddr(src, sp), byteaddr(mask, mp));
+		dumpimage("src", src, src->data->bdata, sp);
+		dumpimage("mask", mask, mask->data->bdata, mp);
+		dumpimage("origdst", dst, dstbits, p);
+		dumpimage("dst", dst, dst->data->bdata, p);
+		dumpimage("gooddst", dst, savedstbits, p);
+		abort();
+	}
+}
+
+/*
+ * Verify that the destination line has the same value as the saved line.
+ */
+#define RECTPTS(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
+void
+checkline(Rectangle r, Point sp, Point mp, int y, Memimage *stmp, Memimage *mtmp)
+{
+	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<nbytes/2; i++)
+			*up++ = lrand() >> 7;
+		if(i+i != nbytes)
+			*(uchar*)up = lrand() >> 7;
+	}else{
+		data = img->data->bdata;
+		img->data->bdata = ucbits;
+
+		for(x=img->r.min.x; x<img->r.max.x; x++)
+		for(y=img->r.min.y; y<img->r.max.y; y++){
+			alpha = rand() >> 4;
+			r = rand()%(alpha+1);
+			g = rand()%(alpha+1);
+			b = rand()%(alpha+1);
+			putpixel(img, Pt(x,y), rgbatopix(r,g,b,alpha));
+		}
+		img->data->bdata = data;
+	}
+		
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyonemask(void)
+{
+	Point dp, sp, mp;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(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; i<niters; i++)
+		verifyonemask();
+
+	/* mask all ones */
+	memset(maskbits, 0xFF, nbytes);
+	for(i=0; i<niters; i++)
+		verifyonemask();
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifyonemask();
+	}
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifylinemask(void)
+{
+	Point sp, mp, tp, up;
+	Rectangle dr;
+	int x;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(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; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
+		memimagedraw(dst, Rect(x, dr.min.y, x+1, dr.min.y+1), src, tp, mask, up, SoverD);
+	memmove(savedstbits, dst->data->bdata, dst->width*sizeof(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; i<niters; i++)
+		verifylinemask();
+
+	/* mask all zeros */
+	memset(maskbits, 0, nbytes);
+	for(i=0; i<niters; i++)
+		verifylinemask();
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifylinemask();
+	}
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyrectmask(void)
+{
+	Point sp, mp, tp, up;
+	Rectangle dr;
+	int x, y;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(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; y<dr.max.y && tp.y<Yrange && up.y<Yrange; y++,tp.y++,up.y++){
+		for(x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
+			memimagedraw(dst, Rect(x, y, x+1, y+1), src, tp, mask, up, SoverD);
+		tp.x = sp.x;
+		up.x = mp.x;
+	}
+	memmove(savedstbits, dst->data->bdata, dst->width*sizeof(u32int)*Yrange);
+
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(u32int)*Yrange);
+
+	memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
+	for(y=0; y<Yrange; y++)
+		checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), y, nil, nil);
+}
+
+void
+verifyrect(void)
+{
+	int i;
+
+	/* mask all zeros */
+	memset(maskbits, 0, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmask();
+
+	/* mask all ones */
+	memset(maskbits, 0xFF, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmask();
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifyrectmask();
+	}
+}
+
+Rectangle
+randrect(void)
+{
+	Rectangle r;
+
+	r.min.x = nrand(Xrange-1);
+	r.min.y = nrand(Yrange-1);
+	r.max.x = r.min.x + 1 + nrand(Xrange-1-r.min.x);
+	r.max.y = r.min.y + 1 + nrand(Yrange-1-r.min.y);
+	return r;
+}
+
+/*
+ * Return coordinate corresponding to x withing range [minx, maxx)
+ */
+int
+tilexy(int minx, int maxx, int x)
+{
+	int sx;
+
+	sx = (x-minx) % (maxx-minx);
+	if(sx < 0)
+		sx += maxx-minx;
+	return sx+minx;
+}
+
+void
+replicate(Memimage *i, Memimage *tmp)
+{
+	Rectangle r, r1;
+	int x, y, nb;
+
+	/* choose the replication window (i->r) */
+	r.min.x = nrand(Xrange-1);
+	r.min.y = nrand(Yrange-1);
+	/* make it trivial more often than pure chance allows */
+	switch(lrand()&0){
+	case 1:
+		r.max.x = r.min.x + 2;
+		r.max.y = r.min.y + 2;
+		if(r.max.x < Xrange && r.max.y < Yrange)
+			break;
+		/* fall through */
+	case 0:
+		r.max.x = r.min.x + 1;
+		r.max.y = r.min.y + 1;
+		break;
+	default:
+		if(r.min.x+3 >= Xrange)
+			r.max.x = Xrange;
+		else
+			r.max.x = r.min.x+3 + nrand(Xrange-(r.min.x+3));
+
+		if(r.min.y+3 >= Yrange)
+			r.max.y = Yrange;
+		else
+			r.max.y = r.min.y+3 + nrand(Yrange-(r.min.y+3));
+	}
+	assert(r.min.x >= 0);	
+	assert(r.max.x <= Xrange);
+	assert(r.min.y >= 0);
+	assert(r.max.y <= Yrange);
+	/* copy from i to tmp so we have just the replicated bits */
+	nb = tmp->width*sizeof(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(; x<Xrange; x+=Dx(r)){
+		y = -(tilexy(r.min.y, r.max.y, 0)-r.min.y);
+		for(; y<Yrange; y+=Dy(r)){
+			/* set r1 to instance of tile by translation */
+			r1.min.x = x;
+			r1.min.y = y;
+			r1.max.x = r1.min.x+Dx(r);
+			r1.max.y = r1.min.y+Dy(r);
+			memimagedraw(tmp, r1, i, r.min, ones, r.min, SoverD);
+		}
+	}
+	i->flags |= Frepl;
+	i->r = r;
+	i->clipr = randrect();
+//	fprint(2, "replicate [[%d %d] [%d %d]] [[%d %d][%d %d]]\n", r.min.x, r.min.y, r.max.x, r.max.y,
+//		i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
+	tmp->clipr = i->clipr;
+}
+
+/*
+ * Mask is preset; do the rest
+ */
+void
+verifyrectmaskrepl(int srcrepl, int maskrepl)
+{
+	Point sp, mp, tp, up;
+	Rectangle dr;
+	int x, y;
+	Memimage *s, *m;
+
+//	print("verfrect %d %d\n", srcrepl, maskrepl);
+	src->flags &= ~Frepl;
+	src->r = Rect(0, 0, Xrange, Yrange);
+	src->clipr = src->r;
+	stmp->flags &= ~Frepl;
+	stmp->r = Rect(0, 0, Xrange, Yrange);
+	stmp->clipr = src->r;
+	mask->flags &= ~Frepl;
+	mask->r = Rect(0, 0, Xrange, Yrange);
+	mask->clipr = mask->r;
+	mtmp->flags &= ~Frepl;
+	mtmp->r = Rect(0, 0, Xrange, Yrange);
+	mtmp->clipr = mask->r;
+
+	fill(dst, dstbits);
+	fill(src, srcbits);
+
+	memmove(dst->data->bdata, dstbits, dst->width*sizeof(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; y<dr.max.y && tp.y<Yrange && up.y<Yrange; y++,tp.y++,up.y++)
+		for(tp.x=sp.x,up.x=mp.x,x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
+			memimagedraw(dst, Rect(x, y, x+1, y+1), s, tp, m, up, SoverD);
+	memmove(savedstbits, dst->data->bdata, dst->width*sizeof(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; y<Yrange; y++)
+		checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), y, srcrepl?stmp:nil, maskrepl?mtmp:nil);
+}
+
+void
+verifyrectrepl(int srcrepl, int maskrepl)
+{
+	int i;
+
+	/* mask all ones */
+	memset(maskbits, 0xFF, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmaskrepl(srcrepl, maskrepl);
+
+	/* mask all zeros */
+	memset(maskbits, 0, nbytes);
+	for(i=0; i<niters; i++)
+		verifyrectmaskrepl(srcrepl, maskrepl);
+
+	/* random mask */
+	for(i=0; i<niters; i++){
+		fill(mask, maskbits);
+		verifyrectmaskrepl(srcrepl, maskrepl);
+	}
+}
+
+/*
+ * Trivial draw implementation.
+ * Color values are passed around as u32ints containing ααRRGGBB
+ */
+
+/*
+ * Convert v, which is nhave bits wide, into its nwant bits wide equivalent.
+ * Replicates to widen the value, truncates to narrow it.
+ */
+u32int
+replbits(u32int v, int nhave, int nwant)
+{
+	v &= (1<<nhave)-1;
+	for(; nhave<nwant; nhave*=2)
+		v |= v<<nhave;
+	v >>= (nhave-nwant);
+	return v & ((1<<nwant)-1);
+}
+
+/*
+ * Decode a pixel into the uchar* values.
+ */
+void
+pixtorgba(u32int v, uchar *r, uchar *g, uchar *b, uchar *a)
+{
+	*a = v>>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<<bpp)-1;
+		r = g = b = replbits(v, bpp, 8);
+	}else{
+		/*
+		 * General case.  We need to parse the channel descriptor and do what it says.
+		 * In all channels but the color map, we replicate to 8 bits because that's the
+		 * precision that all calculations are done at.
+		 *
+		 * In the case of the color map, we leave the bits alone, in case a color map
+		 * with less than 8 bits of index is used.  This is currently disallowed, so it's
+		 * sort of silly.
+		 */
+
+		for(c=img->chan; c; c>>=8){
+			nbits = NBITS(c);
+			bits = v & ((1<<nbits)-1);
+			rbits = replbits(bits, nbits, 8);
+			v >>= nbits;
+			switch(TYPE(c)){
+			case CRed:
+				r = rbits;
+				break;
+			case CGreen:
+				g = rbits;
+				break;
+			case CBlue:
+				b = rbits;
+				break;
+			case CGrey:
+				r = g = b = rbits;
+				break;
+			case CAlpha:
+				a = rbits;
+				break;
+			case CMap:
+				p = img->cmap->cmap2rgb + 3*bits;
+				r = p[0];
+				g = p[1];
+				b = p[2];
+				break;
+			case CIgnore:
+				break;
+			default:
+				fprint(2, "unknown channel type %lud\n", TYPE(c));
+				abort();
+			}
+		}
+	}
+	return rgbatopix(r, g, b, a);
+}
+
+/*
+ * Return the greyscale equivalent of a pixel.
+ */
+uchar
+getgrey(Memimage *img, Point pt)
+{
+	uchar r, g, b, a;
+	pixtorgba(getpixel(img, pt), &r, &g, &b, &a);
+	return RGB2K(r, g, b);
+}
+
+/*
+ * Return the value at pt in image, if image is interpreted
+ * as a mask.  This means the alpha channel if present, else
+ * the greyscale or its computed equivalent.
+ */
+uchar
+getmask(Memimage *img, Point pt)
+{
+	if(img->flags&Falpha)
+		return getpixel(img, pt)>>24;
+	else
+		return getgrey(img, pt);
+}
+#undef DBG
+
+#define DBG if(0)
+/*
+ * Write a pixel to img at point pt.
+ * 
+ * We do this by reading a 32-bit little endian
+ * value from p and then writing it back
+ * after tweaking the appropriate bits.  Because
+ * the data is little endian, we don't have to worry
+ * about what the actual depth is, as long as it is
+ * less than 32 bits.
+ */
+void
+putpixel(Memimage *img, Point pt, 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<<bpp)-1;
+DBG print("bits %lux mask %lux sh %d...", bits, mask, sh);
+		mask <<= sh;
+		bits <<= sh;
+DBG print("(%lux & %lux) | (%lux & %lux)", v, ~mask, bits, mask);
+		v = (v & ~mask) | (bits & mask);
+	} else {
+		/*
+		 * General case.  We need to parse the channel descriptor again.
+		 */
+		sh = 0;
+		for(c=img->chan; c; c>>=8){
+			nbits = NBITS(c);
+			switch(TYPE(c)){
+			case CRed:
+				bits = r;
+				break;
+			case CGreen:
+				bits = g;
+				break;
+			case CBlue:
+				bits = b;
+				break;
+			case CGrey:
+				bits = RGB2K(r, g, b);
+				break;
+			case CAlpha:
+				bits = a;
+				break;
+			case CIgnore:
+				bits = 0;
+				break;
+			case CMap:
+				q = img->cmap->rgb2cmap;
+				bits = q[(r>>4)*16*16+(g>>4)*16+(b>>4)];
+				break;
+			default:
+				SET(bits);
+				fprint(2, "unknown channel type %lud\n", TYPE(c));
+				abort();
+			}
+
+DBG print("repl %lux 8 %d = %lux...", bits, nbits, replbits(bits, 8, nbits));
+			if(TYPE(c) != CMap)
+				bits = replbits(bits, 8, nbits);
+			mask = (1<<nbits)-1;
+DBG print("bits %lux mask %lux sh %d...", bits, mask, sh);
+			bits <<= sh;
+			mask <<= sh;
+			v = (v & ~mask) | (bits & mask);
+			sh += nbits;
+		}
+	}
+DBG print("v %.8lux\n", v);
+	p[0] = v;
+	p[1] = v>>8;
+	p[2] = v>>16;
+	p[3] = v>>24;	
+}
+#undef DBG
+
+#define DBG if(0)
+void
+drawonepixel(Memimage *dst, Point dp, Memimage *src, Point sp, Memimage *mask, Point mp)
+{
+	uchar m, M, sr, sg, sb, sa, sk, dr, dg, db, da, dk;
+
+	pixtorgba(getpixel(dst, dp), &dr, &dg, &db, &da);
+	pixtorgba(getpixel(src, sp), &sr, &sg, &sb, &sa);
+	m = getmask(mask, mp);
+	M = 255-(sa*m)/255;
+
+DBG print("dst %x %x %x %x src %x %x %x %x m %x = ", dr,dg,db,da, sr,sg,sb,sa, m);
+	if(dst->flags&Fgrey){
+		/*
+		 * We need to do the conversion to grey before the alpha calculation
+		 * because the draw operator does this, and we need to be operating
+		 * at the same precision so we get exactly the same answers.
+		 */
+		sk = RGB2K(sr, sg, sb);
+		dk = RGB2K(dr, dg, db);
+		dk = (sk*m + dk*M)/255;
+		dr = dg = db = dk;
+		da = (sa*m + da*M)/255;
+	}else{
+		/*
+		 * True color alpha calculation treats all channels (including alpha)
+		 * the same.  It might have been nice to use an array, but oh well.
+		 */
+		dr = (sr*m + dr*M)/255;
+		dg = (sg*m + dg*M)/255;
+		db = (sb*m + db*M)/255;
+		da = (sa*m + da*M)/255;
+	}
+
+DBG print("%x %x %x %x\n", dr,dg,db,da);
+	putpixel(dst, dp, rgbatopix(dr, dg, db, da));
+}
diff --git a/src/libdraw/md-ellipse.c b/src/libdraw/md-ellipse.c
new file mode 100644
index 0000000..7dc45cd
--- /dev/null
+++ b/src/libdraw/md-ellipse.c
@@ -0,0 +1,247 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+/*
+ * ellipse(dst, c, a, b, t, src, sp)
+ *   draws an ellipse centered at c with semiaxes a,b>=0
+ *   and semithickness t>=0, or filled if t<0.  point sp
+ *   in src maps to c in dst
+ *
+ *   very thick skinny ellipses are brushed with circles (slow)
+ *   others are approximated by filling between 2 ellipses
+ *   criterion for very thick when b<a: t/b > 0.5*x/(1-x)
+ *   where x = b/a
+ */
+
+typedef struct Param	Param;
+typedef struct State	State;
+
+static	void	bellipse(int, State*, Param*);
+static	void	erect(int, int, int, int, Param*);
+static	void	eline(int, int, int, int, Param*);
+
+struct Param {
+	Memimage	*dst;
+	Memimage	*src;
+	Point			c;
+	int			t;
+	Point			sp;
+	Memimage	*disc;
+	int			op;
+};
+
+/*
+ * denote residual error by e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2
+ * e(x,y) = 0 on ellipse, e(x,y) < 0 inside, e(x,y) > 0 outside
+ */
+
+struct State {
+	int	a;
+	int	x;
+	vlong	a2;	/* a^2 */
+	vlong	b2;	/* b^2 */
+	vlong	b2x;	/* b^2 * x */
+	vlong	a2y;	/* a^2 * y */
+	vlong	c1;
+	vlong	c2;	/* test criteria */
+	vlong	ee;	/* ee = e(x+1/2,y-1/2) - (a^2+b^2)/4 */
+	vlong	dxe;
+	vlong	dye;
+	vlong	d2xe;
+	vlong	d2ye;
+};
+
+static
+State*
+newstate(State *s, int a, int b)
+{
+	s->x = 0;
+	s->a = a;
+	s->a2 = (vlong)(a*a);
+	s->b2 = (vlong)(b*b);
+	s->b2x = (vlong)0;
+	s->a2y = s->a2*(vlong)b;
+	s->c1 = -((s->a2>>2) + (vlong)(a&1) + s->b2);
+	s->c2 = -((s->b2>>2) + (vlong)(b&1));
+	s->ee = -s->a2y;
+	s->dxe = (vlong)0;
+	s->dye = s->ee<<1;
+	s->d2xe = s->b2<<1;
+	s->d2ye = s->a2<<1;
+	return s;
+}
+
+/*
+ * return x coord of rightmost pixel on next scan line
+ */
+static
+int
+step(State *s)
+{
+	while(s->x < s->a) {
+		if(s->ee+s->b2x <= s->c1 ||	/* e(x+1,y-1/2) <= 0 */
+		   s->ee+s->a2y <= s->c2) {	/* e(x+1/2,y) <= 0 (rare) */
+			s->dxe += s->d2xe;	  
+			s->ee += s->dxe;	  
+			s->b2x += s->b2;
+			s->x++;	  
+			continue;
+		}
+		s->dye += s->d2ye;	  
+		s->ee += s->dye;	  
+		s->a2y -= s->a2;
+		if(s->ee-s->a2y <= s->c2) {	/* e(x+1/2,y-1) <= 0 */
+			s->dxe += s->d2xe;	  
+			s->ee += s->dxe;	  
+			s->b2x += s->b2;
+			return s->x++;
+		}
+		break;
+	}
+	return s->x;	  
+}
+
+void
+memellipse(Memimage *dst, Point c, int a, int b, int t, Memimage *src, Point sp, int op)
+{
+	State in, out;
+	int y, inb, inx, outx, u;
+	Param p;
+
+	if(a < 0)
+		a = -a;
+	if(b < 0)
+		b = -b;
+	p.dst = dst;
+	p.src = src;
+	p.c = c;
+	p.t = t;
+	p.sp = subpt(sp, c);
+	p.disc = nil;
+	p.op = op;
+
+	u = (t<<1)*(a-b);
+	if(b<a && u>b*b || a<b && -u>a*a) {
+/*	if(b<a&&(t<<1)>b*b/a || a<b&&(t<<1)>a*a/b)	# very thick */
+		bellipse(b, newstate(&in, a, b), &p);
+		return;
+	}
+
+	if(t < 0) {
+		inb = -1;
+		newstate(&out, a, y = b);
+	} else {	
+		inb = b - t;
+		newstate(&out, a+t, y = b+t);
+	}
+	if(t > 0)
+		newstate(&in, a-t, inb);
+	inx = 0;
+	for( ; y>=0; y--) {
+		outx = step(&out);
+		if(y > inb) {
+			erect(-outx, y, outx, y, &p);
+			if(y != 0)
+				erect(-outx, -y, outx, -y, &p);
+			continue;
+		}
+		if(t > 0) {
+			inx = step(&in);
+			if(y == inb)
+				inx = 0;
+		} else if(inx > outx)
+			inx = outx;
+		erect(inx, y, outx, y, &p);
+		if(y != 0)
+			erect(inx, -y, outx, -y, &p);
+		erect(-outx, y, -inx, y, &p);
+		if(y != 0)
+			erect(-outx, -y, -inx, -y, &p);
+		inx = outx + 1;
+	}
+}
+
+static Point p00 = {0, 0};
+
+/*
+ * a brushed ellipse
+ */
+static
+void
+bellipse(int y, State *s, Param *p)
+{
+	int t, ox, oy, x, nx;
+
+	t = p->t;
+	p->disc = allocmemimage(Rect(-t,-t,t+1,t+1), GREY1);
+	if(p->disc == nil)
+		return;
+	memfillcolor(p->disc, DTransparent);
+	memellipse(p->disc, p00, t, t, -1, memopaque, p00, p->op);
+	oy = y;
+	ox = 0;
+	nx = x = step(s);
+	do {
+		while(nx==x && y-->0)
+			nx = step(s);
+		y++;
+		eline(-x,-oy,-ox, -y, p);
+		eline(ox,-oy,  x, -y, p);
+		eline(-x,  y,-ox, oy, p);
+		eline(ox,  y,  x, oy, p);
+		ox = x+1;
+		x = nx;
+		y--;
+		oy = y;
+	} while(oy > 0);
+}
+
+/*
+ * a rectangle with closed (not half-open) coordinates expressed
+ * relative to the center of the ellipse
+ */
+static
+void
+erect(int x0, int y0, int x1, int y1, Param *p)
+{
+	Rectangle r;
+
+/*	print("R %d,%d %d,%d\n", x0, y0, x1, y1); */
+	r = Rect(p->c.x+x0, p->c.y+y0, p->c.x+x1+1, p->c.y+y1+1);
+	memdraw(p->dst, r, p->src, addpt(p->sp, r.min), memopaque, p00, p->op);
+}
+
+/*
+ * a brushed point similarly specified
+ */
+static
+void
+epoint(int x, int y, Param *p)
+{
+	Point p0;
+	Rectangle r;
+
+/*	print("P%d %d,%d\n", p->t, x, y);	*/
+	p0 = Pt(p->c.x+x, p->c.y+y);
+	r = Rpt(addpt(p0, p->disc->r.min), addpt(p0, p->disc->r.max));
+	memdraw(p->dst, r, p->src, addpt(p->sp, r.min), p->disc, p->disc->r.min, p->op);
+}
+
+/* 
+ * a brushed horizontal or vertical line similarly specified
+ */
+static
+void
+eline(int x0, int y0, int x1, int y1, Param *p)
+{
+/*	print("L%d %d,%d %d,%d\n", p->t, x0, y0, x1, y1); */
+	if(x1 > x0+1)
+		erect(x0+1, y0-p->t, x1-1, y1+p->t, p);
+	else if(y1 > y0+1)
+		erect(x0-p->t, y0+1, x1+p->t, y1-1, p);
+	epoint(x0, y0, p);
+	if(x1-x0 || y1-y0)
+		epoint(x1, y1, p);
+}
diff --git a/src/libdraw/md-fillpoly.c b/src/libdraw/md-fillpoly.c
new file mode 100644
index 0000000..928ae1e
--- /dev/null
+++ b/src/libdraw/md-fillpoly.c
@@ -0,0 +1,524 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+typedef struct Seg	Seg;
+
+struct Seg
+{
+	Point	p0;
+	Point	p1;
+	long	num;
+	long	den;
+	long	dz;
+	long	dzrem;
+	long	z;
+	long	zerr;
+	long	d;
+};
+
+static	void	zsort(Seg **seg, Seg **ep);
+static	int	ycompare(const void*, const void*);
+static	int	xcompare(const void*, const void*);
+static	int	zcompare(const void*, const void*);
+static	void	xscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int, int, int, int);
+static	void	yscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int, int);
+
+#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; i<nseg; i++, s++) {
+		*p = s;
+		if(s->p0.y == s->p1.y)
+			continue;
+		if(s->p0.y > s->p1.y) {
+			pt = s->p0;
+			s->p0 = s->p1;
+			s->p1 = pt;
+			s->d = -s->d;
+		}
+		s->num = s->p1.x - s->p0.x;
+		s->den = s->p1.y - s->p0.y;
+		s->dz = sdiv(s->num, s->den) << fixshift;
+		s->dzrem = mod(s->num, s->den) << fixshift;
+		s->dz += sdiv(s->dzrem, s->den);
+		s->dzrem = mod(s->dzrem, s->den);
+		p++;
+	}
+	n = p-seg;
+	if(n == 0)
+		return;
+	*p = 0;
+	qsort(seg, p-seg , sizeof(Seg*), ycompare);
+
+	onehalf = 0;
+	if(fixshift)
+		onehalf = 1 << (fixshift-1);
+
+	minx = dst->clipr.min.x;
+	maxx = dst->clipr.max.x;
+
+	y = seg[0]->p0.y;
+	if(y < (dst->clipr.min.y << fixshift))
+		y = dst->clipr.min.y << fixshift;
+	iy = (y + onehalf) >> fixshift;
+	y = (iy << fixshift) + onehalf;
+	maxy = dst->clipr.max.y << fixshift;
+
+	ep = next = seg;
+
+	while(y<maxy) {
+		for(q = p = seg; p < ep; p++) {
+			s = *p;
+			if(s->p1.y < y)
+				continue;
+			s->z += s->dz;
+			s->zerr += s->dzrem;
+			if(s->zerr >= s->den) {
+				s->z++;
+				s->zerr -= s->den;
+				if(s->zerr < 0 || s->zerr >= s->den)
+					print("bad ratzerr1: %ld den %ld dzrem %ld\n", s->zerr, s->den, s->dzrem);
+			}
+			*q++ = s;
+		}
+
+		for(p = next; *p; p++) {
+			s = *p;
+			if(s->p0.y >= y)
+				break;
+			if(s->p1.y < y)
+				continue;
+			s->z = s->p0.x;
+			s->z += smuldivmod(y - s->p0.y, s->num, s->den, &s->zerr);
+			if(s->zerr < 0 || s->zerr >= s->den)
+				print("bad ratzerr2: %ld den %ld ratdzrem %ld\n", s->zerr, s->den, s->dzrem);
+			*q++ = s;
+		}
+		ep = q;
+		next = p;
+
+		if(ep == seg) {
+			if(*next == 0)
+				break;
+			iy = (next[0]->p0.y + onehalf) >> fixshift;
+			y = (iy << fixshift) + onehalf;
+			continue;
+		}
+
+		zsort(seg, ep);
+
+		for(p = seg; p < ep; p++) {
+			cnt = 0;
+			x = p[0]->z;
+			xerr = p[0]->zerr;
+			xden = p[0]->den;
+			ix = (x + onehalf) >> fixshift;
+			if(ix >= maxx)
+				break;
+			if(ix < minx)
+				ix = minx;
+			cnt += p[0]->d;
+			p++;
+			for(;;) {
+				if(p == ep) {
+					print("xscan: fill to infinity");
+					return;
+				}
+				cnt += p[0]->d;
+				if((cnt&wind) == 0)
+					break;
+				p++;
+			}
+			x2 = p[0]->z;
+			ix2 = (x2 + onehalf) >> fixshift;
+			if(ix2 <= minx)
+				continue;
+			if(ix2 > maxx)
+				ix2 = maxx;
+			if(ix == ix2 && detail) {
+				if(xerr*p[0]->den + p[0]->zerr*xden > p[0]->den*xden)
+					x++;
+				ix = (x + x2) >> (fixshift+1);
+				ix2 = ix+1;
+			}
+			(*fill)(dst, ix, ix2, iy, src, sp, op);
+		}
+		y += (1<<fixshift);
+		iy++;
+	}
+}
+
+static void
+yscan(Memimage *dst, Seg **seg, Seg *segtab, int nseg, int wind, Memimage *src, Point sp, int fixshift, int op)
+{
+	long x, maxx, y, y2, yerr, yden, onehalf;
+	Seg **ep, **next, **p, **q, *s;
+	int n, i, ix, cnt, iy, iy2, miny, maxy;
+	Point pt;
+
+	for(i=0, s=segtab, p=seg; i<nseg; i++, s++) {
+		*p = s;
+		if(s->p0.x == s->p1.x)
+			continue;
+		if(s->p0.x > s->p1.x) {
+			pt = s->p0;
+			s->p0 = s->p1;
+			s->p1 = pt;
+			s->d = -s->d;
+		}
+		s->num = s->p1.y - s->p0.y;
+		s->den = s->p1.x - s->p0.x;
+		s->dz = sdiv(s->num, s->den) << fixshift;
+		s->dzrem = mod(s->num, s->den) << fixshift;
+		s->dz += sdiv(s->dzrem, s->den);
+		s->dzrem = mod(s->dzrem, s->den);
+		p++;
+	}
+	n = p-seg;
+	if(n == 0)
+		return;
+	*p = 0;
+	qsort(seg, n , sizeof(Seg*), xcompare);
+
+	onehalf = 0;
+	if(fixshift)
+		onehalf = 1 << (fixshift-1);
+
+	miny = dst->clipr.min.y;
+	maxy = dst->clipr.max.y;
+
+	x = seg[0]->p0.x;
+	if(x < (dst->clipr.min.x << fixshift))
+		x = dst->clipr.min.x << fixshift;
+	ix = (x + onehalf) >> fixshift;
+	x = (ix << fixshift) + onehalf;
+	maxx = dst->clipr.max.x << fixshift;
+
+	ep = next = seg;
+
+	while(x<maxx) {
+		for(q = p = seg; p < ep; p++) {
+			s = *p;
+			if(s->p1.x < x)
+				continue;
+			s->z += s->dz;
+			s->zerr += s->dzrem;
+			if(s->zerr >= s->den) {
+				s->z++;
+				s->zerr -= s->den;
+				if(s->zerr < 0 || s->zerr >= s->den)
+					print("bad ratzerr1: %ld den %ld ratdzrem %ld\n", s->zerr, s->den, s->dzrem);
+			}
+			*q++ = s;
+		}
+
+		for(p = next; *p; p++) {
+			s = *p;
+			if(s->p0.x >= x)
+				break;
+			if(s->p1.x < x)
+				continue;
+			s->z = s->p0.y;
+			s->z += smuldivmod(x - s->p0.x, s->num, s->den, &s->zerr);
+			if(s->zerr < 0 || s->zerr >= s->den)
+				print("bad ratzerr2: %ld den %ld ratdzrem %ld\n", s->zerr, s->den, s->dzrem);
+			*q++ = s;
+		}
+		ep = q;
+		next = p;
+
+		if(ep == seg) {
+			if(*next == 0)
+				break;
+			ix = (next[0]->p0.x + onehalf) >> fixshift;
+			x = (ix << fixshift) + onehalf;
+			continue;
+		}
+
+		zsort(seg, ep);
+
+		for(p = seg; p < ep; p++) {
+			cnt = 0;
+			y = p[0]->z;
+			yerr = p[0]->zerr;
+			yden = p[0]->den;
+			iy = (y + onehalf) >> fixshift;
+			if(iy >= maxy)
+				break;
+			if(iy < miny)
+				iy = miny;
+			cnt += p[0]->d;
+			p++;
+			for(;;) {
+				if(p == ep) {
+					print("yscan: fill to infinity");
+					return;
+				}
+				cnt += p[0]->d;
+				if((cnt&wind) == 0)
+					break;
+				p++;
+			}
+			y2 = p[0]->z;
+			iy2 = (y2 + onehalf) >> fixshift;
+			if(iy2 <= miny)
+				continue;
+			if(iy2 > maxy)
+				iy2 = maxy;
+			if(iy == iy2) {
+				if(yerr*p[0]->den + p[0]->zerr*yden > p[0]->den*yden)
+					y++;
+				iy = (y + y2) >> (fixshift+1);
+				fillpoint(dst, ix, iy, src, sp, op);
+			}
+		}
+		x += (1<<fixshift);
+		ix++;
+	}
+}
+
+static void
+zsort(Seg **seg, Seg **ep)
+{
+	int done;
+	Seg **q, **p, *s;
+
+	if(ep-seg < 20) {
+		/* bubble sort by z - they should be almost sorted already */
+		q = ep;
+		do {
+			done = 1;
+			q--;
+			for(p = seg; p < q; p++) {
+				if(p[0]->z > p[1]->z) {
+					s = p[0];
+					p[0] = p[1];
+					p[1] = s;
+					done = 0;
+				}
+			}
+		} while(!done);
+	} else {
+		q = ep-1;
+		for(p = seg; p < q; p++) {
+			if(p[0]->z > p[1]->z) {
+				qsort(seg, ep-seg, sizeof(Seg*), zcompare);
+				break;
+			}
+		}
+	}
+}
+
+static int
+ycompare(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
new file mode 100644
index 0000000..3f36250
--- /dev/null
+++ b/src/libdraw/md-hwdraw.c
@@ -0,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+hwdraw(Memdrawparam *p)
+{
+	USED(p);
+	return 0;	/* could not satisfy request */
+}
+
diff --git a/src/libdraw/md-iprint.c b/src/libdraw/md-iprint.c
new file mode 100644
index 0000000..923b6b4
--- /dev/null
+++ b/src/libdraw/md-iprint.c
@@ -0,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+iprint(char *fmt,...)
+{
+	USED(fmt);
+	return -1;
+}
+
diff --git a/src/libdraw/md-line.c b/src/libdraw/md-line.c
new file mode 100644
index 0000000..632e823
--- /dev/null
+++ b/src/libdraw/md-line.c
@@ -0,0 +1,484 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+enum
+{
+	Arrow1 = 8,
+	Arrow2 = 10,
+	Arrow3 = 3,
+};
+
+static
+int
+lmin(int a, int b)
+{
+	if(a < b)
+		return a;
+	return b;
+}
+
+static
+int
+lmax(int a, int b)
+{
+	if(a > b)
+		return a;
+	return b;
+}
+
+#ifdef NOTUSED
+/*
+ * Rather than line clip, we run the Bresenham loop over the full line,
+ * and clip on each pixel.  This is more expensive but means that
+ * lines look the same regardless of how the windowing has tiled them.
+ * For speed, we check for clipping outside the loop and make the
+ * test easy when possible.
+ */
+
+static
+void
+horline1(Memimage *dst, Point p0, Point p1, int srcval, Rectangle clipr)
+{
+	int x, y, dy, deltay, deltax, maxx;
+	int dd, easy, e, bpp, m, m0;
+	uchar *d;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	dd = dst->width*sizeof(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<clipr.max.y))
+			*d ^= (*d^srcval) & m;
+		if(e > 0){
+			y += dy;
+			d += dd;
+			e += deltax;
+		}else
+			e += deltay;
+		d++;
+		m >>= bpp;
+		if(m == 0)
+			m = m0;
+	}
+}
+
+static
+void
+verline1(Memimage *dst, Point p0, Point p1, int srcval, Rectangle clipr)
+{
+	int x, y, deltay, deltax, maxy;
+	int easy, e, bpp, m, m0, dd;
+	uchar *d;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	dd = 1;
+	if(deltax < 0){
+		dd = -1;
+		deltax = -deltax;
+	}
+	maxy = lmin(p1.y, clipr.max.y-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	m = m0 >> (p0.x&(7/dst->depth))*bpp;
+	easy = ptinrect(p0, clipr) && ptinrect(p1, clipr);
+	e = 2*deltax - deltay;
+	x = p0.x;
+	d = byteaddr(dst, p0);
+	deltax *= 2;
+	deltay = deltax - 2*deltay;
+	for(y=p0.y; y<=maxy; y++){
+		if(easy || (clipr.min.y<=y && clipr.min.x<=x && x<clipr.max.x))
+			*d ^= (*d^srcval) & m;
+		if(e > 0){
+			x += dd;
+			d += dd;
+			e += deltay;
+		}else
+			e += deltax;
+		d += dst->width*sizeof(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 && y<clipr.max.y){
+			d = byteaddr(dst, Pt(x, y));
+			sy = drawreplxy(src->r.min.y, src->r.max.y, y+dsrc.y);
+			s = byteaddr(src, Pt(sx, sy));
+			*d ^= (*d^*s) & m;
+		}
+		if(++sx >= src->r.max.x)
+			sx = src->r.min.x;
+		m >>= bpp;
+		if(m == 0)
+			m = m0;
+	}
+}
+
+static
+void
+verliner(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
+{
+	int x, y, sx, sy, deltay, deltax, miny, maxy;
+	int bpp, m, m0;
+	uchar *d, *s;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	sy = drawreplxy(src->r.min.y, src->r.max.y, p0.y+dsrc.y);
+	miny = lmax(p0.y, clipr.min.y);
+	maxy = lmin(p1.y, clipr.max.y-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	for(y=miny; y<=maxy; y++){
+		if(deltay == 0)	/* degenerate line */
+			x = p0.x;
+		else
+			x = p0.x + (deltax*(y-p0.y)+deltay/2)/deltay;
+		if(clipr.min.x<=x && x<clipr.max.x){
+			m = m0 >> (x&(7/dst->depth))*bpp;
+			d = byteaddr(dst, Pt(x, y));
+			sx = drawreplxy(src->r.min.x, src->r.max.x, x+dsrc.x);
+			s = byteaddr(src, Pt(sx, sy));
+			*d ^= (*d^*s) & m;
+		}
+		if(++sy >= src->r.max.y)
+			sy = src->r.min.y;
+	}
+}
+
+static
+void
+horline(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
+{
+	int x, y, deltay, deltax, minx, maxx;
+	int bpp, m, m0;
+	uchar *d, *s;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	minx = lmax(p0.x, clipr.min.x);
+	maxx = lmin(p1.x, clipr.max.x-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	m = m0 >> (minx&(7/dst->depth))*bpp;
+	for(x=minx; x<=maxx; x++){
+		y = p0.y + (deltay*(x-p0.x)+deltay/2)/deltax;
+		if(clipr.min.y<=y && y<clipr.max.y){
+			d = byteaddr(dst, Pt(x, y));
+			s = byteaddr(src, addpt(dsrc, Pt(x, y)));
+			*d ^= (*d^*s) & m;
+		}
+		m >>= bpp;
+		if(m == 0)
+			m = m0;
+	}
+}
+
+static
+void
+verline(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
+{
+	int x, y, deltay, deltax, miny, maxy;
+	int bpp, m, m0;
+	uchar *d, *s;
+
+	deltax = p1.x - p0.x;
+	deltay = p1.y - p0.y;
+	miny = lmax(p0.y, clipr.min.y);
+	maxy = lmin(p1.y, clipr.max.y-1);
+	bpp = dst->depth;
+	m0 = 0xFF^(0xFF>>bpp);
+	for(y=miny; y<=maxy; y++){
+		if(deltay == 0)	/* degenerate line */
+			x = p0.x;
+		else
+			x = p0.x + deltax*(y-p0.y)/deltay;
+		if(clipr.min.x<=x && x<clipr.max.x){
+			m = m0 >> (x&(7/dst->depth))*bpp;
+			d = byteaddr(dst, Pt(x, y));
+			s = byteaddr(src, addpt(dsrc, Pt(x, y)));
+			*d ^= (*d^*s) & m;
+		}
+	}
+}
+#endif /* NOTUSED */
+
+static Memimage*
+membrush(int radius)
+{
+	static Memimage *brush;
+	static int brushradius;
+
+	if(brush==nil || brushradius!=radius){
+		freememimage(brush);
+		brush = allocmemimage(Rect(0, 0, 2*radius+1, 2*radius+1), memopaque->chan);
+		if(brush != nil){
+			memfillcolor(brush, DTransparent);	/* zeros */
+			memellipse(brush, Pt(radius, radius), radius, radius, -1, memopaque, Pt(radius, radius), S);
+		}
+		brushradius = radius;
+	}
+	return brush;
+}
+
+static
+void
+discend(Point p, int radius, Memimage *dst, Memimage *src, Point dsrc, int op)
+{
+	Memimage *disc;
+	Rectangle r;
+
+	disc = membrush(radius);
+	if(disc != nil){
+		r.min.x = p.x - radius;
+		r.min.y = p.y - radius;
+		r.max.x = p.x + radius+1;
+		r.max.y = p.y + radius+1;
+		memdraw(dst, r, src, addpt(r.min, dsrc), disc, Pt(0,0), op);
+	}
+}
+
+static
+void
+arrowend(Point tip, Point *pp, int end, int sin, int cos, int radius)
+{
+	int x1, x2, x3;
+
+	/* before rotation */
+	if(end == Endarrow){
+		x1 = Arrow1;
+		x2 = Arrow2;
+		x3 = Arrow3;
+	}else{
+		x1 = (end>>5) & 0x1FF;	/* distance along line from end of line to tip */
+		x2 = (end>>14) & 0x1FF;	/* distance along line from barb to tip */
+		x3 = (end>>23) & 0x1FF;	/* distance perpendicular from edge of line to barb */
+	}
+
+	/* comments follow track of right-facing arrowhead */
+	pp->x = tip.x+((2*radius+1)*sin/2-x1*cos);		/* upper side of shaft */
+	pp->y = tip.y-((2*radius+1)*cos/2+x1*sin);
+	pp++;
+	pp->x = tip.x+((2*radius+2*x3+1)*sin/2-x2*cos);		/* upper barb */
+	pp->y = tip.y-((2*radius+2*x3+1)*cos/2+x2*sin);
+	pp++;
+	pp->x = tip.x;
+	pp->y = tip.y;
+	pp++;
+	pp->x = tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos);	/* lower barb */
+	pp->y = tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin);
+	pp++;
+	pp->x = tip.x+(-(2*radius+1)*sin/2-x1*cos);		/* lower side of shaft */
+	pp->y = tip.y+((2*radius+1)*cos/2-x1*sin);
+}
+
+void
+_memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, Rectangle clipr, int op)
+{
+	/*
+	 * BUG: We should really really pick off purely horizontal and purely
+	 * vertical lines and handle them separately with calls to memimagedraw
+	 * on rectangles.
+	 */
+
+	int hor;
+	int sin, cos, dx, dy, t;
+	Rectangle oclipr, r;
+	Point q, pts[10], *pp, d;
+
+	if(radius < 0)
+		return;
+	if(rectclip(&clipr, dst->r) == 0)
+		return;
+	if(rectclip(&clipr, dst->clipr) == 0)
+		return;
+	d = subpt(sp, p0);
+	if(rectclip(&clipr, rectsubpt(src->clipr, d)) == 0)
+		return;
+	if((src->flags&Frepl)==0 && rectclip(&clipr, rectsubpt(src->r, d))==0)
+		return;
+	/* this means that only verline() handles degenerate lines (p0==p1) */
+	hor = (abs(p1.x-p0.x) > abs(p1.y-p0.y));
+	/*
+	 * Clipping is a little peculiar.  We can't use Sutherland-Cohen
+	 * clipping because lines are wide.  But this is probably just fine:
+	 * we do all math with the original p0 and p1, but clip when deciding
+	 * what pixels to draw.  This means the layer code can call this routine,
+	 * using clipr to define the region being written, and get the same set
+	 * of pixels regardless of the dicing.
+	 */
+	if((hor && p0.x>p1.x) || (!hor && p0.y>p1.y)){
+		q = p0;
+		p0 = p1;
+		p1 = q;
+		t = end0;
+		end0 = end1;
+		end1 = t;
+	}
+
+	if((p0.x == p1.x || p0.y == p1.y) && (end0&0x1F) == Endsquare && (end1&0x1F) == Endsquare){
+		r.min = p0;
+		r.max = p1;
+		if(p0.x == p1.x){
+			r.min.x -= radius;
+			r.max.x += radius+1;
+		}
+		else{
+			r.min.y -= radius;
+			r.max.y += radius+1;
+		}
+		oclipr = dst->clipr;
+		dst->clipr = clipr;
+		memimagedraw(dst, r, src, sp, memopaque, sp, op);
+		dst->clipr = oclipr;
+		return;
+	}
+
+/*    Hard: */
+	/* draw thick line using polygon fill */
+	icossin2(p1.x-p0.x, p1.y-p0.y, &cos, &sin);
+	dx = (sin*(2*radius+1))/2;
+	dy = (cos*(2*radius+1))/2;
+	pp = pts;
+	oclipr = dst->clipr;
+	dst->clipr = clipr;
+	q.x = ICOSSCALE*p0.x+ICOSSCALE/2-cos/2;
+	q.y = ICOSSCALE*p0.y+ICOSSCALE/2-sin/2;
+	switch(end0 & 0x1F){
+	case Enddisc:
+		discend(p0, radius, dst, src, d, op);
+		/* fall through */
+	case Endsquare:
+	default:
+		pp->x = q.x-dx;
+		pp->y = q.y+dy;
+		pp++;
+		pp->x = q.x+dx;
+		pp->y = q.y-dy;
+		pp++;
+		break;
+	case Endarrow:
+		arrowend(q, pp, end0, -sin, -cos, radius);
+		_memfillpolysc(dst, pts, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
+		pp[1] = pp[4];
+		pp += 2;
+	}
+	q.x = ICOSSCALE*p1.x+ICOSSCALE/2+cos/2;
+	q.y = ICOSSCALE*p1.y+ICOSSCALE/2+sin/2;
+	switch(end1 & 0x1F){
+	case Enddisc:
+		discend(p1, radius, dst, src, d, op);
+		/* fall through */
+	case Endsquare:
+	default:
+		pp->x = q.x+dx;
+		pp->y = q.y-dy;
+		pp++;
+		pp->x = q.x-dx;
+		pp->y = q.y+dy;
+		pp++;
+		break;
+	case Endarrow:
+		arrowend(q, pp, end1, sin, cos, radius);
+		_memfillpolysc(dst, pp, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
+		pp[1] = pp[4];
+		pp += 2;
+	}
+	_memfillpolysc(dst, pts, pp-pts, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 0, 10, 1, op);
+	dst->clipr = oclipr;
+	return;
+}
+
+void
+memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+	_memimageline(dst, p0, p1, end0, end1, radius, src, sp, dst->clipr, op);
+}
+
+/*
+ * Simple-minded conservative code to compute bounding box of line.
+ * Result is probably a little larger than it needs to be.
+ */
+static
+void
+addbbox(Rectangle *r, Point p)
+{
+	if(r->min.x > p.x)
+		r->min.x = p.x;
+	if(r->min.y > p.y)
+		r->min.y = p.y;
+	if(r->max.x < p.x+1)
+		r->max.x = p.x+1;
+	if(r->max.y < p.y+1)
+		r->max.y = p.y+1;
+}
+
+int
+memlineendsize(int end)
+{
+	int x3;
+
+	if((end&0x3F) != Endarrow)
+		return 0;
+	if(end == Endarrow)
+		x3 = Arrow3;
+	else
+		x3 = (end>>23) & 0x1FF;
+	return x3;
+}
+
+Rectangle
+memlinebbox(Point p0, Point p1, int end0, int end1, int radius)
+{
+	Rectangle r, r1;
+	int extra;
+
+	r.min.x = 10000000;
+	r.min.y = 10000000;
+	r.max.x = -10000000;
+	r.max.y = -10000000;
+	extra = lmax(memlineendsize(end0), memlineendsize(end1));
+	r1 = insetrect(canonrect(Rpt(p0, p1)), -(radius+extra));
+	addbbox(&r, r1.min);
+	addbbox(&r, r1.max);
+	return r;
+}
diff --git a/src/libdraw/md-load.c b/src/libdraw/md-load.c
new file mode 100644
index 0000000..6788fa9
--- /dev/null
+++ b/src/libdraw/md-load.c
@@ -0,0 +1,72 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+_loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int y, l, lpart, rpart, mx, m, mr;
+	uchar *q;
+
+	if(!rectinrect(r, i->r))
+		return -1;
+	l = bytesperline(r, i->depth);
+	if(ndata < l*Dy(r))
+		return -1;
+	ndata = l*Dy(r);
+	q = byteaddr(i, r.min);
+	mx = 7/i->depth;
+	lpart = (r.min.x & mx) * i->depth;
+	rpart = (r.max.x & mx) * i->depth;
+	m = 0xFF >> lpart;
+	/* may need to do bit insertion on edges */
+	if(l == 1){	/* all in one byte */
+		if(rpart)
+			m ^= 0xFF >> rpart;
+		for(y=r.min.y; y<r.max.y; y++){
+			*q ^= (*data^*q) & m;
+			q += i->width*sizeof(u32int);
+			data++;
+		}
+		return ndata;
+	}
+	if(lpart==0 && rpart==0){	/* easy case */
+		for(y=r.min.y; y<r.max.y; y++){
+			memmove(q, data, l);
+			q += i->width*sizeof(u32int);
+			data += l;
+		}
+		return ndata;
+	}
+	mr = 0xFF ^ (0xFF >> rpart);
+	if(lpart!=0 && rpart==0){
+		for(y=r.min.y; y<r.max.y; y++){
+			*q ^= (*data^*q) & m;
+			if(l > 1)
+				memmove(q+1, data+1, l-1);
+			q += i->width*sizeof(u32int);
+			data += l;
+		}
+		return ndata;
+	}
+	if(lpart==0 && rpart!=0){
+		for(y=r.min.y; y<r.max.y; y++){
+			if(l > 1)
+				memmove(q, data, l-1);
+			q[l-1] ^= (data[l-1]^q[l-1]) & mr;
+			q += i->width*sizeof(u32int);
+			data += l;
+		}
+		return ndata;
+	}
+	for(y=r.min.y; y<r.max.y; y++){
+		*q ^= (*data^*q) & m;
+		if(l > 2)
+			memmove(q+1, data+1, l-2);
+		q[l-1] ^= (data[l-1]^q[l-1]) & mr;
+		q += i->width*sizeof(u32int);
+		data += l;
+	}
+	return ndata;
+}
diff --git a/src/libdraw/md-mkcmap.c b/src/libdraw/md-mkcmap.c
new file mode 100644
index 0000000..e8d5efc
--- /dev/null
+++ b/src/libdraw/md-mkcmap.c
@@ -0,0 +1,79 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+/*
+struct Memcmap
+{
+	uchar	cmap2rgb[3*256];
+	uchar	rgb2cmap[16*16*16];
+};
+*/
+
+static Memcmap*
+mkcmap(void)
+{
+	static Memcmap def;
+
+	int i, rgb, r, g, b;
+
+	for(i=0; i<256; i++){
+		rgb = cmap2rgb(i);
+		r = (rgb>>16)&0xff;
+		g = (rgb>>8)&0xff;
+		b = rgb&0xff;
+		def.cmap2rgb[3*i] = r;
+		def.cmap2rgb[3*i+1] = g;
+		def.cmap2rgb[3*i+2] = b;
+	}
+
+	for(r=0; r<16; r++)
+	for(g=0; g<16; g++)
+	for(b=0; b<16; b++)
+		def.rgb2cmap[r*16*16+g*16+b] = rgb2cmap(r*0x11, g*0x11, b*0x11);
+	return &def;
+}
+
+void
+main(int argc, char **argv)
+{
+	Memcmap *c;
+	int i, j, inferno;
+
+	inferno = 0;
+	ARGBEGIN{
+	case 'i':
+		inferno = 1;
+	}ARGEND
+
+	memimageinit();
+	c = mkcmap();
+	if(!inferno)
+		print("#include <u.h>\n#include <libc.h>\n");
+	else
+		print("#include \"lib9.h\"\n");
+	print("#include <draw.h>\n");
+	print("#include <memdraw.h>\n\n");
+	print("static Memcmap def = {\n");
+	print("/* cmap2rgb */ {\n");
+	for(i=0; i<sizeof(c->cmap2rgb); ){
+		print("\t");
+		for(j=0; j<16; j++, i++)
+			print("0x%2.2ux,", c->cmap2rgb[i]);
+		print("\n");
+	}
+	print("},\n");
+	print("/* rgb2cmap */ {\n");
+	for(i=0; i<sizeof(c->rgb2cmap);){
+		print("\t");
+		for(j=0; j<16; j++, i++)
+			print("0x%2.2ux,", c->rgb2cmap[i]);
+		print("\n");
+	}
+	print("}\n");
+	print("};\n");
+	print("Memcmap *memdefcmap = &def;\n");
+	print("void _memmkcmap(void){}\n");
+	exits(0);
+}
diff --git a/src/libdraw/md-openmemsubfont.c b/src/libdraw/md-openmemsubfont.c
new file mode 100644
index 0000000..c8d926e
--- /dev/null
+++ b/src/libdraw/md-openmemsubfont.c
@@ -0,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memsubfont*
+openmemsubfont(char *name)
+{
+	Memsubfont *sf;
+	Memimage *i;
+	Fontchar *fc;
+	int fd, n;
+	char hdr[3*12+4+1];
+	uchar *p;
+
+	fd = open(name, OREAD);
+	if(fd < 0)
+		return nil;
+	p = nil;
+	i = readmemimage(fd);
+	if(i == nil)
+		goto Err;
+	if(read(fd, hdr, 3*12) != 3*12){
+		werrstr("openmemsubfont: header read error: %r");
+		goto Err;
+	}
+	n = atoi(hdr);
+	p = malloc(6*(n+1));
+	if(p == nil)
+		goto Err;
+	if(read(fd, p, 6*(n+1)) != 6*(n+1)){
+		werrstr("openmemsubfont: fontchar read error: %r");
+		goto Err;
+	}
+	fc = malloc(sizeof(Fontchar)*(n+1));
+	if(fc == nil)
+		goto Err;
+	_unpackinfo(fc, p, n);
+	sf = allocmemsubfont(name, n, atoi(hdr+12), atoi(hdr+24), fc, i);
+	if(sf == nil){
+		free(fc);
+		goto Err;
+	}
+	free(p);
+	return sf;
+Err:
+	close(fd);
+	if (i != nil)
+		freememimage(i);
+	if (p != nil)
+		free(p);
+	return nil;
+}
diff --git a/src/libdraw/md-poly.c b/src/libdraw/md-poly.c
new file mode 100644
index 0000000..d16c0a9
--- /dev/null
+++ b/src/libdraw/md-poly.c
@@ -0,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+void
+mempoly(Memimage *dst, Point *vert, int nvert, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+	int i, e0, e1;
+	Point d;
+
+	if(nvert < 2)
+		return;
+	d = subpt(sp, vert[0]);
+	for(i=1; i<nvert; i++){
+		e0 = e1 = Enddisc;
+		if(i == 1)
+			e0 = end0;
+		if(i == nvert-1)
+			e1 = end1;
+		memline(dst, vert[i-1], vert[i], e0, e1, radius, src, addpt(d, vert[i-1]), op);
+	}
+}
diff --git a/src/libdraw/md-read.c b/src/libdraw/md-read.c
new file mode 100644
index 0000000..d7a535d
--- /dev/null
+++ b/src/libdraw/md-read.c
@@ -0,0 +1,111 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memimage*
+readmemimage(int fd)
+{
+	char hdr[5*12+1];
+	int dy;
+	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<chunk; j++)
+				tmp[j] ^= 0xFF;
+
+		if(loadmemimage(i, Rect(r.min.x, miny, r.max.x, miny+dy), tmp, chunk) <= 0)
+			goto Err;
+		miny += dy;
+	}
+	free(tmp);
+	return i;
+}
diff --git a/src/libdraw/md-string.c b/src/libdraw/md-string.c
new file mode 100644
index 0000000..6ae19c0
--- /dev/null
+++ b/src/libdraw/md-string.c
@@ -0,0 +1,66 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Point
+memimagestring(Memimage *b, Point p, Memimage *color, Point cp, Memsubfont *f, char *cs)
+{
+	int w, width;
+	uchar *s;
+	Rune c;
+	Fontchar *i;
+
+	s = (uchar*)cs;
+	for(; c=*s; p.x+=width, cp.x+=width){
+		width = 0;
+		if(c < Runeself)
+			s++;
+		else{
+			w = chartorune(&c, (char*)s);
+			if(w == 0){
+				s++;
+				continue;
+			}
+			s += w;
+		}
+		if(c >= f->n)
+			continue;
+		i = f->info+c;
+		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
new file mode 100644
index 0000000..e2bdee5
--- /dev/null
+++ b/src/libdraw/md-subfont.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+Memsubfont*
+allocmemsubfont(char *name, int n, int height, int ascent, Fontchar *info, Memimage *i)
+{
+	Memsubfont *f;
+
+	f = malloc(sizeof(Memsubfont));
+	if(f == 0)
+		return 0;
+	f->n = n;
+	f->height = height;
+	f->ascent = ascent;
+	f->info = info;
+	f->bits = i;
+	if(name)
+		f->name = strdup(name);
+	else
+		f->name = 0;
+	return f;
+}
+
+void
+freememsubfont(Memsubfont *f)
+{
+	if(f == 0)
+		return;
+	free(f->info);	/* note: f->info must have been malloc'ed! */
+	freememimage(f->bits);
+	free(f);
+}
diff --git a/src/libdraw/md-unload.c b/src/libdraw/md-unload.c
new file mode 100644
index 0000000..8683575
--- /dev/null
+++ b/src/libdraw/md-unload.c
@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+int
+_unloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
+{
+	int y, l;
+	uchar *q;
+
+	if(!rectinrect(r, i->r))
+		return -1;
+	l = bytesperline(r, i->depth);
+	if(ndata < l*Dy(r))
+		return -1;
+	ndata = l*Dy(r);
+	q = byteaddr(i, r.min);
+	for(y=r.min.y; y<r.max.y; y++){
+		memmove(data, q, l);
+		q += i->width*sizeof(u32int);
+		data += l;
+	}
+	return ndata;
+}
diff --git a/src/libdraw/md-write.c b/src/libdraw/md-write.c
new file mode 100644
index 0000000..c03c58e
--- /dev/null
+++ b/src/libdraw/md-write.c
@@ -0,0 +1,183 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+#define	CHUNK	8000
+
+#define	HSHIFT	3	/* HSHIFT==5 runs slightly faster, but hash table is 64x bigger */
+#define	NHASH	(1<<(HSHIFT*NMATCH))
+#define	HMASK	(NHASH-1)
+#define	hupdate(h, c)	((((h)<<HSHIFT)^(c))&HMASK)
+typedef struct Hlist Hlist;
+struct Hlist{
+	uchar *s;
+	Hlist *next, *prev;
+};
+
+int
+writememimage(int fd, Memimage *i)
+{
+	uchar *outbuf, *outp, *eout;		/* encoded data, pointer, end */
+	uchar *loutp;				/* start of encoded line */
+	Hlist *hash;				/* heads of hash chains of past strings */
+	Hlist *chain, *hp;			/* hash chain members, pointer */
+	Hlist *cp;				/* next Hlist to fall out of window */
+	int h;					/* hash value */
+	uchar *line, *eline;			/* input line, end pointer */
+	uchar *data, *edata;			/* input buffer, end pointer */
+	u32int n;				/* length of input buffer */
+	u32int nb;				/* # of bytes returned by unloadimage */
+	int bpl;				/* input line length */
+	int offs, runlen;			/* offset, length of consumed data */
+	uchar dumpbuf[NDUMP];			/* dump accumulator */
+	int ndump;				/* length of dump accumulator */
+	int miny, dy;				/* y values while unloading input */
+	int ncblock;				/* size of compressed blocks */
+	Rectangle r;
+	uchar *p, *q, *s, *es, *t;
+	char hdr[11+5*12+1];
+	char cbuf[20];
+
+	r = i->r;
+	bpl = bytesperline(r, i->depth);
+	n = Dy(r)*bpl;
+	data = malloc(n);
+	ncblock = _compblocksize(r, i->depth);
+	outbuf = malloc(ncblock);
+	hash = malloc(NHASH*sizeof(Hlist));
+	chain = malloc(NMEM*sizeof(Hlist));
+	if(data == 0 || outbuf == 0 || hash == 0 || chain == 0){
+	ErrOut:
+		free(data);
+		free(outbuf);
+		free(hash);
+		free(chain);
+		return -1;
+	}
+	for(miny = r.min.y; miny != r.max.y; miny += dy){
+		dy = r.max.y-miny;
+		if(dy*bpl > CHUNK)
+			dy = CHUNK/bpl;
+		nb = unloadmemimage(i, Rect(r.min.x, miny, r.max.x, miny+dy),
+			data+(miny-r.min.y)*bpl, dy*bpl);
+		if(nb != dy*bpl)
+			goto ErrOut;
+	}
+	sprint(hdr, "compressed\n%11s %11d %11d %11d %11d ",
+		chantostr(cbuf, i->chan), r.min.x, r.min.y, r.max.x, r.max.y);
+	if(write(fd, hdr, 11+5*12) != 11+5*12)
+		goto ErrOut;
+	edata = data+n;
+	eout = outbuf+ncblock;
+	line = data;
+	r.max.y = r.min.y;
+	while(line != edata){
+		memset(hash, 0, NHASH*sizeof(Hlist));
+		memset(chain, 0, NMEM*sizeof(Hlist));
+		cp = chain;
+		h = 0;
+		outp = outbuf;
+		for(n = 0; n != NMATCH; n++)
+			h = hupdate(h, line[n]);
+		loutp = outbuf;
+		while(line != edata){
+			ndump = 0;
+			eline = line+bpl;
+			for(p = line; p != eline; ){
+				if(eline-p < NRUN)
+					es = eline;
+				else
+					es = p+NRUN;
+				q = 0;
+				runlen = 0;
+				for(hp = hash[h].next; hp; hp = hp->next){
+					s = p + runlen;
+					if(s >= es)
+						continue;
+					t = hp->s + runlen;
+					for(; s >= p; s--)
+						if(*s != *t--)
+							goto matchloop;
+					t += runlen+2;
+					s += runlen+2;
+					for(; s < es; s++)
+						if(*s != *t++)
+							break;
+					n = s-p;
+					if(n > runlen){
+						runlen = n;
+						q = hp->s;
+						if(n == NRUN)
+							break;
+					}
+			matchloop: ;
+				}
+				if(runlen < NMATCH){
+					if(ndump == NDUMP){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					dumpbuf[ndump++] = *p;
+					runlen = 1;
+				}
+				else{
+					if(ndump != 0){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					offs = p-q-1;
+					if(eout-outp < 2)
+						goto Bfull;
+					*outp++ = ((runlen-NMATCH)<<2) + (offs>>8);
+					*outp++ = offs&255;
+				}
+				for(q = p+runlen; p != q; p++){
+					if(cp->prev)
+						cp->prev->next = 0;
+					cp->next = hash[h].next;
+					cp->prev = &hash[h];
+					if(cp->next)
+						cp->next->prev = cp;
+					cp->prev->next = cp;
+					cp->s = p;
+					if(++cp == &chain[NMEM])
+						cp = chain;
+					if(edata-p > NMATCH)
+						h = hupdate(h, p[NMATCH]);
+				}
+			}
+			if(ndump != 0){
+				if(eout-outp < ndump+1)
+					goto Bfull;
+				*outp++ = ndump-1+128;
+				memmove(outp, dumpbuf, ndump);
+				outp += ndump;
+			}
+			line = eline;
+			loutp = outp;
+			r.max.y++;
+		}
+	Bfull:
+		if(loutp == outbuf)
+			goto ErrOut;
+		n = loutp-outbuf;
+		sprint(hdr, "%11d %11ld ", r.max.y, n);
+		write(fd, hdr, 2*12);
+		write(fd, outbuf, n);
+		r.min.y = r.max.y;
+	}
+	free(data);
+	free(outbuf);
+	free(hash);
+	free(chain);
+	return 0;
+}
diff --git a/src/libdraw/memdraw.h b/src/libdraw/memdraw.h
new file mode 100644
index 0000000..08784ce
--- /dev/null
+++ b/src/libdraw/memdraw.h
@@ -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
new file mode 100644
index 0000000..36d8776
--- /dev/null
+++ b/src/libdraw/memlayer.h
@@ -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
new file mode 100644
index 0000000..8830433
--- /dev/null
+++ b/src/libdraw/menuhit.c
@@ -0,0 +1,277 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+
+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; i<nitemdrawn; i++)
+		paintitem(m, menu, textr, off, i, 0, nil, nil);
+}
+
+static void
+menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn)
+{
+	Rectangle r;
+
+	draw(m, scrollr, back, nil, ZP);
+	r.min.x = scrollr.min.x;
+	r.max.x = scrollr.max.x;
+	r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
+	r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
+	if(r.max.y < r.min.y+2)
+		r.max.y = r.min.y+2;
+	border(m, r, 1, bord, ZP);
+	if(menutxt == 0)
+		menutxt = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 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.x<screen->r.min.x)
+		pt.x = screen->r.min.x-r.min.x;
+	if(r.min.y<screen->r.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
new file mode 100644
index 0000000..bb99a25
--- /dev/null
+++ b/src/libdraw/mkfile
@@ -0,0 +1 @@
+<../libutf/mkfile
diff --git a/src/libdraw/ml-draw.c b/src/libdraw/ml-draw.c
new file mode 100644
index 0000000..c352a0b
--- /dev/null
+++ b/src/libdraw/ml-draw.c
@@ -0,0 +1,192 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+struct Draw
+{
+	Point	deltas;
+	Point	deltam;
+	Memlayer		*dstlayer;
+	Memimage	*src;
+	Memimage	*mask;
+	int	op;
+};
+
+static
+void
+ldrawop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	struct Draw *d;
+	Point p0, p1;
+	Rectangle oclipr, srcr, r, mr;
+	int ok;
+
+	d = etc;
+	if(insave && d->dstlayer->save==nil)
+		return;
+
+	p0 = addpt(screenr.min, d->deltas);
+	p1 = addpt(screenr.min, d->deltam);
+
+	if(insave){
+		r = rectsubpt(screenr, d->dstlayer->delta);
+		clipr = rectsubpt(clipr, d->dstlayer->delta);
+	}else
+		r = screenr;
+
+	/* now in logical coordinates */
+
+	/* clipr may have narrowed what we should draw on, so clip if necessary */
+	if(!rectinrect(r, clipr)){
+		oclipr = dst->clipr;
+		dst->clipr = clipr;
+		ok = drawclip(dst, &r, d->src, &p0, d->mask, &p1, &srcr, &mr);
+		dst->clipr = oclipr;
+		if(!ok)
+			return;
+	}
+	memdraw(dst, r, d->src, p0, d->mask, p1, d->op);
+}
+
+void
+memdraw(Memimage *dst, Rectangle r, Memimage *src, Point p0, Memimage *mask, Point p1, int op)
+{
+	struct Draw d;
+	Rectangle srcr, tr, mr;
+	Memlayer *dl, *sl;
+
+	if(drawdebug)
+		iprint("memdraw %p %R %p %P %p %P\n", dst, r, src, p0, mask, p1);
+
+	if(mask == nil)
+		mask = memopaque;
+
+	if(mask->layer){
+if(drawdebug)	iprint("mask->layer != nil\n");
+		return;	/* too hard, at least for now */
+	}
+
+    Top:
+	if(dst->layer==nil && src->layer==nil){
+		memimagedraw(dst, r, src, p0, mask, p1, op);
+		return;
+	}
+
+	if(drawclip(dst, &r, src, &p0, mask, &p1, &srcr, &mr) == 0){
+if(drawdebug)	iprint("drawclip dstcr %R srccr %R maskcr %R\n", dst->clipr, src->clipr, mask->clipr);
+		return;
+	}
+
+	/*
+ 	 * Convert to screen coordinates.
+	 */
+	dl = dst->layer;
+	if(dl != nil){
+		r.min.x += dl->delta.x;
+		r.min.y += dl->delta.y;
+		r.max.x += dl->delta.x;
+		r.max.y += dl->delta.y;
+	}
+    Clearlayer:
+	if(dl!=nil && dl->clear){
+		if(src == dst){
+			p0.x += dl->delta.x;
+			p0.y += dl->delta.y;
+			src = dl->screen->image;
+		}
+		dst = dl->screen->image;
+		goto Top;
+	}
+
+	sl = src->layer;
+	if(sl != nil){
+		p0.x += sl->delta.x;
+		p0.y += sl->delta.y;
+		srcr.min.x += sl->delta.x;
+		srcr.min.y += sl->delta.y;
+		srcr.max.x += sl->delta.x;
+		srcr.max.y += sl->delta.y;
+	}
+
+	/*
+	 * Now everything is in screen coordinates.
+	 * mask is an image.  dst and src are images or obscured layers.
+	 */
+
+	/*
+	 * if dst and src are the same layer, just draw in save area and expose.
+	 */
+	if(dl!=nil && dst==src){
+		if(dl->save == nil)
+			return;	/* refresh function makes this case unworkable */
+		if(rectXrect(r, srcr)){
+			tr = r;
+			if(srcr.min.x < tr.min.x){
+				p1.x += tr.min.x - srcr.min.x;
+				tr.min.x = srcr.min.x;
+			}
+			if(srcr.min.y < tr.min.y){
+				p1.y += tr.min.x - srcr.min.x;
+				tr.min.y = srcr.min.y;
+			}
+			if(srcr.max.x > tr.max.x)
+				tr.max.x = srcr.max.x;
+			if(srcr.max.y > tr.max.y)
+				tr.max.y = srcr.max.y;
+			memlhide(dst, tr);
+		}else{
+			memlhide(dst, r);
+			memlhide(dst, srcr);
+		}
+		memdraw(dl->save, rectsubpt(r, dl->delta), dl->save,
+			subpt(srcr.min, src->layer->delta), mask, p1, op);
+		memlexpose(dst, r);
+		return;
+	}
+
+	if(sl){
+		if(sl->clear){
+			src = sl->screen->image;
+			if(dl != nil){
+				r.min.x -= dl->delta.x;
+				r.min.y -= dl->delta.y;
+				r.max.x -= dl->delta.x;
+				r.max.y -= dl->delta.y;
+			}
+			goto Top;
+		}
+		/* relatively rare case; use save area */
+		if(sl->save == nil)
+			return;	/* refresh function makes this case unworkable */
+		memlhide(src, srcr);
+		/* convert back to logical coordinates */
+		p0.x -= sl->delta.x;
+		p0.y -= sl->delta.y;
+		srcr.min.x -= sl->delta.x;
+		srcr.min.y -= sl->delta.y;
+		srcr.max.x -= sl->delta.x;
+		srcr.max.y -= sl->delta.y;
+		src = src->layer->save;
+	}
+
+	/*
+	 * src is now an image.  dst may be an image or a clear layer
+	 */
+	if(dst->layer==nil)
+		goto Top;
+	if(dst->layer->clear)
+		goto Clearlayer;
+
+	/*
+	 * dst is an obscured layer
+	 */
+	d.deltas = subpt(p0, r.min);
+	d.deltam = subpt(p1, r.min);
+	d.dstlayer = dl;
+	d.src = src;
+	d.op = op;
+	d.mask = mask;
+	_memlayerop(ldrawop, dst, r, r, &d);
+}
diff --git a/src/libdraw/ml-lalloc.c b/src/libdraw/ml-lalloc.c
new file mode 100644
index 0000000..17d934d
--- /dev/null
+++ b/src/libdraw/ml-lalloc.c
@@ -0,0 +1,79 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+Memimage*
+memlalloc(Memscreen *s, Rectangle screenr, Refreshfn refreshfn, void *refreshptr, 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
new file mode 100644
index 0000000..27fdcfc
--- /dev/null
+++ b/src/libdraw/ml-layerop.c
@@ -0,0 +1,112 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+#define	RECUR(a,b,c,d)	_layerop(fn, i, Rect(a.x, b.y, c.x, d.y), clipr, etc, front->layer->rear);
+
+static void
+_layerop(
+	void (*fn)(Memimage*, Rectangle, Rectangle, void*, int),
+	Memimage *i,
+	Rectangle r,
+	Rectangle clipr,
+	void *etc,
+	Memimage *front)
+{
+	Rectangle fr;
+
+    Top:
+	if(front == i){
+		/* no one is in front of this part of window; use the screen */
+		fn(i->layer->screen->image, r, clipr, etc, 0);
+		return;
+	}
+	fr = front->layer->screenr;
+	if(rectXrect(r, fr) == 0){
+		/* r doesn't touch this window; continue on next rearmost */
+		// assert(front && front->layer && front->layer->screen && front->layer->rear);
+		front = front->layer->rear;
+		goto Top;
+	}
+	if(fr.max.y < r.max.y){
+		RECUR(r.min, fr.max, r.max, r.max);
+		r.max.y = fr.max.y;
+	}
+	if(r.min.y < fr.min.y){
+		RECUR(r.min, r.min, r.max, fr.min);
+		r.min.y = fr.min.y;
+	}
+	if(fr.max.x < r.max.x){
+		RECUR(fr.max, r.min, r.max, r.max);
+		r.max.x = fr.max.x;
+	}
+	if(r.min.x < fr.min.x){
+		RECUR(r.min, r.min, fr.min, r.max);
+		r.min.x = fr.min.x;
+	}
+	/* r is covered by front, so put in save area */
+	(*fn)(i->layer->save, r, clipr, etc, 1);
+}
+
+/*
+ * Assumes incoming rectangle has already been clipped to i's logical r and clipr
+ */
+void
+_memlayerop(
+	void (*fn)(Memimage*, Rectangle, Rectangle, void*, int),
+	Memimage *i,
+	Rectangle screenr,	/* clipped to window boundaries */
+	Rectangle clipr,		/* clipped also to clipping rectangles of hierarchy */
+	void *etc)
+{
+	Memlayer *l;
+	Rectangle r, scr;
+
+	l = i->layer;
+	if(!rectclip(&screenr, l->screenr))
+		return;
+	if(l->clear){
+		fn(l->screen->image, screenr, clipr, etc, 0);
+		return;
+	}
+	r = screenr;
+	scr = l->screen->image->clipr;
+
+	/*
+	 * Do the piece on the screen
+	 */
+	if(rectclip(&screenr, scr))
+		_layerop(fn, i, screenr, clipr, etc, l->screen->frontmost);
+	if(rectinrect(r, scr))
+		return;
+
+	/*
+	 * Do the piece off the screen
+	*/
+	if(!rectXrect(r, scr)){
+		/* completely offscreen; easy */
+		fn(l->save, r, clipr, etc, 1);
+		return;
+	}
+	if(r.min.y < scr.min.y){
+		/* above screen */
+		fn(l->save, Rect(r.min.x, r.min.y, r.max.x, scr.min.y), clipr, etc, 1);
+		r.min.y = scr.min.y;
+	}
+	if(r.max.y > scr.max.y){
+		/* below screen */
+		fn(l->save, Rect(r.min.x, scr.max.y, r.max.x, r.max.y), clipr, etc, 1);
+		r.max.y = scr.max.y;
+	}
+	if(r.min.x < scr.min.x){
+		/* left of screen */
+		fn(l->save, Rect(r.min.x, r.min.y, scr.min.x, r.max.y), clipr, etc, 1);
+		r.min.x = scr.min.x;
+	}
+	if(r.max.x > scr.max.x){
+		/* right of screen */
+		fn(l->save, Rect(scr.max.x, r.min.y, r.max.x, r.max.y), clipr, etc, 1);
+	}
+}
diff --git a/src/libdraw/ml-ldelete.c b/src/libdraw/ml-ldelete.c
new file mode 100644
index 0000000..34cd6ea
--- /dev/null
+++ b/src/libdraw/ml-ldelete.c
@@ -0,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+void
+memldelete(Memimage *i)
+{
+	Memscreen *s;
+	Memlayer *l;
+
+	l = i->layer;
+	/* free backing store and disconnect refresh, to make pushback fast */
+	freememimage(l->save);
+	l->save = nil;
+	l->refreshptr = nil;
+	memltorear(i);
+
+	/* window is now the rearmost;  clean up screen structures and deallocate */
+	s = i->layer->screen;
+	if(s->fill){
+		i->clipr = i->r;
+		memdraw(i, i->r, s->fill, i->r.min, nil, i->r.min, S);
+	}
+	if(l->front){
+		l->front->layer->rear = nil;
+		s->rearmost = l->front;
+	}else{
+		s->frontmost = nil;
+		s->rearmost = nil;
+	}
+	free(l);
+	freememimage(i);
+}
+
+/*
+ * Just free the data structures, don't do graphics
+ */
+void
+memlfree(Memimage *i)
+{
+	Memlayer *l;
+
+	l = i->layer;
+	freememimage(l->save);
+	free(l);
+	freememimage(i);
+}
+
+void
+_memlsetclear(Memscreen *s)
+{
+	Memimage *i, *j;
+	Memlayer *l;
+
+	for(i=s->rearmost; i; i=i->layer->front){
+		l = i->layer;
+		l->clear = rectinrect(l->screenr, l->screen->image->clipr);
+		if(l->clear)
+			for(j=l->front; j; j=j->layer->front)
+				if(rectXrect(l->screenr, j->layer->screenr)){
+					l->clear = 0;
+					break;
+				}
+	}
+}
diff --git a/src/libdraw/ml-lhide.c b/src/libdraw/ml-lhide.c
new file mode 100644
index 0000000..d6aaa55
--- /dev/null
+++ b/src/libdraw/ml-lhide.c
@@ -0,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * Hide puts that portion of screenr now on the screen into the window's save area.
+ * Expose puts that portion of screenr now in the save area onto the screen.
+ *
+ * Hide and Expose both require that the layer structures in the screen
+ * match the geometry they are being asked to update, that is, they update the
+ * save area (hide) or screen (expose) based on what those structures tell them.
+ * This means they must be called at the correct time during window shuffles.
+ */
+
+static
+void
+lhideop(Memimage *src, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	Rectangle r;
+	Memlayer *l;
+
+	USED(clipr.min.x);
+	USED(insave);
+	l = etc;
+	if(src != l->save){	/* do nothing if src is already in save area */
+		r = rectsubpt(screenr, l->delta);
+		memdraw(l->save, r, src, screenr.min, nil, screenr.min, S);
+	}
+}
+
+void
+memlhide(Memimage *i, Rectangle screenr)
+{
+	if(i->layer->save == nil)
+		return;
+	if(rectclip(&screenr, i->layer->screen->image->r) == 0)
+		return;
+	_memlayerop(lhideop, i, screenr, screenr, i->layer);
+}
+
+static
+void
+lexposeop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	Memlayer *l;
+	Rectangle r;
+
+	USED(clipr.min.x);
+	if(insave)	/* if dst is save area, don't bother */
+		return;
+	l = etc;
+	r = rectsubpt(screenr, l->delta);
+	if(l->save)
+		memdraw(dst, screenr, l->save, r.min, nil, r.min, S);
+	else
+		l->refreshfn(dst, r, l->refreshptr);
+}
+
+void
+memlexpose(Memimage *i, Rectangle screenr)
+{
+	if(rectclip(&screenr, i->layer->screen->image->r) == 0)
+		return;
+	_memlayerop(lexposeop, i, screenr, screenr, i->layer);
+}
diff --git a/src/libdraw/ml-line.c b/src/libdraw/ml-line.c
new file mode 100644
index 0000000..8c09a53
--- /dev/null
+++ b/src/libdraw/ml-line.c
@@ -0,0 +1,122 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+struct Lline
+{
+	Point			p0;
+	Point			p1;
+	Point			delta;
+	int			end0;
+	int			end1;
+	int			radius;
+	Point			sp;
+	Memlayer		*dstlayer;
+	Memimage	*src;
+	int			op;
+};
+
+static void llineop(Memimage*, Rectangle, Rectangle, void*, int);
+
+static
+void
+_memline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, Rectangle clipr, int op)
+{
+	Rectangle r;
+	struct Lline ll;
+	Point d;
+	int srcclipped;
+	Memlayer *dl;
+
+	if(radius < 0)
+		return;
+	if(src->layer)	/* can't draw line with layered source */
+		return;
+	srcclipped = 0;
+
+   Top:
+	dl = dst->layer;
+	if(dl == nil){
+		_memimageline(dst, p0, p1, end0, end1, radius, src, sp, clipr, op);
+		return;
+	}
+	if(!srcclipped){
+		d = subpt(sp, p0);
+		if(rectclip(&clipr, rectsubpt(src->clipr, d)) == 0)
+			return;
+		if((src->flags&Frepl)==0 && rectclip(&clipr, rectsubpt(src->r, d))==0)
+			return;
+		srcclipped = 1;
+	}
+
+	/* dst is known to be a layer */
+	p0.x += dl->delta.x;
+	p0.y += dl->delta.y;
+	p1.x += dl->delta.x;
+	p1.y += dl->delta.y;
+	clipr.min.x += dl->delta.x;
+	clipr.min.y += dl->delta.y;
+	clipr.max.x += dl->delta.x;
+	clipr.max.y += dl->delta.y;
+	if(dl->clear){
+		dst = dst->layer->screen->image;
+		goto Top;
+	}
+
+	/* XXX */
+	/* this is not the correct set of tests */
+//	if(log2[dst->depth] != log2[src->depth] || log2[dst->depth]!=3)
+//		return;
+
+	/* can't use sutherland-cohen clipping because lines are wide */
+	r = memlinebbox(p0, p1, end0, end1, radius);
+	/*
+	 * r is now a bounding box for the line;
+	 * use it as a clipping rectangle for subdivision
+	 */
+	if(rectclip(&r, clipr) == 0)
+		return;
+	ll.p0 = p0;
+	ll.p1 = p1;
+	ll.end0 = end0;
+	ll.end1 = end1;
+	ll.sp = sp;
+	ll.dstlayer = dst->layer;
+	ll.src = src;
+	ll.radius = radius;
+	ll.delta = dl->delta;
+	ll.op = op;
+	_memlayerop(llineop, dst, r, r, &ll);
+}
+
+static
+void
+llineop(Memimage *dst, Rectangle screenr, Rectangle clipr, void *etc, int insave)
+{
+	struct Lline *ll;
+	Point p0, p1;
+
+	USED(screenr.min.x);
+	ll = etc;
+	if(insave && ll->dstlayer->save==nil)
+		return;
+	if(!rectclip(&clipr, screenr))
+		return;
+	if(insave){
+		p0 = subpt(ll->p0, ll->delta);
+		p1 = subpt(ll->p1, ll->delta);
+		clipr = rectsubpt(clipr, ll->delta);
+	}else{
+		p0 = ll->p0;
+		p1 = ll->p1;
+	}
+	_memline(dst, p0, p1, ll->end0, ll->end1, ll->radius, ll->src, ll->sp, clipr, ll->op);
+}
+
+void
+memline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, int op)
+{
+	_memline(dst, p0, p1, end0, end1, radius, src, sp, dst->clipr, op);
+}
diff --git a/src/libdraw/ml-load.c b/src/libdraw/ml-load.c
new file mode 100644
index 0000000..d211564
--- /dev/null
+++ b/src/libdraw/ml-load.c
@@ -0,0 +1,55 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+int
+memload(Memimage *dst, Rectangle r, uchar *data, int n, int iscompressed)
+{
+	int (*loadfn)(Memimage*, Rectangle, uchar*, int);
+	Memimage *tmp;
+	Memlayer *dl;
+	Rectangle lr;
+	int dx;
+
+	loadfn = loadmemimage;
+	if(iscompressed)
+		loadfn = cloadmemimage;
+
+    Top:
+	dl = dst->layer;
+	if(dl == nil)
+		return loadfn(dst, r, data, n);
+
+	/*
+ 	 * Convert to screen coordinates.
+	 */
+	lr = r;
+	r.min.x += dl->delta.x;
+	r.min.y += dl->delta.y;
+	r.max.x += dl->delta.x;
+	r.max.y += dl->delta.y;
+	dx = dl->delta.x&(7/dst->depth);
+	if(dl->clear && dx==0){
+		dst = dl->screen->image;
+		goto Top;
+	}
+
+	/*
+	 * dst is an obscured layer or data is unaligned
+	 */
+	if(dl->save && dx==0){
+		n = loadfn(dl->save, lr, data, n);
+		if(n > 0)
+			memlexpose(dst, r);
+		return n;
+	}
+	tmp = allocmemimage(lr, dst->chan);
+	if(tmp == nil)
+		return -1;
+	n = loadfn(tmp, lr, data, n);
+	memdraw(dst, lr, tmp, lr.min, nil, lr.min, S);
+	freememimage(tmp);
+	return n;
+}
diff --git a/src/libdraw/ml-lorigin.c b/src/libdraw/ml-lorigin.c
new file mode 100644
index 0000000..0926ee8
--- /dev/null
+++ b/src/libdraw/ml-lorigin.c
@@ -0,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * Place i so i->r.min = log, i->layer->screenr.min == scr.
+*/
+int
+memlorigin(Memimage *i, Point log, Point scr)
+{
+	Memlayer *l;
+	Memscreen *s;
+	Memimage *t, *shad, *nsave;
+	Rectangle x, newr, oldr;
+	Point delta;
+	int overlap, eqlog, eqscr, wasclear;
+
+	l = i->layer;
+	s = l->screen;
+	oldr = l->screenr;
+	newr = Rect(scr.x, scr.y, scr.x+Dx(oldr), scr.y+Dy(oldr));
+	eqscr = eqpt(scr, oldr.min);
+	eqlog = eqpt(log, i->r.min);
+	if(eqscr && eqlog)
+		return 0;
+	nsave = nil;
+	if(eqlog==0 && l->save!=nil){
+		nsave = allocmemimage(Rect(log.x, log.y, log.x+Dx(oldr), log.y+Dy(oldr)), i->chan);
+		if(nsave == nil)
+			return -1;
+	}
+
+	/*
+	 * Bring it to front and move logical coordinate system.
+	 */
+	memltofront(i);
+	wasclear = l->clear;
+	if(nsave){
+		if(!wasclear)
+			memimagedraw(nsave, nsave->r, l->save, l->save->r.min, nil, Pt(0,0), S);
+		freememimage(l->save);
+		l->save = nsave;
+	}
+	delta = subpt(log, i->r.min);
+	i->r = rectaddpt(i->r, delta);
+	i->clipr = rectaddpt(i->clipr, delta);
+	l->delta = subpt(l->screenr.min, i->r.min);
+	if(eqscr)
+		return 0;
+
+	/*
+	 * To clean up old position, make a shadow window there, don't paint it,
+	 * push it behind this one, and (later) delete it.  Because the refresh function
+	 * for this fake window is a no-op, this will cause no graphics action except
+	 * to restore the background and expose the windows previously hidden.
+	 */
+	shad = memlalloc(s, oldr, memlnorefresh, nil, DNofill);
+	if(shad == nil)
+		return -1;
+	s->frontmost = i;
+	if(s->rearmost == i)
+		s->rearmost = shad;
+	else
+		l->rear->layer->front = shad;
+	shad->layer->front = i;
+	shad->layer->rear = l->rear;
+	l->rear = shad;
+	l->front = nil;
+	shad->layer->clear = 0;
+
+	/*
+	 * Shadow is now holding down the fort at the old position.
+	 * Move the window and hide things obscured by new position.
+	 */
+	for(t=l->rear->layer->rear; t!=nil; t=t->layer->rear){
+		x = newr;
+		overlap = rectclip(&x, t->layer->screenr);
+		if(overlap){
+			memlhide(t, x);
+			t->layer->clear = 0;
+		}
+	}
+	l->screenr = newr;
+	l->delta = subpt(scr, i->r.min);
+	l->clear = rectinrect(newr, l->screen->image->clipr);
+
+	/*
+	 * Everything's covered.  Copy to new position and delete shadow window.
+	 */
+	if(wasclear)
+		memdraw(s->image, newr, s->image, oldr.min, nil, Pt(0,0), S);
+	else
+		memlexpose(i, newr);
+	memldelete(shad);
+
+	return 1;
+}
+
+void
+memlnorefresh(Memimage *l, Rectangle r, void *v)
+{
+	USED(l);
+	USED(r.min.x);
+	USED(v);
+}
diff --git a/src/libdraw/ml-lsetrefresh.c b/src/libdraw/ml-lsetrefresh.c
new file mode 100644
index 0000000..44079be
--- /dev/null
+++ b/src/libdraw/ml-lsetrefresh.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+int
+memlsetrefresh(Memimage *i, Refreshfn fn, void *ptr)
+{
+	Memlayer *l;
+
+	l = i->layer;
+	if(l->refreshfn!=nil && fn!=nil){	/* just change functions */
+		l->refreshfn = fn;
+		l->refreshptr = ptr;
+		return 1;
+	}
+
+	if(l->refreshfn == nil){	/* is using backup image; just free it */
+		freememimage(l->save);
+		l->save = nil;
+		l->refreshfn = fn;
+		l->refreshptr = ptr;
+		return 1;
+	}
+
+	l->save = allocmemimage(i->r, i->chan);
+	if(l->save == nil)
+		return 0;
+	/* easiest way is just to update the entire save area */
+	l->refreshfn(i, i->r, l->refreshptr);
+	l->refreshfn = nil;
+	l->refreshptr = nil;
+	return 1;
+}
diff --git a/src/libdraw/ml-ltofront.c b/src/libdraw/ml-ltofront.c
new file mode 100644
index 0000000..447b40b
--- /dev/null
+++ b/src/libdraw/ml-ltofront.c
@@ -0,0 +1,80 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+/*
+ * Pull i towards top of screen, just behind front
+*/
+static
+void
+_memltofront(Memimage *i, Memimage *front, int fill)
+{
+	Memlayer *l;
+	Memscreen *s;
+	Memimage *f, *ff, *rr;
+	Rectangle x;
+	int overlap;
+
+	l = i->layer;
+	s = l->screen;
+	while(l->front != front){
+		f = l->front;
+		x = l->screenr;
+		overlap = rectclip(&x, f->layer->screenr);
+		if(overlap){
+			memlhide(f, x);
+			f->layer->clear = 0;
+		}
+		/* swap l and f in screen's list */
+		ff = f->layer->front;
+		rr = l->rear;
+		if(ff == nil)
+			s->frontmost = i;
+		else
+			ff->layer->rear = i;
+		if(rr == nil)
+			s->rearmost = f;
+		else
+			rr->layer->front = f;
+		l->front = ff;
+		l->rear = f;
+		f->layer->front = i;
+		f->layer->rear = rr;
+		if(overlap && fill)
+			memlexpose(i, x);
+	}
+}
+
+void
+_memltofrontfill(Memimage *i, int fill)
+{
+	_memltofront(i, nil, fill);
+	_memlsetclear(i->layer->screen);
+}
+
+void
+memltofront(Memimage *i)
+{
+	_memltofront(i, nil, 1);
+	_memlsetclear(i->layer->screen);
+}
+
+void
+memltofrontn(Memimage **ip, int n)
+{
+	Memimage *i, *front;
+	Memscreen *s;
+
+	if(n == 0)
+		return;
+	front = nil;
+	while(--n >= 0){
+		i = *ip++;
+		_memltofront(i, front, 1);
+		front = i;
+	}
+	s = front->layer->screen;
+	_memlsetclear(s);
+}
diff --git a/src/libdraw/ml-ltorear.c b/src/libdraw/ml-ltorear.c
new file mode 100644
index 0000000..d53e8cc
--- /dev/null
+++ b/src/libdraw/ml-ltorear.c
@@ -0,0 +1,69 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+void
+_memltorear(Memimage *i, Memimage *rear)
+{
+	Memlayer *l;
+	Memscreen *s;
+	Memimage *f, *r, *rr;
+	Rectangle x;
+	int overlap;
+
+	l = i->layer;
+	s = l->screen;
+	while(l->rear != rear){
+		r = l->rear;
+		x = l->screenr;
+		overlap = rectclip(&x, r->layer->screenr);
+		if(overlap){
+			memlhide(i, x);
+			l->clear = 0;
+		}
+		/* swap l and r in screen's list */
+		rr = r->layer->rear;
+		f = l->front;
+		if(rr == nil)
+			s->rearmost = i;
+		else
+			rr->layer->front = i;
+		if(f == nil)
+			s->frontmost = r;
+		else
+			f->layer->rear = r;
+		l->rear = rr;
+		l->front = r;
+		r->layer->rear = i;
+		r->layer->front = f;
+		if(overlap)
+			memlexpose(r, x);
+	}
+}
+
+void
+memltorear(Memimage *i)
+{
+	_memltorear(i, nil);
+	_memlsetclear(i->layer->screen);
+}
+
+void
+memltorearn(Memimage **ip, int n)
+{
+	Memimage *i, *rear;
+	Memscreen *s;
+
+	if(n == 0)
+		return;
+	rear = nil;
+	while(--n >= 0){
+		i = *ip++;
+		_memltorear(i, rear);
+		rear = i;
+	}
+	s = rear->layer->screen;
+	_memlsetclear(s);
+}
diff --git a/src/libdraw/ml-unload.c b/src/libdraw/ml-unload.c
new file mode 100644
index 0000000..23a8b29
--- /dev/null
+++ b/src/libdraw/ml-unload.c
@@ -0,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <memlayer.h>
+
+int
+memunload(Memimage *src, Rectangle r, uchar *data, int n)
+{
+	Memimage *tmp;
+	Memlayer *dl;
+	Rectangle lr;
+	int dx;
+
+    Top:
+	dl = src->layer;
+	if(dl == nil)
+		return unloadmemimage(src, r, data, n);
+
+	/*
+ 	 * Convert to screen coordinates.
+	 */
+	lr = r;
+	r.min.x += dl->delta.x;
+	r.min.y += dl->delta.y;
+	r.max.x += dl->delta.x;
+	r.max.y += dl->delta.y;
+	dx = dl->delta.x&(7/src->depth);
+	if(dl->clear && dx==0){
+		src = dl->screen->image;
+		goto Top;
+	}
+
+	/*
+	 * src is an obscured layer or data is unaligned
+	 */
+	if(dl->save && dx==0){
+		if(dl->refreshfn != nil)
+			return -1;	/* can't unload window if it's not Refbackup */
+		if(n > 0)
+			memlhide(src, r);
+		n = unloadmemimage(dl->save, lr, data, n);
+		return n;
+	}
+	tmp = allocmemimage(lr, src->chan);
+	if(tmp == nil)
+		return -1;
+	memdraw(tmp, lr, src, lr.min, nil, lr.min, S);
+	n = unloadmemimage(tmp, lr, data, n);
+	freememimage(tmp);
+	return n;
+}
diff --git a/src/libdraw/mouse.c b/src/libdraw/mouse.c
new file mode 100644
index 0000000..e7b8f89
--- /dev/null
+++ b/src/libdraw/mouse.c
@@ -0,0 +1,139 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+
+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
new file mode 100644
index 0000000..f0a0f69
--- /dev/null
+++ b/src/libdraw/mouse.h
@@ -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
new file mode 100644
index 0000000..dc16e37
--- /dev/null
+++ b/src/libdraw/openfont.c
@@ -0,0 +1,32 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..6eb8ee2
--- /dev/null
+++ b/src/libdraw/readcolmap.c
@@ -0,0 +1,49 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <bio.h>
+
+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
new file mode 100644
index 0000000..1d2717b
--- /dev/null
+++ b/src/libdraw/readimage.c
@@ -0,0 +1,118 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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<chunk; j++)
+				tmp[j] ^= 0xFF;
+
+		if(dolock)
+			lockdisplay(d);
+		if(loadimage(i, Rect(r.min.x, miny, r.max.x, miny+dy), tmp, chunk) <= 0)
+			goto Err1;
+		if(dolock)
+			unlockdisplay(d);
+		miny += dy;
+	}
+	free(tmp);
+	return i;
+}
diff --git a/src/libdraw/readsubfont.c b/src/libdraw/readsubfont.c
new file mode 100644
index 0000000..0e587b4
--- /dev/null
+++ b/src/libdraw/readsubfont.c
@@ -0,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..4dfb27f
--- /dev/null
+++ b/src/libdraw/string.c
@@ -0,0 +1,137 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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; c<ec; c++, b+=2)
+				BPSHORT(b, *c);
+			pt.x += wid;
+			bgp.x += wid;
+			agefont(f);
+			len -= n;
+		}
+		if(subfontname){
+			if(_getsubfont(f->display, 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
new file mode 100644
index 0000000..c487791
--- /dev/null
+++ b/src/libdraw/stringwidth.c
@@ -0,0 +1,97 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..61838b0
--- /dev/null
+++ b/src/libdraw/subfont.c
@@ -0,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..eb0bdba
--- /dev/null
+++ b/src/libdraw/subfontcache.c
@@ -0,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * 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
new file mode 100644
index 0000000..bcb4e3a
--- /dev/null
+++ b/src/libdraw/subfontname.c
@@ -0,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * 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
new file mode 100644
index 0000000..2203b08
--- /dev/null
+++ b/src/libdraw/unix.c
@@ -0,0 +1,16 @@
+#include <sys/stat.h>
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..c203b9c
--- /dev/null
+++ b/src/libdraw/unloadimage.c
@@ -0,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+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
new file mode 100644
index 0000000..30b026f
--- /dev/null
+++ b/src/libdraw/writecolmap.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * 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
new file mode 100644
index 0000000..19475c7
--- /dev/null
+++ b/src/libdraw/x11-alloc.c
@@ -0,0 +1,120 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..25a325c
--- /dev/null
+++ b/src/libdraw/x11-cload.c
@@ -0,0 +1,19 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..33b92c8
--- /dev/null
+++ b/src/libdraw/x11-draw.c
@@ -0,0 +1,143 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..9a23547
--- /dev/null
+++ b/src/libdraw/x11-event.c
@@ -0,0 +1,136 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <cursor.h>
+#include <event.h>
+
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..ff0b2e8
--- /dev/null
+++ b/src/libdraw/x11-fill.c
@@ -0,0 +1,57 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..feed46c
--- /dev/null
+++ b/src/libdraw/x11-get.c
@@ -0,0 +1,110 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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; y<r.max.y; y++)
+		for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
+			*p = _x.toplan9[*p];
+	}
+	xm->dirty = 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; y<r.max.y; y++)
+		for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
+			*p = _x.tox11[*p];
+	}
+
+	XPutImage(_x.display, xm->pixmap, 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; y<r.max.y; y++)
+		for(x=r.min.x, p=byteaddr(m, Pt(x,y)); x<r.max.x; x++, p++)
+			*p = _x.toplan9[*p];
+	}
+}
+
+void
+xdirtyxdata(Memimage *m, Rectangle r)
+{
+	Xmem *xm;
+
+	xm = m->X;
+	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
new file mode 100644
index 0000000..4baf4b1
--- /dev/null
+++ b/src/libdraw/x11-inc.h
@@ -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 <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+
+#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
new file mode 100644
index 0000000..3100124
--- /dev/null
+++ b/src/libdraw/x11-init.c
@@ -0,0 +1,584 @@
+/*
+ * Some of the stuff in this file is not X-dependent and should be elsewhere.
+ */
+#include "x11-inc.h"
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <cursor.h>
+#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; i<n; i++){
+		if(pfmt[i].depth == _x.depth){
+			switch(pfmt[i].bits_per_pixel){
+			case 1:	/* untested */
+				_x.chan = GREY1;
+				break;
+			case 2:	/* untested */
+				_x.chan = GREY2;
+				break;
+			case 4:	/* untested */
+				_x.chan = GREY4;
+				break;
+			case 8:
+				_x.chan = CMAP8;
+				break;
+			case 16: /* how to tell RGB15? */
+				_x.chan = RGB16;
+				break;
+			case 24: /* untested (impossible?) */
+				_x.chan = RGB24;
+				break;
+			case 32:
+				_x.chan = XRGB32;
+				break;
+			}
+		}
+	}
+	if(_x.chan == 0){
+		werrstr("could not determine screen pixel format");
+		goto err0;
+	}
+
+	/*
+	 * Set up color map if necessary.
+	 */
+	xscreen = DefaultScreenOfDisplay(_x.display);
+	_x.cmap = DefaultColormapOfScreen(xscreen);
+	if(_x.vis->class != 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
new file mode 100644
index 0000000..49ee3fb
--- /dev/null
+++ b/src/libdraw/x11-itrans.c
@@ -0,0 +1,258 @@
+/* input event and data structure translation */
+
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#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
new file mode 100644
index 0000000..188e52b
--- /dev/null
+++ b/src/libdraw/x11-keyboard.c
@@ -0,0 +1,71 @@
+#include "x11-inc.h"
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <memdraw.h>
+#include <keyboard.h>
+#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
new file mode 100644
index 0000000..e4e32bf2
--- /dev/null
+++ b/src/libdraw/x11-load.c
@@ -0,0 +1,19 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..c8234b2
--- /dev/null
+++ b/src/libdraw/x11-memdraw.h
@@ -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
new file mode 100644
index 0000000..c6a2685
--- /dev/null
+++ b/src/libdraw/x11-mouse.c
@@ -0,0 +1,107 @@
+#include "x11-inc.h"
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..8635b0b
--- /dev/null
+++ b/src/libdraw/x11-pixelbits.c
@@ -0,0 +1,17 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..3e8a635
--- /dev/null
+++ b/src/libdraw/x11-unload.c
@@ -0,0 +1,16 @@
+#include "x11-inc.h"
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#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
new file mode 100644
index 0000000..2f391bf
--- /dev/null
+++ b/src/libthread/386.c
@@ -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
new file mode 100644
index 0000000..624518e
--- /dev/null
+++ b/src/libthread/FreeBSD-386.s
@@ -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
new file mode 100644
index 0000000..a5d7d87
--- /dev/null
+++ b/src/libthread/LICENSE
@@ -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
new file mode 100644
index 0000000..14b8d4e
--- /dev/null
+++ b/src/libthread/Make.Darwin-PowerMacintosh
@@ -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
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/libthread/Make.FreeBSD-386
@@ -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
new file mode 100644
index 0000000..edbdc11
--- /dev/null
+++ b/src/libthread/Make.HP-UX-9000
@@ -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
new file mode 100644
index 0000000..74b0252
--- /dev/null
+++ b/src/libthread/Make.Linux-386
@@ -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
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/libthread/Make.NetBSD-386
@@ -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
new file mode 100644
index 0000000..3d45279
--- /dev/null
+++ b/src/libthread/Make.OSF1-alpha
@@ -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
new file mode 100644
index 0000000..c5fe67b
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u
@@ -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
new file mode 100644
index 0000000..829301d
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u-cc
@@ -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
new file mode 100644
index 0000000..5c41594
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u-gcc
@@ -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
new file mode 100644
index 0000000..ebccc1a
--- /dev/null
+++ b/src/libthread/Makefile
@@ -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
new file mode 100644
index 0000000..05c3416
--- /dev/null
+++ b/src/libthread/Makefile.MID
@@ -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
new file mode 100644
index 0000000..8bf69d6
--- /dev/null
+++ b/src/libthread/NOTICE
@@ -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 <rsc@post.harvard.edu>.
+
diff --git a/src/libthread/README b/src/libthread/README
new file mode 100644
index 0000000..8bf69d6
--- /dev/null
+++ b/src/libthread/README
@@ -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 <rsc@post.harvard.edu>.
+
diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s
new file mode 100644
index 0000000..7cad85c
--- /dev/null
+++ b/src/libthread/asm-FreeBSD-386.s
@@ -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
new file mode 100644
index 0000000..75d965b
--- /dev/null
+++ b/src/libthread/asm-Linux-386.s
@@ -0,0 +1 @@
+.include "asm-FreeBSD-386.s"
diff --git a/src/libthread/bundle.ports b/src/libthread/bundle.ports
new file mode 100644
index 0000000..adfeb90
--- /dev/null
+++ b/src/libthread/bundle.ports
@@ -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 <bsd.port.pre.mk>
+
+post-patch:
+	${REINPLACE_CMD} -e 's,@@LOCAL@@,${PREFIX},g' ${WRKSRC}/Makefile
+
+.include <bsd.port.post.mk>
+
+--- 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
new file mode 100644
index 0000000..384f23f
--- /dev/null
+++ b/src/libthread/channel.c
@@ -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; i<c->nentry; 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; i<c->nentry; 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; i<c->nentry; 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; i<c->nentry; 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
new file mode 100644
index 0000000..af9e810
--- /dev/null
+++ b/src/libthread/chanprint.c
@@ -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
new file mode 100644
index 0000000..ab803a2
--- /dev/null
+++ b/src/libthread/create.c
@@ -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
new file mode 100644
index 0000000..63e2e1b
--- /dev/null
+++ b/src/libthread/debug.c
@@ -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
new file mode 100644
index 0000000..5a37e34
--- /dev/null
+++ b/src/libthread/exec-unix.c
@@ -0,0 +1,124 @@
+#include <fcntl.h>
+#include <unistd.h>
+#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
new file mode 100644
index 0000000..bcf2080
--- /dev/null
+++ b/src/libthread/exec.c
@@ -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
new file mode 100644
index 0000000..ddf7001
--- /dev/null
+++ b/src/libthread/exit.c
@@ -0,0 +1,63 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+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<npid; i++)
+		if(pid[i] != mypid)
+			kill(pid[i], SIGTERM);
+
+	/* leave */
+	exit(0);
+}
+
+Channel*
+threadwaitchan(void)
+{
+	if(_threadwaitchan==nil)
+		_threadwaitchan = chancreate(sizeof(Waitmsg*), 16);
+	return _threadwaitchan;
+}
diff --git a/src/libthread/getpid.c b/src/libthread/getpid.c
new file mode 100644
index 0000000..da03bd3
--- /dev/null
+++ b/src/libthread/getpid.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <unistd.h>
+
+int
+_threadgetpid(void)
+{
+	return getpid();
+}
diff --git a/src/libthread/id.c b/src/libthread/id.c
new file mode 100644
index 0000000..727798d
--- /dev/null
+++ b/src/libthread/id.c
@@ -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; i++)
+		if(!(privmask&(1<<i))){
+			privmask |= 1<<i;
+			unlock(&privlock);
+			return i;
+		}
+	unlock(&privlock);
+	return -1;
+}
+
+void
+tprivfree(int i)
+{
+	if(i < 0 || i >= NPRIV)
+		abort();
+	lock(&privlock);
+	privmask &= ~(1<<i);
+}
+
+void**
+tprivaddr(int i)
+{
+	return &_threadgetproc()->thread->udata[i];
+}
diff --git a/src/libthread/iocall.c b/src/libthread/iocall.c
new file mode 100644
index 0000000..d9cf9d0
--- /dev/null
+++ b/src/libthread/iocall.c
@@ -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
new file mode 100644
index 0000000..fbaabb7
--- /dev/null
+++ b/src/libthread/ioclose.c
@@ -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
new file mode 100644
index 0000000..8171156
--- /dev/null
+++ b/src/libthread/iodial.c
@@ -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
new file mode 100644
index 0000000..efca4ca
--- /dev/null
+++ b/src/libthread/ioopen.c
@@ -0,0 +1,20 @@
+#include <unistd.h>
+#include <fcntl.h>
+#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
new file mode 100644
index 0000000..1cada2d
--- /dev/null
+++ b/src/libthread/ioproc.3
@@ -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 <u.h>
+#include <libc.h>
+#include <thread.h>
+.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
new file mode 100644
index 0000000..2b2a602
--- /dev/null
+++ b/src/libthread/ioproc.c
@@ -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
new file mode 100644
index 0000000..62b1be0
--- /dev/null
+++ b/src/libthread/ioread.c
@@ -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
new file mode 100644
index 0000000..b843f60
--- /dev/null
+++ b/src/libthread/ioreadn.c
@@ -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
new file mode 100644
index 0000000..5575645
--- /dev/null
+++ b/src/libthread/iosleep.c
@@ -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
new file mode 100644
index 0000000..664a84b
--- /dev/null
+++ b/src/libthread/iowrite.c
@@ -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
new file mode 100644
index 0000000..a9392ea
--- /dev/null
+++ b/src/libthread/kill.c
@@ -0,0 +1,89 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+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
new file mode 100644
index 0000000..0c9f303
--- /dev/null
+++ b/src/libthread/label.h
@@ -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
new file mode 100644
index 0000000..86e7506
--- /dev/null
+++ b/src/libthread/lib.c
@@ -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
new file mode 100644
index 0000000..1acd834
--- /dev/null
+++ b/src/libthread/main.c
@@ -0,0 +1,124 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+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
new file mode 100644
index 0000000..dd74fb6
--- /dev/null
+++ b/src/libthread/memset.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <string.h>
+
+void
+_threadmemset(void *v, int c, int n)
+{
+	memset(v, c, n);
+}
diff --git a/src/libthread/memsetd.c b/src/libthread/memsetd.c
new file mode 100644
index 0000000..ef3f960
--- /dev/null
+++ b/src/libthread/memsetd.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <string.h>
+
+void
+_threaddebugmemset(void *v, int c, int n)
+{
+	memset(v, c, n);
+}
diff --git a/src/libthread/mkfile b/src/libthread/mkfile
new file mode 100644
index 0000000..703f6b0
--- /dev/null
+++ b/src/libthread/mkfile
@@ -0,0 +1,2 @@
+<../libutf/mkfile
+
diff --git a/src/libthread/note.c b/src/libthread/note.c
new file mode 100644
index 0000000..b7f4b13
--- /dev/null
+++ b/src/libthread/note.c
@@ -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; i<NFN; i++)
+		if(onnote[i]==from){
+			onnote[i] = to;
+			onnotepid[i] = topid;
+			break;
+		}
+	unlock(&onnotelock);
+	return i<NFN;
+}
+
+static void
+delayednotes(Proc *p, void *v)
+{
+	int i;
+	Note *n;
+	int (*fn)(void*, char*);
+
+	if(!p->pending)
+		return;
+
+	p->pending = 0;
+	for(n=notes; n<enotes; n++){
+		if(n->proc == p){
+			for(i=0; i<NFN; i++){
+				if(onnotepid[i]!=p->pid || (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; n<enotes; n++)
+		if(canlock(&n->inuse))
+			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
new file mode 100644
index 0000000..4029652
--- /dev/null
+++ b/src/libthread/proctab.c
@@ -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
new file mode 100644
index 0000000..9b78e63
--- /dev/null
+++ b/src/libthread/ref.c
@@ -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
new file mode 100644
index 0000000..62b825b
--- /dev/null
+++ b/src/libthread/rendez.c
@@ -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; i<nelem(_threadrgrp.hash); i++){
+		l = &_threadrgrp.hash[i];
+		for(t=*l; t; t=*l){
+			if(t->rendbreak){
+				*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
new file mode 100644
index 0000000..fdc72de
--- /dev/null
+++ b/src/libthread/rpm.spec
@@ -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 <rsc@post.harvard.edu>
+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
new file mode 100644
index 0000000..8609833
--- /dev/null
+++ b/src/libthread/sched.c
@@ -0,0 +1,192 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+//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
new file mode 100644
index 0000000..9ba8682
--- /dev/null
+++ b/src/libthread/texec.c
@@ -0,0 +1,34 @@
+#include <lib9.h>
+#include <thread.h>
+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
new file mode 100644
index 0000000..3009ac8
--- /dev/null
+++ b/src/libthread/thread.3
@@ -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 <u.h>
+#include <libc.h>
+#include <thread.h>
+.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
new file mode 100644
index 0000000..10aac28
--- /dev/null
+++ b/src/libthread/thread.h
@@ -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
new file mode 100644
index 0000000..24d9b21
--- /dev/null
+++ b/src/libthread/threadimpl.h
@@ -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 <assert.h>
+#include <lib9.h>
+#include <thread.h>
+#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
new file mode 100755
index 0000000..52d6f05
--- /dev/null
+++ b/src/libthread/tprimes
Binary files differ
diff --git a/src/libthread/tprimes.c b/src/libthread/tprimes.c
new file mode 100644
index 0000000..9426bdf
--- /dev/null
+++ b/src/libthread/tprimes.c
@@ -0,0 +1,62 @@
+#include <lib9.h>
+#include <thread.h>
+
+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);
+}