tInitial revision - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 76193d7cb0457807b2f0b95f909ab5de19480cd7
 (DIR) parent ed7c8e8d02c02bdbff1e88a6d8d1419f39af48ad
 (HTM) Author: rsc <devnull@localhost>
       Date:   Tue, 30 Sep 2003 17:47:42 +0000
       
       Initial revision
       
       Diffstat:
         A man/man7/regexp9.7                  |     150 +++++++++++++++++++++++++++++++
         A man/man7/utf.7                      |      91 +++++++++++++++++++++++++++++++
         A src/cmd/mk/LICENSE                  |     258 +++++++++++++++++++++++++++++++
         A src/cmd/mk/Make.FreeBSD-386         |       7 +++++++
         A src/cmd/mk/Make.HP-UX-9000          |       6 ++++++
         A src/cmd/mk/Make.Linux-386           |       7 +++++++
         A src/cmd/mk/Make.OSF1-alpha          |       6 ++++++
         A src/cmd/mk/Make.SunOS-sun4u         |       2 ++
         A src/cmd/mk/Make.SunOS-sun4u-cc      |       6 ++++++
         A src/cmd/mk/Make.SunOS-sun4u-gcc     |       6 ++++++
         A src/cmd/mk/Makefile                 |     117 +++++++++++++++++++++++++++++++
         A src/cmd/mk/Makefile.MID             |      45 +++++++++++++++++++++++++++++++
         A src/cmd/mk/arc.c                    |      52 +++++++++++++++++++++++++++++++
         A src/cmd/mk/archive.c                |     180 +++++++++++++++++++++++++++++++
         A src/cmd/mk/bundle.ports             |      46 +++++++++++++++++++++++++++++++
         A src/cmd/mk/env.c                    |     149 +++++++++++++++++++++++++++++++
         A src/cmd/mk/file.c                   |      90 +++++++++++++++++++++++++++++++
         A src/cmd/mk/fns.h                    |      84 +++++++++++++++++++++++++++++++
         A src/cmd/mk/graph.c                  |     279 +++++++++++++++++++++++++++++++
         A src/cmd/mk/lex.c                    |     147 +++++++++++++++++++++++++++++++
         A src/cmd/mk/main.c                   |     285 +++++++++++++++++++++++++++++++
         A src/cmd/mk/match.c                  |      49 +++++++++++++++++++++++++++++++
         A src/cmd/mk/mk.1                     |     665 +++++++++++++++++++++++++++++++
         A src/cmd/mk/mk.c                     |     226 +++++++++++++++++++++++++++++++
         A src/cmd/mk/mk.h                     |     203 +++++++++++++++++++++++++++++++
         A src/cmd/mk/mkfile                   |       9 +++++++++
         A src/cmd/mk/mkfile.test              |       5 +++++
         A src/cmd/mk/parse.c                  |     307 +++++++++++++++++++++++++++++++
         A src/cmd/mk/rc.c                     |     175 +++++++++++++++++++++++++++++++
         A src/cmd/mk/recipe.c                 |     117 +++++++++++++++++++++++++++++++
         A src/cmd/mk/rpm.spec                 |      29 +++++++++++++++++++++++++++++
         A src/cmd/mk/rule.c                   |     107 +++++++++++++++++++++++++++++++
         A src/cmd/mk/run.c                    |     296 +++++++++++++++++++++++++++++++
         A src/cmd/mk/sh.c                     |     189 +++++++++++++++++++++++++++++++
         A src/cmd/mk/shprint.c                |     123 +++++++++++++++++++++++++++++++
         A src/cmd/mk/symtab.c                 |      95 ++++++++++++++++++++++++++++++
         A src/cmd/mk/unix.c                   |     306 +++++++++++++++++++++++++++++++
         A src/cmd/mk/var.c                    |      41 +++++++++++++++++++++++++++++++
         A src/cmd/mk/varsub.c                 |     256 +++++++++++++++++++++++++++++++
         A src/cmd/mk/word.c                   |     180 +++++++++++++++++++++++++++++++
         A src/cmd/sam/LICENSE                 |     258 +++++++++++++++++++++++++++++++
         A src/cmd/sam/Makefile                |      18 ++++++++++++++++++
         A src/cmd/sam/address.c               |     240 +++++++++++++++++++++++++++++++
         A src/cmd/sam/buff.c                  |     302 +++++++++++++++++++++++++++++++
         A src/cmd/sam/cmd.c                   |     594 +++++++++++++++++++++++++++++++
         A src/cmd/sam/disk.c                  |     122 +++++++++++++++++++++++++++++++
         A src/cmd/sam/error.c                 |     144 +++++++++++++++++++++++++++++++
         A src/cmd/sam/errors.h                |      65 +++++++++++++++++++++++++++++++
         A src/cmd/sam/file.c                  |     631 +++++++++++++++++++++++++++++++
         A src/cmd/sam/io.c                    |     262 +++++++++++++++++++++++++++++++
         A src/cmd/sam/list.c                  |      47 +++++++++++++++++++++++++++++++
         A src/cmd/sam/mesg.c                  |     821 ++++++++++++++++++++++++++++++
         A src/cmd/sam/mesg.h                  |     131 +++++++++++++++++++++++++++++++
         A src/cmd/sam/mkfile                  |      40 +++++++++++++++++++++++++++++++
         A src/cmd/sam/moveto.c                |     173 +++++++++++++++++++++++++++++++
         A src/cmd/sam/multi.c                 |     123 +++++++++++++++++++++++++++++++
         A src/cmd/sam/parse.h                 |      68 +++++++++++++++++++++++++++++++
         A src/cmd/sam/plan9.c                 |     185 ++++++++++++++++++++++++++++++
         A src/cmd/sam/plumb.c                 |       9 +++++++++
         A src/cmd/sam/rasp.c                  |     325 +++++++++++++++++++++++++++++++
         A src/cmd/sam/regexp.c                |     801 ++++++++++++++++++++++++++++++
         A src/cmd/sam/sam                     |       0 
         A src/cmd/sam/sam.c                   |     739 ++++++++++++++++++++++++++++++
         A src/cmd/sam/sam.h                   |     407 +++++++++++++++++++++++++++++++
         A src/cmd/sam/shell.c                 |     152 +++++++++++++++++++++++++++++++
         A src/cmd/sam/unix.c                  |     272 +++++++++++++++++++++++++++++++
         A src/cmd/sam/xec.c                   |     508 +++++++++++++++++++++++++++++++
         A src/libdraw/BOT                     |       0 
         A src/libdraw/Make.Darwin-PowerMacin… |       6 ++++++
         A src/libdraw/Make.FreeBSD-386        |       7 +++++++
         A src/libdraw/Make.HP-UX-9000         |       6 ++++++
         A src/libdraw/Make.Linux-386          |       7 +++++++
         A src/libdraw/Make.NetBSD-386         |       7 +++++++
         A src/libdraw/Make.OSF1-alpha         |       6 ++++++
         A src/libdraw/Make.SunOS-sun4u        |       2 ++
         A src/libdraw/Make.SunOS-sun4u-cc     |       6 ++++++
         A src/libdraw/Make.SunOS-sun4u-gcc    |       6 ++++++
         A src/libdraw/Makefile                |     194 ++++++++++++++++++++++++++++++
         A src/libdraw/Makefile.MID            |     123 +++++++++++++++++++++++++++++++
         A src/libdraw/alloc.c                 |     237 +++++++++++++++++++++++++++++++
         A src/libdraw/arith.c                 |     206 +++++++++++++++++++++++++++++++
         A src/libdraw/buildfont.c             |     141 +++++++++++++++++++++++++++++++
         A src/libdraw/bytesperline.c          |      34 +++++++++++++++++++++++++++++++
         A src/libdraw/chan.c                  |      77 +++++++++++++++++++++++++++++++
         A src/libdraw/creadimage.c            |     113 +++++++++++++++++++++++++++++++
         A src/libdraw/cursor.h                |       7 +++++++
         A src/libdraw/devdraw.c               |    1587 +++++++++++++++++++++++++++++++
         A src/libdraw/draw.h                  |     520 +++++++++++++++++++++++++++++++
         A src/libdraw/ellipse.c               |      82 +++++++++++++++++++++++++++++++
         A src/libdraw/emenuhit.c              |     271 +++++++++++++++++++++++++++++++
         A src/libdraw/event.c                 |     486 +++++++++++++++++++++++++++++++
         A src/libdraw/event.h                 |      63 +++++++++++++++++++++++++++++++
         A src/libdraw/font.c                  |     401 +++++++++++++++++++++++++++++++
         A src/libdraw/getsubfont.c            |      36 +++++++++++++++++++++++++++++++
         A src/libdraw/init.c                  |     203 +++++++++++++++++++++++++++++++
         A src/libdraw/keyboard.c              |     102 +++++++++++++++++++++++++++++++
         A src/libdraw/keyboard.h              |      36 +++++++++++++++++++++++++++++++
         A src/libdraw/libdraw.x               |       1 +
         A src/libdraw/md-alloc.c              |     200 +++++++++++++++++++++++++++++++
         A src/libdraw/md-arc.c                |     116 ++++++++++++++++++++++++++++++
         A src/libdraw/md-arctest.c            |      61 +++++++++++++++++++++++++++++++
         A src/libdraw/md-cload.c              |      68 +++++++++++++++++++++++++++++++
         A src/libdraw/md-cmap.c               |     320 +++++++++++++++++++++++++++++++
         A src/libdraw/md-cread.c              |      96 +++++++++++++++++++++++++++++++
         A src/libdraw/md-defont.c             |      68 +++++++++++++++++++++++++++++++
         A src/libdraw/md-draw.c               |    2487 +++++++++++++++++++++++++++++++
         A src/libdraw/md-drawtest.c           |    1004 +++++++++++++++++++++++++++++++
         A src/libdraw/md-ellipse.c            |     247 +++++++++++++++++++++++++++++++
         A src/libdraw/md-fillpoly.c           |     524 +++++++++++++++++++++++++++++++
         A src/libdraw/md-hwdraw.c             |      12 ++++++++++++
         A src/libdraw/md-iprint.c             |      12 ++++++++++++
         A src/libdraw/md-line.c               |     484 +++++++++++++++++++++++++++++++
         A src/libdraw/md-load.c               |      72 +++++++++++++++++++++++++++++++
         A src/libdraw/md-mkcmap.c             |      79 +++++++++++++++++++++++++++++++
         A src/libdraw/md-openmemsubfont.c     |      53 ++++++++++++++++++++++++++++++
         A src/libdraw/md-poly.c               |      23 +++++++++++++++++++++++
         A src/libdraw/md-read.c               |     111 ++++++++++++++++++++++++++++++
         A src/libdraw/md-string.c             |      66 +++++++++++++++++++++++++++++++
         A src/libdraw/md-subfont.c            |      34 +++++++++++++++++++++++++++++++
         A src/libdraw/md-unload.c             |      25 +++++++++++++++++++++++++
         A src/libdraw/md-write.c              |     183 +++++++++++++++++++++++++++++++
         A src/libdraw/memdraw.h               |     209 +++++++++++++++++++++++++++++++
         A src/libdraw/memlayer.h              |      48 +++++++++++++++++++++++++++++++
         A src/libdraw/menuhit.c               |     277 +++++++++++++++++++++++++++++++
         A src/libdraw/mkfile                  |       1 +
         A src/libdraw/ml-draw.c               |     192 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-lalloc.c             |      79 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-layerop.c            |     112 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-ldelete.c            |      67 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-lhide.c              |      67 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-line.c               |     122 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-load.c               |      55 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-lorigin.c            |     107 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-lsetrefresh.c        |      35 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-ltofront.c           |      80 +++++++++++++++++++++++++++++++
         A src/libdraw/ml-ltorear.c            |      69 ++++++++++++++++++++++++++++++
         A src/libdraw/ml-unload.c             |      52 +++++++++++++++++++++++++++++++
         A src/libdraw/mouse.c                 |     139 ++++++++++++++++++++++++++++++
         A src/libdraw/mouse.h                 |      44 +++++++++++++++++++++++++++++++
         A src/libdraw/openfont.c              |      32 +++++++++++++++++++++++++++++++
         A src/libdraw/readcolmap.c            |      49 +++++++++++++++++++++++++++++++
         A src/libdraw/readimage.c             |     118 +++++++++++++++++++++++++++++++
         A src/libdraw/readsubfont.c           |      58 ++++++++++++++++++++++++++++++
         A src/libdraw/string.c                |     137 +++++++++++++++++++++++++++++++
         A src/libdraw/stringwidth.c           |      97 ++++++++++++++++++++++++++++++
         A src/libdraw/subfont.c               |      28 ++++++++++++++++++++++++++++
         A src/libdraw/subfontcache.c          |      39 +++++++++++++++++++++++++++++++
         A src/libdraw/subfontname.c           |      44 +++++++++++++++++++++++++++++++
         A src/libdraw/unix.c                  |      16 ++++++++++++++++
         A src/libdraw/unloadimage.c           |      53 ++++++++++++++++++++++++++++++
         A src/libdraw/writecolmap.c           |      35 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-alloc.c             |     120 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-cload.c             |      19 +++++++++++++++++++
         A src/libdraw/x11-draw.c              |     143 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-event.c             |     136 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-fill.c              |      57 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-get.c               |     110 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-inc.h               |      31 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-init.c              |     584 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-itrans.c            |     258 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-keyboard.c          |      71 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-load.c              |      19 +++++++++++++++++++
         A src/libdraw/x11-memdraw.h           |      93 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-mouse.c             |     107 +++++++++++++++++++++++++++++++
         A src/libdraw/x11-pixelbits.c         |      17 +++++++++++++++++
         A src/libdraw/x11-unload.c            |      16 ++++++++++++++++
         A src/libthread/386.c                 |      21 +++++++++++++++++++++
         A src/libthread/FreeBSD-386.s         |      18 ++++++++++++++++++
         A src/libthread/LICENSE               |     258 +++++++++++++++++++++++++++++++
         A src/libthread/Make.Darwin-PowerMac… |       6 ++++++
         A src/libthread/Make.FreeBSD-386      |       7 +++++++
         A src/libthread/Make.HP-UX-9000       |       6 ++++++
         A src/libthread/Make.Linux-386        |       7 +++++++
         A src/libthread/Make.NetBSD-386       |       7 +++++++
         A src/libthread/Make.OSF1-alpha       |       6 ++++++
         A src/libthread/Make.SunOS-sun4u      |       2 ++
         A src/libthread/Make.SunOS-sun4u-cc   |       6 ++++++
         A src/libthread/Make.SunOS-sun4u-gcc  |       6 ++++++
         A src/libthread/Makefile              |     125 +++++++++++++++++++++++++++++++
         A src/libthread/Makefile.MID          |      54 +++++++++++++++++++++++++++++++
         A src/libthread/NOTICE                |      19 +++++++++++++++++++
         A src/libthread/README                |      19 +++++++++++++++++++
         A src/libthread/asm-FreeBSD-386.s     |      49 +++++++++++++++++++++++++++++++
         A src/libthread/asm-Linux-386.s       |       1 +
         A src/libthread/bundle.ports          |      42 +++++++++++++++++++++++++++++++
         A src/libthread/channel.c             |     485 +++++++++++++++++++++++++++++++
         A src/libthread/chanprint.c           |      18 ++++++++++++++++++
         A src/libthread/create.c              |     182 +++++++++++++++++++++++++++++++
         A src/libthread/debug.c               |      48 +++++++++++++++++++++++++++++++
         A src/libthread/exec-unix.c           |     124 +++++++++++++++++++++++++++++++
         A src/libthread/exec.c                |      77 +++++++++++++++++++++++++++++++
         A src/libthread/exit.c                |      63 +++++++++++++++++++++++++++++++
         A src/libthread/getpid.c              |       8 ++++++++
         A src/libthread/id.c                  |     135 +++++++++++++++++++++++++++++++
         A src/libthread/iocall.c              |      49 +++++++++++++++++++++++++++++++
         A src/libthread/ioclose.c             |      16 ++++++++++++++++
         A src/libthread/iodial.c              |      21 +++++++++++++++++++++
         A src/libthread/ioopen.c              |      20 ++++++++++++++++++++
         A src/libthread/ioproc.3              |     179 +++++++++++++++++++++++++++++++
         A src/libthread/ioproc.c              |      74 +++++++++++++++++++++++++++++++
         A src/libthread/ioread.c              |      20 ++++++++++++++++++++
         A src/libthread/ioreadn.c             |      21 +++++++++++++++++++++
         A src/libthread/iosleep.c             |      16 ++++++++++++++++
         A src/libthread/iowrite.c             |      21 +++++++++++++++++++++
         A src/libthread/kill.c                |      89 +++++++++++++++++++++++++++++++
         A src/libthread/label.h               |      24 ++++++++++++++++++++++++
         A src/libthread/lib.c                 |      35 +++++++++++++++++++++++++++++++
         A src/libthread/main.c                |     124 +++++++++++++++++++++++++++++++
         A src/libthread/memset.c              |       8 ++++++++
         A src/libthread/memsetd.c             |       8 ++++++++
         A src/libthread/mkfile                |       2 ++
         A src/libthread/note.c                |     143 +++++++++++++++++++++++++++++++
         A src/libthread/proctab.c             |      64 +++++++++++++++++++++++++++++++
         A src/libthread/ref.c                 |      13 +++++++++++++
         A src/libthread/rendez.c              |     104 +++++++++++++++++++++++++++++++
         A src/libthread/rpm.spec              |      26 ++++++++++++++++++++++++++
         A src/libthread/sched.c               |     192 +++++++++++++++++++++++++++++++
         A src/libthread/texec.c               |      34 +++++++++++++++++++++++++++++++
         A src/libthread/thread.3              |     576 +++++++++++++++++++++++++++++++
         A src/libthread/thread.h              |     132 +++++++++++++++++++++++++++++++
         A src/libthread/threadimpl.h          |     219 +++++++++++++++++++++++++++++++
         A src/libthread/tprimes               |       0 
         A src/libthread/tprimes.c             |      62 +++++++++++++++++++++++++++++++
       
       223 files changed, 32479 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/man/man7/regexp9.7 b/man/man7/regexp9.7
       t@@ -0,0 +1,150 @@
       +.TH REGEXP9 7
       +.de EX
       +.nf
       +.ft B
       +..
       +.de EE
       +.fi
       +.ft R
       +..
       +.de LR
       +.if t .BR \\$1 \\$2
       +.if n .RB ` \\$1 '\\$2
       +..
       +.de L
       +.nh
       +.if t .B \\$1
       +.if n .RB ` \\$1 '
       +..
       +.SH NAME
       +regexp9 \- Plan 9 regular expression notation
       +.SH DESCRIPTION
       +This manual page describes the regular expression
       +syntax used by the Plan 9 regular expression library
       +.IR regexp9 (3).
       +It is the form used by
       +.IR egrep (1)
       +before
       +.I egrep
       +got complicated.
       +.PP
       +A 
       +.I "regular expression"
       +specifies
       +a set of strings of characters.
       +A member of this set of strings is said to be
       +.I matched
       +by the regular expression.  In many applications
       +a delimiter character, commonly
       +.LR / ,
       +bounds a regular expression.
       +In the following specification for regular expressions
       +the word `character' means any character (rune) but newline.
       +.PP
       +The syntax for a regular expression
       +.B e0
       +is
       +.IP
       +.EX
       +e3:  literal | charclass | '.' | '^' | '$' | '(' e0 ')'
       +
       +e2:  e3
       +  |  e2 REP
       +
       +REP: '*' | '+' | '?'
       +
       +e1:  e2
       +  |  e1 e2
       +
       +e0:  e1
       +  |  e0 '|' e1
       +.EE
       +.PP
       +A
       +.B literal
       +is any non-metacharacter, or a metacharacter
       +(one of
       +.BR .*+?[]()|\e^$ ),
       +or the delimiter
       +preceded by 
       +.LR \e .
       +.PP
       +A
       +.B charclass
       +is a nonempty string
       +.I s
       +bracketed
       +.BI [ \|s\| ]
       +(or
       +.BI [^ s\| ]\fR);
       +it matches any character in (or not in)
       +.IR s .
       +A negated character class never
       +matches newline.
       +A substring 
       +.IB a - b\f1,
       +with
       +.I a
       +and
       +.I b
       +in ascending
       +order, stands for the inclusive
       +range of
       +characters between
       +.I a
       +and
       +.IR b .
       +In 
       +.IR s ,
       +the metacharacters
       +.LR - ,
       +.LR ] ,
       +an initial
       +.LR ^ ,
       +and the regular expression delimiter
       +must be preceded by a
       +.LR \e ;
       +other metacharacters 
       +have no special meaning and
       +may appear unescaped.
       +.PP
       +A 
       +.L .
       +matches any character.
       +.PP
       +A
       +.L ^
       +matches the beginning of a line;
       +.L $
       +matches the end of the line.
       +.PP
       +The 
       +.B REP
       +operators match zero or more
       +.RB ( * ),
       +one or more
       +.RB ( + ),
       +zero or one
       +.RB ( ? ),
       +instances respectively of the preceding regular expression 
       +.BR e2 .
       +.PP
       +A concatenated regular expression,
       +.BR "e1\|e2" ,
       +matches a match to 
       +.B e1
       +followed by a match to
       +.BR e2 .
       +.PP
       +An alternative regular expression,
       +.BR "e0\||\|e1" ,
       +matches either a match to
       +.B e0
       +or a match to
       +.BR e1 .
       +.PP
       +A match to any part of a regular expression
       +extends as far as possible without preventing
       +a match to the remainder of the regular expression.
       +.SH "SEE ALSO"
       +.IR regexp9 (3)
 (DIR) diff --git a/man/man7/utf.7 b/man/man7/utf.7
       t@@ -0,0 +1,91 @@
       +.TH UTF 7
       +.SH NAME
       +UTF, Unicode, ASCII, rune \- character set and format
       +.SH DESCRIPTION
       +The Plan 9 character set and representation are
       +based on the Unicode Standard and on the ISO multibyte
       +.SM UTF-8
       +encoding (Universal Character
       +Set Transformation Format, 8 bits wide).
       +The Unicode Standard represents its characters in 16
       +bits;
       +.SM UTF-8
       +represents such
       +values in an 8-bit byte stream.
       +Throughout this manual,
       +.SM UTF-8
       +is shortened to
       +.SM UTF.
       +.PP
       +In Plan 9, a
       +.I rune
       +is a 16-bit quantity representing a Unicode character.
       +Internally, programs may store characters as runes.
       +However, any external manifestation of textual information,
       +in files or at the interface between programs, uses a
       +machine-independent, byte-stream encoding called
       +.SM UTF.
       +.PP
       +.SM UTF
       +is designed so the 7-bit
       +.SM ASCII
       +set (values hexadecimal 00 to 7F),
       +appear only as themselves
       +in the encoding.
       +Runes with values above 7F appear as sequences of two or more
       +bytes with values only from 80 to FF.
       +.PP
       +The
       +.SM UTF
       +encoding of the Unicode Standard is backward compatible with
       +.SM ASCII\c
       +:
       +programs presented only with
       +.SM ASCII
       +work on Plan 9
       +even if not written to deal with
       +.SM UTF,
       +as do
       +programs that deal with uninterpreted byte streams.
       +However, programs that perform semantic processing on
       +.SM ASCII
       +graphic
       +characters must convert from
       +.SM UTF
       +to runes
       +in order to work properly with non-\c
       +.SM ASCII
       +input.
       +See
       +.IR rune (2).
       +.PP
       +Letting numbers be binary,
       +a rune x is converted to a multibyte
       +.SM UTF
       +sequence
       +as follows:
       +.PP
       +01.   x in [00000000.0bbbbbbb] → 0bbbbbbb
       +.br
       +10.   x in [00000bbb.bbbbbbbb] → 110bbbbb, 10bbbbbb
       +.br
       +11.   x in [bbbbbbbb.bbbbbbbb] → 1110bbbb, 10bbbbbb, 10bbbbbb
       +.br
       +.PP
       +Conversion 01 provides a one-byte sequence that spans the
       +.SM ASCII
       +character set in a compatible way.
       +Conversions 10 and 11 represent higher-valued characters
       +as sequences of two or three bytes with the high bit set.
       +Plan 9 does not support the 4, 5, and 6 byte sequences proposed by X-Open.
       +When there are multiple ways to encode a value, for example rune 0,
       +the shortest encoding is used.
       +.PP
       +In the inverse mapping,
       +any sequence except those described above
       +is incorrect and is converted to rune hexadecimal 0080.
       +.SH "SEE ALSO"
       +.IR ascii (1),
       +.IR tcs (1),
       +.IR rune (3),
       +.IR "The Unicode Standard" .
 (DIR) diff --git a/src/cmd/mk/LICENSE b/src/cmd/mk/LICENSE
       t@@ -0,0 +1,258 @@
       +The Plan 9 software is provided under the terms of the
       +Lucent Public License, Version 1.02, reproduced below,
       +with the following exceptions:
       +
       +1. No right is granted to create derivative works of or
       +   to redistribute (other than with the Plan 9 Operating System)
       +   the screen imprinter fonts identified in subdirectory
       +   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
       +   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
       +   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
       +   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
       +
       +2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
       +   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
       +
       +3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
       +   covered by the Aladdin Free Public License, reproduced in the file
       +   /LICENSE.afpl.
       +
       +===================================================================
       +
       +Lucent Public License Version 1.02
       +
       +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
       +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
       +PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
       +
       +1. DEFINITIONS
       +
       +"Contribution" means:
       +
       +  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
       +     Program, and
       +  b. in the case of each Contributor,
       +
       +     i. changes to the Program, and
       +    ii. additions to the Program;
       +
       +    where such changes and/or additions to the Program were added to the
       +    Program by such Contributor itself or anyone acting on such
       +    Contributor's behalf, and the Contributor explicitly consents, in
       +    accordance with Section 3C, to characterization of the changes and/or
       +    additions as Contributions.
       +
       +"Contributor" means LUCENT and any other entity that has Contributed a
       +Contribution to the Program.
       +
       +"Distributor" means a Recipient that distributes the Program,
       +modifications to the Program, or any part thereof.
       +
       +"Licensed Patents" mean patent claims licensable by a Contributor
       +which are necessarily infringed by the use or sale of its Contribution
       +alone or when combined with the Program.
       +
       +"Original Program" means the original version of the software
       +accompanying this Agreement as released by LUCENT, including source
       +code, object code and documentation, if any.
       +
       +"Program" means the Original Program and Contributions or any part
       +thereof
       +
       +"Recipient" means anyone who receives the Program under this
       +Agreement, including all Contributors.
       +
       +2. GRANT OF RIGHTS
       +
       + a. Subject to the terms of this Agreement, each Contributor hereby
       +    grants Recipient a non-exclusive, worldwide, royalty-free copyright
       +    license to reproduce, prepare derivative works of, publicly display,
       +    publicly perform, distribute and sublicense the Contribution of such
       +    Contributor, if any, and such derivative works, in source code and
       +    object code form.
       +    
       + b. Subject to the terms of this Agreement, each Contributor hereby
       +    grants Recipient a non-exclusive, worldwide, royalty-free patent
       +    license under Licensed Patents to make, use, sell, offer to sell,
       +    import and otherwise transfer the Contribution of such Contributor, if
       +    any, in source code and object code form. The patent license granted
       +    by a Contributor shall also apply to the combination of the
       +    Contribution of that Contributor and the Program if, at the time the
       +    Contribution is added by the Contributor, such addition of the
       +    Contribution causes such combination to be covered by the Licensed
       +    Patents. The patent license granted by a Contributor shall not apply
       +    to (i) any other combinations which include the Contribution, nor to
       +    (ii) Contributions of other Contributors. No hardware per se is
       +    licensed hereunder.
       +    
       + c. Recipient understands that although each Contributor grants the
       +    licenses to its Contributions set forth herein, no assurances are
       +    provided by any Contributor that the Program does not infringe the
       +    patent or other intellectual property rights of any other entity. Each
       +    Contributor disclaims any liability to Recipient for claims brought by
       +    any other entity based on infringement of intellectual property rights
       +    or otherwise. As a condition to exercising the rights and licenses
       +    granted hereunder, each Recipient hereby assumes sole responsibility
       +    to secure any other intellectual property rights needed, if any. For
       +    example, if a third party patent license is required to allow
       +    Recipient to distribute the Program, it is Recipient's responsibility
       +    to acquire that license before distributing the Program.
       +
       + d. Each Contributor represents that to its knowledge it has sufficient
       +    copyright rights in its Contribution, if any, to grant the copyright
       +    license set forth in this Agreement.
       +
       +3. REQUIREMENTS
       +
       +A. Distributor may choose to distribute the Program in any form under
       +this Agreement or under its own license agreement, provided that:
       +
       + a. it complies with the terms and conditions of this Agreement;
       +
       + b. if the Program is distributed in source code or other tangible
       +    form, a copy of this Agreement or Distributor's own license agreement
       +    is included with each copy of the Program; and
       +
       + c. if distributed under Distributor's own license agreement, such
       +    license agreement:
       +
       +      i. effectively disclaims on behalf of all Contributors all warranties
       +         and conditions, express and implied, including warranties or
       +         conditions of title and non-infringement, and implied warranties or
       +         conditions of merchantability and fitness for a particular purpose;
       +     ii. effectively excludes on behalf of all Contributors all liability
       +         for damages, including direct, indirect, special, incidental and
       +         consequential damages, such as lost profits; and
       +    iii. states that any provisions which differ from this Agreement are
       +         offered by that Contributor alone and not by any other party.
       +
       +B. Each Distributor must include the following in a conspicuous
       +   location in the Program:
       +
       +   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
       +   Reserved.
       +
       +C. In addition, each Contributor must identify itself as the
       +originator of its Contribution in a manner that reasonably allows
       +subsequent Recipients to identify the originator of the Contribution.
       +Also, each Contributor must agree that the additions and/or changes
       +are intended to be a Contribution. Once a Contribution is contributed,
       +it may not thereafter be revoked.
       +
       +4. COMMERCIAL DISTRIBUTION
       +
       +Commercial distributors of software may accept certain
       +responsibilities with respect to end users, business partners and the
       +like. While this license is intended to facilitate the commercial use
       +of the Program, the Distributor who includes the Program in a
       +commercial product offering should do so in a manner which does not
       +create potential liability for Contributors. Therefore, if a
       +Distributor includes the Program in a commercial product offering,
       +such Distributor ("Commercial Distributor") hereby agrees to defend
       +and indemnify every Contributor ("Indemnified Contributor") against
       +any losses, damages and costs (collectively"Losses") arising from
       +claims, lawsuits and other legal actions brought by a third party
       +against the Indemnified Contributor to the extent caused by the acts
       +or omissions of such Commercial Distributor in connection with its
       +distribution of the Program in a commercial product offering. The
       +obligations in this section do not apply to any claims or Losses
       +relating to any actual or alleged intellectual property infringement.
       +In order to qualify, an Indemnified Contributor must: a) promptly
       +notify the Commercial Distributor in writing of such claim, and b)
       +allow the Commercial Distributor to control, and cooperate with the
       +Commercial Distributor in, the defense and any related settlement
       +negotiations. The Indemnified Contributor may participate in any such
       +claim at its own expense.
       +
       +For example, a Distributor might include the Program in a commercial
       +product offering, Product X. That Distributor is then a Commercial
       +Distributor. If that Commercial Distributor then makes performance
       +claims, or offers warranties related to Product X, those performance
       +claims and warranties are such Commercial Distributor's responsibility
       +alone. Under this section, the Commercial Distributor would have to
       +defend claims against the Contributors related to those performance
       +claims and warranties, and if a court requires any Contributor to pay
       +any damages as a result, the Commercial Distributor must pay those
       +damages.
       +
       +5. NO WARRANTY
       +
       +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
       +PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       +KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
       +WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
       +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
       +responsible for determining the appropriateness of using and
       +distributing the Program and assumes all risks associated with its
       +exercise of rights under this Agreement, including but not limited to
       +the risks and costs of program errors, compliance with applicable
       +laws, damage to or loss of data, programs or equipment, and
       +unavailability or interruption of operations.
       +
       +6. DISCLAIMER OF LIABILITY
       +
       +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
       +ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
       +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
       +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
       +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
       +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
       +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
       +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
       +
       +7. EXPORT CONTROL
       +
       +Recipient agrees that Recipient alone is responsible for compliance
       +with the United States export administration regulations (and the
       +export control laws and regulation of any other countries).
       +
       +8. GENERAL
       +
       +If any provision of this Agreement is invalid or unenforceable under
       +applicable law, it shall not affect the validity or enforceability of
       +the remainder of the terms of this Agreement, and without further
       +action by the parties hereto, such provision shall be reformed to the
       +minimum extent necessary to make such provision valid and enforceable.
       +
       +If Recipient institutes patent litigation against a Contributor with
       +respect to a patent applicable to software (including a cross-claim or
       +counterclaim in a lawsuit), then any patent licenses granted by that
       +Contributor to such Recipient under this Agreement shall terminate as
       +of the date such litigation is filed. In addition, if Recipient
       +institutes patent litigation against any entity (including a
       +cross-claim or counterclaim in a lawsuit) alleging that the Program
       +itself (excluding combinations of the Program with other software or
       +hardware) infringes such Recipient's patent(s), then such Recipient's
       +rights granted under Section 2(b) shall terminate as of the date such
       +litigation is filed.
       +
       +All Recipient's rights under this Agreement shall terminate if it
       +fails to comply with any of the material terms or conditions of this
       +Agreement and does not cure such failure in a reasonable period of
       +time after becoming aware of such noncompliance. If all Recipient's
       +rights under this Agreement terminate, Recipient agrees to cease use
       +and distribution of the Program as soon as reasonably practicable.
       +However, Recipient's obligations under this Agreement and any licenses
       +granted by Recipient relating to the Program shall continue and
       +survive.
       +
       +LUCENT may publish new versions (including revisions) of this
       +Agreement from time to time. Each new version of the Agreement will be
       +given a distinguishing version number. The Program (including
       +Contributions) may always be distributed subject to the version of the
       +Agreement under which it was received. In addition, after a new
       +version of the Agreement is published, Contributor may elect to
       +distribute the Program (including its Contributions) under the new
       +version. No one other than LUCENT has the right to modify this
       +Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
       +Recipient receives no rights or licenses to the intellectual property
       +of any Contributor under this Agreement, whether expressly, by
       +implication, estoppel or otherwise. All rights in the Program not
       +expressly granted under this Agreement are reserved.
       +
       +This Agreement is governed by the laws of the State of New York and
       +the intellectual property laws of the United States of America. No
       +party to this Agreement will bring a legal action under this Agreement
       +more than one year after the cause of action arose. Each party waives
       +its rights to a jury trial in any resulting litigation.
       +
 (DIR) diff --git a/src/cmd/mk/Make.FreeBSD-386 b/src/cmd/mk/Make.FreeBSD-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/cmd/mk/Make.HP-UX-9000 b/src/cmd/mk/Make.HP-UX-9000
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS=-O -c -Ae -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/cmd/mk/Make.Linux-386 b/src/cmd/mk/Make.Linux-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/cmd/mk/Make.OSF1-alpha b/src/cmd/mk/Make.OSF1-alpha
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS+=-g -c -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/cmd/mk/Make.SunOS-sun4u b/src/cmd/mk/Make.SunOS-sun4u
       t@@ -0,0 +1,2 @@
       +include Make.SunOS-sun4u-$(CC)
       +NAN=nan64.$O
 (DIR) diff --git a/src/cmd/mk/Make.SunOS-sun4u-cc b/src/cmd/mk/Make.SunOS-sun4u-cc
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS+=-g -c -I. -O
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/cmd/mk/Make.SunOS-sun4u-gcc b/src/cmd/mk/Make.SunOS-sun4u-gcc
       t@@ -0,0 +1,6 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/cmd/mk/Makefile b/src/cmd/mk/Makefile
       t@@ -0,0 +1,117 @@
       +
       +# this works in gnu make
       +SYSNAME:=${shell uname}
       +OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
       +
       +# this works in bsd make
       +SYSNAME!=uname
       +OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
       +
       +# the gnu rules will mess up bsd but not vice versa,
       +# hence the gnu rules come first.
       +
       +include Make.$(SYSNAME)-$(OBJTYPE)
       +
       +PREFIX=/usr/local
       +
       +NUKEFILES=
       +
       +TGZFILES=
       +
       +TARG=mk
       +VERSION=2.0
       +PORTPLACE=devel/mk
       +NAME=mk
       +
       +OFILES=\
       +        arc.$O\
       +        archive.$O\
       +        bufblock.$O\
       +        env.$O\
       +        file.$O\
       +        graph.$O\
       +        job.$O\
       +        lex.$O\
       +        main.$O\
       +        match.$O\
       +        mk.$O\
       +        parse.$O\
       +        recipe.$O\
       +        rule.$O\
       +        run.$O\
       +        sh.$O\
       +        shprint.$O\
       +        symtab.$O\
       +        var.$O\
       +        varsub.$O\
       +        word.$O\
       +        unix.$O\
       +
       +HFILES=\
       +        mk.h\
       +        fns.h\
       +
       +all: $(TARG)
       +
       +TGZFILES+=mk.pdf
       +
       +install: $(LIB)
       +        test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
       +        test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
       +        install -m 0755 mk $(PREFIX)/bin/mk
       +        cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
       +        install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
       +        install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
       +
       +
       +$(TARG): $(OFILES)
       +        $(CC) -o $(TARG) $(OFILES) -L$(PREFIX)/lib -lregexp9 -lbio -lfmt -lutf
       +
       +
       +.c.$O:
       +        $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
       +
       +%.$O: %.c
       +        $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
       +
       +
       +$(OFILES): $(HFILES)
       +
       +tgz:
       +        rm -rf $(NAME)-$(VERSION)
       +        mkdir $(NAME)-$(VERSION)
       +        cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
       +        tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
       +        rm -rf $(NAME)-$(VERSION)
       +
       +clean:
       +        rm -f $(OFILES) $(LIB)
       +
       +nuke:
       +        rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
       +
       +rpm:
       +        make tgz
       +        cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
       +        rpm -ba rpm.spec
       +        cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
       +        cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
       +        scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
       +
       +PORTDIR=/usr/ports/$(PORTPLACE)
       +
       +ports:
       +        make tgz
       +        rm -rf $(PORTDIR)
       +        mkdir $(PORTDIR)
       +        cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
       +        cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
       +        (cd $(PORTDIR); make makesum)
       +        (cd $(PORTDIR); make)
       +        (cd $(PORTDIR); /usr/local/bin/portlint)
       +        rm -rf $(PORTDIR)/work
       +        shar `find $(PORTDIR)` > ports.shar
       +        (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
       +        scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
       +
       +.phony: all clean nuke install tgz rpm ports
 (DIR) diff --git a/src/cmd/mk/Makefile.MID b/src/cmd/mk/Makefile.MID
       t@@ -0,0 +1,45 @@
       +TARG=mk
       +VERSION=2.0
       +PORTPLACE=devel/mk
       +NAME=mk
       +
       +OFILES=\
       +        arc.$O\
       +        archive.$O\
       +        bufblock.$O\
       +        env.$O\
       +        file.$O\
       +        graph.$O\
       +        job.$O\
       +        lex.$O\
       +        main.$O\
       +        match.$O\
       +        mk.$O\
       +        parse.$O\
       +        recipe.$O\
       +        rule.$O\
       +        run.$O\
       +        sh.$O\
       +        shprint.$O\
       +        symtab.$O\
       +        var.$O\
       +        varsub.$O\
       +        word.$O\
       +        unix.$O\
       +
       +HFILES=\
       +        mk.h\
       +        fns.h\
       +
       +all: $(TARG)
       +
       +TGZFILES+=mk.pdf
       +
       +install: $(LIB)
       +        test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
       +        test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
       +        install -m 0755 mk $(PREFIX)/bin/mk
       +        cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
       +        install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
       +        install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
       +
 (DIR) diff --git a/src/cmd/mk/arc.c b/src/cmd/mk/arc.c
       t@@ -0,0 +1,52 @@
       +#include        "mk.h"
       +
       +Arc *
       +newarc(Node *n, Rule *r, char *stem, Resub *match)
       +{
       +        Arc *a;
       +
       +        a = (Arc *)Malloc(sizeof(Arc));
       +        a->n = n;
       +        a->r = r;
       +        a->stem = strdup(stem);
       +        rcopy(a->match, match, NREGEXP);
       +        a->next = 0;
       +        a->flag = 0;
       +        a->prog = r->prog;
       +        return(a);
       +}
       +
       +void
       +dumpa(char *s, Arc *a)
       +{
       +        char buf[1024];
       +
       +        Bprint(&bout, "%sArc@%p: n=%p r=%p flag=0x%x stem='%s'",
       +                s, a, a->n, a->r, a->flag, a->stem);
       +        if(a->prog)
       +                Bprint(&bout, " prog='%s'", a->prog);
       +        Bprint(&bout, "\n");
       +
       +        if(a->n){
       +                snprint(buf, sizeof(buf), "%s    ", (*s == ' ')? s:"");
       +                dumpn(buf, a->n);
       +        }
       +}
       +
       +void
       +nrep(void)
       +{
       +        Symtab *sym;
       +        Word *w;
       +
       +        sym = symlook("NREP", S_VAR, 0);
       +        if(sym){
       +                w = (Word *) sym->value;
       +                if (w && w->s && *w->s)
       +                        nreps = atoi(w->s);
       +        }
       +        if(nreps < 1)
       +                nreps = 1;
       +        if(DEBUG(D_GRAPH))
       +                Bprint(&bout, "nreps = %d\n", nreps);
       +}
 (DIR) diff --git a/src/cmd/mk/archive.c b/src/cmd/mk/archive.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/mk/bundle.ports b/src/cmd/mk/bundle.ports
       t@@ -0,0 +1,46 @@
       +--- Makefile ---
       +# New ports collection makefile for: mk
       +# Date Created:                11 Feb 2003
       +# Whom:                        rsc
       +#
       +# THIS LINE NEEDS REPLACING.  IT'S HERE TO GET BY PORTLINT
       +# $FreeBSD: ports/devel/mk/Makefile,v 1.1 2003/02/12 00:51:22 rsc Exp $
       +
       +PORTNAME=        mk
       +PORTVERSION=        2.0
       +CATEGORIES=        devel
       +MASTER_SITES=        http://pdos.lcs.mit.edu/~rsc/software/
       +EXTRACT_SUFX=        .tgz
       +
       +MAINTAINER=        rsc@post.harvard.edu
       +
       +DEPENDS=        ${PORTSDIR}/devel/libutf \
       +        ${PORTSDIR}/devel/libfmt \
       +        ${PORTSDIR}/devel/libbio \
       +        ${PORTSDIR}/devel/libregexp9
       +
       +MAN1=                mk.1
       +USE_REINPLACE=        yes
       +
       +.include <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.
 (DIR) diff --git a/src/cmd/mk/env.c b/src/cmd/mk/env.c
       t@@ -0,0 +1,149 @@
       +#include        "mk.h"
       +
       +enum {
       +        ENVQUANTA=10
       +};
       +
       +Envy        *envy;
       +static int nextv;
       +
       +static char        *myenv[] =
       +{
       +        "target",
       +        "stem",
       +        "prereq",
       +        "pid",
       +        "nproc",
       +        "newprereq",
       +        "alltarget",
       +        "newmember",
       +        "stem0",                /* must be in order from here */
       +        "stem1",
       +        "stem2",
       +        "stem3",
       +        "stem4",
       +        "stem5",
       +        "stem6",
       +        "stem7",
       +        "stem8",
       +        "stem9",
       +        0,
       +};
       +
       +void
       +initenv(void)
       +{
       +        char **p;
       +
       +        for(p = myenv; *p; p++)
       +                symlook(*p, S_INTERNAL, (void *)"");
       +        readenv();                                /* o.s. dependent */
       +}
       +
       +static void
       +envinsert(char *name, Word *value)
       +{
       +        static int envsize;
       +
       +        if (nextv >= envsize) {
       +                envsize += ENVQUANTA;
       +                envy = (Envy *) Realloc((char *) envy, envsize*sizeof(Envy));
       +        }
       +        envy[nextv].name = name;
       +        envy[nextv++].values = value;
       +}
       +
       +static void
       +envupd(char *name, Word *value)
       +{
       +        Envy *e;
       +
       +        for(e = envy; e->name; e++)
       +                if(strcmp(name, e->name) == 0){
       +                        delword(e->values);
       +                        e->values = value;
       +                        return;
       +                }
       +        e->name = name;
       +        e->values = value;
       +        envinsert(0,0);
       +}
       +
       +static void
       +ecopy(Symtab *s)
       +{
       +        char **p;
       +
       +        if(symlook(s->name, S_NOEXPORT, 0))
       +                return;
       +        for(p = myenv; *p; p++)
       +                if(strcmp(*p, s->name) == 0)
       +                        return;
       +        envinsert(s->name, (Word *) s->value);
       +}
       +
       +void
       +execinit(void)
       +{
       +        char **p;
       +
       +        nextv = 0;
       +        for(p = myenv; *p; p++)
       +                envinsert(*p, stow(""));
       +
       +        symtraverse(S_VAR, ecopy);
       +        envinsert(0, 0);
       +}
       +
       +Envy*
       +buildenv(Job *j, int slot)
       +{
       +        char **p, *cp, *qp;
       +        Word *w, *v, **l;
       +        int i;
       +        char buf[256];
       +
       +        envupd("target", wdup(j->t));
       +        if(j->r->attr&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;
       +}
 (DIR) diff --git a/src/cmd/mk/file.c b/src/cmd/mk/file.c
       t@@ -0,0 +1,90 @@
       +#include        "mk.h"
       +
       +/* table-driven version in bootes dump of 12/31/96 */
       +
       +long
       +mtime(char *name)
       +{
       +        return mkmtime(name);
       +}
       +
       +long
       +timeof(char *name, int force)
       +{
       +        Symtab *sym;
       +        long t;
       +
       +        if(utfrune(name, '('))
       +                return atimeof(force, name);        /* archive */
       +
       +        if(force)
       +                return mtime(name);
       +
       +
       +        sym = symlook(name, S_TIME, 0);
       +        if (sym)
       +                return (long) sym->value;                /* uggh */
       +
       +        t = mtime(name);
       +        if(t == 0)
       +                return 0;
       +
       +        symlook(name, S_TIME, (void*)t);                /* install time in cache */
       +        return t;
       +}
       +
       +void
       +touch(char *name)
       +{
       +        Bprint(&bout, "touch(%s)\n", name);
       +        if(nflag)
       +                return;
       +
       +        if(utfrune(name, '('))
       +                atouch(name);                /* archive */
       +        else if(chgtime(name) < 0) {
       +                fprint(2, "%s: %r\n", name);
       +                Exit();
       +        }
       +}
       +
       +void
       +delete(char *name)
       +{
       +        if(utfrune(name, '(') == 0) {                /* file */
       +                if(remove(name) < 0)
       +                        fprint(2, "remove %s: %r\n", name);
       +        } else
       +                fprint(2, "hoon off; mk can'tdelete archive members\n");
       +}
       +
       +void
       +timeinit(char *s)
       +{
       +        long t;
       +        char *cp;
       +        Rune r;
       +        int c, n;
       +
       +        t = time(0);
       +        while (*s) {
       +                cp = s;
       +                do{
       +                        n = chartorune(&r, s);
       +                        if (r == ' ' || r == ',' || r == '\n')
       +                                break;
       +                        s += n;
       +                } while(*s);
       +                c = *s;
       +                *s = 0;
       +                symlook(strdup(cp), S_TIME, (void *)t)->value = (void *)t;
       +                if (c)
       +                        *s++ = c;
       +                while(*s){
       +                        n = chartorune(&r, s);
       +                        if(r != ' ' && r != ',' && r != '\n')
       +                                break;
       +                        s += n;
       +                }
       +        }
       +}
 (DIR) diff --git a/src/cmd/mk/fns.h b/src/cmd/mk/fns.h
       t@@ -0,0 +1,84 @@
       +void        addrule(char*, Word*, char*, Word*, int, int, char*);
       +void        addrules(Word*, Word*, char*, int, int, char*);
       +void        addw(Word*, char*);
       +void        assert(char*, int);
       +int        assline(Biobuf *, Bufblock *);
       +long        atimeof(int,char*);
       +void        atouch(char*);
       +void        bufcpy(Bufblock *, char *, int);
       +Envy        *buildenv(Job*, int);
       +void        catchnotes(void);
       +char         *charin(char *, char *);
       +int        chgtime(char*);
       +void        clrmade(Node*);
       +char        *copyq(char*, Rune, Bufblock*);
       +void        delete(char*);
       +void        delword(Word*);
       +int        dorecipe(Node*);
       +void        dumpa(char*, Arc*);
       +void        dumpj(char*, Job*, int);
       +void        dumpn(char*, Node*);
       +void        dumpr(char*, Rule*);
       +void        dumpv(char*);
       +void        dumpw(char*, Word*);
       +int        escapetoken(Biobuf*, Bufblock*, int, int);
       +void        execinit(void);
       +int        execsh(char*, char*, Bufblock*, Envy*);
       +void        Exit(void);
       +char        *expandquote(char*, Rune, Bufblock*);
       +void        expunge(int, char*);
       +void        freebuf(Bufblock*);
       +void        front(char*);
       +Node        *graph(char*);
       +void        growbuf(Bufblock *);
       +void        initenv(void);
       +void        insert(Bufblock *, int);
       +void        ipop(void);
       +void        ipush(void);
       +void        killchildren(char*);
       +void        *Malloc(int);
       +char        *maketmp(int*);
       +int        match(char*, char*, char*);
       +char *membername(char*, int, char*);
       +void        mk(char*);
       +ulong        mkmtime(char*);
       +long        mtime(char*);
       +Arc        *newarc(Node*, Rule*, char*, Resub*);
       +Bufblock *newbuf(void);
       +Job        *newjob(Rule*, Node*, char*, char**, Word*, Word*, Word*, Word*);
       +Word        *newword(char*);
       +int        nextrune(Biobuf*, int);
       +int        nextslot(void);
       +void        nproc(void);
       +void        nrep(void);
       +int        outofdate(Node*, Arc*, int);
       +void        parse(char*, int, int);
       +int        pipecmd(char*, Envy*, int*);
       +void        prusage(void);
       +void        rcopy(char**, Resub*, int);
       +void        readenv(void);
       +void        *Realloc(void*, int);
       +void        rinsert(Bufblock *, Rune);
       +char        *rulecnt(void);
       +void        run(Job*);
       +void        setvar(char*, void*);
       +char        *shname(char*);
       +void        shprint(char*, Envy*, Bufblock*);
       +Word        *stow(char*);
       +void        subst(char*, char*, char*);
       +void        symdel(char*, int);
       +void        syminit(void);
       +Symtab        *symlook(char*, int, void*);
       +void        symstat(void);
       +void        symtraverse(int, void(*)(Symtab*));
       +void        timeinit(char*);
       +long        timeof(char*, int);
       +void        touch(char*);
       +void        update(int, Node*);
       +void        usage(void);
       +Word        *varsub(char**);
       +int        waitfor(char*);
       +int        waitup(int, int*);
       +Word        *wdup(Word*);
       +int        work(Node*, Node*, Arc*);
       +char        *wtos(Word*, int);
 (DIR) diff --git a/src/cmd/mk/graph.c b/src/cmd/mk/graph.c
       t@@ -0,0 +1,279 @@
       +#include        "mk.h"
       +
       +static Node *applyrules(char *, char *);
       +static void togo(Node *);
       +static int vacuous(Node *);
       +static Node *newnode(char *);
       +static void trace(char *, Arc *);
       +static void cyclechk(Node *);
       +static void ambiguous(Node *);
       +static void attribute(Node *);
       +
       +Node *
       +graph(char *target)
       +{
       +        Node *node;
       +        char *cnt;
       +
       +        cnt = rulecnt();
       +        node = applyrules(target, cnt);
       +        free(cnt);
       +        cyclechk(node);
       +        node->flags |= PROBABLE;        /* make sure it doesn't get deleted */
       +        vacuous(node);
       +        ambiguous(node);
       +        attribute(node);
       +        return(node);
       +}
       +
       +static Node *
       +applyrules(char *target, char *cnt)
       +{
       +        Symtab *sym;
       +        Node *node;
       +        Rule *r;
       +        Arc head, *a = &head;
       +        Word *w;
       +        char stem[NAMEBLOCK], buf[NAMEBLOCK];
       +        Resub rmatch[NREGEXP];
       +
       +/*        print("applyrules(%lux='%s')\n", target, target);*//**/
       +        sym = symlook(target, S_NODE, 0);
       +        if(sym)
       +                return (Node *)(sym->value);
       +        target = strdup(target);
       +        node = newnode(target);
       +        head.n = 0;
       +        head.next = 0;
       +        sym = symlook(target, S_TARGET, 0);
       +        memset((char*)rmatch, 0, sizeof(rmatch));
       +        for(r = sym? (Rule *)(sym->value):0; r; r = r->chain){
       +                if(r->attr&META) continue;
       +                if(strcmp(target, r->target)) continue;
       +                if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue;        /* no effect; ignore */
       +                if(cnt[r->rule] >= nreps) continue;
       +                cnt[r->rule]++;
       +                node->flags |= PROBABLE;
       +
       +/*                if(r->attr&VIR)
       + *                        node->flags |= VIRTUAL;
       + *                if(r->attr&NOREC)
       + *                        node->flags |= NORECIPE;
       + *                if(r->attr&DEL)
       + *                        node->flags |= DELETE;
       + */
       +                if(!r->tail || !r->tail->s || !*r->tail->s) {
       +                        a->next = newarc((Node *)0, r, "", rmatch);
       +                        a = a->next;
       +                } else
       +                        for(w = r->tail; w; w = w->next){
       +                                a->next = newarc(applyrules(w->s, cnt), r, "", rmatch);
       +                                a = a->next;
       +                }
       +                cnt[r->rule]--;
       +                head.n = node;
       +        }
       +        for(r = metarules; r; r = r->next){
       +                if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue;        /* no effect; ignore */
       +                if ((r->attr&NOVIRT) && a != &head && (a->r->attr&VIR))
       +                        continue;
       +                if(r->attr&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;
       +}
 (DIR) diff --git a/src/cmd/mk/lex.c b/src/cmd/mk/lex.c
       t@@ -0,0 +1,147 @@
       +#include        "mk.h"
       +
       +static        int        bquote(Biobuf*, Bufblock*);
       +
       +/*
       + *        Assemble a line skipping blank lines, comments, and eliding
       + *        escaped newlines
       + */
       +int
       +assline(Biobuf *bp, Bufblock *buf)
       +{
       +        int c;
       +        int lastc;
       +
       +        buf->current=buf->start;
       +        while ((c = nextrune(bp, 1)) >= 0){
       +                switch(c)
       +                {
       +                case '\r':                /* consumes CRs for Win95 */
       +                        continue;
       +                case '\n':
       +                        if (buf->current != buf->start) {
       +                                insert(buf, 0);
       +                                return 1;
       +                        }
       +                        break;                /* skip empty lines */
       +                case '\\':
       +                case '\'':
       +                case '"':
       +                        rinsert(buf, c);
       +                        if (escapetoken(bp, buf, 1, c) == 0)
       +                                Exit();
       +                        break;
       +                case '`':
       +                        if (bquote(bp, buf) == 0)
       +                                Exit();
       +                        break;
       +                case '#':
       +                        lastc = '#';
       +                        while ((c = Bgetc(bp)) != '\n') {
       +                                if (c < 0)
       +                                        goto eof;
       +                                if(c != '\r')
       +                                        lastc = c;
       +                        }
       +                        mkinline++;
       +                        if (lastc == '\\')
       +                                break;                /* propagate escaped newlines??*/
       +                        if (buf->current != buf->start) {
       +                                insert(buf, 0);
       +                                return 1;
       +                        }
       +                        break;
       +                default:
       +                        rinsert(buf, c);
       +                        break;
       +                }
       +        }
       +eof:
       +        insert(buf, 0);
       +        return *buf->start != 0;
       +}
       +
       +/*
       + *        assemble a back-quoted shell command into a buffer
       + */
       +static int
       +bquote(Biobuf *bp, Bufblock *buf)
       +{
       +        int c, line, term;
       +        int start;
       +
       +        line = mkinline;
       +        while((c = Bgetrune(bp)) == ' ' || c == '\t')
       +                        ;
       +        if(c == '{'){
       +                term = '}';                /* rc style */
       +                while((c = Bgetrune(bp)) == ' ' || c == '\t')
       +                        ;
       +        } else
       +                term = '`';                /* sh style */
       +
       +        start = buf->current-buf->start;
       +        for(;c > 0; c = nextrune(bp, 0)){
       +                if(c == term){
       +                        insert(buf, '\n');
       +                        insert(buf,0);
       +                        buf->current = buf->start+start;
       +                        execinit();
       +                        execsh(0, buf->current, buf, envy);
       +                        return 1;
       +                }
       +                if(c == '\n')
       +                        break;
       +                if(c == '\'' || c == '"' || c == '\\'){
       +                        insert(buf, c);
       +                        if(!escapetoken(bp, buf, 1, c))
       +                                return 0;
       +                        continue;
       +                }
       +                rinsert(buf, c);
       +        }
       +        SYNERR(line);
       +        fprint(2, "missing closing %c after `\n", term);
       +        return 0;
       +}
       +
       +/*
       + *        get next character stripping escaped newlines
       + *        the flag specifies whether escaped newlines are to be elided or
       + *        replaced with a blank.
       + */
       +int
       +nextrune(Biobuf *bp, int elide)
       +{
       +        int c, c2;
       +        static int savec;
       +
       +        if(savec){
       +                c = savec;
       +                savec = 0;
       +                return c;
       +        }
       +
       +        for (;;) {
       +                c = Bgetrune(bp);
       +                if (c == '\\') {
       +                        c2 = Bgetrune(bp);
       +                        if(c2 == '\r'){
       +                                savec = c2;
       +                                c2 = Bgetrune(bp);
       +                        }
       +                        if (c2 == '\n') {
       +                                savec = 0;
       +                                mkinline++;
       +                                if (elide)
       +                                        continue;
       +                                return ' ';
       +                        }
       +                        Bungetrune(bp);
       +                }
       +                if (c == '\n')
       +                        mkinline++;
       +                return c;
       +        }
       +        return 0;
       +}
 (DIR) diff --git a/src/cmd/mk/main.c b/src/cmd/mk/main.c
       t@@ -0,0 +1,285 @@
       +#include        "mk.h"
       +
       +#define                MKFILE                "mkfile"
       +
       +int debug;
       +Rule *rules, *metarules;
       +int nflag = 0;
       +int tflag = 0;
       +int iflag = 0;
       +int kflag = 0;
       +int aflag = 0;
       +int uflag = 0;
       +char *explain = 0;
       +Word *target1;
       +int nreps = 1;
       +Job *jobs;
       +Biobuf bout;
       +Rule *patrule;
       +void badusage(void);
       +#ifdef        PROF
       +short buf[10000];
       +#endif
       +
       +int
       +main(int argc, char **argv)
       +{
       +        Word *w;
       +        char *s, *temp;
       +        char *files[256], **f = files, **ff;
       +        int sflag = 0;
       +        int i;
       +        int tfd = -1;
       +        Biobuf tb;
       +        Bufblock *buf;
       +        Bufblock *whatif;
       +
       +        /*
       +         *  start with a copy of the current environment variables
       +         *  instead of sharing them
       +         */
       +
       +        Binit(&bout, 1, OWRITE);
       +        buf = newbuf();
       +        whatif = 0;
       +        USED(argc);
       +        for(argv++; *argv && (**argv == '-'); argv++)
       +        {
       +                bufcpy(buf, argv[0], strlen(argv[0]));
       +                insert(buf, ' ');
       +                switch(argv[0][1])
       +                {
       +                case 'a':
       +                        aflag = 1;
       +                        break;
       +                case 'd':
       +                        if(*(s = &argv[0][2]))
       +                                while(*s) switch(*s++)
       +                                {
       +                                case 'p':        debug |= D_PARSE; break;
       +                                case 'g':        debug |= D_GRAPH; break;
       +                                case 'e':        debug |= D_EXEC; break;
       +                                }
       +                        else
       +                                debug = 0xFFFF;
       +                        break;
       +                case 'e':
       +                        explain = &argv[0][2];
       +                        break;
       +                case 'f':
       +                        if(*++argv == 0)
       +                                badusage();
       +                        *f++ = *argv;
       +                        bufcpy(buf, argv[0], strlen(argv[0]));
       +                        insert(buf, ' ');
       +                        break;
       +                case 'i':
       +                        iflag = 1;
       +                        break;
       +                case 'k':
       +                        kflag = 1;
       +                        break;
       +                case 'n':
       +                        nflag = 1;
       +                        break;
       +                case 's':
       +                        sflag = 1;
       +                        break;
       +                case 't':
       +                        tflag = 1;
       +                        break;
       +                case 'u':
       +                        uflag = 1;
       +                        break;
       +                case 'w':
       +                        if(whatif == 0)
       +                                whatif = newbuf();
       +                        else
       +                                insert(whatif, ' ');
       +                        if(argv[0][2])
       +                                bufcpy(whatif, &argv[0][2], strlen(&argv[0][2]));
       +                        else {
       +                                if(*++argv == 0)
       +                                        badusage();
       +                                bufcpy(whatif, &argv[0][0], strlen(&argv[0][0]));
       +                        }
       +                        break;
       +                default:
       +                        badusage();
       +                }
       +        }
       +#ifdef        PROF
       +        {
       +                extern etext();
       +                monitor(main, etext, buf, sizeof buf, 300);
       +        }
       +#endif
       +
       +        if(aflag)
       +                iflag = 1;
       +        usage();
       +        syminit();
       +        initenv();
       +        usage();
       +
       +        /*
       +                assignment args become null strings
       +        */
       +        temp = 0;
       +        for(i = 0; argv[i]; i++) if(utfrune(argv[i], '=')){
       +                bufcpy(buf, argv[i], strlen(argv[i]));
       +                insert(buf, ' ');
       +                if(tfd < 0){
       +                        temp = maketmp(&tfd);
       +                        if(temp == 0) {
       +                                fprint(2, "temp file: %r\n");
       +                                Exit();
       +                        }
       +                        Binit(&tb, tfd, OWRITE);
       +                }
       +                Bprint(&tb, "%s\n", argv[i]);
       +                *argv[i] = 0;
       +        }
       +        if(tfd >= 0){
       +                Bflush(&tb);
       +                LSEEK(tfd, 0L, 0);
       +                parse("command line args", tfd, 1);
       +                remove(temp);
       +        }
       +
       +        if (buf->current != buf->start) {
       +                buf->current--;
       +                insert(buf, 0);
       +        }
       +        symlook("MKFLAGS", S_VAR, (void *) stow(buf->start));
       +        buf->current = buf->start;
       +        for(i = 0; argv[i]; i++){
       +                if(*argv[i] == 0) continue;
       +                if(i)
       +                        insert(buf, ' ');
       +                bufcpy(buf, argv[i], strlen(argv[i]));
       +        }
       +        insert(buf, 0);
       +        symlook("MKARGS", S_VAR, (void *) stow(buf->start));
       +        freebuf(buf);
       +
       +        if(f == files){
       +                if(access(MKFILE, 4) == 0)
       +                        parse(MKFILE, open(MKFILE, 0), 0);
       +        } else
       +                for(ff = files; ff < f; ff++)
       +                        parse(*ff, open(*ff, 0), 0);
       +        if(DEBUG(D_PARSE)){
       +                dumpw("default targets", target1);
       +                dumpr("rules", rules);
       +                dumpr("metarules", metarules);
       +                dumpv("variables");
       +        }
       +        if(whatif){
       +                insert(whatif, 0);
       +                timeinit(whatif->start);
       +                freebuf(whatif);
       +        }
       +        execinit();
       +        /* skip assignment args */
       +        while(*argv && (**argv == 0))
       +                argv++;
       +
       +        catchnotes();
       +        if(*argv == 0){
       +                if(target1)
       +                        for(w = target1; w; w = w->next)
       +                                mk(w->s);
       +                else {
       +                        fprint(2, "mk: nothing to mk\n");
       +                        Exit();
       +                }
       +        } else {
       +                if(sflag){
       +                        for(; *argv; argv++)
       +                                if(**argv)
       +                                        mk(*argv);
       +                } else {
       +                        Word *head, *tail, *t;
       +
       +                        /* fake a new rule with all the args as prereqs */
       +                        tail = 0;
       +                        t = 0;
       +                        for(; *argv; argv++)
       +                                if(**argv){
       +                                        if(tail == 0)
       +                                                tail = t = newword(*argv);
       +                                        else {
       +                                                t->next = newword(*argv);
       +                                                t = t->next;
       +                                        }
       +                                }
       +                        if(tail->next == 0)
       +                                mk(tail->s);
       +                        else {
       +                                head = newword("command line arguments");
       +                                addrules(head, tail, strdup(""), VIR, mkinline, 0);
       +                                mk(head->s);
       +                        }
       +                }
       +        }
       +        if(uflag)
       +                prusage();
       +        exits(0);
       +}
       +
       +void
       +badusage(void)
       +{
       +
       +        fprint(2, "Usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n");
       +        Exit();
       +}
       +
       +void *
       +Malloc(int n)
       +{
       +        register void *s;
       +
       +        s = malloc(n);
       +        if(!s) {
       +                fprint(2, "mk: cannot alloc %d bytes\n", n);
       +                Exit();
       +        }
       +        return(s);
       +}
       +
       +void *
       +Realloc(void *s, int n)
       +{
       +        if(s)
       +                s = realloc(s, n);
       +        else
       +                s = malloc(n);
       +        if(!s) {
       +                fprint(2, "mk: cannot alloc %d bytes\n", n);
       +                Exit();
       +        }
       +        return(s);
       +}
       +
       +void
       +assert(char *s, int n)
       +{
       +        if(!n){
       +                fprint(2, "mk: Assertion ``%s'' failed.\n", s);
       +                Exit();
       +        }
       +}
       +
       +void
       +regerror(char *s)
       +{
       +        if(patrule)
       +                fprint(2, "mk: %s:%d: regular expression error; %s\n",
       +                        patrule->file, patrule->line, s);
       +        else
       +                fprint(2, "mk: %s:%d: regular expression error; %s\n",
       +                        infile, mkinline, s);
       +        Exit();
       +}
 (DIR) diff --git a/src/cmd/mk/match.c b/src/cmd/mk/match.c
       t@@ -0,0 +1,49 @@
       +#include        "mk.h"
       +
       +int
       +match(char *name, char *template, char *stem)
       +{
       +        Rune r;
       +        int n;
       +
       +        while(*name && *template){
       +                n = chartorune(&r, template);
       +                if (PERCENT(r))
       +                        break;
       +                while (n--)
       +                        if(*name++ != *template++)
       +                                return 0;
       +        }
       +        if(!PERCENT(*template))
       +                return 0;
       +        n = strlen(name)-strlen(template+1);
       +        if (n < 0)
       +                return 0;
       +        if (strcmp(template+1, name+n))
       +                return 0;
       +        strncpy(stem, name, n);
       +        stem[n] = 0;
       +        if(*template == '&')
       +                return !charin(stem, "./");
       +        return 1;
       +}
       +
       +void
       +subst(char *stem, char *template, char *dest)
       +{
       +        Rune r;
       +        char *s;
       +        int n;
       +
       +        while(*template){
       +                n = chartorune(&r, template);
       +                if (PERCENT(r)) {
       +                        template += n;
       +                        for (s = stem; *s; s++)
       +                                *dest++ = *s;
       +                } else
       +                        while (n--)
       +                                *dest++ = *template++;
       +        }
       +        *dest = 0;
       +}
 (DIR) diff --git a/src/cmd/mk/mk.1 b/src/cmd/mk/mk.1
       t@@ -0,0 +1,665 @@
       +.TH MK 1
       +.de EX
       +.nf
       +.ft B
       +..
       +.de EE
       +.fi
       +.ft R
       +..
       +.de LR
       +.if t .BR \\$1 \\$2
       +.if n .RB ` \\$1 '\\$2
       +..
       +.de L
       +.nh
       +.if t .B \\$1
       +.if n .RB ` \\$1 '
       +..
       +.SH NAME
       +mk \- maintain (make) related files
       +.SH SYNOPSIS
       +.B mk
       +[
       +.B -f
       +.I mkfile
       +] ...
       +[
       +.I option ...
       +]
       +[
       +.I target ...
       +]
       +.SH DESCRIPTION
       +.I Mk
       +uses the dependency rules specified in
       +.I mkfile
       +to control the update (usually by compilation) of
       +.I targets
       +(usually files)
       +from the source files upon which they depend.
       +The
       +.I mkfile
       +(default
       +.LR mkfile )
       +contains a
       +.I rule
       +for each target that identifies the files and other
       +targets upon which it depends and an
       +.IR sh (1)
       +script, a
       +.IR recipe ,
       +to update the target.
       +The script is run if the target does not exist
       +or if it is older than any of the files it depends on.
       +.I Mkfile
       +may also contain
       +.I meta-rules
       +that define actions for updating implicit targets.
       +If no
       +.I target
       +is specified, the target of the first rule (not meta-rule) in
       +.I mkfile
       +is updated.
       +.PP
       +The environment variable
       +.B $NPROC
       +determines how many targets may be updated simultaneously;
       +Some operating systems, e.g., Plan 9, set
       +.B $NPROC
       +automatically to the number of CPUs on the current machine.
       +.PP
       +Options are:
       +.TP \w'\fL-d[egp]\ 'u
       +.B -a
       +Assume all targets to be out of date.
       +Thus, everything is updated.
       +.PD 0
       +.TP
       +.BR -d [ egp ]
       +Produce debugging output
       +.RB ( p
       +is for parsing,
       +.B g
       +for graph building,
       +.B e
       +for execution).
       +.TP
       +.B -e
       +Explain why each target is made.
       +.TP
       +.B -i
       +Force any missing intermediate targets to be made.
       +.TP
       +.B -k
       +Do as much work as possible in the face of errors.
       +.TP
       +.B -n
       +Print, but do not execute, the commands
       +needed to update the targets.
       +.TP
       +.B -s
       +Make the command line arguments sequentially rather than in parallel.
       +.TP
       +.B -t
       +Touch (update the modified date of) file targets, without
       +executing any recipes.
       +.TP
       +.BI -w target1 , target2,...
       +Pretend the modify time for each
       +.I target
       +is the current time; useful in conjunction with
       +.B -n
       +to learn what updates would be triggered by
       +modifying the
       +.IR targets .
       +.PD
       +.SS The \fLmkfile\fP
       +A
       +.I mkfile
       +consists of
       +.I assignments
       +(described under `Environment') and
       +.IR rules .
       +A rule contains
       +.I targets
       +and a
       +.IR tail .
       +A target is a literal string
       +and is normally a file name.
       +The tail contains zero or more 
       +.I prerequisites
       +and an optional
       +.IR recipe ,
       +which is an
       +.B shell
       +script.
       +Each line of the recipe must begin with white space.
       +A rule takes the form
       +.IP
       +.EX
       +target: prereq1 prereq2
       +        \f2recipe using\fP prereq1, prereq2 \f2to build\fP target
       +.EE
       +.PP
       +When the recipe is executed,
       +the first character on every line is elided.
       +.PP
       +After the colon on the target line, a rule may specify
       +.IR attributes ,
       +described below.
       +.PP
       +A
       +.I meta-rule 
       +has a target of the form
       +.IB A % B
       +where
       +.I A
       +and
       +.I B
       +are (possibly empty) strings.
       +A meta-rule acts as a rule for any potential target whose
       +name matches
       +.IB A % B
       +with
       +.B %
       +replaced by an arbitrary string, called the
       +.IR stem .
       +In interpreting a meta-rule,
       +the stem is substituted for all occurrences of
       +.B %
       +in the prerequisite names.
       +In the recipe of a meta-rule, the environment variable
       +.B $stem
       +contains the string matched by the
       +.BR % .
       +For example, a meta-rule to compile a C program using
       +.IR cc (1)
       +might be:
       +.IP
       +.EX
       +%:    %.c
       +        cc -c $stem.c
       +        cc -o $stem $stem.o
       +.EE
       +.PP
       +Meta-rules may contain an ampersand
       +.B &
       +rather than a percent sign
       +.BR % .
       +A
       +.B %
       +matches a maximal length string of any characters;
       +an
       +.B &
       +matches a maximal length string of any characters except period
       +or slash.
       +.PP
       +The text of the
       +.I mkfile
       +is processed as follows.
       +Lines beginning with
       +.B <
       +followed by a file name are replaced by the contents of the named
       +file.
       +Lines beginning with
       +.B "<|"
       +followed by a file name are replaced by the output
       +of the execution of the named
       +file.
       +Blank lines and comments, which run from unquoted
       +.B #
       +characters to the following newline, are deleted.
       +The character sequence backslash-newline is deleted,
       +so long lines in
       +.I mkfile
       +may be folded.
       +Non-recipe lines are processed by substituting for
       +.BI `{ command }
       +the output of the
       +.I command
       +when run by
       +.IR sh .
       +References to variables are replaced by the variables' values.
       +Special characters may be quoted using single quotes
       +.BR \&''
       +as in
       +.IR sh (1).
       +.PP
       +Assignments and rules are distinguished by
       +the first unquoted occurrence of
       +.B :
       +(rule)
       +or
       +.B =
       +(assignment).
       +.PP
       +A later rule may modify or override an existing rule under the
       +following conditions:
       +.TP
       +\-
       +If the targets of the rules exactly match and one rule
       +contains only a prerequisite clause and no recipe, the
       +clause is added to the prerequisites of the other rule.
       +If either or both targets are virtual, the recipe is
       +always executed.
       +.TP
       +\-
       +If the targets of the rules match exactly and the
       +prerequisites do not match and both rules
       +contain recipes,
       +.I mk
       +reports an ``ambiguous recipe'' error.
       +.TP
       +\-
       +If the target and prerequisites of both rules match exactly,
       +the second rule overrides the first.
       +.SS Environment
       +Rules may make use of
       +shell
       +environment variables.
       +A legal reference of the form
       +.B $OBJ
       +or
       +.B ${name}
       +is expanded as in
       +.IR sh (1).
       +A reference of the form
       +.BI ${name: A % B = C\fL%\fID\fL}\fR,
       +where
       +.I A, B, C, D
       +are (possibly empty) strings,
       +has the value formed by expanding
       +.B $name
       +and substituting
       +.I C
       +for
       +.I A
       +and
       +.I D
       +for
       +.I B
       +in each word in
       +.B $name
       +that matches pattern
       +.IB A % B\f1.
       +.PP
       +Variables can be set by
       +assignments of the form
       +.I
       +        var\fL=\fR[\fIattr\fL=\fR]\fIvalue\fR
       +.br
       +Blanks in the
       +.I value
       +break it into words.
       +Such variables are exported
       +to the environment of
       +recipes as they are executed, unless
       +.BR U ,
       +the only legal attribute
       +.IR attr ,
       +is present.
       +The initial value of a variable is
       +taken from (in increasing order of precedence)
       +the default values below,
       +.I mk's
       +environment, the
       +.IR mkfiles ,
       +and any command line assignment as an argument to
       +.IR mk .
       +A variable assignment argument overrides the first (but not any subsequent)
       +assignment to that variable.
       +The variable
       +.B MKFLAGS
       +contains all the option arguments (arguments starting with
       +.L -
       +or containing
       +.LR = )
       +and
       +.B MKARGS
       +contains all the targets in the call to
       +.IR mk .
       +.PP
       +Dynamic information may be included in the mkfile by using a line of the form
       +.IP
       +\fR<|\fIcommand\fR \fIargs\fR
       +.LP
       +This runs the command 
       +.I command
       +with the given arguments
       +.I args
       +and pipes its standard output to
       +.I mk
       +to be included as part of the mkfile. For instance, the Inferno kernels
       +use this technique
       +to run a shell command with an awk script and a configuration
       +file as arguments in order for
       +the
       +.I awk
       +script to process the file and output a set of variables and their values.
       +.SS Execution
       +.PP
       +During execution,
       +.I mk
       +determines which targets must be updated, and in what order,
       +to build the
       +.I names
       +specified on the command line.
       +It then runs the associated recipes.
       +.PP
       +A target is considered up to date if it has no prerequisites or
       +if all its prerequisites are up to date and it is newer
       +than all its prerequisites.
       +Once the recipe for a target has executed, the target is
       +considered up to date.
       +.PP
       +The date stamp
       +used to determine if a target is up to date is computed
       +differently for different types of targets.
       +If a target is
       +.I virtual
       +(the target of a rule with the
       +.B V
       +attribute),
       +its date stamp is initially zero; when the target is
       +updated the date stamp is set to
       +the most recent date stamp of its prerequisites.
       +Otherwise, if a target does not exist as a file,
       +its date stamp is set to the most recent date stamp of its prerequisites,
       +or zero if it has no prerequisites.
       +Otherwise, the target is the name of a file and
       +the target's date stamp is always that file's modification date.
       +The date stamp is computed when the target is needed in
       +the execution of a rule; it is not a static value.
       +.PP
       +Nonexistent targets that have prerequisites
       +and are themselves prerequisites are treated specially.
       +Such a target
       +.I t
       +is given the date stamp of its most recent prerequisite
       +and if this causes all the targets which have
       +.I t
       +as a prerequisite to be up to date,
       +.I t
       +is considered up to date.
       +Otherwise,
       +.I t
       +is made in the normal fashion.
       +The
       +.B -i
       +flag overrides this special treatment.
       +.PP
       +Files may be made in any order that respects
       +the preceding restrictions.
       +.PP
       +A recipe is executed by supplying the recipe as standard input to
       +the command
       +.BR /bin/sh .
       +(Note that unlike
       +.IR make ,
       +.I mk
       +feeds the entire recipe to the shell rather than running each line
       +of the recipe separately.)
       +The environment is augmented by the following variables:
       +.TP 14
       +.B $alltarget
       +all the targets of this rule.
       +.TP
       +.B $newprereq
       +the prerequisites that caused this rule to execute.
       +.TP
       +.B $newmember
       +the prerequisites that are members of an aggregate
       +that caused this rule to execute.
       +When the prerequisites of a rule are members of an
       +aggregate,
       +.B $newprereq
       +contains the name of the aggregate and out of date
       +members, while
       +.B $newmember
       +contains only the name of the members.
       +.TP
       +.B $nproc
       +the process slot for this recipe.
       +It satisfies
       +.RB 0≤ $nproc < $NPROC .
       +.TP
       +.B $pid
       +the process id for the
       +.I mk
       +executing the recipe.
       +.TP
       +.B $prereq
       +all the prerequisites for this rule.
       +.TP
       +.B $stem
       +if this is a meta-rule,
       +.B $stem
       +is the string that matched
       +.B %
       +or
       +.BR & .
       +Otherwise, it is empty.
       +For regular expression meta-rules (see below), the variables
       +.LR stem0 ", ...,"
       +.L stem9
       +are set to the corresponding subexpressions.
       +.TP
       +.B $target
       +the targets for this rule that need to be remade.
       +.PP
       +These variables are available only during the execution of a recipe,
       +not while evaluating the
       +.IR mkfile .
       +.PP
       +Unless the rule has the
       +.B Q
       +attribute,
       +the recipe is printed prior to execution
       +with recognizable environment variables expanded.
       +Commands returning error status
       +cause
       +.I mk
       +to terminate.
       +.PP
       +Recipes and backquoted
       +.B rc
       +commands in places such as assignments
       +execute in a copy of
       +.I mk's
       +environment; changes they make to
       +environment variables are not visible from
       +.IR mk .
       +.PP
       +Variable substitution in a rule is done when
       +the rule is read; variable substitution in the recipe is done
       +when the recipe is executed.  For example:
       +.IP
       +.EX
       +bar=a.c
       +foo:        $bar
       +        $CC -o foo $bar
       +bar=b.c
       +.EE
       +.PP
       +will compile
       +.B b.c
       +into
       +.BR foo ,
       +if
       +.B a.c
       +is newer than
       +.BR foo .
       +.SS Aggregates
       +Names of the form
       +.IR a ( b )
       +refer to member
       +.I b
       +of the aggregate
       +.IR a .
       +Currently, the only aggregates supported are
       +.IR ar (1)
       +archives.
       +.SS Attributes
       +The colon separating the target from the prerequisites
       +may be
       +immediately followed by
       +.I attributes
       +and another colon.
       +The attributes are:
       +.TP
       +.B D
       +If the recipe exits with a non-null status, the target is deleted.
       +.TP
       +.B E
       +Continue execution if the recipe draws errors.
       +.TP
       +.B N
       +If there is no recipe, the target has its time updated.
       +.TP
       +.B n
       +The rule is a meta-rule that cannot be a target of a virtual rule.
       +Only files match the pattern in the target.
       +.TP
       +.B P
       +The characters after the
       +.B P
       +until the terminating
       +.B :
       +are taken as a program name.
       +It will be invoked as
       +.B "sh -c prog 'arg1' 'arg2'"
       +and should return a zero exit status
       +if and only if arg1 is up to date with respect to arg2.
       +Date stamps are still propagated in the normal way.
       +.TP
       +.B Q
       +The recipe is not printed prior to execution.
       +.TP
       +.B R
       +The rule is a meta-rule using regular expressions.
       +In the rule,
       +.B %
       +has no special meaning.
       +The target is interpreted as a regular expression as defined in
       +.IR regexp (6).
       +The prerequisites may contain references
       +to subexpressions in form
       +.BI \e n\f1,
       +as in the substitute command of
       +.IR sed (1).
       +.TP
       +.B U
       +The targets are considered to have been updated
       +even if the recipe did not do so.
       +.TP
       +.B V
       +The targets of this rule are marked as virtual.
       +They are distinct from files of the same name.
       +.PD
       +.SH EXAMPLES
       +A simple mkfile to compile a program:
       +.IP
       +.EX
       +.ta 8n +8n +8n +8n +8n +8n +8n
       +</$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.
 (DIR) diff --git a/src/cmd/mk/mk.c b/src/cmd/mk/mk.c
       t@@ -0,0 +1,226 @@
       +#include        "mk.h"
       +
       +int runerrs;
       +
       +void
       +mk(char *target)
       +{
       +        Node *node;
       +        int did = 0;
       +
       +        nproc();                /* it can be updated dynamically */
       +        nrep();                        /* it can be updated dynamically */
       +        runerrs = 0;
       +        node = graph(target);
       +        if(DEBUG(D_GRAPH)){
       +                dumpn("new target\n", node);
       +                Bflush(&bout);
       +        }
       +        clrmade(node);
       +        while(node->flags&NOTMADE){
       +                if(work(node, (Node *)0, (Arc *)0))
       +                        did = 1;        /* found something to do */
       +                else {
       +                        if(waitup(1, (int *)0) > 0){
       +                                if(node->flags&(NOTMADE|BEINGMADE)){
       +                                        assert("must be run errors", runerrs);
       +                                        break;        /* nothing more waiting */
       +                                }
       +                        }
       +                }
       +        }
       +        if(node->flags&BEINGMADE)
       +                waitup(-1, (int *)0);
       +        while(jobs)
       +                waitup(-2, (int *)0);
       +        assert("target didn't get done", runerrs || (node->flags&MADE));
       +        if(did == 0)
       +                Bprint(&bout, "mk: '%s' is up to date\n", node->name);
       +}
       +
       +void
       +clrmade(Node *n)
       +{
       +        Arc *a;
       +
       +        n->flags &= ~(CANPRETEND|PRETENDING);
       +        if(strchr(n->name, '(') ==0 || n->time)
       +                n->flags |= CANPRETEND;
       +        MADESET(n, NOTMADE);
       +        for(a = n->prereqs; a; a = a->next)
       +                if(a->n)
       +                        clrmade(a->n);
       +}
       +
       +static void
       +unpretend(Node *n)
       +{
       +        MADESET(n, NOTMADE);
       +        n->flags &= ~(CANPRETEND|PRETENDING);
       +        n->time = 0;
       +}
       +
       +int
       +work(Node *node, Node *p, Arc *parc)
       +{
       +        Arc *a, *ra;
       +        int weoutofdate;
       +        int ready;
       +        int did = 0;
       +
       +        /*print("work(%s) flags=0x%x time=%ld\n", node->name, node->flags, node->time);*//**/
       +        if(node->flags&BEINGMADE)
       +                return(did);
       +        if((node->flags&MADE) && (node->flags&PRETENDING) && p && outofdate(p, parc, 0)){
       +                if(explain)
       +                        fprint(1, "unpretending %s(%ld) because %s is out of date(%ld)\n",
       +                                node->name, node->time, p->name, p->time);
       +                unpretend(node);
       +        }
       +        /*
       +                have a look if we are pretending in case
       +                someone has been unpretended out from underneath us
       +        */
       +        if(node->flags&MADE){
       +                if(node->flags&PRETENDING){
       +                        node->time = 0;
       +                }else
       +                        return(did);
       +        }
       +        /* consider no prerequsite case */
       +        if(node->prereqs == 0){
       +                if(node->time == 0){
       +                        fprint(2, "mk: don't know how to make '%s'\n", node->name);
       +                        if(kflag){
       +                                node->flags |= BEINGMADE;
       +                                runerrs++;
       +                        } else
       +                                Exit();
       +                } else
       +                        MADESET(node, MADE);
       +                return(did);
       +        }
       +        /*
       +                now see if we are out of date or what
       +        */
       +        ready = 1;
       +        weoutofdate = aflag;
       +        ra = 0;
       +        for(a = node->prereqs; a; a = a->next)
       +                if(a->n){
       +                        did = work(a->n, node, a) || did;
       +                        if(a->n->flags&(NOTMADE|BEINGMADE))
       +                                ready = 0;
       +                        if(outofdate(node, a, 0)){
       +                                weoutofdate = 1;
       +                                if((ra == 0) || (ra->n == 0)
       +                                                || (ra->n->time < a->n->time))
       +                                        ra = a;
       +                        }
       +                } else {
       +                        if(node->time == 0){
       +                                if(ra == 0)
       +                                        ra = a;
       +                                weoutofdate = 1;
       +                        }
       +                }
       +        if(ready == 0)        /* can't do anything now */
       +                return(did);
       +        if(weoutofdate == 0){
       +                MADESET(node, MADE);
       +                return(did);
       +        }
       +        /*
       +                can we pretend to be made?
       +        */
       +        if((iflag == 0) && (node->time == 0) && (node->flags&(PRETENDING|CANPRETEND))
       +                        && p && ra->n && !outofdate(p, ra, 0)){
       +                node->flags &= ~CANPRETEND;
       +                MADESET(node, MADE);
       +                if(explain && ((node->flags&PRETENDING) == 0))
       +                        fprint(1, "pretending %s has time %ld\n", node->name, node->time);
       +                node->flags |= PRETENDING;
       +                return(did);
       +        }
       +        /*
       +                node is out of date and we REALLY do have to do something.
       +                quickly rescan for pretenders
       +        */
       +        for(a = node->prereqs; a; a = a->next)
       +                if(a->n && (a->n->flags&PRETENDING)){
       +                        if(explain)
       +                                Bprint(&bout, "unpretending %s because of %s because of %s\n",
       +                                a->n->name, node->name, ra->n? ra->n->name : "rule with no prerequisites");
       +
       +                        unpretend(a->n);
       +                        did = work(a->n, node, a) || did;
       +                        ready = 0;
       +                }
       +        if(ready == 0)        /* try later unless nothing has happened for -k's sake */
       +                return(did || work(node, p, parc));
       +        did = dorecipe(node) || did;
       +        return(did);
       +}
       +
       +void
       +update(int fake, Node *node)
       +{
       +        Arc *a;
       +
       +        MADESET(node, fake? BEINGMADE : MADE);
       +        if(((node->flags&VIRTUAL) == 0) && (access(node->name, 0) == 0)){
       +                node->time = timeof(node->name, 1);
       +                node->flags &= ~(CANPRETEND|PRETENDING);
       +                for(a = node->prereqs; a; a = a->next)
       +                        if(a->prog)
       +                                outofdate(node, a, 1);
       +        } else {
       +                node->time = 1;
       +                for(a = node->prereqs; a; a = a->next)
       +                        if(a->n && outofdate(node, a, 1))
       +                                node->time = a->n->time;
       +        }
       +/*        print("----node %s time=%ld flags=0x%x\n", node->name, node->time, node->flags);*//**/
       +}
       +
       +static int
       +pcmp(char *prog, char *p, char *q)
       +{
       +        char buf[3*NAMEBLOCK];
       +        int pid;
       +
       +        Bflush(&bout);
       +        sprint(buf, "%s '%s' '%s'\n", prog, p, q);
       +        pid = pipecmd(buf, 0, 0);
       +        while(waitup(-3, &pid) >= 0)
       +                ;
       +        return(pid? 2:1);
       +}
       +
       +int
       +outofdate(Node *node, Arc *arc, int eval)
       +{
       +        char buf[3*NAMEBLOCK], *str;
       +        Symtab *sym;
       +        int ret;
       +
       +        str = 0;
       +        if(arc->prog){
       +                sprint(buf, "%s%c%s", node->name, 0377, arc->n->name);
       +                sym = symlook(buf, S_OUTOFDATE, 0);
       +                if(sym == 0 || eval){
       +                        if(sym == 0)
       +                                str = strdup(buf);
       +                        ret = pcmp(arc->prog, node->name, arc->n->name);
       +                        if(sym)
       +                                sym->value = (void *)ret;
       +                        else
       +                                symlook(str, S_OUTOFDATE, (void *)ret);
       +                } else
       +                        ret = (int)sym->value;
       +                return(ret-1);
       +        } else if(strchr(arc->n->name, '(') && arc->n->time == 0)  /* missing archive member */
       +                return 1;
       +        else
       +                return node->time < arc->n->time;
       +}
 (DIR) diff --git a/src/cmd/mk/mk.h b/src/cmd/mk/mk.h
       t@@ -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"
 (DIR) diff --git a/src/cmd/mk/mkfile b/src/cmd/mk/mkfile
       t@@ -0,0 +1,9 @@
       +all:V: Makefile Make.FreeBSD-386 Make.Linux-386 Make.HP-UX-9000 Make.OSF1-alpha \
       +        Make.SunOS-sun4u Make.SunOS-sun4u-cc Make.SunOS-sun4u-gcc \
       +        Make.NetBSD-386 Make.Darwin-PowerMacintosh
       +
       +Makefile:D: ../libutf/Makefile.TOP Makefile.MID ../libutf/Makefile.CMD ../libutf/Makefile.BOT
       +        cat $prereq >$target
       +
       +Make.%: ../libutf/Make.%
       +        cp $prereq $target
 (DIR) diff --git a/src/cmd/mk/mkfile.test b/src/cmd/mk/mkfile.test
       t@@ -0,0 +1,5 @@
       +a: b
       +        cp b a
       +
       +c:V:
       +        echo hello world
 (DIR) diff --git a/src/cmd/mk/parse.c b/src/cmd/mk/parse.c
       t@@ -0,0 +1,307 @@
       +#include        "mk.h"
       +
       +char *infile;
       +int mkinline;
       +static int rhead(char *, Word **, Word **, int *, char **);
       +static char *rbody(Biobuf*);
       +extern Word *target1;
       +
       +void
       +parse(char *f, int fd, int varoverride)
       +{
       +        int hline;
       +        char *body;
       +        Word *head, *tail;
       +        int attr, set, pid;
       +        char *prog, *p;
       +        int newfd;
       +        Biobuf in;
       +        Bufblock *buf;
       +
       +        if(fd < 0){
       +                fprint(2, "open %s: %r\n", f);
       +                Exit();
       +        }
       +        ipush();
       +        infile = strdup(f);
       +        mkinline = 1;
       +        Binit(&in, fd, OREAD);
       +        buf = newbuf();
       +        while(assline(&in, buf)){
       +                hline = mkinline;
       +                switch(rhead(buf->start, &head, &tail, &attr, &prog))
       +                {
       +                case '<':
       +                        p = wtos(tail, ' ');
       +                        if(*p == 0){
       +                                SYNERR(-1);
       +                                fprint(2, "missing include file name\n");
       +                                Exit();
       +                        }
       +                        newfd = open(p, OREAD);
       +                        if(newfd < 0){
       +                                fprint(2, "warning: skipping missing include file %s: %r\n", p);
       +                        } else
       +                                parse(p, newfd, 0);
       +                        break;
       +                case '|':
       +                        p = wtos(tail, ' ');
       +                        if(*p == 0){
       +                                SYNERR(-1);
       +                                fprint(2, "missing include program name\n");
       +                                Exit();
       +                        }
       +                        execinit();
       +                        pid=pipecmd(p, envy, &newfd);
       +                        if(newfd < 0){
       +                                fprint(2, "warning: skipping missing program file %s: %r\n", p);
       +                        } else
       +                                parse(p, newfd, 0);
       +                        while(waitup(-3, &pid) >= 0)
       +                                ;
       +                        if(pid != 0){
       +                                fprint(2, "bad include program status\n");
       +                                Exit();
       +                        }
       +                        break;
       +                case ':':
       +                        body = rbody(&in);
       +                        addrules(head, tail, body, attr, hline, prog);
       +                        break;
       +                case '=':
       +                        if(head->next){
       +                                SYNERR(-1);
       +                                fprint(2, "multiple vars on left side of assignment\n");
       +                                Exit();
       +                        }
       +                        if(symlook(head->s, S_OVERRIDE, 0)){
       +                                set = varoverride;
       +                        } else {
       +                                set = 1;
       +                                if(varoverride)
       +                                        symlook(head->s, S_OVERRIDE, (void *)"");
       +                        }
       +                        if(set){
       +/*
       +char *cp;
       +dumpw("tail", tail);
       +cp = wtos(tail, ' '); print("assign %s to %s\n", head->s, cp); free(cp);
       +*/
       +                                setvar(head->s, (void *) tail);
       +                                symlook(head->s, S_WESET, (void *)"");
       +                        }
       +                        if(attr)
       +                                symlook(head->s, S_NOEXPORT, (void *)"");
       +                        break;
       +                default:
       +                        SYNERR(hline);
       +                        fprint(2, "expected one of :<=\n");
       +                        Exit();
       +                        break;
       +                }
       +        }
       +        close(fd);
       +        freebuf(buf);
       +        ipop();
       +}
       +
       +void
       +addrules(Word *head, Word *tail, char *body, int attr, int hline, char *prog)
       +{
       +        Word *w;
       +
       +        assert("addrules args", head && body);
       +                /* tuck away first non-meta rule as default target*/
       +        if(target1 == 0 && !(attr&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);
       +}
 (DIR) diff --git a/src/cmd/mk/rc.c b/src/cmd/mk/rc.c
       t@@ -0,0 +1,175 @@
       +#include        "mk.h"
       +
       +char        *termchars = "'= \t";        /*used in parse.c to isolate assignment attribute*/
       +char        *shflags = "-I";        /* rc flag to force non-interactive mode */
       +int        IWS = '\1';                /* inter-word separator in env - not used in plan 9 */
       +
       +/*
       + *        This file contains functions that depend on rc's syntax.  Most
       + *        of the routines extract strings observing rc's escape conventions
       + */
       +
       +
       +/*
       + *        skip a token in single quotes.
       + */
       +static char *
       +squote(char *cp)
       +{
       +        Rune r;
       +        int n;
       +
       +        while(*cp){
       +                n = chartorune(&r, cp);
       +                if(r == '\'') {
       +                        n += chartorune(&r, cp+n);
       +                        if(r != '\'')
       +                                return(cp);
       +                }
       +                cp += n;
       +        }
       +        SYNERR(-1);                /* should never occur */
       +        fprint(2, "missing closing '\n");
       +        return 0;
       +}
       +
       +/*
       + *        search a string for characters in a pattern set
       + *        characters in quotes and variable generators are escaped
       + */
       +char *
       +charin(char *cp, char *pat)
       +{
       +        Rune r;
       +        int n, vargen;
       +
       +        vargen = 0;
       +        while(*cp){
       +                n = chartorune(&r, cp);
       +                switch(r){
       +                case '\'':                        /* skip quoted string */
       +                        cp = squote(cp+1);        /* n must = 1 */
       +                        if(!cp)
       +                                return 0;
       +                        break;
       +                case '$':
       +                        if(*(cp+1) == '{')
       +                                vargen = 1;
       +                        break;
       +                case '}':
       +                        if(vargen)
       +                                vargen = 0;
       +                        else if(utfrune(pat, r))
       +                                return cp;
       +                        break;
       +                default:
       +                        if(vargen == 0 && utfrune(pat, r))
       +                                return cp;
       +                        break;
       +                }
       +                cp += n;
       +        }
       +        if(vargen){
       +                SYNERR(-1);
       +                fprint(2, "missing closing } in pattern generator\n");
       +        }
       +        return 0;
       +}
       +
       +/*
       + *        extract an escaped token.  Possible escape chars are single-quote,
       + *        double-quote,and backslash.  Only the first is valid for rc. the
       + *        others are just inserted into the receiving buffer.
       + */
       +char*
       +expandquote(char *s, Rune r, Bufblock *b)
       +{
       +        if (r != '\'') {
       +                rinsert(b, r);
       +                return s;
       +        }
       +
       +        while(*s){
       +                s += chartorune(&r, s);
       +                if(r == '\'') {
       +                        if(*s == '\'')
       +                                s++;
       +                        else
       +                                return s;
       +                }
       +                rinsert(b, r);
       +        }
       +        return 0;
       +}
       +
       +/*
       + *        Input an escaped token.  Possible escape chars are single-quote,
       + *        double-quote and backslash.  Only the first is a valid escape for
       + *        rc; the others are just inserted into the receiving buffer.
       + */
       +int
       +escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
       +{
       +        int c, line;
       +
       +        if(esc != '\'')
       +                return 1;
       +
       +        line = mkinline;
       +        while((c = nextrune(bp, 0)) > 0){
       +                if(c == '\''){
       +                        if(preserve)
       +                                rinsert(buf, c);
       +                        c = Bgetrune(bp);
       +                        if (c < 0)
       +                                break;
       +                        if(c != '\''){
       +                                Bungetrune(bp);
       +                                return 1;
       +                        }
       +                }
       +                rinsert(buf, c);
       +        }
       +        SYNERR(line); fprint(2, "missing closing %c\n", esc);
       +        return 0;
       +}
       +
       +/*
       + *        copy a single-quoted string; s points to char after opening quote
       + */
       +static char *
       +copysingle(char *s, Bufblock *buf)
       +{
       +        Rune r;
       +
       +        while(*s){
       +                s += chartorune(&r, s);
       +                rinsert(buf, r);
       +                if(r == '\'')
       +                        break;
       +        }
       +        return s;
       +}
       +/*
       + *        check for quoted strings.  backquotes are handled here; single quotes above.
       + *        s points to char after opening quote, q.
       + */
       +char *
       +copyq(char *s, Rune q, Bufblock *buf)
       +{
       +        if(q == '\'')                                /* copy quoted string */
       +                return copysingle(s, buf);
       +
       +        if(q != '`')                                /* not quoted */
       +                return s;
       +
       +        while(*s){                                /* copy backquoted string */
       +                s += chartorune(&q, s);
       +                rinsert(buf, q);
       +                if(q == '}')
       +                        break;
       +                if(q == '\'')
       +                        s = copysingle(s, buf);        /* copy quoted string */
       +        }
       +        return s;
       +}
 (DIR) diff --git a/src/cmd/mk/recipe.c b/src/cmd/mk/recipe.c
       t@@ -0,0 +1,117 @@
       +#include        "mk.h"
       +
       +int
       +dorecipe(Node *node)
       +{
       +        char buf[BIGBLOCK];
       +        register Node *n;
       +        Rule *r = 0;
       +        Arc *a, *aa;
       +        Word head, ahead, lp, ln, *w, *ww, *aw;
       +        Symtab *s;
       +        int did = 0;
       +
       +        aa = 0;
       +        /*
       +                pick up the rule
       +        */
       +        for(a = node->prereqs; a; a = a->next)
       +                if(*a->r->recipe)
       +                        r = (aa = a)->r;
       +        /*
       +                no recipe? go to buggery!
       +        */
       +        if(r == 0){
       +                if(!(node->flags&VIRTUAL) && !(node->flags&NORECIPE)){
       +                        fprint(2, "mk: no recipe to make '%s'\n", node->name);
       +                        Exit();
       +                }
       +                if(strchr(node->name, '(') && node->time == 0)
       +                        MADESET(node, MADE);
       +                else
       +                        update(0, node);
       +                if(tflag){
       +                        if(!(node->flags&VIRTUAL))
       +                                touch(node->name);
       +                        else if(explain)
       +                                Bprint(&bout, "no touch of virtual '%s'\n", node->name);
       +                }
       +                return(did);
       +        }
       +        /*
       +                build the node list
       +        */
       +        node->next = 0;
       +        head.next = 0;
       +        ww = &head;
       +        ahead.next = 0;
       +        aw = &ahead;
       +        if(r->attr&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);
       +}
 (DIR) diff --git a/src/cmd/mk/rpm.spec b/src/cmd/mk/rpm.spec
       t@@ -0,0 +1,29 @@
       +Summary: Streamlined replacement for make
       +Name: mk
       +Version: 2.0
       +Release: 1
       +Group: Development/Utils
       +Copyright: Public Domain
       +Packager: Russ Cox <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
 (DIR) diff --git a/src/cmd/mk/rule.c b/src/cmd/mk/rule.c
       t@@ -0,0 +1,107 @@
       +#include        "mk.h"
       +
       +static Rule *lr, *lmr;
       +static int rcmp(Rule *r, char *target, Word *tail);
       +static int nrules = 0;
       +
       +void
       +addrule(char *head, Word *tail, char *body, Word *ahead, int attr, int hline, char *prog)
       +{
       +        Rule *r;
       +        Rule *rr;
       +        Symtab *sym;
       +        int reuse;
       +
       +        r = 0;
       +        reuse = 0;
       +        if(sym = symlook(head, S_TARGET, 0)){
       +                for(r = (Rule *)sym->value; r; r = r->chain)
       +                        if(rcmp(r, head, tail) == 0){
       +                                reuse = 1;
       +                                break;
       +                        }
       +        }
       +        if(r == 0)
       +                r = (Rule *)Malloc(sizeof(Rule));
       +        r->target = head;
       +        r->tail = tail;
       +        r->recipe = body;
       +        r->line = hline;
       +        r->file = infile;
       +        r->attr = attr;
       +        r->alltargets = ahead;
       +        r->prog = prog;
       +        r->rule = nrules++;
       +        if(!reuse){
       +                rr = (Rule *)symlook(head, S_TARGET, (void *)r)->value;
       +                if(rr != r){
       +                        r->chain = rr->chain;
       +                        rr->chain = r;
       +                } else
       +                        r->chain = 0;
       +        }
       +        if(!reuse)
       +                r->next = 0;
       +        if((attr&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);
       +}
 (DIR) diff --git a/src/cmd/mk/run.c b/src/cmd/mk/run.c
       t@@ -0,0 +1,296 @@
       +#include        "mk.h"
       +
       +typedef struct Event
       +{
       +        int pid;
       +        Job *job;
       +} Event;
       +static Event *events;
       +static int nevents, nrunning, nproclimit;
       +
       +typedef struct Process
       +{
       +        int pid;
       +        int status;
       +        struct Process *b, *f;
       +} Process;
       +static Process *phead, *pfree;
       +static void sched(void);
       +static void pnew(int, int), pdelete(Process *);
       +
       +int pidslot(int);
       +
       +void
       +run(Job *j)
       +{
       +        Job *jj;
       +
       +        if(jobs){
       +                for(jj = jobs; jj->next; jj = jj->next)
       +                        ;
       +                jj->next = j;
       +        } else 
       +                jobs = j;
       +        j->next = 0;
       +        /* this code also in waitup after parse redirect */
       +        if(nrunning < nproclimit)
       +                sched();
       +}
       +
       +static void
       +sched(void)
       +{
       +        char *flags;
       +        Job *j;
       +        Bufblock *buf;
       +        int slot;
       +        Node *n;
       +        Envy *e;
       +
       +        if(jobs == 0){
       +                usage();
       +                return;
       +        }
       +        j = jobs;
       +        jobs = j->next;
       +        if(DEBUG(D_EXEC))
       +                fprint(1, "firing up job for target %s\n", wtos(j->t, ' '));
       +        slot = nextslot();
       +        events[slot].job = j;
       +        buf = newbuf();
       +        e = buildenv(j, slot);
       +        shprint(j->r->recipe, e, buf);
       +        if(!tflag && (nflag || !(j->r->attr&QUIET)))
       +                Bwrite(&bout, buf->start, (long)strlen(buf->start));
       +        freebuf(buf);
       +        if(nflag||tflag){
       +                for(n = j->n; n; n = n->next){
       +                        if(tflag){
       +                                if(!(n->flags&VIRTUAL))
       +                                        touch(n->name);
       +                                else if(explain)
       +                                        Bprint(&bout, "no touch of virtual '%s'\n", n->name);
       +                        }
       +                        n->time = time((long *)0);
       +                        MADESET(n, MADE);
       +                }
       +        } else {
       +                if(DEBUG(D_EXEC))
       +                        fprint(1, "recipe='%s'", j->r->recipe);/**/
       +                Bflush(&bout);
       +                if(j->r->attr&NOMINUSE)
       +                        flags = 0;
       +                else
       +                        flags = "-e";
       +                events[slot].pid = execsh(flags, j->r->recipe, 0, e);
       +                usage();
       +                nrunning++;
       +                if(DEBUG(D_EXEC))
       +                        fprint(1, "pid for target %s = %d\n", wtos(j->t, ' '), events[slot].pid);
       +        }
       +}
       +
       +int
       +waitup(int echildok, int *retstatus)
       +{
       +        Envy *e;
       +        int pid;
       +        int slot;
       +        Symtab *s;
       +        Word *w;
       +        Job *j;
       +        char buf[ERRMAX];
       +        Bufblock *bp;
       +        int uarg = 0;
       +        int done;
       +        Node *n;
       +        Process *p;
       +        extern int runerrs;
       +
       +        /* first check against the proces slist */
       +        if(retstatus)
       +                for(p = phead; p; p = p->f)
       +                        if(p->pid == *retstatus){
       +                                *retstatus = p->status;
       +                                pdelete(p);
       +                                return(-1);
       +                        }
       +again:                /* rogue processes */
       +        pid = waitfor(buf);
       +        if(pid == -1){
       +                if(echildok > 0)
       +                        return(1);
       +                else {
       +                        fprint(2, "mk: (waitup %d): %r\n", echildok);
       +                        Exit();
       +                }
       +        }
       +        if(DEBUG(D_EXEC))
       +                fprint(1, "waitup got pid=%d, status='%s'\n", pid, buf);
       +        if(retstatus && pid == *retstatus){
       +                *retstatus = buf[0]? 1:0;
       +                return(-1);
       +        }
       +        slot = pidslot(pid);
       +        if(slot < 0){
       +                if(DEBUG(D_EXEC))
       +                        fprint(2, "mk: wait returned unexpected process %d\n", pid);
       +                pnew(pid, buf[0]? 1:0);
       +                goto again;
       +        }
       +        j = events[slot].job;
       +        usage();
       +        nrunning--;
       +        events[slot].pid = -1;
       +        if(buf[0]){
       +                e = buildenv(j, slot);
       +                bp = newbuf();
       +                shprint(j->r->recipe, e, bp);
       +                front(bp->start);
       +                fprint(2, "mk: %s: exit status=%s", bp->start, buf);
       +                freebuf(bp);
       +                for(n = j->n, done = 0; n; n = n->next)
       +                        if(n->flags&DELETE){
       +                                if(done++ == 0)
       +                                        fprint(2, ", deleting");
       +                                fprint(2, " '%s'", n->name);
       +                                delete(n->name);
       +                        }
       +                fprint(2, "\n");
       +                if(kflag){
       +                        runerrs++;
       +                        uarg = 1;
       +                } else {
       +                        jobs = 0;
       +                        Exit();
       +                }
       +        }
       +        for(w = j->t; w; w = w->next){
       +                if((s = symlook(w->s, S_NODE, 0)) == 0)
       +                        continue;        /* not interested in this node */
       +                update(uarg, (Node *)s->value);
       +        }
       +        if(nrunning < nproclimit)
       +                sched();
       +        return(0);
       +}
       +
       +void
       +nproc(void)
       +{
       +        Symtab *sym;
       +        Word *w;
       +
       +        if(sym = symlook("NPROC", S_VAR, 0)) {
       +                w = (Word *) sym->value;
       +                if (w && w->s && w->s[0])
       +                        nproclimit = atoi(w->s);
       +        }
       +        if(nproclimit < 1)
       +                nproclimit = 1;
       +        if(DEBUG(D_EXEC))
       +                fprint(1, "nprocs = %d\n", nproclimit);
       +        if(nproclimit > nevents){
       +                if(nevents)
       +                        events = (Event *)Realloc((char *)events, nproclimit*sizeof(Event));
       +                else
       +                        events = (Event *)Malloc(nproclimit*sizeof(Event));
       +                while(nevents < nproclimit)
       +                        events[nevents++].pid = 0;
       +        }
       +}
       +
       +int
       +nextslot(void)
       +{
       +        int i;
       +
       +        for(i = 0; i < nproclimit; i++)
       +                if(events[i].pid <= 0) return i;
       +        assert("out of slots!!", 0);
       +        return 0;        /* cyntax */
       +}
       +
       +int
       +pidslot(int pid)
       +{
       +        int i;
       +
       +        for(i = 0; i < nevents; i++)
       +                if(events[i].pid == pid) return(i);
       +        if(DEBUG(D_EXEC))
       +                fprint(2, "mk: wait returned unexpected process %d\n", pid);
       +        return(-1);
       +}
       +
       +
       +static void
       +pnew(int pid, int status)
       +{
       +        Process *p;
       +
       +        if(pfree){
       +                p = pfree;
       +                pfree = p->f;
       +        } else
       +                p = (Process *)Malloc(sizeof(Process));
       +        p->pid = pid;
       +        p->status = status;
       +        p->f = phead;
       +        phead = p;
       +        if(p->f)
       +                p->f->b = p;
       +        p->b = 0;
       +}
       +
       +static void
       +pdelete(Process *p)
       +{
       +        if(p->f)
       +                p->f->b = p->b;
       +        if(p->b)
       +                p->b->f = p->f;
       +        else
       +                phead = p->f;
       +        p->f = pfree;
       +        pfree = p;
       +}
       +
       +void
       +killchildren(char *msg)
       +{
       +        Process *p;
       +
       +        kflag = 1;        /* to make sure waitup doesn't exit */
       +        jobs = 0;        /* make sure no more get scheduled */
       +        for(p = phead; p; p = p->f)
       +                expunge(p->pid, msg);
       +        while(waitup(1, (int *)0) == 0)
       +                ;
       +        Bprint(&bout, "mk: %s\n", msg);
       +        Exit();
       +}
       +
       +static long tslot[1000];
       +static long tick;
       +
       +void
       +usage(void)
       +{
       +        long t;
       +
       +        time(&t);
       +        if(tick)
       +                tslot[nrunning] += (t-tick);
       +        tick = t;
       +}
       +
       +void
       +prusage(void)
       +{
       +        int i;
       +
       +        usage();
       +        for(i = 0; i <= nevents; i++)
       +                fprint(1, "%d: %ld\n", i, tslot[i]);
       +}
 (DIR) diff --git a/src/cmd/mk/sh.c b/src/cmd/mk/sh.c
       t@@ -0,0 +1,189 @@
       +#include        "mk.h"
       +
       +char        *termchars = "\"'= \t";        /*used in parse.c to isolate assignment attribute*/
       +char        *shflags = 0;
       +int        IWS = ' ';                /* inter-word separator in env */
       +
       +/*
       + *        This file contains functions that depend on the shell's syntax.  Most
       + *        of the routines extract strings observing the shell's escape conventions.
       + */
       +
       +
       +/*
       + *        skip a token in quotes.
       + */
       +static char *
       +squote(char *cp, int c)
       +{
       +        Rune r;
       +        int n;
       +
       +        while(*cp){
       +                n = chartorune(&r, cp);
       +                if(r == c)
       +                        return cp;
       +                if(r == '\\')
       +                        n += chartorune(&r, cp+n);
       +                cp += n;
       +        }
       +        SYNERR(-1);                /* should never occur */
       +        fprint(2, "missing closing '\n");
       +        return 0;
       +}
       +/*
       + *        search a string for unescaped characters in a pattern set
       + */
       +char *
       +charin(char *cp, char *pat)
       +{
       +        Rune r;
       +        int n, vargen;
       +
       +        vargen = 0;
       +        while(*cp){
       +                n = chartorune(&r, cp);
       +                switch(r){
       +                case '\\':                        /* skip escaped char */
       +                        cp += n;
       +                        n = chartorune(&r, cp);
       +                        break;
       +                case '\'':                        /* skip quoted string */
       +                case '"':
       +                        cp = squote(cp+1, r);        /* n must = 1 */
       +                        if(!cp)
       +                                return 0;
       +                        break;
       +                case '$':
       +                        if(*(cp+1) == '{')
       +                                vargen = 1;
       +                        break;
       +                case '}':
       +                        if(vargen)
       +                                vargen = 0;
       +                        else if(utfrune(pat, r))
       +                                return cp;
       +                        break;
       +                default:
       +                        if(vargen == 0 && utfrune(pat, r))
       +                                return cp;
       +                        break;
       +                }
       +                cp += n;
       +        }
       +        if(vargen){
       +                SYNERR(-1);
       +                fprint(2, "missing closing } in pattern generator\n");
       +        }
       +        return 0;
       +}
       +
       +/*
       + *        extract an escaped token.  Possible escape chars are single-quote,
       + *        double-quote,and backslash.
       + */
       +char*
       +expandquote(char *s, Rune esc, Bufblock *b)
       +{
       +        Rune r;
       +
       +        if (esc == '\\') {
       +                s += chartorune(&r, s);
       +                rinsert(b, r);
       +                return s;
       +        }
       +
       +        while(*s){
       +                s += chartorune(&r, s);
       +                if(r == esc)
       +                        return s;
       +                if (r == '\\') {
       +                        rinsert(b, r);
       +                        s += chartorune(&r, s);
       +                }
       +                rinsert(b, r);
       +        }
       +        return 0;
       +}
       +
       +/*
       + *        Input an escaped token.  Possible escape chars are single-quote,
       + *        double-quote and backslash.
       + */
       +int
       +escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
       +{
       +        int c, line;
       +
       +        if(esc == '\\') {
       +                c = Bgetrune(bp);
       +                if(c == '\r')
       +                        c = Bgetrune(bp);
       +                if (c == '\n')
       +                        mkinline++;
       +                rinsert(buf, c);
       +                return 1;
       +        }
       +
       +        line = mkinline;
       +        while((c = nextrune(bp, 0)) >= 0){
       +                if(c == esc){
       +                        if(preserve)
       +                                rinsert(buf, c);
       +                        return 1;
       +                }
       +                if(c == '\\') {
       +                        rinsert(buf, c);
       +                        c = Bgetrune(bp);
       +                        if(c == '\r')
       +                                c = Bgetrune(bp);
       +                        if (c < 0)
       +                                break;
       +                        if (c == '\n')
       +                                mkinline++;
       +                }
       +                rinsert(buf, c);
       +        }
       +        SYNERR(line); fprint(2, "missing closing %c\n", esc);
       +        return 0;
       +}
       +
       +/*
       + *        copy a quoted string; s points to char after opening quote
       + */
       +static char *
       +copysingle(char *s, Rune q, Bufblock *buf)
       +{
       +        Rune r;
       +
       +        while(*s){
       +                s += chartorune(&r, s);
       +                rinsert(buf, r);
       +                if(r == q)
       +                        break;
       +        }
       +        return s;
       +}
       +/*
       + *        check for quoted strings.  backquotes are handled here; single quotes above.
       + *        s points to char after opening quote, q.
       + */
       +char *
       +copyq(char *s, Rune q, Bufblock *buf)
       +{
       +        if(q == '\'' || q == '"')                /* copy quoted string */
       +                return copysingle(s, q, buf);
       +
       +        if(q != '`')                                /* not quoted */
       +                return s;
       +
       +        while(*s){                                /* copy backquoted string */
       +                s += chartorune(&q, s);
       +                rinsert(buf, q);
       +                if(q == '`')
       +                        break;
       +                if(q == '\'' || q == '"')
       +                        s = copysingle(s, q, buf);        /* copy quoted string */
       +        }
       +        return s;
       +}
 (DIR) diff --git a/src/cmd/mk/shprint.c b/src/cmd/mk/shprint.c
       t@@ -0,0 +1,123 @@
       +#include        "mk.h"
       +
       +static char *vexpand(char*, Envy*, Bufblock*);
       +
       +static int
       +getfields(char *str, char **args, int max, int mflag, char *set)
       +{
       +        Rune r;
       +        int nr, intok, narg;
       +
       +        if(max <= 0)
       +                return 0;
       +
       +        narg = 0;
       +        args[narg] = str;
       +        if(!mflag)
       +                narg++;
       +        intok = 0;
       +        for(;; str += nr) {
       +                nr = chartorune(&r, str);
       +                if(r == 0)
       +                        break;
       +                if(utfrune(set, r)) {
       +                        if(narg >= max)
       +                                break;
       +                        *str = 0;
       +                        intok = 0;
       +                        args[narg] = str + nr;
       +                        if(!mflag)
       +                                narg++;
       +                } else {
       +                        if(!intok && mflag)
       +                                narg++;
       +                        intok = 1;
       +                }
       +        }
       +        return narg;
       +}
       +
       +void
       +shprint(char *s, Envy *env, Bufblock *buf)
       +{
       +        int n;
       +        Rune r;
       +
       +        while(*s) {
       +                n = chartorune(&r, s);
       +                if (r == '$')
       +                        s = vexpand(s, env, buf);
       +                else {
       +                        rinsert(buf, r);
       +                        s += n;
       +                        s = copyq(s, r, buf);        /*handle quoted strings*/
       +                }
       +        }
       +        insert(buf, 0);
       +}
       +
       +static char *
       +mygetenv(char *name, Envy *env)
       +{
       +        if (!env)
       +                return 0;
       +        if (symlook(name, S_WESET, 0) == 0 && symlook(name, S_INTERNAL, 0) == 0)
       +                return 0;
       +                /* only resolve internal variables and variables we've set */
       +        for(; env->name; env++){
       +                if (strcmp(env->name, name) == 0)
       +                        return wtos(env->values, ' ');
       +        }
       +        return 0;
       +}
       +
       +static char *
       +vexpand(char *w, Envy *env, Bufblock *buf)
       +{
       +        char *s, carry, *p, *q;
       +
       +        assert("vexpand no $", *w == '$');
       +        p = w+1;        /* skip dollar sign */
       +        if(*p == '{') {
       +                p++;
       +                q = utfrune(p, '}');
       +                if (!q)
       +                        q = strchr(p, 0);
       +        } else
       +                q = shname(p);
       +        carry = *q;
       +        *q = 0;
       +        s = mygetenv(p, env);
       +        *q = carry;
       +        if (carry == '}')
       +                q++;
       +        if (s) {
       +                bufcpy(buf, s, strlen(s));
       +                free(s);
       +        } else                 /* copy name intact*/
       +                bufcpy(buf, w, q-w);
       +        return(q);
       +}
       +
       +void
       +front(char *s)
       +{
       +        char *t, *q;
       +        int i, j;
       +        char *flds[512];
       +
       +        q = strdup(s);
       +        i = getfields(q, flds, 512, 0, " \t\n");
       +        if(i > 5){
       +                flds[4] = flds[i-1];
       +                flds[3] = "...";
       +                i = 5;
       +        }
       +        t = s;
       +        for(j = 0; j < i; j++){
       +                for(s = flds[j]; *s; *t++ = *s++);
       +                *t++ = ' ';
       +        }
       +        *t = 0;
       +        free(q);
       +}
 (DIR) diff --git a/src/cmd/mk/symtab.c b/src/cmd/mk/symtab.c
       t@@ -0,0 +1,95 @@
       +#include        "mk.h"
       +
       +#define        NHASH        4099
       +#define        HASHMUL        79L        /* this is a good value */
       +static Symtab *hash[NHASH];
       +
       +void
       +syminit(void)
       +{
       +        Symtab **s, *ss;
       +
       +        for(s = hash; s < &hash[NHASH]; s++){
       +                for(ss = *s; ss; ss = ss->next)
       +                        free((char *)ss);
       +                *s = 0;
       +        }
       +}
       +
       +Symtab *
       +symlook(char *sym, int space, void *install)
       +{
       +        long h;
       +        char *p;
       +        Symtab *s;
       +
       +        for(p = sym, h = space; *p; h += *p++)
       +                h *= HASHMUL;
       +        if(h < 0)
       +                h = ~h;
       +        h %= NHASH;
       +        for(s = hash[h]; s; s = s->next)
       +                if((s->space == space) && (strcmp(s->name, sym) == 0))
       +                        return(s);
       +        if(install == 0)
       +                return(0);
       +        s = (Symtab *)Malloc(sizeof(Symtab));
       +        s->space = space;
       +        s->name = sym;
       +        s->value = install;
       +        s->next = hash[h];
       +        hash[h] = s;
       +        return(s);
       +}
       +
       +void
       +symdel(char *sym, int space)
       +{
       +        long h;
       +        char *p;
       +        Symtab *s, *ls;
       +
       +        /* multiple memory leaks */
       +
       +        for(p = sym, h = space; *p; h += *p++)
       +                h *= HASHMUL;
       +        if(h < 0)
       +                h = ~h;
       +        h %= NHASH;
       +        for(s = hash[h], ls = 0; s; ls = s, s = s->next)
       +                if((s->space == space) && (strcmp(s->name, sym) == 0)){
       +                        if(ls)
       +                                ls->next = s->next;
       +                        else
       +                                hash[h] = s->next;
       +                        free((char *)s);
       +                }
       +}
       +
       +void
       +symtraverse(int space, void (*fn)(Symtab*))
       +{
       +        Symtab **s, *ss;
       +
       +        for(s = hash; s < &hash[NHASH]; s++)
       +                for(ss = *s; ss; ss = ss->next)
       +                        if(ss->space == space)
       +                                (*fn)(ss);
       +}
       +
       +void
       +symstat(void)
       +{
       +        Symtab **s, *ss;
       +        int n;
       +        int l[1000];
       +
       +        memset((char *)l, 0, sizeof(l));
       +        for(s = hash; s < &hash[NHASH]; s++){
       +                for(ss = *s, n = 0; ss; ss = ss->next)
       +                        n++;
       +                l[n]++;
       +        }
       +        for(n = 0; n < 1000; n++)
       +                if(l[n]) Bprint(&bout, "%ld of length %d\n", l[n], n);
       +}
 (DIR) diff --git a/src/cmd/mk/unix.c b/src/cmd/mk/unix.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/mk/var.c b/src/cmd/mk/var.c
       t@@ -0,0 +1,41 @@
       +#include        "mk.h"
       +
       +void
       +setvar(char *name, void *value)
       +{
       +        symlook(name, S_VAR, value)->value = value;
       +        symlook(name, S_MAKEVAR, (void*)"");
       +}
       +
       +static void
       +print1(Symtab *s)
       +{
       +        Word *w;
       +
       +        Bprint(&bout, "\t%s=", s->name);
       +        for (w = (Word *) s->value; w; w = w->next)
       +                Bprint(&bout, "'%s'", w->s);
       +        Bprint(&bout, "\n");
       +}
       +
       +void
       +dumpv(char *s)
       +{
       +        Bprint(&bout, "%s:\n", s);
       +        symtraverse(S_VAR, print1);
       +}
       +
       +char *
       +shname(char *a)
       +{
       +        Rune r;
       +        int n;
       +
       +        while (*a) {
       +                n = chartorune(&r, a);
       +                if (!WORDCHR(r))
       +                        break;
       +                a += n;
       +        }
       +        return a;
       +}
 (DIR) diff --git a/src/cmd/mk/varsub.c b/src/cmd/mk/varsub.c
       t@@ -0,0 +1,256 @@
       +#include        "mk.h"
       +
       +static        Word                *subsub(Word*, char*, char*);
       +static        Word                *expandvar(char**);
       +static        Bufblock        *varname(char**);
       +static        Word                *extractpat(char*, char**, char*, char*);
       +static        int                submatch(char*, Word*, Word*, int*, char**);
       +static        Word                *varmatch(char *, char**);
       +
       +Word *
       +varsub(char **s)
       +{
       +        Bufblock *b;
       +        Word *w;
       +
       +        if(**s == '{')                /* either ${name} or ${name: A%B==C%D}*/
       +                return expandvar(s);
       +
       +        b = varname(s);
       +        if(b == 0)
       +                return 0;
       +
       +        w = varmatch(b->start, s);
       +        freebuf(b);
       +        return w;
       +}
       +
       +/*
       + *        extract a variable name
       + */
       +static Bufblock*
       +varname(char **s)
       +{
       +        Bufblock *b;
       +        char *cp;
       +        Rune r;
       +        int n;
       +
       +        b = newbuf();
       +        cp = *s;
       +        for(;;){
       +                n = chartorune(&r, cp);
       +                if (!WORDCHR(r))
       +                        break;
       +                rinsert(b, r);
       +                cp += n;
       +        }
       +        if (b->current == b->start){
       +                SYNERR(-1);
       +                fprint(2, "missing variable name <%s>\n", *s);
       +                freebuf(b);
       +                return 0;
       +        }
       +        *s = cp;
       +        insert(b, 0);
       +        return b;
       +}
       +
       +static Word*
       +varmatch(char *name, char **s)
       +{
       +        Word *w;
       +        Symtab *sym;
       +        char *cp;
       +        
       +        sym = symlook(name, S_VAR, 0);
       +        if(sym){
       +                        /* check for at least one non-NULL value */
       +                for (w = (Word*)sym->value; w; w = w->next)
       +                        if(w->s && *w->s)
       +                                return wdup(w);
       +        }
       +        for(cp = *s; *cp == ' ' || *cp == '\t'; cp++)        /* skip trailing whitespace */
       +                        ;
       +        *s = cp;
       +        return 0;
       +}
       +
       +static Word*
       +expandvar(char **s)
       +{
       +        Word *w;
       +        Bufblock *buf;
       +        Symtab *sym;
       +        char *cp, *begin, *end;
       +
       +        begin = *s;
       +        (*s)++;                                                /* skip the '{' */
       +        buf = varname(s);
       +        if (buf == 0)
       +                return 0;
       +        cp = *s;
       +        if (*cp == '}') {                                /* ${name} variant*/
       +                (*s)++;                                        /* skip the '}' */
       +                w = varmatch(buf->start, s);
       +                freebuf(buf);
       +                return w;
       +        }
       +        if (*cp != ':') {
       +                SYNERR(-1);
       +                fprint(2, "bad variable name <%s>\n", buf->start);
       +                freebuf(buf);
       +                return 0;
       +        }
       +        cp++;
       +        end = charin(cp , "}");
       +        if(end == 0){
       +                SYNERR(-1);
       +                fprint(2, "missing '}': %s\n", begin);
       +                Exit();
       +        }
       +        *end = 0;
       +        *s = end+1;
       +        
       +        sym = symlook(buf->start, S_VAR, 0);
       +        if(sym == 0 || sym->value == 0)
       +                w = newword(buf->start);
       +        else
       +                w = subsub((Word*) sym->value, cp, end);
       +        freebuf(buf);
       +        return w;
       +}
       +
       +static Word*
       +extractpat(char *s, char **r, char *term, char *end)
       +{
       +        int save;
       +        char *cp;
       +        Word *w;
       +
       +        cp = charin(s, term);
       +        if(cp){
       +                *r = cp;
       +                if(cp == s)
       +                        return 0;
       +                save = *cp;
       +                *cp = 0;
       +                w = stow(s);
       +                *cp = save;
       +        } else {
       +                *r = end;
       +                w = stow(s);
       +        }
       +        return w;
       +}
       +
       +static Word*
       +subsub(Word *v, char *s, char *end)
       +{
       +        int nmid;
       +        Word *head, *tail, *w, *h;
       +        Word *a, *b, *c, *d;
       +        Bufblock *buf;
       +        char *cp, *enda;
       +
       +        a = extractpat(s, &cp, "=%&", end);
       +        b = c = d = 0;
       +        if(PERCENT(*cp))
       +                b = extractpat(cp+1, &cp, "=", end);
       +        if(*cp == '=')
       +                c = extractpat(cp+1, &cp, "&%", end);
       +        if(PERCENT(*cp))
       +                d = stow(cp+1);
       +        else if(*cp)
       +                d = stow(cp);
       +
       +        head = tail = 0;
       +        buf = newbuf();
       +        for(; v; v = v->next){
       +                h = w = 0;
       +                if(submatch(v->s, a, b, &nmid, &enda)){
       +                        /* enda points to end of A match in source;
       +                         * nmid = number of chars between end of A and start of B
       +                         */
       +                        if(c){
       +                                h = w = wdup(c);
       +                                while(w->next)
       +                                        w = w->next;
       +                        }
       +                        if(PERCENT(*cp) && nmid > 0){        
       +                                if(w){
       +                                        bufcpy(buf, w->s, strlen(w->s));
       +                                        bufcpy(buf, enda, nmid);
       +                                        insert(buf, 0);
       +                                        free(w->s);
       +                                        w->s = strdup(buf->start);
       +                                } else {
       +                                        bufcpy(buf, enda, nmid);
       +                                        insert(buf, 0);
       +                                        h = w = newword(buf->start);
       +                                }
       +                                buf->current = buf->start;
       +                        }
       +                        if(d && *d->s){
       +                                if(w){
       +
       +                                        bufcpy(buf, w->s, strlen(w->s));
       +                                        bufcpy(buf, d->s, strlen(d->s));
       +                                        insert(buf, 0);
       +                                        free(w->s);
       +                                        w->s = strdup(buf->start);
       +                                        w->next = wdup(d->next);
       +                                        while(w->next)
       +                                                w = w->next;
       +                                        buf->current = buf->start;
       +                                } else
       +                                        h = w = wdup(d);
       +                        }
       +                }
       +                if(w == 0)
       +                        h = w = newword(v->s);
       +        
       +                if(head == 0)
       +                        head = h;
       +                else
       +                        tail->next = h;
       +                tail = w;
       +        }
       +        freebuf(buf);
       +        delword(a);
       +        delword(b);
       +        delword(c);
       +        delword(d);
       +        return head;
       +}
       +
       +static int
       +submatch(char *s, Word *a, Word *b, int *nmid, char **enda)
       +{
       +        Word *w;
       +        int n;
       +        char *end;
       +
       +        n = 0;
       +        for(w = a; w; w = w->next){
       +                n = strlen(w->s);
       +                if(strncmp(s, w->s, n) == 0)
       +                        break;
       +        }
       +        if(a && w == 0)                /*  a == NULL matches everything*/
       +                return 0;
       +
       +        *enda = s+n;                /* pointer to end a A part match */
       +        *nmid = strlen(s)-n;        /* size of remainder of source */
       +        end = *enda+*nmid;
       +        for(w = b; w; w = w->next){
       +                n = strlen(w->s);
       +                if(strcmp(w->s, end-n) == 0){
       +                        *nmid -= n;
       +                        break;
       +                }
       +        }
       +        if(b && w == 0)                /* b == NULL matches everything */
       +                return 0;
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/mk/word.c b/src/cmd/mk/word.c
       t@@ -0,0 +1,180 @@
       +#include        "mk.h"
       +
       +static        Word        *nextword(char**);
       +
       +Word*
       +newword(char *s)
       +{
       +        Word *w;
       +
       +        w = (Word *)Malloc(sizeof(Word));
       +        w->s = strdup(s);
       +        w->next = 0;
       +        return(w);
       +}
       +
       +Word *
       +stow(char *s)
       +{
       +        Word *head, *w, *new;
       +
       +        w = head = 0;
       +        while(*s){
       +                new = nextword(&s);
       +                if(new == 0)
       +                        break;
       +                if (w)
       +                        w->next = new;
       +                else
       +                        head = w = new;
       +                while(w->next)
       +                        w = w->next;
       +                
       +        }
       +        if (!head)
       +                head = newword("");
       +        return(head);
       +}
       +
       +char *
       +wtos(Word *w, int sep)
       +{
       +        Bufblock *buf;
       +        char *cp;
       +
       +        buf = newbuf();
       +        for(; w; w = w->next){
       +                for(cp = w->s; *cp; cp++)
       +                        insert(buf, *cp);
       +                if(w->next)
       +                        insert(buf, sep);
       +        }
       +        insert(buf, 0);
       +        cp = strdup(buf->start);
       +        freebuf(buf);
       +        return(cp);
       +}
       +
       +Word*
       +wdup(Word *w)
       +{
       +        Word *v, *new, *base;
       +
       +        v = base = 0;
       +        while(w){
       +                new = newword(w->s);
       +                if(v)
       +                        v->next = new;
       +                else
       +                        base = new;
       +                v = new;
       +                w = w->next;
       +        }
       +        return base;
       +}
       +
       +void
       +delword(Word *w)
       +{
       +        Word *v;
       +
       +        while(v = w){
       +                w = w->next;
       +                if(v->s)
       +                        free(v->s);
       +                free(v);
       +        }
       +}
       +
       +/*
       + *        break out a word from a string handling quotes, executions,
       + *        and variable expansions.
       + */
       +static Word*
       +nextword(char **s)
       +{
       +        Bufblock *b;
       +        Word *head, *tail, *w;
       +        Rune r;
       +        char *cp;
       +
       +        cp = *s;
       +        b = newbuf();
       +        head = tail = 0;
       +        while(*cp == ' ' || *cp == '\t')                /* leading white space */
       +                cp++;
       +        while(*cp){
       +                cp += chartorune(&r, cp);
       +                switch(r)
       +                {
       +                case ' ':
       +                case '\t':
       +                case '\n':
       +                        goto out;
       +                case '\\':
       +                case '\'':
       +                case '"':
       +                        cp = expandquote(cp, r, b);
       +                        if(cp == 0){
       +                                fprint(2, "missing closing quote: %s\n", *s);
       +                                Exit();
       +                        }
       +                        break;
       +                case '$':
       +                        w = varsub(&cp);
       +                        if(w == 0)
       +                                break;
       +                        if(b->current != b->start){
       +                                bufcpy(b, w->s, strlen(w->s));
       +                                insert(b, 0);
       +                                free(w->s);
       +                                w->s = strdup(b->start);
       +                                b->current = b->start;
       +                        }
       +                        if(head){
       +                                bufcpy(b, tail->s, strlen(tail->s));
       +                                bufcpy(b, w->s, strlen(w->s));
       +                                insert(b, 0);
       +                                free(tail->s);
       +                                tail->s = strdup(b->start);
       +                                tail->next = w->next;
       +                                free(w->s);
       +                                free(w);
       +                                b->current = b->start;
       +                        } else
       +                                tail = head = w;
       +                        while(tail->next)
       +                                tail = tail->next;
       +                        break;
       +                default:
       +                        rinsert(b, r);
       +                        break;
       +                }
       +        }
       +out:
       +        *s = cp;
       +        if(b->current != b->start){
       +                if(head){
       +                        cp = b->current;
       +                        bufcpy(b, tail->s, strlen(tail->s));
       +                        bufcpy(b, b->start, cp-b->start);
       +                        insert(b, 0);
       +                        free(tail->s);
       +                        tail->s = strdup(cp);
       +                } else {
       +                        insert(b, 0);
       +                        head = newword(b->start);
       +                }
       +        }
       +        freebuf(b);
       +        return head;
       +}
       +
       +void
       +dumpw(char *s, Word *w)
       +{
       +        Bprint(&bout, "%s", s);
       +        for(; w; w = w->next)
       +                Bprint(&bout, " '%s'", w->s);
       +        Bputc(&bout, '\n');
       +}
 (DIR) diff --git a/src/cmd/sam/LICENSE b/src/cmd/sam/LICENSE
       t@@ -0,0 +1,258 @@
       +The Plan 9 software is provided under the terms of the
       +Lucent Public License, Version 1.02, reproduced below,
       +with the following exceptions:
       +
       +1. No right is granted to create derivative works of or
       +   to redistribute (other than with the Plan 9 Operating System)
       +   the screen imprinter fonts identified in subdirectory
       +   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
       +   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
       +   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
       +   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
       +
       +2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
       +   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
       +
       +3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
       +   covered by the Aladdin Free Public License, reproduced in the file
       +   /LICENSE.afpl.
       +
       +===================================================================
       +
       +Lucent Public License Version 1.02
       +
       +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
       +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
       +PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
       +
       +1. DEFINITIONS
       +
       +"Contribution" means:
       +
       +  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
       +     Program, and
       +  b. in the case of each Contributor,
       +
       +     i. changes to the Program, and
       +    ii. additions to the Program;
       +
       +    where such changes and/or additions to the Program were added to the
       +    Program by such Contributor itself or anyone acting on such
       +    Contributor's behalf, and the Contributor explicitly consents, in
       +    accordance with Section 3C, to characterization of the changes and/or
       +    additions as Contributions.
       +
       +"Contributor" means LUCENT and any other entity that has Contributed a
       +Contribution to the Program.
       +
       +"Distributor" means a Recipient that distributes the Program,
       +modifications to the Program, or any part thereof.
       +
       +"Licensed Patents" mean patent claims licensable by a Contributor
       +which are necessarily infringed by the use or sale of its Contribution
       +alone or when combined with the Program.
       +
       +"Original Program" means the original version of the software
       +accompanying this Agreement as released by LUCENT, including source
       +code, object code and documentation, if any.
       +
       +"Program" means the Original Program and Contributions or any part
       +thereof
       +
       +"Recipient" means anyone who receives the Program under this
       +Agreement, including all Contributors.
       +
       +2. GRANT OF RIGHTS
       +
       + a. Subject to the terms of this Agreement, each Contributor hereby
       +    grants Recipient a non-exclusive, worldwide, royalty-free copyright
       +    license to reproduce, prepare derivative works of, publicly display,
       +    publicly perform, distribute and sublicense the Contribution of such
       +    Contributor, if any, and such derivative works, in source code and
       +    object code form.
       +    
       + b. Subject to the terms of this Agreement, each Contributor hereby
       +    grants Recipient a non-exclusive, worldwide, royalty-free patent
       +    license under Licensed Patents to make, use, sell, offer to sell,
       +    import and otherwise transfer the Contribution of such Contributor, if
       +    any, in source code and object code form. The patent license granted
       +    by a Contributor shall also apply to the combination of the
       +    Contribution of that Contributor and the Program if, at the time the
       +    Contribution is added by the Contributor, such addition of the
       +    Contribution causes such combination to be covered by the Licensed
       +    Patents. The patent license granted by a Contributor shall not apply
       +    to (i) any other combinations which include the Contribution, nor to
       +    (ii) Contributions of other Contributors. No hardware per se is
       +    licensed hereunder.
       +    
       + c. Recipient understands that although each Contributor grants the
       +    licenses to its Contributions set forth herein, no assurances are
       +    provided by any Contributor that the Program does not infringe the
       +    patent or other intellectual property rights of any other entity. Each
       +    Contributor disclaims any liability to Recipient for claims brought by
       +    any other entity based on infringement of intellectual property rights
       +    or otherwise. As a condition to exercising the rights and licenses
       +    granted hereunder, each Recipient hereby assumes sole responsibility
       +    to secure any other intellectual property rights needed, if any. For
       +    example, if a third party patent license is required to allow
       +    Recipient to distribute the Program, it is Recipient's responsibility
       +    to acquire that license before distributing the Program.
       +
       + d. Each Contributor represents that to its knowledge it has sufficient
       +    copyright rights in its Contribution, if any, to grant the copyright
       +    license set forth in this Agreement.
       +
       +3. REQUIREMENTS
       +
       +A. Distributor may choose to distribute the Program in any form under
       +this Agreement or under its own license agreement, provided that:
       +
       + a. it complies with the terms and conditions of this Agreement;
       +
       + b. if the Program is distributed in source code or other tangible
       +    form, a copy of this Agreement or Distributor's own license agreement
       +    is included with each copy of the Program; and
       +
       + c. if distributed under Distributor's own license agreement, such
       +    license agreement:
       +
       +      i. effectively disclaims on behalf of all Contributors all warranties
       +         and conditions, express and implied, including warranties or
       +         conditions of title and non-infringement, and implied warranties or
       +         conditions of merchantability and fitness for a particular purpose;
       +     ii. effectively excludes on behalf of all Contributors all liability
       +         for damages, including direct, indirect, special, incidental and
       +         consequential damages, such as lost profits; and
       +    iii. states that any provisions which differ from this Agreement are
       +         offered by that Contributor alone and not by any other party.
       +
       +B. Each Distributor must include the following in a conspicuous
       +   location in the Program:
       +
       +   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
       +   Reserved.
       +
       +C. In addition, each Contributor must identify itself as the
       +originator of its Contribution in a manner that reasonably allows
       +subsequent Recipients to identify the originator of the Contribution.
       +Also, each Contributor must agree that the additions and/or changes
       +are intended to be a Contribution. Once a Contribution is contributed,
       +it may not thereafter be revoked.
       +
       +4. COMMERCIAL DISTRIBUTION
       +
       +Commercial distributors of software may accept certain
       +responsibilities with respect to end users, business partners and the
       +like. While this license is intended to facilitate the commercial use
       +of the Program, the Distributor who includes the Program in a
       +commercial product offering should do so in a manner which does not
       +create potential liability for Contributors. Therefore, if a
       +Distributor includes the Program in a commercial product offering,
       +such Distributor ("Commercial Distributor") hereby agrees to defend
       +and indemnify every Contributor ("Indemnified Contributor") against
       +any losses, damages and costs (collectively"Losses") arising from
       +claims, lawsuits and other legal actions brought by a third party
       +against the Indemnified Contributor to the extent caused by the acts
       +or omissions of such Commercial Distributor in connection with its
       +distribution of the Program in a commercial product offering. The
       +obligations in this section do not apply to any claims or Losses
       +relating to any actual or alleged intellectual property infringement.
       +In order to qualify, an Indemnified Contributor must: a) promptly
       +notify the Commercial Distributor in writing of such claim, and b)
       +allow the Commercial Distributor to control, and cooperate with the
       +Commercial Distributor in, the defense and any related settlement
       +negotiations. The Indemnified Contributor may participate in any such
       +claim at its own expense.
       +
       +For example, a Distributor might include the Program in a commercial
       +product offering, Product X. That Distributor is then a Commercial
       +Distributor. If that Commercial Distributor then makes performance
       +claims, or offers warranties related to Product X, those performance
       +claims and warranties are such Commercial Distributor's responsibility
       +alone. Under this section, the Commercial Distributor would have to
       +defend claims against the Contributors related to those performance
       +claims and warranties, and if a court requires any Contributor to pay
       +any damages as a result, the Commercial Distributor must pay those
       +damages.
       +
       +5. NO WARRANTY
       +
       +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
       +PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       +KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
       +WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
       +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
       +responsible for determining the appropriateness of using and
       +distributing the Program and assumes all risks associated with its
       +exercise of rights under this Agreement, including but not limited to
       +the risks and costs of program errors, compliance with applicable
       +laws, damage to or loss of data, programs or equipment, and
       +unavailability or interruption of operations.
       +
       +6. DISCLAIMER OF LIABILITY
       +
       +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
       +ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
       +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
       +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
       +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
       +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
       +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
       +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
       +
       +7. EXPORT CONTROL
       +
       +Recipient agrees that Recipient alone is responsible for compliance
       +with the United States export administration regulations (and the
       +export control laws and regulation of any other countries).
       +
       +8. GENERAL
       +
       +If any provision of this Agreement is invalid or unenforceable under
       +applicable law, it shall not affect the validity or enforceability of
       +the remainder of the terms of this Agreement, and without further
       +action by the parties hereto, such provision shall be reformed to the
       +minimum extent necessary to make such provision valid and enforceable.
       +
       +If Recipient institutes patent litigation against a Contributor with
       +respect to a patent applicable to software (including a cross-claim or
       +counterclaim in a lawsuit), then any patent licenses granted by that
       +Contributor to such Recipient under this Agreement shall terminate as
       +of the date such litigation is filed. In addition, if Recipient
       +institutes patent litigation against any entity (including a
       +cross-claim or counterclaim in a lawsuit) alleging that the Program
       +itself (excluding combinations of the Program with other software or
       +hardware) infringes such Recipient's patent(s), then such Recipient's
       +rights granted under Section 2(b) shall terminate as of the date such
       +litigation is filed.
       +
       +All Recipient's rights under this Agreement shall terminate if it
       +fails to comply with any of the material terms or conditions of this
       +Agreement and does not cure such failure in a reasonable period of
       +time after becoming aware of such noncompliance. If all Recipient's
       +rights under this Agreement terminate, Recipient agrees to cease use
       +and distribution of the Program as soon as reasonably practicable.
       +However, Recipient's obligations under this Agreement and any licenses
       +granted by Recipient relating to the Program shall continue and
       +survive.
       +
       +LUCENT may publish new versions (including revisions) of this
       +Agreement from time to time. Each new version of the Agreement will be
       +given a distinguishing version number. The Program (including
       +Contributions) may always be distributed subject to the version of the
       +Agreement under which it was received. In addition, after a new
       +version of the Agreement is published, Contributor may elect to
       +distribute the Program (including its Contributions) under the new
       +version. No one other than LUCENT has the right to modify this
       +Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
       +Recipient receives no rights or licenses to the intellectual property
       +of any Contributor under this Agreement, whether expressly, by
       +implication, estoppel or otherwise. All rights in the Program not
       +expressly granted under this Agreement are reserved.
       +
       +This Agreement is governed by the laws of the State of New York and
       +the intellectual property laws of the United States of America. No
       +party to this Agreement will bring a legal action under this Agreement
       +more than one year after the cause of action arose. Each party waives
       +its rights to a jury trial in any resulting litigation.
       +
 (DIR) diff --git a/src/cmd/sam/Makefile b/src/cmd/sam/Makefile
       t@@ -0,0 +1,18 @@
       +H=errors.h mesg.h parse.h plumb.h sam.h
       +SRC= address.c buff.c cmd.c disk.c error.c file.c io.c\
       +     list.c mesg.c moveto.c multi.c unix.c rasp.c regexp.c\
       +     sam.c shell.c string.c sys.c util.c xec.c plumb.c
       +
       +CC=gcc
       +PREFIX=$(HOME)
       +#PREFIX=/usr/local
       +CFLAGS=-I. -I$(PREFIX)/include -O -g
       +LDFLAGS=-L$(PREFIX)/lib
       +LDLIBS=-l9 -lfmt -lutf
       +
       +all: sam
       +sam: $(SRC) $(H)
       +        $(CC) -o $@ $(CFLAGS) $(SRC) $(LDFLAGS) $(LDLIBS)
       +clean:
       +        rm -f *.o *~
       +        rm -f sam
 (DIR) diff --git a/src/cmd/sam/address.c b/src/cmd/sam/address.c
       t@@ -0,0 +1,240 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +Address        addr;
       +String        lastpat;
       +int        patset;
       +File        *menu;
       +
       +File        *matchfile(String*);
       +Address        charaddr(Posn, Address, int);
       +
       +Address
       +address(Addr *ap, Address a, int sign)
       +{
       +        File *f = a.f;
       +        Address a1, a2;
       +
       +        do{
       +                switch(ap->type){
       +                case 'l':
       +                case '#':
       +                        a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
       +                        break;
       +
       +                case '.':
       +                        a = f->dot;
       +                        break;
       +
       +                case '$':
       +                        a.r.p1 = a.r.p2 = f->_.nc;
       +                        break;
       +
       +                case '\'':
       +                        a.r = f->mark;
       +                        break;
       +
       +                case '?':
       +                        sign = -sign;
       +                        if(sign == 0)
       +                                sign = -1;
       +                        /* fall through */
       +                case '/':
       +                        nextmatch(f, ap->are, sign>=0? a.r.p2 : a.r.p1, sign);
       +                        a.r = sel.p[0];
       +                        break;
       +
       +                case '"':
       +                        a = matchfile(ap->are)->dot;
       +                        f = a.f;
       +                        if(f->unread)
       +                                load(f);
       +                        break;
       +
       +                case '*':
       +                        a.r.p1 = 0, a.r.p2 = f->_.nc;
       +                        return a;
       +
       +                case ',':
       +                case ';':
       +                        if(ap->left)
       +                                a1 = address(ap->left, a, 0);
       +                        else
       +                                a1.f = a.f, a1.r.p1 = a1.r.p2 = 0;
       +                        if(ap->type == ';'){
       +                                f = a1.f;
       +                                a = a1;
       +                                f->dot = a1;
       +                        }
       +                        if(ap->next)
       +                                a2 = address(ap->next, a, 0);
       +                        else
       +                                a2.f = a.f, a2.r.p1 = a2.r.p2 = f->_.nc;
       +                        if(a1.f != a2.f)
       +                                error(Eorder);
       +                        a.f = a1.f, a.r.p1 = a1.r.p1, a.r.p2 = a2.r.p2;
       +                        if(a.r.p2 < a.r.p1)
       +                                error(Eorder);
       +                        return a;
       +
       +                case '+':
       +                case '-':
       +                        sign = 1;
       +                        if(ap->type == '-')
       +                                sign = -1;
       +                        if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
       +                                a = lineaddr(1L, a, sign);
       +                        break;
       +                default:
       +                        panic("address");
       +                        return a;
       +                }
       +        }while(ap = ap->next);        /* assign = */
       +        return a;
       +}
       +
       +void
       +nextmatch(File *f, String *r, Posn p, int sign)
       +{
       +        compile(r);
       +        if(sign >= 0){
       +                if(!execute(f, p, INFINITY))
       +                        error(Esearch);
       +                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p1==p){
       +                        if(++p>f->_.nc)
       +                                p = 0;
       +                        if(!execute(f, p, INFINITY))
       +                                panic("address");
       +                }
       +        }else{
       +                if(!bexecute(f, p))
       +                        error(Esearch);
       +                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p2==p){
       +                        if(--p<0)
       +                                p = f->_.nc;
       +                        if(!bexecute(f, p))
       +                                panic("address");
       +                }
       +        }
       +}
       +
       +File *
       +matchfile(String *r)
       +{
       +        File *f;
       +        File *match = 0;
       +        int i;
       +
       +        for(i = 0; 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;
       +}
 (DIR) diff --git a/src/cmd/sam/buff.c b/src/cmd/sam/buff.c
       t@@ -0,0 +1,302 @@
       +#include "sam.h"
       +
       +enum
       +{
       +        Slop = 100,        /* room to grow with reallocation */
       +};
       +
       +static
       +void
       +sizecache(Buffer *b, uint n)
       +{
       +        if(n <= b->cmax)
       +                return;
       +        b->cmax = n+Slop;
       +        b->c = runerealloc(b->c, b->cmax);
       +}
       +
       +static
       +void
       +addblock(Buffer *b, uint i, uint n)
       +{
       +        if(i > b->nbl)
       +                panic("internal error: addblock");
       +
       +        b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
       +        if(i < b->nbl)
       +                memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
       +        b->bl[i] = disknewblock(disk, n);
       +        b->nbl++;
       +}
       +
       +
       +static
       +void
       +delblock(Buffer *b, uint i)
       +{
       +        if(i >= b->nbl)
       +                panic("internal error: delblock");
       +
       +        diskrelease(disk, b->bl[i]);
       +        b->nbl--;
       +        if(i < b->nbl)
       +                memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
       +        b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
       +}
       +
       +/*
       + * Move cache so b->cq <= q0 < b->cq+b->cnc.
       + * If at very end, q0 will fall on end of cache block.
       + */
       +
       +static
       +void
       +flush(Buffer *b)
       +{
       +        if(b->cdirty || b->cnc==0){
       +                if(b->cnc == 0)
       +                        delblock(b, b->cbi);
       +                else
       +                        diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
       +                b->cdirty = FALSE;
       +        }
       +}
       +
       +static
       +void
       +setcache(Buffer *b, uint q0)
       +{
       +        Block **blp, *bl;
       +        uint i, q;
       +
       +        if(q0 > b->nc)
       +                panic("internal error: setcache");
       +        /*
       +         * flush and reload if q0 is not in cache.
       +         */
       +        if(b->nc == 0 || (b->cq<=q0 && 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;
       +}
 (DIR) diff --git a/src/cmd/sam/cmd.c b/src/cmd/sam/cmd.c
       t@@ -0,0 +1,594 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +static char        linex[]="\n";
       +static char        wordx[]=" \t\n";
       +struct cmdtab cmdtab[]={
       +/*        cmdc        text        regexp        addr        defcmd        defaddr        count        token         fn        */
       +        '\n',        0,        0,        0,        0,        aDot,        0,        0,        nl_cmd,
       +        'a',        1,        0,        0,        0,        aDot,        0,        0,        a_cmd,
       +        'b',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
       +        'B',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
       +        'c',        1,        0,        0,        0,        aDot,        0,        0,        c_cmd,
       +        'd',        0,        0,        0,        0,        aDot,        0,        0,        d_cmd,
       +        'D',        0,        0,        0,        0,        aNo,        0,        linex,        D_cmd,
       +        'e',        0,        0,        0,        0,        aNo,        0,        wordx,        e_cmd,
       +        'f',        0,        0,        0,        0,        aNo,        0,        wordx,        f_cmd,
       +        'g',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
       +        'i',        1,        0,        0,        0,        aDot,        0,        0,        i_cmd,
       +        'k',        0,        0,        0,        0,        aDot,        0,        0,        k_cmd,
       +        'm',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
       +        'n',        0,        0,        0,        0,        aNo,        0,        0,        n_cmd,
       +        'p',        0,        0,        0,        0,        aDot,        0,        0,        p_cmd,
       +        'q',        0,        0,        0,        0,        aNo,        0,        0,        q_cmd,
       +        'r',        0,        0,        0,        0,        aDot,        0,        wordx,        e_cmd,
       +        's',        0,        1,        0,        0,        aDot,        1,        0,        s_cmd,
       +        't',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
       +        'u',        0,        0,        0,        0,        aNo,        2,        0,        u_cmd,
       +        'v',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
       +        'w',        0,        0,        0,        0,        aAll,        0,        wordx,        w_cmd,
       +        'x',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
       +        'y',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
       +        'X',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
       +        'Y',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
       +        '!',        0,        0,        0,        0,        aNo,        0,        linex,        plan9_cmd,
       +        '>',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '<',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '|',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '=',        0,        0,        0,        0,        aDot,        0,        linex,        eq_cmd,
       +        'c'|0x100,0,        0,        0,        0,        aNo,        0,        wordx,        cd_cmd,
       +        0,        0,        0,        0,        0,        0,        0,        0,
       +};
       +Cmd        *parsecmd(int);
       +Addr        *compoundaddr(void);
       +Addr        *simpleaddr(void);
       +void        freecmd(void);
       +void        okdelim(int);
       +
       +Rune        line[BLOCKSIZE];
       +Rune        termline[BLOCKSIZE];
       +Rune        *linep = line;
       +Rune        *terminp = termline;
       +Rune        *termoutp = termline;
       +List        cmdlist;
       +List        addrlist;
       +List        relist;
       +List        stringlist;
       +int        eof;
       +
       +void
       +resetcmd(void)
       +{
       +        linep = line;
       +        *linep = 0;
       +        terminp = termoutp = termline;
       +        freecmd();
       +}
       +
       +int
       +inputc(void)
       +{
       +        int n, nbuf;
       +        char buf[3];
       +        Rune r;
       +
       +    Again:
       +        nbuf = 0;
       +        if(downloaded){
       +                while(termoutp == terminp){
       +                        cmdupdate();
       +                        if(patset)
       +                                tellpat();
       +                        while(termlocked > 0){
       +                                outT0(Hunlock);
       +                                termlocked--;
       +                        }
       +                        if(rcv() == 0)
       +                                return -1;
       +                }
       +                r = *termoutp++;
       +                if(termoutp == terminp)
       +                        terminp = termoutp = termline;
       +        }else{
       +                   do{
       +                        n = read(0, buf+nbuf, 1);
       +                        if(n <= 0)
       +                                return -1;
       +                        nbuf += n;
       +                }while(!fullrune(buf, nbuf));
       +                chartorune(&r, buf);
       +        }
       +        if(r == 0){
       +                warn(Wnulls);
       +                goto Again;
       +        }
       +        return r;
       +}
       +
       +int
       +inputline(void)
       +{
       +        int i, c;
       +
       +        linep = line;
       +        i = 0;
       +        do{
       +                if((c = inputc())<=0)
       +                        return -1;
       +                if(i == (sizeof line)/RUNESIZE-1)
       +                        error(Etoolong);
       +        }while((line[i++]=c) != '\n');
       +        line[i] = 0;
       +        return 1;
       +}
       +
       +int
       +getch(void)
       +{
       +        if(eof)
       +                return -1;
       +        if(*linep==0 && inputline()<0){
       +                eof = TRUE;
       +                return -1;
       +        }
       +        return *linep++;
       +}
       +
       +int
       +nextc(void)
       +{
       +        if(*linep == 0)
       +                return -1;
       +        return *linep;
       +}
       +
       +void
       +ungetch(void)
       +{
       +        if(--linep < line)
       +                panic("ungetch");
       +}
       +
       +Posn
       +getnum(int signok)
       +{
       +        Posn n=0;
       +        int c, sign;
       +
       +        sign = 1;
       +        if(signok>1 && nextc()=='-'){
       +                sign = -1;
       +                getch();
       +        }
       +        if((c=nextc())<'0' || '9'<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;
       +}
 (DIR) diff --git a/src/cmd/sam/disk.c b/src/cmd/sam/disk.c
       t@@ -0,0 +1,122 @@
       +#include "sam.h"
       +
       +static        Block        *blist;
       +
       +#if 0
       +static int
       +tempdisk(void)
       +{
       +        char buf[128];
       +        int i, fd;
       +
       +        snprint(buf, sizeof buf, "/tmp/X%d.%.4ssam", getpid(), getuser());
       +        for(i='A'; i<='Z'; i++){
       +                buf[5] = i;
       +                if(access(buf, AEXIST) == 0)
       +                        continue;
       +                fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
       +                if(fd >= 0)
       +                        return fd;
       +        }
       +        return -1;
       +}
       +#else
       +extern int tempdisk(void);
       +#endif
       +
       +Disk*
       +diskinit()
       +{
       +        Disk *d;
       +
       +        d = emalloc(sizeof(Disk));
       +        d->fd = tempdisk();
       +        if(d->fd < 0){
       +                fprint(2, "sam: can't create temp file: %r\n");
       +                exits("diskinit");
       +        }
       +        return d;
       +}
       +
       +static
       +uint
       +ntosize(uint n, uint *ip)
       +{
       +        uint size;
       +
       +        if(n > Maxblock)
       +                panic("internal error: ntosize");
       +        size = n;
       +        if(size & (Blockincr-1))
       +                size += Blockincr - (size & (Blockincr-1));
       +        /* last bucket holds blocks of exactly Maxblock */
       +        if(ip)
       +                *ip = size/Blockincr;
       +        return size * sizeof(Rune);
       +}
       +
       +Block*
       +disknewblock(Disk *d, uint n)
       +{
       +        uint i, j, size;
       +        Block *b;
       +
       +        size = ntosize(n, &i);
       +        b = d->free[i];
       +        if(b)
       +                d->free[i] = b->_.next;
       +        else{
       +                /* allocate in chunks to reduce malloc overhead */
       +                if(blist == nil){
       +                        blist = emalloc(100*sizeof(Block));
       +                        for(j=0; j<100-1; j++)
       +                                blist[j]._.next = &blist[j+1];
       +                }
       +                b = blist;
       +                blist = b->_.next;
       +                b->addr = d->addr;
       +                d->addr += size;
       +        }
       +        b->_.n = n;
       +        return b;
       +}
       +
       +void
       +diskrelease(Disk *d, Block *b)
       +{
       +        uint i;
       +
       +        ntosize(b->_.n, &i);
       +        b->_.next = d->free[i];
       +        d->free[i] = b;
       +}
       +
       +void
       +diskwrite(Disk *d, Block **bp, Rune *r, uint n)
       +{
       +        int size, nsize;
       +        Block *b;
       +
       +        b = *bp;
       +        size = ntosize(b->_.n, nil);
       +        nsize = ntosize(n, nil);
       +        if(size != nsize){
       +                diskrelease(d, b);
       +                b = disknewblock(d, n);
       +                *bp = b;
       +        }
       +        if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
       +                panic("write error to temp file");
       +        b->_.n = n;
       +}
       +
       +void
       +diskread(Disk *d, Block *b, Rune *r, uint n)
       +{
       +        if(n > b->_.n)
       +                panic("internal error: diskread");
       +
       +        ntosize(b->_.n, nil);        /* called only for sanity check on Maxblock */
       +        if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
       +                panic("read error from temp file");
       +}
 (DIR) diff --git a/src/cmd/sam/error.c b/src/cmd/sam/error.c
       t@@ -0,0 +1,144 @@
       +#include "sam.h"
       +
       +static char *emsg[]={
       +        /* error_s */
       +        "can't open",
       +        "can't create",
       +        "not in menu:",
       +        "changes to",
       +        "I/O error:",
       +        "can't write while changing:",
       +        /* error_c */
       +        "unknown command",
       +        "no operand for",
       +        "bad delimiter",
       +        /* error */
       +        "can't fork",
       +        "interrupt",
       +        "address",
       +        "search",
       +        "pattern",
       +        "newline expected",
       +        "blank expected",
       +        "pattern expected",
       +        "can't nest X or Y",
       +        "unmatched `}'",
       +        "command takes no address",
       +        "addresses overlap",
       +        "substitution",
       +        "& match too long",
       +        "bad \\ in rhs",
       +        "address range",
       +        "changes not in sequence",
       +        "addresses out of order",
       +        "no file name",
       +        "unmatched `('",
       +        "unmatched `)'",
       +        "malformed `[]'",
       +        "malformed regexp",
       +        "reg. exp. list overflow",
       +        "plan 9 command",
       +        "can't pipe",
       +        "no current file",
       +        "string too long",
       +        "changed files",
       +        "empty string",
       +        "file search",
       +        "non-unique match for \"\"",
       +        "tag match too long",
       +        "too many subexpressions",
       +        "temporary file too large",
       +        "file is append-only",
       +        "no destination for plumb message",
       +        "internal read error in buffer load",
       +};
       +static char *wmsg[]={
       +        /* warn_s */
       +        "duplicate file name",
       +        "no such file",
       +        "write might change good version of",
       +        /* warn_S */
       +        "files might be aliased",
       +        /* warn */
       +        "null characters elided",
       +        "can't run pwd",
       +        "last char not newline",
       +        "exit status not 0",
       +};
       +
       +void
       +error(Err s)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s", emsg[s]);
       +        hiccough(buf);
       +}
       +
       +void
       +error_s(Err s, char *a)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s \"%s\"", emsg[s], a);
       +        hiccough(buf);
       +}
       +
       +void
       +error_r(Err s, char *a)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s \"%s\": %r", emsg[s], a);
       +        hiccough(buf);
       +}
       +
       +void
       +error_c(Err s, int c)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s `%C'", emsg[s], c);
       +        hiccough(buf);
       +}
       +
       +void
       +warn(Warn s)
       +{
       +        dprint("?warning: %s\n", wmsg[s]);
       +}
       +
       +void
       +warn_S(Warn s, String *a)
       +{
       +        print_s(wmsg[s], a);
       +}
       +
       +void
       +warn_SS(Warn s, String *a, String *b)
       +{
       +        print_ss(wmsg[s], a, b);
       +}
       +
       +void
       +warn_s(Warn s, char *a)
       +{
       +        dprint("?warning: %s `%s'\n", wmsg[s], a);
       +}
       +
       +void
       +termwrite(char *s)
       +{
       +        String *p;
       +
       +        if(downloaded){
       +                p = tmpcstr(s);
       +                if(cmd)
       +                        loginsert(cmd, cmdpt, p->s, p->n);
       +                else
       +                        Strinsert(&cmdstr, p, cmdstr.n);
       +                cmdptadv += p->n;
       +                free(p);
       +        }else
       +                Write(2, s, strlen(s));
       +}
 (DIR) diff --git a/src/cmd/sam/errors.h b/src/cmd/sam/errors.h
       t@@ -0,0 +1,65 @@
       +typedef enum Err{
       +        /* error_s */
       +        Eopen,
       +        Ecreate,
       +        Emenu,
       +        Emodified,
       +        Eio,
       +        Ewseq,
       +        /* error_c */
       +        Eunk,
       +        Emissop,
       +        Edelim,
       +        /* error */
       +        Efork,
       +        Eintr,
       +        Eaddress,
       +        Esearch,
       +        Epattern,
       +        Enewline,
       +        Eblank,
       +        Enopattern,
       +        EnestXY,
       +        Enolbrace,
       +        Enoaddr,
       +        Eoverlap,
       +        Enosub,
       +        Elongrhs,
       +        Ebadrhs,
       +        Erange,
       +        Esequence,
       +        Eorder,
       +        Enoname,
       +        Eleftpar,
       +        Erightpar,
       +        Ebadclass,
       +        Ebadregexp,
       +        Eoverflow,
       +        Enocmd,
       +        Epipe,
       +        Enofile,
       +        Etoolong,
       +        Echanges,
       +        Eempty,
       +        Efsearch,
       +        Emanyfiles,
       +        Elongtag,
       +        Esubexp,
       +        Etmpovfl,
       +        Eappend,
       +        Ecantplumb,
       +        Ebufload,
       +}Err;
       +typedef enum Warn{
       +        /* warn_s */
       +        Wdupname,
       +        Wfile,
       +        Wdate,
       +        /* warn_ss */
       +        Wdupfile,
       +        /* warn */
       +        Wnulls,
       +        Wpwd,
       +        Wnotnewline,
       +        Wbadstatus,
       +}Warn;
 (DIR) diff --git a/src/cmd/sam/file.c b/src/cmd/sam/file.c
       t@@ -0,0 +1,631 @@
       +#include "sam.h"
       +
       +/*
       + * Structure of Undo list:
       + *         The Undo structure follows any associated data, so the list
       + *        can be read backwards: read the structure, then read whatever
       + *        data is associated (insert string, file name) and precedes it.
       + *        The structure includes the previous value of the modify bit
       + *        and a sequence number; successive Undo structures with the
       + *        same sequence number represent simultaneous changes.
       + */
       +
       +typedef struct Undo Undo;
       +typedef struct Merge Merge;
       +
       +struct Undo
       +{
       +        short        type;                /* Delete, Insert, Filename, Dot, Mark */
       +        short        mod;                /* modify bit */
       +        uint        seq;                /* sequence number */
       +        uint        p0;                /* location of change (unused in f) */
       +        uint        n;                /* # runes in string or file name */
       +};
       +
       +struct Merge
       +{
       +        File        *f;
       +        uint        seq;                /* of logged change */
       +        uint        p0;                /* location of change (unused in f) */
       +        uint        n;                /* # runes to delete */
       +        uint        nbuf;                /* # runes to insert */
       +        Rune        buf[RBUFSIZE];
       +};
       +
       +enum
       +{
       +        Maxmerge = 50,
       +        Undosize = sizeof(Undo)/sizeof(Rune),
       +};
       +
       +static Merge        merge;
       +
       +File*
       +fileopen(void)
       +{
       +        File *f;
       +
       +        f = emalloc(sizeof(File));
       +        f->dot.f = f;
       +        f->ndot.f = f;
       +        f->seq = 0;
       +        f->mod = FALSE;
       +        f->unread = TRUE;
       +        Strinit0(&f->name);
       +        return f;
       +}
       +
       +int
       +fileisdirty(File *f)
       +{
       +        return f->seq != f->cleanseq;
       +}
       +
       +static void
       +wrinsert(Buffer *delta, int seq, int mod, uint p0, Rune *s, uint ns)
       +{
       +        Undo u;
       +
       +        u.type = Insert;
       +        u.mod = mod;
       +        u.seq = seq;
       +        u.p0 = p0;
       +        u.n = ns;
       +        bufinsert(delta, delta->nc, s, ns);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +static void
       +wrdelete(Buffer *delta, int seq, int mod, uint p0, uint p1)
       +{
       +        Undo u;
       +
       +        u.type = Delete;
       +        u.mod = mod;
       +        u.seq = seq;
       +        u.p0 = p0;
       +        u.n = p1 - p0;
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +void
       +flushmerge(void)
       +{
       +        File *f;
       +
       +        f = merge.f;
       +        if(f == nil)
       +                return;
       +        if(merge.seq != f->seq)
       +                panic("flushmerge seq mismatch");
       +        if(merge.n != 0)
       +                wrdelete(&f->epsilon, f->seq, TRUE, merge.p0, merge.p0+merge.n);
       +        if(merge.nbuf != 0)
       +                wrinsert(&f->epsilon, f->seq, TRUE, merge.p0+merge.n, merge.buf, merge.nbuf);
       +        merge.f = nil;
       +        merge.n = 0;
       +        merge.nbuf = 0;
       +}
       +
       +void
       +mergeextend(File *f, uint p0)
       +{
       +        uint mp0n;
       +
       +        mp0n = merge.p0+merge.n;
       +        if(mp0n != p0){
       +                bufread(f, mp0n, merge.buf+merge.nbuf, p0-mp0n);
       +                merge.nbuf += p0-mp0n;
       +                merge.n = p0-merge.p0;
       +        }
       +}
       +
       +/*
       + * like fileundelete, but get the data from arguments
       + */
       +void
       +loginsert(File *f, uint p0, Rune *s, uint ns)
       +{
       +        if(f->rescuing)
       +                return;
       +        if(ns == 0)
       +                return;
       +        if(ns<0 || ns>STRSIZE)
       +                panic("loginsert");
       +        if(f->seq < seq)
       +                filemark(f);
       +        if(p0 < f->hiposn)
       +                error(Esequence);
       +
       +        if(merge.f != f
       +        || p0-(merge.p0+merge.n)>Maxmerge                        /* too far */
       +        || merge.nbuf+((p0+ns)-(merge.p0+merge.n))>RBUFSIZE)        /* too long */
       +                flushmerge();
       +
       +        if(ns>=RBUFSIZE){
       +                if(!(merge.n == 0 && merge.nbuf == 0 && merge.f == nil))
       +                        panic("loginsert bad merge state");
       +                wrinsert(&f->epsilon, f->seq, TRUE, p0, s, ns);
       +        }else{
       +                if(merge.f != f){
       +                        merge.f = f;
       +                        merge.p0 = p0;
       +                        merge.seq = f->seq;
       +                }
       +                mergeextend(f, p0);
       +
       +                /* append string to merge */
       +                runemove(merge.buf+merge.nbuf, s, ns);
       +                merge.nbuf += ns;
       +        }
       +
       +        f->hiposn = p0;
       +        if(!f->unread && !f->mod)
       +                state(f, Dirty);
       +}
       +
       +void
       +logdelete(File *f, uint p0, uint p1)
       +{
       +        if(f->rescuing)
       +                return;
       +        if(p0 == p1)
       +                return;
       +        if(f->seq < seq)
       +                filemark(f);
       +        if(p0 < f->hiposn)
       +                error(Esequence);
       +
       +        if(merge.f != f
       +        || p0-(merge.p0+merge.n)>Maxmerge                        /* too far */
       +        || merge.nbuf+(p0-(merge.p0+merge.n))>RBUFSIZE){        /* too long */
       +                flushmerge();
       +                merge.f = f;
       +                merge.p0 = p0;
       +                merge.seq = f->seq;
       +        }
       +
       +        mergeextend(f, p0);
       +
       +        /* add to deletion */
       +        merge.n = p1-merge.p0;
       +
       +        f->hiposn = p1;
       +        if(!f->unread && !f->mod)
       +                state(f, Dirty);
       +}
       +
       +/*
       + * like fileunsetname, but get the data from arguments
       + */
       +void
       +logsetname(File *f, String *s)
       +{
       +        Undo u;
       +        Buffer *delta;
       +
       +        if(f->rescuing)
       +                return;
       +
       +        if(f->unread){        /* This is setting initial file name */
       +                filesetname(f, s);
       +                return;
       +        }
       +
       +        if(f->seq < seq)
       +                filemark(f);
       +
       +        /* undo a file name change by restoring old name */
       +        delta = &f->epsilon;
       +        u.type = Filename;
       +        u.mod = TRUE;
       +        u.seq = f->seq;
       +        u.p0 = 0;        /* unused */
       +        u.n = s->n;
       +        if(s->n)
       +                bufinsert(delta, delta->nc, s->s, s->n);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +        if(!f->unread && !f->mod)
       +                state(f, Dirty);
       +}
       +
       +#ifdef NOTEXT
       +File*
       +fileaddtext(File *f, Text *t)
       +{
       +        if(f == nil){
       +                f = emalloc(sizeof(File));
       +                f->unread = TRUE;
       +        }
       +        f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
       +        f->text[f->ntext++] = t;
       +        f->curtext = t;
       +        return f;
       +}
       +
       +void
       +filedeltext(File *f, Text *t)
       +{
       +        int i;
       +
       +        for(i=0; 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;
       +}
 (DIR) diff --git a/src/cmd/sam/io.c b/src/cmd/sam/io.c
       t@@ -0,0 +1,262 @@
       +#include "sam.h"
       +
       +#define        NSYSFILE        3
       +#define        NOFILE                128
       +
       +void
       +checkqid(File *f)
       +{
       +        int i, w;
       +        File *g;
       +
       +        w = whichmenu(f);
       +        for(i=1; 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);
       +}
 (DIR) diff --git a/src/cmd/sam/list.c b/src/cmd/sam/list.c
       t@@ -0,0 +1,47 @@
       +#include "sam.h"
       +
       +/*
       + * Check that list has room for one more element.
       + */
       +void
       +growlist(List *l)
       +{
       +        if(l->listptr==0 || l->nalloc==0){
       +                l->nalloc = INCR;
       +                l->listptr = emalloc(INCR*sizeof(long));
       +                l->nused = 0;
       +        }else if(l->nused == l->nalloc){
       +                l->listptr = erealloc(l->listptr, (l->nalloc+INCR)*sizeof(long));
       +                memset((void*)(l->longptr+l->nalloc), 0, INCR*sizeof(long));
       +                l->nalloc += INCR;
       +        }
       +}
       +
       +/*
       + * Remove the ith element from the list
       + */
       +void
       +dellist(List *l, int i)
       +{
       +        memmove(&l->longptr[i], &l->longptr[i+1], (l->nused-(i+1))*sizeof(long));
       +        l->nused--;
       +}
       +
       +/*
       + * Add a new element, whose position is i, to the list
       + */
       +void
       +inslist(List *l, int i, long val)
       +{
       +        growlist(l);
       +        memmove(&l->longptr[i+1], &l->longptr[i], (l->nused-i)*sizeof(long));
       +        l->longptr[i] = val;
       +        l->nused++;
       +}
       +
       +void
       +listfree(List *l)
       +{
       +        free(l->listptr);
       +        free(l);
       +}
 (DIR) diff --git a/src/cmd/sam/mesg.c b/src/cmd/sam/mesg.c
       t@@ -0,0 +1,821 @@
       +#include "sam.h"
       +
       +Header        h;
       +uchar        indata[DATASIZE];
       +uchar        outdata[2*DATASIZE+3];        /* room for overflow message */
       +uchar        *inp;
       +uchar        *outp;
       +uchar        *outmsg = outdata;
       +Posn        cmdpt;
       +Posn        cmdptadv;
       +Buffer        snarfbuf;
       +int        waitack;
       +int        noflush;
       +int        tversion;
       +
       +long        inlong(void);
       +long        invlong(void);
       +int        inshort(void);
       +int        inmesg(Tmesg);
       +void        setgenstr(File*, Posn, Posn);
       +
       +#ifdef DEBUG
       +char *hname[] = {
       +        [Hversion]        "Hversion",
       +        [Hbindname]        "Hbindname",
       +        [Hcurrent]        "Hcurrent",
       +        [Hnewname]        "Hnewname",
       +        [Hmovname]        "Hmovname",
       +        [Hgrow]                "Hgrow",
       +        [Hcheck0]        "Hcheck0",
       +        [Hcheck]        "Hcheck",
       +        [Hunlock]        "Hunlock",
       +        [Hdata]                "Hdata",
       +        [Horigin]        "Horigin",
       +        [Hunlockfile]        "Hunlockfile",
       +        [Hsetdot]        "Hsetdot",
       +        [Hgrowdata]        "Hgrowdata",
       +        [Hmoveto]        "Hmoveto",
       +        [Hclean]        "Hclean",
       +        [Hdirty]        "Hdirty",
       +        [Hcut]                "Hcut",
       +        [Hsetpat]        "Hsetpat",
       +        [Hdelname]        "Hdelname",
       +        [Hclose]        "Hclose",
       +        [Hsetsnarf]        "Hsetsnarf",
       +        [Hsnarflen]        "Hsnarflen",
       +        [Hack]                "Hack",
       +        [Hexit]                "Hexit",
       +        [Hplumb]                "Hplumb",
       +};
       +
       +char *tname[] = {
       +        [Tversion]        "Tversion",
       +        [Tstartcmdfile]        "Tstartcmdfile",
       +        [Tcheck]        "Tcheck",
       +        [Trequest]        "Trequest",
       +        [Torigin]        "Torigin",
       +        [Tstartfile]        "Tstartfile",
       +        [Tworkfile]        "Tworkfile",
       +        [Ttype]                "Ttype",
       +        [Tcut]                "Tcut",
       +        [Tpaste]        "Tpaste",
       +        [Tsnarf]        "Tsnarf",
       +        [Tstartnewfile]        "Tstartnewfile",
       +        [Twrite]        "Twrite",
       +        [Tclose]        "Tclose",
       +        [Tlook]                "Tlook",
       +        [Tsearch]        "Tsearch",
       +        [Tsend]                "Tsend",
       +        [Tdclick]        "Tdclick",
       +        [Tstartsnarf]        "Tstartsnarf",
       +        [Tsetsnarf]        "Tsetsnarf",
       +        [Tack]                "Tack",
       +        [Texit]                "Texit",
       +        [Tplumb]                "Tplumb",
       +};
       +
       +void
       +journal(int out, char *s)
       +{
       +        static int fd = 0;
       +
       +        if(fd <= 0)
       +                fd = create("/tmp/sam.out", 1, 0666L);
       +        fprint(fd, "%s%s\n", out? "out: " : "in:  ", s);
       +}
       +
       +void
       +journaln(int out, long n)
       +{
       +        char buf[32];
       +
       +        sprint(buf, "%ld", n);
       +        journal(out, buf);
       +}
       +#else
       +#define        journal(a, b)
       +#define journaln(a, b)
       +#endif
       +
       +int
       +rcvchar(void){
       +        static uchar buf[64];
       +        static i, nleft = 0;
       +
       +        if(nleft <= 0){
       +                nleft = read(0, (char *)buf, sizeof buf);
       +                if(nleft <= 0)
       +                        return -1;
       +                i = 0;
       +        }
       +        --nleft;
       +        return buf[i++];
       +}
       +
       +int
       +rcv(void){
       +        int c;
       +        static state = 0;
       +        static count = 0;
       +        static i = 0;
       +
       +        while((c=rcvchar()) != -1)
       +                switch(state){
       +                case 0:
       +                        h.type = c;
       +                        state++;
       +                        break;
       +
       +                case 1:
       +                        h.count0 = c;
       +                        state++;
       +                        break;
       +
       +                case 2:
       +                        h.count1 = c;
       +                        count = h.count0|(h.count1<<8);
       +                        i = 0;
       +                        if(count > DATASIZE)
       +                                panic("count>DATASIZE");
       +                        if(count == 0)
       +                                goto zerocount;
       +                        state++;
       +                        break;
       +
       +                case 3:
       +                        indata[i++] = c;
       +                        if(i == count){
       +                zerocount:
       +                                indata[i] = 0;
       +                                state = count = 0;
       +                                return inmesg(h.type);
       +                        }
       +                        break;
       +                }
       +        return 0;
       +}
       +
       +File *
       +whichfile(int tag)
       +{
       +        int i;
       +
       +        for(i = 0; 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;
       +}
 (DIR) diff --git a/src/cmd/sam/mesg.h b/src/cmd/sam/mesg.h
       t@@ -0,0 +1,131 @@
       +/* VERSION 1 introduces plumbing
       +        2 increases SNARFSIZE from 4096 to 32000
       + */
       +#define        VERSION        2
       +
       +#define        TBLOCKSIZE 512                  /* largest piece of text sent to terminal */
       +#define        DATASIZE  (UTFmax*TBLOCKSIZE+30) /* ... including protocol header stuff */
       +#define        SNARFSIZE 32000                /* maximum length of exchanged snarf buffer, must fit in 15 bits */
       +/*
       + * Messages originating at the terminal
       + */
       +typedef enum Tmesg
       +{
       +        Tversion,        /* version */
       +        Tstartcmdfile,        /* terminal just opened command frame */
       +        Tcheck,                /* ask host to poke with Hcheck */
       +        Trequest,        /* request data to fill a hole */
       +        Torigin,        /* gimme an Horigin near here */
       +        Tstartfile,        /* terminal just opened a file's frame */
       +        Tworkfile,        /* set file to which commands apply */
       +        Ttype,                /* add some characters, but terminal already knows */
       +        Tcut,
       +        Tpaste,
       +        Tsnarf,
       +        Tstartnewfile,        /* terminal just opened a new frame */
       +        Twrite,                /* write file */
       +        Tclose,                /* terminal requests file close; check mod. status */
       +        Tlook,                /* search for literal current text */
       +        Tsearch,        /* search for last regular expression */
       +        Tsend,                /* pretend he typed stuff */
       +        Tdclick,        /* double click */
       +        Tstartsnarf,        /* initiate snarf buffer exchange */
       +        Tsetsnarf,        /* remember string in snarf buffer */
       +        Tack,                /* acknowledge Hack */
       +        Texit,                /* exit */
       +        Tplumb,                /* send plumb message */
       +        TMAX,
       +}Tmesg;
       +/*
       + * Messages originating at the host
       + */
       +typedef enum Hmesg
       +{
       +        Hversion,        /* version */
       +        Hbindname,        /* attach name[0] to text in terminal */
       +        Hcurrent,        /* make named file the typing file */
       +        Hnewname,        /* create "" name in menu */
       +        Hmovname,        /* move file name in menu */
       +        Hgrow,                /* insert space in rasp */
       +        Hcheck0,        /* see below */
       +        Hcheck,                /* ask terminal to check whether it needs more data */
       +        Hunlock,        /* command is finished; user can do things */
       +        Hdata,                /* store this data in previously allocated space */
       +        Horigin,        /* set origin of file/frame in terminal */
       +        Hunlockfile,        /* unlock file in terminal */
       +        Hsetdot,        /* set dot in terminal */
       +        Hgrowdata,        /* Hgrow + Hdata folded together */
       +        Hmoveto,        /* scrolling, context search, etc. */
       +        Hclean,                /* named file is now 'clean' */
       +        Hdirty,                /* named file is now 'dirty' */
       +        Hcut,                /* remove space from rasp */
       +        Hsetpat,        /* set remembered regular expression */
       +        Hdelname,        /* delete file name from menu */
       +        Hclose,                /* close file and remove from menu */
       +        Hsetsnarf,        /* remember string in snarf buffer */
       +        Hsnarflen,        /* report length of implicit snarf */
       +        Hack,                /* request acknowledgement */
       +        Hexit,
       +        Hplumb,                /* return plumb message to terminal */
       +        HMAX,
       +}Hmesg;
       +typedef struct Header{
       +        uchar        type;                /* one of the above */
       +        uchar        count0;                /* low bits of data size */
       +        uchar        count1;                /* high bits of data size */
       +        uchar        data[1];        /* variable size */
       +}Header;
       +
       +/*
       + * File transfer protocol schematic, a la Holzmann
       + * #define N        6
       + * 
       + * chan h = [4] of { mtype };
       + * chan t = [4] of { mtype };
       + * 
       + * mtype = {        Hgrow, Hdata,
       + *                 Hcheck, Hcheck0,
       + *                 Trequest, Tcheck,
       + *         };
       + * 
       + * active proctype host()
       + * {        byte n;
       + * 
       + *         do
       + *         :: n <  N -> n++; t!Hgrow
       + *         :: n == N -> n++; t!Hcheck0
       + * 
       + *         :: h?Trequest -> t!Hdata
       + *         :: h?Tcheck   -> t!Hcheck
       + *         od
       + * }
       + * 
       + * active proctype term()
       + * {
       + *         do
       + *         :: t?Hgrow   -> h!Trequest
       + *         :: t?Hdata   -> skip
       + *         :: t?Hcheck0 -> h!Tcheck
       + *         :: t?Hcheck  ->
       + *                 if
       + *                 :: h!Trequest -> progress: h!Tcheck
       + *                 :: break
       + *                 fi
       + *         od;
       + *         printf("term exits\n")
       + * }
       + *
       + * From: gerard@research.bell-labs.com
       + * Date: Tue Jul 17 13:47:23 EDT 2001
       + * To: rob@research.bell-labs.com
       + * 
       + * spin -c         (or -a) spec
       + * pcc -DNP -o pan pan.c
       + * pan -l
       + * 
       + * proves that there are no non-progress cycles
       + * (infinite executions *not* passing through
       + * the statement marked with a label starting
       + * with the prefix "progress")
       + * 
       + */
 (DIR) diff --git a/src/cmd/sam/mkfile b/src/cmd/sam/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/sam/moveto.c b/src/cmd/sam/moveto.c
       t@@ -0,0 +1,173 @@
       +#include "sam.h"
       +
       +void
       +moveto(File *f, Range r)
       +{
       +        Posn p1 = r.p1, p2 = r.p2;
       +
       +        f->dot.r.p1 = p1;
       +        f->dot.r.p2 = p2;
       +        if(f->rasp){
       +                telldot(f);
       +                outTsl(Hmoveto, f->tag, f->dot.r.p1);
       +        }
       +}
       +
       +void
       +telldot(File *f)
       +{
       +        if(f->rasp == 0)
       +                panic("telldot");
       +        if(f->dot.r.p1==f->tdot.p1 && f->dot.r.p2==f->tdot.p2)
       +                return;
       +        outTsll(Hsetdot, f->tag, f->dot.r.p1, f->dot.r.p2);
       +        f->tdot = f->dot.r;
       +}
       +
       +void
       +tellpat(void)
       +{
       +        outTS(Hsetpat, &lastpat);
       +        patset = FALSE;
       +}
       +
       +#define        CHARSHIFT        128
       +
       +void
       +lookorigin(File *f, Posn p0, Posn ls)
       +{
       +        int nl, nc, c;
       +        Posn p, oldp0;
       +
       +        if(p0 > f->_.nc)
       +                p0 = f->_.nc;
       +        oldp0 = p0;
       +        p = p0;
       +        for(nl=nc=c=0; c!=-1 && nl<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--;
       +}
       +
 (DIR) diff --git a/src/cmd/sam/multi.c b/src/cmd/sam/multi.c
       t@@ -0,0 +1,123 @@
       +#include "sam.h"
       +
       +List        file;
       +ushort        tag;
       +
       +File *
       +newfile(void)
       +{
       +        File *f;
       +
       +        f = fileopen();
       +        inslist(&file, 0, (long)f);
       +        f->tag = tag++;
       +        if(downloaded)
       +                outTs(Hnewname, f->tag);
       +        /* already sorted; file name is "" */
       +        return f;
       +}
       +
       +int
       +whichmenu(File *f)
       +{
       +        int i;
       +
       +        for(i=0; 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;
       +}
 (DIR) diff --git a/src/cmd/sam/parse.h b/src/cmd/sam/parse.h
       t@@ -0,0 +1,68 @@
       +typedef struct Addr Addr;
       +typedef struct Cmd Cmd;
       +struct Addr
       +{
       +        char        type;        /* # (char addr), l (line addr), / ? . $ + - , ; */
       +        union{
       +                String        *re;
       +                Addr        *aleft;                /* left side of , and ; */
       +        } g;
       +        Posn        num;
       +        Addr        *next;                        /* or right side of , and ; */
       +};
       +
       +#define        are        g.re
       +#define        left        g.aleft
       +
       +struct Cmd
       +{
       +        Addr        *addr;                        /* address (range of text) */
       +        String        *re;                        /* regular expression for e.g. 'x' */
       +        union{
       +                Cmd        *cmd;                /* target of x, g, {, etc. */
       +                String        *text;                /* text of a, c, i; rhs of s */
       +                Addr        *addr;                /* address for m, t */
       +        } g;
       +        Cmd        *next;                        /* pointer to next element in {} */
       +        short        num;
       +        ushort        flag;                        /* whatever */
       +        ushort        cmdc;                        /* command character; 'x' etc. */
       +};
       +
       +#define        ccmd        g.cmd
       +#define        ctext        g.text
       +#define        caddr        g.addr
       +
       +extern struct cmdtab{
       +        ushort        cmdc;                /* command character */
       +        uchar        text;                /* takes a textual argument? */
       +        uchar        regexp;                /* takes a regular expression? */
       +        uchar        addr;                /* takes an address (m or t)? */
       +        uchar        defcmd;                /* default command; 0==>none */
       +        uchar        defaddr;        /* default address */
       +        uchar        count;                /* takes a count e.g. s2/// */
       +        char        *token;                /* takes text terminated by one of these */
       +        int        (*fn)(File*, Cmd*);        /* function to call with parse tree */
       +}cmdtab[];
       +
       +enum Defaddr{        /* default addresses */
       +        aNo,
       +        aDot,
       +        aAll,
       +};
       +
       +int        nl_cmd(File*, Cmd*), a_cmd(File*, Cmd*), b_cmd(File*, Cmd*);
       +int        c_cmd(File*, Cmd*), cd_cmd(File*, Cmd*), d_cmd(File*, Cmd*);
       +int        D_cmd(File*, Cmd*), e_cmd(File*, Cmd*);
       +int        f_cmd(File*, Cmd*), g_cmd(File*, Cmd*), i_cmd(File*, Cmd*);
       +int        k_cmd(File*, Cmd*), m_cmd(File*, Cmd*), n_cmd(File*, Cmd*);
       +int        p_cmd(File*, Cmd*), q_cmd(File*, Cmd*);
       +int        s_cmd(File*, Cmd*), u_cmd(File*, Cmd*), w_cmd(File*, Cmd*);
       +int        x_cmd(File*, Cmd*), X_cmd(File*, Cmd*), plan9_cmd(File*, Cmd*);
       +int        eq_cmd(File*, Cmd*);
       +
       +
       +String        *getregexp(int);
       +Addr        *newaddr(void);
       +Address        address(Addr*, Address, int);
       +int        cmdexec(File*, Cmd*);
 (DIR) diff --git a/src/cmd/sam/plan9.c b/src/cmd/sam/plan9.c
       t@@ -0,0 +1,185 @@
       +#include "sam.h"
       +
       +Rune        samname[] = L"~~sam~~";
       +
       +Rune *left[]= {
       +        L"{[(<«",
       +        L"\n",
       +        L"'\"`",
       +        0
       +};
       +Rune *right[]= {
       +        L"}])>»",
       +        L"\n",
       +        L"'\"`",
       +        0
       +};
       +
       +char        RSAM[] = "sam";
       +char        SAMTERM[] = "/bin/aux/samterm";
       +char        HOME[] = "home";
       +char        TMPDIR[] = "/tmp";
       +char        SH[] = "rc";
       +char        SHPATH[] = "/bin/rc";
       +char        RX[] = "rx";
       +char        RXPATH[] = "/bin/rx";
       +char        SAMSAVECMD[] = "/bin/rc\n/sys/lib/samsave";
       +
       +void
       +dprint(char *z, ...)
       +{
       +        char buf[BLOCKSIZE];
       +        va_list arg;
       +
       +        va_start(arg, z);
       +        vseprint(buf, &buf[BLOCKSIZE], z, arg);
       +        va_end(arg);
       +        termwrite(buf);
       +}
       +
       +void
       +print_ss(char *s, String *a, String *b)
       +{
       +        dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
       +}
       +
       +void
       +print_s(char *s, String *a)
       +{
       +        dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
       +}
       +
       +char*
       +getuser(void)
       +{
       +        static char user[64];
       +        int fd;
       +
       +        if(user[0] == 0){
       +                fd = open("/dev/user", 0);
       +                if(fd<0 || read(fd, user, sizeof user-1)<=0)
       +                        strcpy(user, "none");
       +                close(fd);
       +        }
       +        return user;
       +}
       +
       +int
       +statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
       +{
       +        Dir *dirb;
       +
       +        dirb = dirstat(name);
       +        if(dirb == nil)
       +                return -1;
       +        if(dev)
       +                *dev = dirb->type|(dirb->dev<<16);
       +        if(id)
       +                *id = dirb->qid.path;
       +        if(time)
       +                *time = dirb->mtime;
       +        if(length)
       +                *length = dirb->length;
       +        if(appendonly)
       +                *appendonly = dirb->mode & DMAPPEND;
       +        free(dirb);
       +        return 1;
       +}
       +
       +int
       +statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
       +{
       +        Dir *dirb;
       +
       +        dirb = dirfstat(fd);
       +        if(dirb == nil)
       +                return -1;
       +        if(dev)
       +                *dev = dirb->type|(dirb->dev<<16);
       +        if(id)
       +                *id = dirb->qid.path;
       +        if(time)
       +                *time = dirb->mtime;
       +        if(length)
       +                *length = dirb->length;
       +        if(appendonly)
       +                *appendonly = dirb->mode & DMAPPEND;
       +        free(dirb);
       +        return 1;
       +}
       +
       +void
       +notifyf(void *a, char *s)
       +{
       +        USED(a);
       +        if(bpipeok && strcmp(s, "sys: write on closed pipe") == 0)
       +                noted(NCONT);
       +        if(strcmp(s, "interrupt") == 0)
       +                noted(NCONT);
       +        panicking = 1;
       +        rescue();
       +        noted(NDFLT);
       +}
       +
       +int
       +newtmp(int num)
       +{
       +        int i, fd;
       +        static char        tempnam[30];
       +
       +        i = getpid();
       +        do
       +                snprint(tempnam, sizeof tempnam, "%s/%d%.4s%dsam", TMPDIR, num, getuser(), i++);
       +        while(access(tempnam, 0) == 0);
       +        fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
       +        if(fd < 0){
       +                remove(tempnam);
       +                fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
       +        }
       +        return fd;
       +}
       +
       +int
       +waitfor(int pid)
       +{
       +        int msg;
       +        Waitmsg *w;
       +
       +        while((w = wait()) != nil){
       +                if(w->pid != pid){
       +                        free(w);
       +                        continue;
       +                }
       +                msg = (w->msg[0] != '\0');
       +                free(w);
       +                return msg;
       +        }
       +        return -1;
       +}
       +
       +void
       +samerr(char *buf)
       +{
       +        sprint(buf, "%s/sam.err", TMPDIR);
       +}
       +
       +void*
       +emalloc(ulong n)
       +{
       +        void *p;
       +
       +        p = malloc(n);
       +        if(p == 0)
       +                panic("malloc fails");
       +        memset(p, 0, n);
       +        return p;
       +}
       +
       +void*
       +erealloc(void *p, ulong n)
       +{
       +        p = realloc(p, n);
       +        if(p == 0)
       +                panic("realloc fails");
       +        return p;
       +}
 (DIR) diff --git a/src/cmd/sam/plumb.c b/src/cmd/sam/plumb.c
       t@@ -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; }
       +
 (DIR) diff --git a/src/cmd/sam/rasp.c b/src/cmd/sam/rasp.c
       t@@ -0,0 +1,325 @@
       +#include "sam.h"
       +/*
       + * GROWDATASIZE must be big enough that all errors go out as Hgrowdata's,
       + * so they will be scrolled into visibility in the ~~sam~~ window (yuck!).
       + */
       +#define        GROWDATASIZE        50        /* if size is > this, send data with grow */
       +
       +void        rcut(List*, Posn, Posn);
       +int        rterm(List*, Posn);
       +void        rgrow(List*, Posn, Posn);
       +
       +static        Posn        growpos;
       +static        Posn        grown;
       +static        Posn        shrinkpos;
       +static        Posn        shrunk;
       +
       +/*
       + * rasp routines inform the terminal of changes to the file.
       + *
       + * a rasp is a list of spans within the file, and an indication
       + * of whether the terminal knows about the span.
       + *
       + * optimize by coalescing multiple updates to the same span
       + * if it is not known by the terminal.
       + *
       + * other possible optimizations: flush terminal's rasp by cut everything,
       + * insert everything if rasp gets too large.
       + */
       +
       +/*
       + * only called for initial load of file
       + */
       +void
       +raspload(File *f)
       +{
       +        if(f->rasp == nil)
       +                return;
       +        grown = f->_.nc;
       +        growpos = 0;
       +        if(f->_.nc)
       +                rgrow(f->rasp, 0, f->_.nc);
       +        raspdone(f, 1);
       +}
       +
       +void
       +raspstart(File *f)
       +{
       +        if(f->rasp == nil)
       +                return;
       +        grown = 0;
       +        shrunk = 0;
       +        noflush = 1;
       +}
       +
       +void
       +raspdone(File *f, int toterm)
       +{
       +        if(f->dot.r.p1 > f->_.nc)
       +                f->dot.r.p1 = f->_.nc;
       +        if(f->dot.r.p2 > f->_.nc)
       +                f->dot.r.p2 = f->_.nc;
       +        if(f->mark.p1 > f->_.nc)
       +                f->mark.p1 = f->_.nc;
       +        if(f->mark.p2 > f->_.nc)
       +                f->mark.p2 = f->_.nc;
       +        if(f->rasp == nil)
       +                return;
       +        if(grown)
       +                outTsll(Hgrow, f->tag, growpos, grown);
       +        else if(shrunk)
       +                outTsll(Hcut, f->tag, shrinkpos, shrunk);
       +        if(toterm)
       +                outTs(Hcheck0, f->tag);
       +        outflush();
       +        noflush = 0;
       +        if(f == cmd){
       +                cmdpt += cmdptadv;
       +                cmdptadv = 0;
       +        }
       +}
       +
       +void
       +raspdelete(File *f, uint p1, uint p2, int toterm)
       +{
       +        long n;
       +
       +        n = p2 - p1;
       +        if(n == 0)
       +                return;
       +
       +        if(p2 <= f->dot.r.p1){
       +                f->dot.r.p1 -= n;
       +                f->dot.r.p2 -= n;
       +        }
       +        if(p2 <= f->mark.p1){
       +                f->mark.p1 -= n;
       +                f->mark.p2 -= n;
       +        }
       +
       +        if(f->rasp == nil)
       +                return;
       +
       +        if(f==cmd && 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;
       +}
 (DIR) diff --git a/src/cmd/sam/regexp.c b/src/cmd/sam/regexp.c
       t@@ -0,0 +1,801 @@
       +#include "sam.h"
       +
       +Rangeset        sel;
       +String                lastregexp;
       +/*
       + * Machine Information
       + */
       +typedef struct Inst Inst;
       +
       +struct Inst
       +{
       +        long        type;        /* < 0x10000 ==> literal, otherwise action */
       +        union {
       +                int rsid;
       +                int rsubid;
       +                int class;
       +                struct Inst *rother;
       +                struct Inst *rright;
       +        } r;
       +        union{
       +                struct Inst *lleft;
       +                struct Inst *lnext;
       +        } l;
       +};
       +#define        sid        r.rsid
       +#define        subid        r.rsubid
       +#define        rclass        r.class
       +#define        other        r.rother
       +#define        right        r.rright
       +#define        left        l.lleft
       +#define        next        l.lnext
       +
       +#define        NPROG        1024
       +Inst        program[NPROG];
       +Inst        *progp;
       +Inst        *startinst;        /* First inst. of program; might not be program[0] */
       +Inst        *bstartinst;        /* same for backwards machine */
       +
       +typedef struct Ilist Ilist;
       +struct Ilist
       +{
       +        Inst        *inst;                /* Instruction of the thread */
       +        Rangeset se;
       +        Posn        startp;                /* first char of match */
       +};
       +
       +#define        NLIST        128
       +
       +Ilist        *tl, *nl;        /* This list, next list */
       +Ilist        list[2][NLIST];
       +static        Rangeset sempty;
       +
       +/*
       + * Actions and Tokens
       + *
       + *        0x100xx are operators, value == precedence
       + *        0x200xx are tokens, i.e. operands for operators
       + */
       +#define        OPERATOR        0x10000        /* Bitmask of all operators */
       +#define        START                0x10000        /* Start, used for marker on stack */
       +#define        RBRA                0x10001        /* Right bracket, ) */
       +#define        LBRA                0x10002        /* Left bracket, ( */
       +#define        OR                0x10003        /* Alternation, | */
       +#define        CAT                0x10004        /* Concatentation, implicit operator */
       +#define        STAR                0x10005        /* Closure, * */
       +#define        PLUS                0x10006        /* a+ == aa* */
       +#define        QUEST                0x10007        /* a? == a|nothing, i.e. 0 or 1 a's */
       +#define        ANY                0x20000        /* Any character but newline, . */
       +#define        NOP                0x20001        /* No operation, internal use only */
       +#define        BOL                0x20002        /* Beginning of line, ^ */
       +#define        EOL                0x20003        /* End of line, $ */
       +#define        CCLASS                0x20004        /* Character class, [] */
       +#define        NCCLASS                0x20005        /* Negated character class, [^] */
       +#define        END                0x20077        /* Terminate: match found */
       +
       +#define        ISATOR                0x10000
       +#define        ISAND                0x20000
       +
       +/*
       + * Parser Information
       + */
       +typedef struct Node Node;
       +struct Node
       +{
       +        Inst        *first;
       +        Inst        *last;
       +};
       +
       +#define        NSTACK        20
       +Node        andstack[NSTACK];
       +Node        *andp;
       +int        atorstack[NSTACK];
       +int        *atorp;
       +int        lastwasand;        /* Last token was operand */
       +int        cursubid;
       +int        subidstack[NSTACK];
       +int        *subidp;
       +int        backwards;
       +int        nbra;
       +Rune        *exprp;                /* pointer to next character in source expression */
       +#define        DCLASS        10        /* allocation increment */
       +int        nclass;                /* number active */
       +int        Nclass;                /* high water mark */
       +Rune        **class;
       +int        negateclass;
       +
       +void        addinst(Ilist *l, Inst *inst, Rangeset *sep);
       +void        newmatch(Rangeset*);
       +void        bnewmatch(Rangeset*);
       +void        pushand(Inst*, Inst*);
       +void        pushator(int);
       +Node        *popand(int);
       +int        popator(void);
       +void        startlex(Rune*);
       +int        lex(void);
       +void        operator(int);
       +void        operand(int);
       +void        evaluntil(int);
       +void        optimize(Inst*);
       +void        bldcclass(void);
       +
       +void
       +regerror(Err e)
       +{
       +        Strzero(&lastregexp);
       +        error(e);
       +}
       +
       +void
       +regerror_c(Err e, int c)
       +{
       +        Strzero(&lastregexp);
       +        error_c(e, c);
       +}
       +
       +Inst *
       +newinst(int t)
       +{
       +        if(progp >= &program[NPROG])
       +                regerror(Etoolong);
       +        progp->type = t;
       +        progp->left = 0;
       +        progp->right = 0;
       +        return progp++;
       +}
       +
       +Inst *
       +realcompile(Rune *s)
       +{
       +        int token;
       +
       +        startlex(s);
       +        atorp = atorstack;
       +        andp = andstack;
       +        subidp = subidstack;
       +        cursubid = 0;
       +        lastwasand = FALSE;
       +        /* Start with a low priority operator to prime parser */
       +        pushator(START-1);
       +        while((token=lex()) != END){
       +                if((token&ISATOR) == OPERATOR)
       +                        operator(token);
       +                else
       +                        operand(token);
       +        }
       +        /* Close with a low priority operator */
       +        evaluntil(START);
       +        /* Force END */
       +        operand(END);
       +        evaluntil(START);
       +        if(nbra)
       +                regerror(Eleftpar);
       +        --andp;        /* points to first and only operand */
       +        return andp->first;
       +}
       +
       +void
       +compile(String *s)
       +{
       +        int i;
       +        Inst *oprogp;
       +
       +        if(Strcmp(s, &lastregexp)==0)
       +                return;
       +        for(i=0; 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;
       +                }
       +}
 (DIR) diff --git a/src/cmd/sam/sam b/src/cmd/sam/sam
       Binary files differ.
 (DIR) diff --git a/src/cmd/sam/sam.c b/src/cmd/sam/sam.c
       t@@ -0,0 +1,739 @@
       +#include "sam.h"
       +
       +Rune        genbuf[BLOCKSIZE];
       +int        io;
       +int        panicking;
       +int        rescuing;
       +String        genstr;
       +String        rhs;
       +String        curwd;
       +String        cmdstr;
       +Rune        empty[] = { 0 };
       +char        *genc;
       +File        *curfile;
       +File        *flist;
       +File        *cmd;
       +jmp_buf        mainloop;
       +List        tempfile;
       +int        quitok = TRUE;
       +int        downloaded;
       +int        dflag;
       +int        Rflag;
       +char        *machine;
       +char        *home;
       +int        bpipeok;
       +int        termlocked;
       +char        *samterm = SAMTERM;
       +char        *rsamname = RSAM;
       +File        *lastfile;
       +Disk        *disk;
       +long        seq;
       +
       +Rune        baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};
       +
       +void        usage(void);
       +
       +int main(int argc, char *argv[])
       +{
       +        int i;
       +        String *t;
       +        char **ap, **arg;
       +
       +        { // libfmt-2.0 uses %lu where we need %lud
       +          extern int __flagfmt(Fmt*);
       +          fmtinstall('u', __flagfmt);
       +        }
       +
       +        arg = argv++;
       +        ap = argv;
       +        while(argc>1 && argv[0] && argv[0][0]=='-'){
       +                switch(argv[0][1]){
       +                case 'd':
       +                        dflag++;
       +                        break;
       +
       +                case 'r':
       +                        --argc, argv++;
       +                        if(argc == 1)
       +                                usage();
       +                        machine = *argv;
       +                        break;
       +
       +                case 'R':
       +                        Rflag++;
       +                        break;
       +
       +                case 't':
       +                        --argc, argv++;
       +                        if(argc == 1)
       +                                usage();
       +                        samterm = *argv;
       +                        break;
       +
       +                case 's':
       +                        --argc, argv++;
       +                        if(argc == 1)
       +                                usage();
       +                        rsamname = *argv;
       +                        break;
       +
       +                case 'x':       /* x11 option - strip the x */
       +                        strcpy(*argv+1, *argv+2);
       +                        *ap++ = *argv++;
       +                        *ap++ = *argv;
       +                        argc--;
       +                        break;
       +
       +                default:
       +                        dprint("sam: unknown flag %c\n", argv[0][1]);
       +                        exits("usage");
       +                }
       +                --argc, argv++;
       +        }
       +        Strinit(&cmdstr);
       +        Strinit0(&lastpat);
       +        Strinit0(&lastregexp);
       +        Strinit0(&genstr);
       +        Strinit0(&rhs);
       +        Strinit0(&curwd);
       +        tempfile.listptr = emalloc(1);        /* so it can be freed later */
       +        Strinit0(&plan9cmd);
       +        home = getenv(HOME);
       +        disk = diskinit();
       +        if(home == 0)
       +                home = "/";
       +        if(!dflag)
       +                startup(machine, Rflag, arg, ap);
       +        notify(notifyf);
       +        getcurwd();
       +        if(argc>1){
       +                for(i=0; i<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*));
       +}
 (DIR) diff --git a/src/cmd/sam/sam.h b/src/cmd/sam/sam.h
       t@@ -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);
 (DIR) diff --git a/src/cmd/sam/shell.c b/src/cmd/sam/shell.c
       t@@ -0,0 +1,152 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +extern        jmp_buf        mainloop;
       +
       +char        errfile[64];
       +String        plan9cmd;        /* null terminated */
       +Buffer        plan9buf;
       +void        checkerrs(void);
       +
       +int
       +plan9(File *f, int type, String *s, int nest)
       +{
       +        long l;
       +        int m;
       +        int pid, fd;
       +        int retcode;
       +        int pipe1[2], pipe2[2];
       +
       +        if(s->s[0]==0 && plan9cmd.s[0]==0)
       +                error(Enocmd);
       +        else if(s->s[0])
       +                Strduplstr(&plan9cmd, s);
       +        if(downloaded){
       +                samerr(errfile);
       +                remove(errfile);
       +        }
       +        if(type!='!' && pipe(pipe1)==-1)
       +                error(Epipe);
       +        if(type=='|')
       +                snarf(f, addr.r.p1, addr.r.p2, &plan9buf, 1);
       +        if((pid=fork()) == 0){
       +                if(downloaded){        /* also put nasty fd's into errfile */
       +                        fd = create(errfile, 1, 0666L);
       +                        if(fd < 0)
       +                                fd = create("/dev/null", 1, 0666L);
       +                        dup(fd, 2);
       +                        close(fd);
       +                        /* 2 now points at err file */
       +                        if(type == '>')
       +                                dup(2, 1);
       +                        else if(type=='!'){
       +                                dup(2, 1);
       +                                fd = open("/dev/null", 0);
       +                                dup(fd, 0);
       +                                close(fd);
       +                        }
       +                }
       +                if(type != '!') {
       +                        if(type=='<' || type=='|')
       +                                dup(pipe1[1], 1);
       +                        else if(type == '>')
       +                                dup(pipe1[0], 0);
       +                        close(pipe1[0]);
       +                        close(pipe1[1]);
       +                }
       +                if(type == '|'){
       +                        if(pipe(pipe2) == -1)
       +                                exits("pipe");
       +                        if((pid = fork())==0){
       +                                /*
       +                                 * It's ok if we get SIGPIPE here
       +                                 */
       +                                close(pipe2[0]);
       +                                io = pipe2[1];
       +                                if(retcode=!setjmp(mainloop)){        /* assignment = */
       +                                        char *c;
       +                                        for(l = 0; 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);
       +}
 (DIR) diff --git a/src/cmd/sam/unix.c b/src/cmd/sam/unix.c
       t@@ -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;                          
       +}
 (DIR) diff --git a/src/cmd/sam/xec.c b/src/cmd/sam/xec.c
       t@@ -0,0 +1,508 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +int        Glooping;
       +int        nest;
       +
       +int        append(File*, Cmd*, Posn);
       +int        display(File*);
       +void        looper(File*, Cmd*, int);
       +void        filelooper(Cmd*, int);
       +void        linelooper(File*, Cmd*);
       +
       +void
       +resetxec(void)
       +{
       +        Glooping = nest = 0;
       +}
       +
       +int
       +cmdexec(File *f, Cmd *cp)
       +{
       +        int i;
       +        Addr *ap;
       +        Address a;
       +
       +        if(f && f->unread)
       +                load(f);
       +        if(f==0 && (cp->addr==0 || cp->addr->type!='"') &&
       +            !utfrune("bBnqUXY!", cp->cmdc) &&
       +            cp->cmdc!=('c'|0x100) && !(cp->cmdc=='D' && cp->ctext))
       +                error(Enofile);
       +        i = lookup(cp->cmdc);
       +        if(i >= 0 && cmdtab[i].defaddr != aNo){
       +                if((ap=cp->addr)==0 && cp->cmdc!='\n'){
       +                        cp->addr = ap = newaddr();
       +                        ap->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->type = '*';
       +                }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
       +                        ap->next = newaddr();
       +                        ap->next->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->next->type = '*';
       +                }
       +                if(cp->addr){        /* may be false for '\n' (only) */
       +                        static Address none = {0,0,0};
       +                        if(f)
       +                                addr = address(ap, f->dot, 0);
       +                        else        /* a " */
       +                                addr = address(ap, none, 0);
       +                        f = addr.f;
       +                }
       +        }
       +        current(f);
       +        switch(cp->cmdc){
       +        case '{':
       +                a = cp->addr? address(cp->addr, f->dot, 0): f->dot;
       +                for(cp = cp->ccmd; cp; cp = cp->next){
       +                        a.f->dot = a;
       +                        cmdexec(a.f, cp);
       +                }
       +                break;
       +        default:
       +                i=(*cmdtab[i].fn)(f, cp);
       +                return i;
       +        }
       +        return 1;
       +}
       +
       +
       +int
       +a_cmd(File *f, Cmd *cp)
       +{
       +        return append(f, cp, addr.r.p2);
       +}
       +
       +int
       +b_cmd(File *f, Cmd *cp)
       +{
       +        USED(f);
       +        f = cp->cmdc=='b'? tofile(cp->ctext) : getfile(cp->ctext);
       +        if(f->unread)
       +                load(f);
       +        else if(nest == 0)
       +                filename(f);
       +        return TRUE;
       +}
       +
       +int
       +c_cmd(File *f, Cmd *cp)
       +{
       +        logdelete(f, addr.r.p1, addr.r.p2);
       +        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p2;
       +        return append(f, cp, addr.r.p2);
       +}
       +
       +int
       +d_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        logdelete(f, addr.r.p1, addr.r.p2);
       +        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p1;
       +        return TRUE;
       +}
       +
       +int
       +D_cmd(File *f, Cmd *cp)
       +{
       +        closefiles(f, cp->ctext);
       +        return TRUE;
       +}
       +
       +int
       +e_cmd(File *f, Cmd *cp)
       +{
       +        if(getname(f, cp->ctext, cp->cmdc=='e')==0)
       +                error(Enoname);
       +        edit(f, cp->cmdc);
       +        return TRUE;
       +}
       +
       +int
       +f_cmd(File *f, Cmd *cp)
       +{
       +        getname(f, cp->ctext, TRUE);
       +        filename(f);
       +        return TRUE;
       +}
       +
       +int
       +g_cmd(File *f, Cmd *cp)
       +{
       +        if(f!=addr.f)panic("g_cmd f!=addr.f");
       +        compile(cp->re);
       +        if(execute(f, addr.r.p1, addr.r.p2) ^ cp->cmdc=='v'){
       +                f->dot = addr;
       +                return cmdexec(f, cp->ccmd);
       +        }
       +        return TRUE;
       +}
       +
       +int
       +i_cmd(File *f, Cmd *cp)
       +{
       +        return append(f, cp, addr.r.p1);
       +}
       +
       +int
       +k_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        f->mark = addr.r;
       +        return TRUE;
       +}
       +
       +int
       +m_cmd(File *f, Cmd *cp)
       +{
       +        Address addr2;
       +
       +        addr2 = address(cp->caddr, f->dot, 0);
       +        if(cp->cmdc=='m')
       +                move(f, addr2);
       +        else
       +                copy(f, addr2);
       +        return TRUE;
       +}
       +
       +int
       +n_cmd(File *f, Cmd *cp)
       +{
       +        int i;
       +        USED(f);
       +        USED(cp);
       +        for(i = 0; 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;
       +}
 (DIR) diff --git a/src/libdraw/BOT b/src/libdraw/BOT
 (DIR) diff --git a/src/libdraw/Make.Darwin-PowerMacintosh b/src/libdraw/Make.Darwin-PowerMacintosh
       t@@ -0,0 +1,6 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.FreeBSD-386 b/src/libdraw/Make.FreeBSD-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.HP-UX-9000 b/src/libdraw/Make.HP-UX-9000
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS=-O -c -Ae -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.Linux-386 b/src/libdraw/Make.Linux-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.NetBSD-386 b/src/libdraw/Make.NetBSD-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.OSF1-alpha b/src/libdraw/Make.OSF1-alpha
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS+=-g -c -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.SunOS-sun4u b/src/libdraw/Make.SunOS-sun4u
       t@@ -0,0 +1,2 @@
       +include Make.SunOS-sun4u-$(CC)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.SunOS-sun4u-cc b/src/libdraw/Make.SunOS-sun4u-cc
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS+=-g -c -I. -O
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Make.SunOS-sun4u-gcc b/src/libdraw/Make.SunOS-sun4u-gcc
       t@@ -0,0 +1,6 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libdraw/Makefile b/src/libdraw/Makefile
       t@@ -0,0 +1,194 @@
       +
       +# this works in gnu make
       +SYSNAME:=${shell uname}
       +OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
       +
       +# this works in bsd make
       +SYSNAME!=uname
       +OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
       +
       +# the gnu rules will mess up bsd but not vice versa,
       +# hence the gnu rules come first.
       +
       +include Make.$(SYSNAME)-$(OBJTYPE)
       +
       +PREFIX=/usr/local
       +
       +NUKEFILES=
       +
       +TGZFILES=
       +
       +LIB=libdraw.a
       +VERSION=2.0
       +PORTPLACE=devel/libdraw
       +NAME=libdraw
       +
       +# keyboard.$O\
       +# newwindow.$O\
       +OFILES=\
       +        alloc.$O\
       +        allocimagemix.$O\
       +        arith.$O\
       +        bezier.$O\
       +        border.$O\
       +        buildfont.$O\
       +        bytesperline.$O\
       +        chan.$O\
       +        cloadimage.$O\
       +        computil.$O\
       +        creadimage.$O\
       +        debug.$O\
       +        defont.$O\
       +        draw.$O\
       +        drawrepl.$O\
       +        egetrect.$O\
       +        ellipse.$O\
       +        emenuhit.$O\
       +        font.$O\
       +        freesubfont.$O\
       +        getdefont.$O\
       +        getrect.$O\
       +        getsubfont.$O\
       +        icossin.$O\
       +        icossin2.$O\
       +        init.$O\
       +        line.$O\
       +        loadimage.$O\
       +        menuhit.$O\
       +        mkfont.$O\
       +        openfont.$O\
       +        poly.$O\
       +        readcolmap.$O\
       +        readimage.$O\
       +        readsubfont.$O\
       +        rectclip.$O\
       +        replclipr.$O\
       +        rgb.$O\
       +        string.$O\
       +        stringbg.$O\
       +        stringsubfont.$O\
       +        stringwidth.$O\
       +        subfont.$O\
       +        subfontcache.$O\
       +        subfontname.$O\
       +        unloadimage.$O\
       +        window.$O\
       +        writecolmap.$O\
       +        writeimage.$O\
       +        writesubfont.$O\
       +        md-alloc.$O\
       +        md-arc.$O\
       +        md-cload.$O\
       +        md-cmap.$O\
       +        md-cread.$O\
       +        md-defont.$O\
       +        md-draw.$O\
       +        md-ellipse.$O\
       +        md-fillpoly.$O\
       +        md-hwdraw.$O\
       +        md-iprint.$O\
       +        md-line.$O\
       +        md-load.$O\
       +        md-openmemsubfont.$O\
       +        md-poly.$O\
       +        md-read.$O\
       +        md-string.$O\
       +        md-subfont.$O\
       +        md-unload.$O\
       +        md-write.$O\
       +        ml-draw.$O\
       +        ml-lalloc.$O\
       +        ml-layerop.$O\
       +        ml-ldelete.$O\
       +        ml-lhide.$O\
       +        ml-line.$O\
       +        ml-load.$O\
       +        ml-lorigin.$O\
       +        ml-lsetrefresh.$O\
       +        ml-ltofront.$O\
       +        ml-ltorear.$O\
       +        ml-unload.$O\
       +        x11-alloc.$O\
       +        x11-cload.$O\
       +        x11-draw.$O\
       +        x11-event.$O\
       +        x11-fill.$O\
       +        x11-get.$O\
       +        x11-init.$O\
       +        x11-itrans.$O\
       +        x11-keyboard.$O\
       +        x11-load.$O\
       +        x11-mouse.$O\
       +        x11-pixelbits.$O\
       +        x11-unload.$O\
       +        devdraw.$O\
       +        unix.$O\
       +
       +HFILES=\
       +        draw.h\
       +        memdraw.h
       +
       +all: $(LIB)
       +
       +install: $(LIB)
       +        install -c -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
       +        install -c -m 0644 draw.h $(PREFIX)/include/draw.h
       +        install -c -m 0644 event.h $(PREFIX)/include/event.h
       +        install -c -m 0644 cursor.h $(PREFIX)/include/cursor.h
       +        install -c -m 0644 mouse.h $(PREFIX)/include/mouse.h
       +        install -c -m 0644 keyboard.h $(PREFIX)/include/keyboard.h
       +
       +test: test.o $(LIB)
       +        gcc -o test test.o $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf -L/usr/X11R6/lib -lX11 -lm
       +
       +$(LIB): $(OFILES)
       +        $(AR) $(ARFLAGS) $(LIB) $(OFILES)
       +
       +NUKEFILES+=$(LIB)
       +.c.$O:
       +        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
       +
       +%.$O: %.c
       +        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
       +
       +
       +$(OFILES): $(HFILES)
       +
       +tgz:
       +        rm -rf $(NAME)-$(VERSION)
       +        mkdir $(NAME)-$(VERSION)
       +        cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
       +        tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
       +        rm -rf $(NAME)-$(VERSION)
       +
       +clean:
       +        rm -f $(OFILES) $(LIB)
       +
       +nuke:
       +        rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
       +
       +rpm:
       +        make tgz
       +        cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
       +        rpm -ba rpm.spec
       +        cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
       +        cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
       +        scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
       +
       +PORTDIR=/usr/ports/$(PORTPLACE)
       +
       +ports:
       +        make tgz
       +        rm -rf $(PORTDIR)
       +        mkdir $(PORTDIR)
       +        cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
       +        cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
       +        (cd $(PORTDIR); make makesum)
       +        (cd $(PORTDIR); make)
       +        (cd $(PORTDIR); /usr/local/bin/portlint)
       +        rm -rf $(PORTDIR)/work
       +        shar `find $(PORTDIR)` > ports.shar
       +        (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
       +        scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
       +
       +.phony: all clean nuke install tgz rpm ports
 (DIR) diff --git a/src/libdraw/Makefile.MID b/src/libdraw/Makefile.MID
       t@@ -0,0 +1,123 @@
       +LIB=libdraw.a
       +VERSION=2.0
       +PORTPLACE=devel/libdraw
       +NAME=libdraw
       +
       +# keyboard.$O\
       +# newwindow.$O\
       +OFILES=\
       +        alloc.$O\
       +        allocimagemix.$O\
       +        arith.$O\
       +        bezier.$O\
       +        border.$O\
       +        buildfont.$O\
       +        bytesperline.$O\
       +        chan.$O\
       +        cloadimage.$O\
       +        computil.$O\
       +        creadimage.$O\
       +        debug.$O\
       +        defont.$O\
       +        draw.$O\
       +        drawrepl.$O\
       +        egetrect.$O\
       +        ellipse.$O\
       +        emenuhit.$O\
       +        font.$O\
       +        freesubfont.$O\
       +        getdefont.$O\
       +        getrect.$O\
       +        getsubfont.$O\
       +        icossin.$O\
       +        icossin2.$O\
       +        init.$O\
       +        line.$O\
       +        loadimage.$O\
       +        menuhit.$O\
       +        mkfont.$O\
       +        openfont.$O\
       +        poly.$O\
       +        readcolmap.$O\
       +        readimage.$O\
       +        readsubfont.$O\
       +        rectclip.$O\
       +        replclipr.$O\
       +        rgb.$O\
       +        string.$O\
       +        stringbg.$O\
       +        stringsubfont.$O\
       +        stringwidth.$O\
       +        subfont.$O\
       +        subfontcache.$O\
       +        subfontname.$O\
       +        unloadimage.$O\
       +        window.$O\
       +        writecolmap.$O\
       +        writeimage.$O\
       +        writesubfont.$O\
       +        md-alloc.$O\
       +        md-arc.$O\
       +        md-cload.$O\
       +        md-cmap.$O\
       +        md-cread.$O\
       +        md-defont.$O\
       +        md-draw.$O\
       +        md-ellipse.$O\
       +        md-fillpoly.$O\
       +        md-hwdraw.$O\
       +        md-iprint.$O\
       +        md-line.$O\
       +        md-load.$O\
       +        md-openmemsubfont.$O\
       +        md-poly.$O\
       +        md-read.$O\
       +        md-string.$O\
       +        md-subfont.$O\
       +        md-unload.$O\
       +        md-write.$O\
       +        ml-draw.$O\
       +        ml-lalloc.$O\
       +        ml-layerop.$O\
       +        ml-ldelete.$O\
       +        ml-lhide.$O\
       +        ml-line.$O\
       +        ml-load.$O\
       +        ml-lorigin.$O\
       +        ml-lsetrefresh.$O\
       +        ml-ltofront.$O\
       +        ml-ltorear.$O\
       +        ml-unload.$O\
       +        x11-alloc.$O\
       +        x11-cload.$O\
       +        x11-draw.$O\
       +        x11-event.$O\
       +        x11-fill.$O\
       +        x11-get.$O\
       +        x11-init.$O\
       +        x11-itrans.$O\
       +        x11-keyboard.$O\
       +        x11-load.$O\
       +        x11-mouse.$O\
       +        x11-pixelbits.$O\
       +        x11-unload.$O\
       +        devdraw.$O\
       +        unix.$O\
       +
       +HFILES=\
       +        draw.h\
       +        memdraw.h
       +
       +all: $(LIB)
       +
       +install: $(LIB)
       +        install -c -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
       +        install -c -m 0644 draw.h $(PREFIX)/include/draw.h
       +        install -c -m 0644 event.h $(PREFIX)/include/event.h
       +        install -c -m 0644 cursor.h $(PREFIX)/include/cursor.h
       +        install -c -m 0644 mouse.h $(PREFIX)/include/mouse.h
       +        install -c -m 0644 keyboard.h $(PREFIX)/include/keyboard.h
       +
       +test: test.o $(LIB)
       +        gcc -o test test.o $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf -L/usr/X11R6/lib -lX11 -lm
       +
 (DIR) diff --git a/src/libdraw/alloc.c b/src/libdraw/alloc.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/arith.c b/src/libdraw/arith.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/libdraw/buildfont.c b/src/libdraw/buildfont.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/bytesperline.c b/src/libdraw/bytesperline.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/chan.c b/src/libdraw/chan.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/creadimage.c b/src/libdraw/creadimage.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/cursor.h b/src/libdraw/cursor.h
       t@@ -0,0 +1,7 @@
       +typedef struct Cursor Cursor;
       +struct        Cursor
       +{
       +        Point        offset;
       +        uchar        clr[2*16];
       +        uchar        set[2*16];
       +};
 (DIR) diff --git a/src/libdraw/devdraw.c b/src/libdraw/devdraw.c
       t@@ -0,0 +1,1587 @@
       +/*
       + * /dev/draw simulator -- handles the messages prepared by the draw library.
       + * Includes all the memlayer code even though most programs don't use it.
       + * This whole approach is overkill, but cpu is cheap and it keeps things simple.
       + */
       +
       +#include <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;
       +}
       +
       +
 (DIR) diff --git a/src/libdraw/draw.h b/src/libdraw/draw.h
       t@@ -0,0 +1,520 @@
       +typedef struct        Cachefont Cachefont;
       +typedef struct        Cacheinfo Cacheinfo;
       +typedef struct        Cachesubf Cachesubf;
       +typedef struct        Display Display;
       +typedef struct        Font Font;
       +typedef struct        Fontchar Fontchar;
       +typedef struct        Image Image;
       +typedef struct        Mouse Mouse;
       +typedef struct        Point Point;
       +typedef struct        Rectangle Rectangle;
       +typedef struct        RGB RGB;
       +typedef struct        Screen Screen;
       +typedef struct        Subfont Subfont;
       +
       +extern        int        Rfmt(Fmt*);
       +extern        int        Pfmt(Fmt*);
       +
       +enum
       +{
       +        DOpaque                = 0xFFFFFFFF,
       +        DTransparent        = 0x00000000,                /* only useful for allocimage, memfillcolor */
       +        DBlack                = 0x000000FF,
       +        DWhite                = 0xFFFFFFFF,
       +        DRed                = 0xFF0000FF,
       +        DGreen                = 0x00FF00FF,
       +        DBlue                = 0x0000FFFF,
       +        DCyan                = 0x00FFFFFF,
       +        DMagenta                = 0xFF00FFFF,
       +        DYellow                = 0xFFFF00FF,
       +        DPaleyellow        = 0xFFFFAAFF,
       +        DDarkyellow        = 0xEEEE9EFF,
       +        DDarkgreen        = 0x448844FF,
       +        DPalegreen        = 0xAAFFAAFF,
       +        DMedgreen        = 0x88CC88FF,
       +        DDarkblue        = 0x000055FF,
       +        DPalebluegreen= 0xAAFFFFFF,
       +        DPaleblue                = 0x0000BBFF,
       +        DBluegreen        = 0x008888FF,
       +        DGreygreen        = 0x55AAAAFF,
       +        DPalegreygreen        = 0x9EEEEEFF,
       +        DYellowgreen        = 0x99994CFF,
       +        DMedblue                = 0x000099FF,
       +        DGreyblue        = 0x005DBBFF,
       +        DPalegreyblue        = 0x4993DDFF,
       +        DPurpleblue        = 0x8888CCFF,
       +
       +        DNotacolor        = 0xFFFFFF00,
       +        DNofill                = DNotacolor,
       +        
       +};
       +
       +enum
       +{
       +        Displaybufsize        = 8000,
       +        ICOSSCALE        = 1024,
       +        Borderwidth =        4,
       +};
       +
       +enum
       +{
       +        /* refresh methods */
       +        Refbackup        = 0,
       +        Refnone                = 1,
       +        Refmesg                = 2
       +};
       +#define        NOREFRESH        ((void*)-1)
       +
       +enum
       +{
       +        /* line ends */
       +        Endsquare        = 0,
       +        Enddisc                = 1,
       +        Endarrow        = 2,
       +        Endmask                = 0x1F
       +};
       +
       +#define        ARROW(a, b, c)        (Endarrow|((a)<<5)|((b)<<14)|((c)<<23))
       +
       +typedef enum
       +{
       +        /* Porter-Duff compositing operators */
       +        Clear        = 0,
       +
       +        SinD        = 8,
       +        DinS        = 4,
       +        SoutD        = 2,
       +        DoutS        = 1,
       +
       +        S                = SinD|SoutD,
       +        SoverD        = SinD|SoutD|DoutS,
       +        SatopD        = SinD|DoutS,
       +        SxorD        = SoutD|DoutS,
       +
       +        D                = DinS|DoutS,
       +        DoverS        = DinS|DoutS|SoutD,
       +        DatopS        = DinS|SoutD,
       +        DxorS        = DoutS|SoutD,        /* == SxorD */
       +
       +        Ncomp = 12,
       +} Drawop;
       +
       +/*
       + * image channel descriptors 
       + */
       +enum {
       +        CRed = 0,
       +        CGreen,
       +        CBlue,
       +        CGrey,
       +        CAlpha,
       +        CMap,
       +        CIgnore,
       +        NChan,
       +};
       +
       +#define __DC(type, nbits)        ((((type)&15)<<4)|((nbits)&15))
       +#define CHAN1(a,b)        __DC(a,b)
       +#define CHAN2(a,b,c,d)        (CHAN1((a),(b))<<8|__DC((c),(d)))
       +#define CHAN3(a,b,c,d,e,f)        (CHAN2((a),(b),(c),(d))<<8|__DC((e),(f)))
       +#define CHAN4(a,b,c,d,e,f,g,h)        (CHAN3((a),(b),(c),(d),(e),(f))<<8|__DC((g),(h)))
       +
       +#define NBITS(c) ((c)&15)
       +#define TYPE(c) (((c)>>4)&15)
       +
       +enum {
       +        GREY1        = CHAN1(CGrey, 1),
       +        GREY2        = CHAN1(CGrey, 2),
       +        GREY4        = CHAN1(CGrey, 4),
       +        GREY8        = CHAN1(CGrey, 8),
       +        CMAP8        = CHAN1(CMap, 8),
       +        RGB15        = CHAN4(CIgnore, 1, CRed, 5, CGreen, 5, CBlue, 5),
       +        RGB16        = CHAN3(CRed, 5, CGreen, 6, CBlue, 5),
       +        RGB24        = CHAN3(CRed, 8, CGreen, 8, CBlue, 8),
       +        BGR24        = CHAN3(CBlue, 8, CGreen, 8, CRed, 8),
       +        RGBA32        = CHAN4(CRed, 8, CGreen, 8, CBlue, 8, CAlpha, 8),
       +        ARGB32        = CHAN4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8),        /* stupid VGAs */
       +        XRGB32  = CHAN4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8),
       +        XBGR32  = CHAN4(CIgnore, 8, CBlue, 8, CGreen, 8, CRed, 8),
       +};
       +
       +extern        char*        chantostr(char*, u32int);
       +extern        u32int        strtochan(char*);
       +extern        int                chantodepth(u32int);
       +
       +struct        Point
       +{
       +        int        x;
       +        int        y;
       +};
       +
       +struct Rectangle
       +{
       +        Point        min;
       +        Point        max;
       +};
       +
       +typedef void        (*Reffn)(Image*, Rectangle, void*);
       +
       +struct Screen
       +{
       +        Display        *display;        /* display holding data */
       +        int        id;                /* id of system-held Screen */
       +        Image        *image;                /* unused; for reference only */
       +        Image        *fill;                /* color to paint behind windows */
       +};
       +
       +struct Display
       +{
       +        QLock                qlock;
       +        int                locking;        /*program is using lockdisplay */
       +        int                dirno;
       +        int                imageid;
       +        int                local;
       +        void                (*error)(Display*, char*);
       +        char                *devdir;
       +        char                *windir;
       +        char                oldlabel[64];
       +        u32int                dataqid;
       +        Image                *image;
       +        Image                *white;
       +        Image                *black;
       +        Image                *opaque;
       +        Image                *transparent;
       +        uchar                *buf;
       +        int                bufsize;
       +        uchar                *bufp;
       +        uchar                *obuf;
       +        int                obufsize;
       +        uchar                *obufp;
       +        Font                *defaultfont;
       +        Subfont                *defaultsubfont;
       +        Image                *windows;
       +        Image                *screenimage;
       +        int                _isnewdisplay;
       +};
       +
       +struct Image
       +{
       +        Display                *display;        /* display holding data */
       +        int                id;                /* id of system-held Image */
       +        Rectangle        r;                /* rectangle in data area, local coords */
       +        Rectangle         clipr;                /* clipping region */
       +        int                depth;                /* number of bits per pixel */
       +        u32int        chan;
       +        int                repl;                /* flag: data replicates to tile clipr */
       +        Screen                *screen;        /* 0 if not a window */
       +        Image                *next;        /* next in list of windows */
       +};
       +
       +struct RGB
       +{
       +        u32int        red;
       +        u32int        green;
       +        u32int        blue;
       +};
       +
       +/*
       + * Subfonts
       + *
       + * given char c, Subfont *f, Fontchar *i, and Point p, one says
       + *        i = f->info+c;
       + *        draw(b, Rect(p.x+i->left, p.y+i->top,
       + *                p.x+i->left+((i+1)->x-i->x), p.y+i->bottom),
       + *                color, f->bits, Pt(i->x, i->top));
       + *        p.x += i->width;
       + * to draw characters in the specified color (itself an Image) in Image b.
       + */
       +
       +struct        Fontchar
       +{
       +        int                x;                /* left edge of bits */
       +        uchar                top;                /* first non-zero scan-line */
       +        uchar                bottom;                /* last non-zero scan-line + 1 */
       +        char                left;                /* offset of baseline */
       +        uchar                width;                /* width of baseline */
       +};
       +
       +struct        Subfont
       +{
       +        char                *name;
       +        short                n;                /* number of chars in font */
       +        uchar                height;                /* height of image */
       +        char                ascent;                /* top of image to baseline */
       +        Fontchar         *info;                /* n+1 character descriptors */
       +        Image                *bits;                /* of font */
       +        int                ref;
       +};
       +
       +enum
       +{
       +        /* starting values */
       +        LOG2NFCACHE =        6,
       +        NFCACHE =        (1<<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);
 (DIR) diff --git a/src/libdraw/ellipse.c b/src/libdraw/ellipse.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/emenuhit.c b/src/libdraw/emenuhit.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/event.c b/src/libdraw/event.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/event.h b/src/libdraw/event.h
       t@@ -0,0 +1,63 @@
       +typedef struct        Event Event;
       +typedef struct        Menu Menu;
       +
       +enum
       +{
       +        Emouse                = 1,
       +        Ekeyboard        = 2,
       +};
       +
       +enum
       +{
       +        MAXSLAVE = 32,
       +        EMAXMSG = 128+8192,        /* size of 9p header+data */
       +};
       +
       +struct        Mouse
       +{
       +        int        buttons;        /* bit array: LMR=124 */
       +        Point        xy;
       +        ulong        msec;
       +};
       +
       +struct        Event
       +{
       +        int        kbdc;
       +        Mouse        mouse;
       +        int        n;                /* number of characters in message */
       +        void        *v;                /* data unpacked by general event-handling function */
       +        uchar        data[EMAXMSG];        /* message from an arbitrary file descriptor */
       +};
       +
       +struct Menu
       +{
       +        char        **item;
       +        char        *(*gen)(int);
       +        int        lasthit;
       +};
       +
       +/*
       + * Events
       + */
       +extern void         einit(ulong);
       +extern ulong         estart(ulong, int, int);
       +extern ulong         estartfn(ulong, int, int, int (*fn)(int, Event*, uchar*, int));
       +extern ulong         etimer(ulong, int);
       +extern ulong         event(Event*);
       +extern ulong         eread(ulong, Event*);
       +extern Mouse         emouse(void);
       +extern int         ekbd(void);
       +extern int         ecanread(ulong);
       +extern int         ecanmouse(void);
       +extern int         ecankbd(void);
       +extern void         eresized(int);        /* supplied by user */
       +extern int         emenuhit(int, Mouse*, Menu*);
       +extern int        eatomouse(Mouse*, char*, int);
       +extern Rectangle        getrect(int, Mouse*);
       +struct Cursor;
       +extern void         esetcursor(struct Cursor*);
       +extern void         emoveto(Point);
       +extern Rectangle        egetrect(int, Mouse*);
       +extern void                edrawgetrect(Rectangle, int);
       +extern int                ereadmouse(Mouse*);
       +extern int                eatomouse(Mouse*, char*, int);
 (DIR) diff --git a/src/libdraw/font.c b/src/libdraw/font.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/getsubfont.c b/src/libdraw/getsubfont.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/init.c b/src/libdraw/init.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/libdraw/keyboard.c b/src/libdraw/keyboard.c
       t@@ -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));
       +}
 (DIR) diff --git a/src/libdraw/keyboard.h b/src/libdraw/keyboard.h
       t@@ -0,0 +1,36 @@
       +typedef struct         Keyboardctl Keyboardctl;
       +
       +struct        Keyboardctl
       +{
       +        struct Channel        *c;        /* chan(Rune)[20] */
       +
       +        char                *file;
       +        int                consfd;                /* to cons file */
       +        int                ctlfd;                /* to ctl file */
       +        int                pid;                /* of slave proc */
       +};
       +
       +
       +extern        Keyboardctl*        initkeyboard(char*);
       +extern        int                        ctlkeyboard(Keyboardctl*, char*);
       +extern        void                        closekeyboard(Keyboardctl*);
       +
       +enum {
       +        KF=        0xF000,        /* Rune: beginning of private Unicode space */
       +        /* KF|1, KF|2, ..., KF|0xC is F1, F2, ..., F12 */
       +        Khome=        KF|0x0D,
       +        Kup=        KF|0x0E,
       +        Kpgup=        KF|0x0F,
       +        Kprint=        KF|0x10,
       +        Kleft=        KF|0x11,
       +        Kright=        KF|0x12,
       +        Kdown=        0x80,
       +        Kview=        0x80,
       +        Kpgdown=        KF|0x13,
       +        Kins=        KF|0x14,
       +        Kend=        '\r',        /* [sic] */
       +
       +        Kalt=                KF|0x15,
       +        Kshift=        KF|0x16,
       +        Kctl=                KF|0x17,
       +};
 (DIR) diff --git a/src/libdraw/libdraw.x b/src/libdraw/libdraw.x
       t@@ -0,0 +1 @@
       +!<arch>
 (DIR) diff --git a/src/libdraw/md-alloc.c b/src/libdraw/md-alloc.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-arc.c b/src/libdraw/md-arc.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/md-arctest.c b/src/libdraw/md-arctest.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/libdraw/md-cload.c b/src/libdraw/md-cload.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-cmap.c b/src/libdraw/md-cmap.c
       t@@ -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){}
 (DIR) diff --git a/src/libdraw/md-cread.c b/src/libdraw/md-cread.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-defont.c b/src/libdraw/md-defont.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-draw.c b/src/libdraw/md-draw.c
       t@@ -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;
       +        }
       +}
       +
 (DIR) diff --git a/src/libdraw/md-drawtest.c b/src/libdraw/md-drawtest.c
       t@@ -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));
       +}
 (DIR) diff --git a/src/libdraw/md-ellipse.c b/src/libdraw/md-ellipse.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/md-fillpoly.c b/src/libdraw/md-fillpoly.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-hwdraw.c b/src/libdraw/md-hwdraw.c
       t@@ -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 */
       +}
       +
 (DIR) diff --git a/src/libdraw/md-iprint.c b/src/libdraw/md-iprint.c
       t@@ -0,0 +1,12 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <memdraw.h>
       +
       +int
       +iprint(char *fmt,...)
       +{
       +        USED(fmt);
       +        return -1;
       +}
       +
 (DIR) diff --git a/src/libdraw/md-line.c b/src/libdraw/md-line.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-load.c b/src/libdraw/md-load.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-mkcmap.c b/src/libdraw/md-mkcmap.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/md-openmemsubfont.c b/src/libdraw/md-openmemsubfont.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-poly.c b/src/libdraw/md-poly.c
       t@@ -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);
       +        }
       +}
 (DIR) diff --git a/src/libdraw/md-read.c b/src/libdraw/md-read.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-string.c b/src/libdraw/md-string.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-subfont.c b/src/libdraw/md-subfont.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/md-unload.c b/src/libdraw/md-unload.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/md-write.c b/src/libdraw/md-write.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/memdraw.h b/src/libdraw/memdraw.h
       t@@ -0,0 +1,209 @@
       +typedef struct        Memimage Memimage;
       +typedef struct        Memdata Memdata;
       +typedef struct        Memsubfont Memsubfont;
       +typedef struct        Memlayer Memlayer;
       +typedef struct        Memcmap Memcmap;
       +typedef struct        Memdrawparam        Memdrawparam;
       +
       +/*
       + * Memdata is allocated from main pool, but .data from the image pool.
       + * Memdata is allocated separately to permit patching its pointer after
       + * compaction when windows share the image data.
       + * The first word of data is a back pointer to the Memdata, to find
       + * The word to patch.
       + */
       +
       +struct Memdata
       +{
       +        u32int        *base;        /* allocated data pointer */
       +        uchar        *bdata;        /* pointer to first byte of actual data; word-aligned */
       +        int        ref;        /* number of Memimages using this data */
       +        void*        imref;
       +        int        allocd;        /* is this malloc'd? */
       +};
       +
       +enum {
       +        Frepl        = 1<<0,        /* is replicated */
       +        Fsimple        = 1<<1,        /* is 1x1 */
       +        Fgrey        = 1<<2,        /* is grey */
       +        Falpha        = 1<<3,        /* has explicit alpha */
       +        Fcmap        = 1<<4,        /* has cmap channel */
       +        Fbytes        = 1<<5,        /* has only 8-bit channels */
       +};
       +
       +struct Memimage
       +{
       +        Rectangle        r;        /* rectangle in data area, local coords */
       +        Rectangle        clipr;        /* clipping region */
       +        int                depth;        /* number of bits of storage per pixel */
       +        int                nchan;        /* number of channels */
       +        u32int                chan;        /* channel descriptions */
       +        Memcmap                *cmap;
       +
       +        Memdata                *data;        /* pointer to data; shared by windows in this image */
       +        int                zero;        /* data->bdata+zero==&byte containing (0,0) */
       +        u32int                width;        /* width in words of a single scan line */
       +        Memlayer        *layer;        /* nil if not a layer*/
       +        u32int                flags;
       +        void                *X;
       +
       +        int                shift[NChan];
       +        int                mask[NChan];
       +        int                nbits[NChan];
       +};
       +
       +struct Memcmap
       +{
       +        uchar        cmap2rgb[3*256];
       +        uchar        rgb2cmap[16*16*16];
       +};
       +
       +/*
       + * Subfonts
       + *
       + * given char c, Subfont *f, Fontchar *i, and Point p, one says
       + *        i = f->info+c;
       + *        draw(b, Rect(p.x+i->left, p.y+i->top,
       + *                p.x+i->left+((i+1)->x-i->x), p.y+i->bottom),
       + *                color, f->bits, Pt(i->x, i->top));
       + *        p.x += i->width;
       + * to draw characters in the specified color (itself a Memimage) in Memimage b.
       + */
       +
       +struct        Memsubfont
       +{
       +        char                *name;
       +        short                n;                /* number of chars in font */
       +        uchar                height;                /* height of bitmap */
       +        char                ascent;                /* top of bitmap to baseline */
       +        Fontchar        *info;                /* n+1 character descriptors */
       +        Memimage        *bits;                /* of font */
       +};
       +
       +/*
       + * Encapsulated parameters and information for sub-draw routines.
       + */
       +enum {
       +        Simplesrc=1<<0,
       +        Simplemask=1<<1,
       +        Replsrc=1<<2,
       +        Replmask=1<<3,
       +        Fullmask=1<<4,
       +};
       +struct        Memdrawparam
       +{
       +        Memimage *dst;
       +        Rectangle        r;
       +        Memimage *src;
       +        Rectangle sr;
       +        Memimage *mask;
       +        Rectangle mr;
       +        int op;
       +
       +        u32int state;
       +        u32int mval;        /* if Simplemask, the mask pixel in mask format */
       +        u32int mrgba;        /* mval in rgba */
       +        u32int sval;        /* if Simplesrc, the source pixel in src format */
       +        u32int srgba;        /* sval in rgba */
       +        u32int sdval;        /* sval in dst format */
       +};
       +
       +/*
       + * Memimage management
       + */
       +
       +extern Memimage*        allocmemimage(Rectangle, u32int);
       +extern Memimage*        allocmemimaged(Rectangle, u32int, Memdata*, void*);
       +extern Memimage*        readmemimage(int);
       +extern Memimage*        creadmemimage(int);
       +extern int                writememimage(int, Memimage*);
       +extern void                freememimage(Memimage*);
       +extern int                loadmemimage(Memimage*, Rectangle, uchar*, int);
       +extern int                cloadmemimage(Memimage*, Rectangle, uchar*, int);
       +extern int                unloadmemimage(Memimage*, Rectangle, uchar*, int);
       +extern u32int*                wordaddr(Memimage*, Point);
       +extern uchar*                byteaddr(Memimage*, Point);
       +extern int                drawclip(Memimage*, Rectangle*, Memimage*, Point*,
       +                                Memimage*, Point*, Rectangle*, Rectangle*);
       +extern void                memfillcolor(Memimage*, u32int);
       +extern int                memsetchan(Memimage*, u32int);
       +extern u32int                pixelbits(Memimage*, Point);
       +
       +/*
       + * Graphics
       + */
       +extern void        memdraw(Memimage*, Rectangle, Memimage*, Point, 
       +                        Memimage*, Point, int);
       +extern void        memline(Memimage*, Point, Point, int, int, int,
       +                        Memimage*, Point, int);
       +extern void        mempoly(Memimage*, Point*, int, int, int, int,
       +                        Memimage*, Point, int);
       +extern void        memfillpoly(Memimage*, Point*, int, int,
       +                        Memimage*, Point, int);
       +extern void        _memfillpolysc(Memimage*, Point*, int, int,
       +                        Memimage*, Point, int, int, int, int);
       +extern void        memimagedraw(Memimage*, Rectangle, Memimage*, Point,
       +                        Memimage*, Point, int);
       +extern int        hwdraw(Memdrawparam*);
       +extern void        memimageline(Memimage*, Point, Point, int, int, int,
       +                        Memimage*, Point, int);
       +extern void        _memimageline(Memimage*, Point, Point, int, int, int,
       +                        Memimage*, Point, Rectangle, int);
       +extern Point        memimagestring(Memimage*, Point, Memimage*, Point,
       +                        Memsubfont*, char*);
       +extern void        memellipse(Memimage*, Point, int, int, int,
       +                        Memimage*, Point, int);
       +extern void        memarc(Memimage*, Point, int, int, int, Memimage*,
       +                        Point, int, int, int);
       +extern Rectangle memlinebbox(Point, Point, int, int, int);
       +extern int        memlineendsize(int);
       +extern void        _memmkcmap(void);
       +extern void        memimageinit(void);
       +
       +/*
       + * Subfont management
       + */
       +extern Memsubfont*        allocmemsubfont(char*, int, int, int, Fontchar*, Memimage*);
       +extern Memsubfont*        openmemsubfont(char*);
       +extern void                freememsubfont(Memsubfont*);
       +extern Point                memsubfontwidth(Memsubfont*, char*);
       +extern Memsubfont*        getmemdefont(void);
       +
       +/*
       + * Predefined 
       + */
       +extern        Memimage*        memwhite;
       +extern        Memimage*        memblack;
       +extern        Memimage*        memopaque;
       +extern        Memimage*        memtransparent;
       +extern        Memcmap*        memdefcmap;
       +
       +/*
       + * Kernel interface
       + */
       +void                        memimagemove(void*, void*);
       +
       +/*
       + * Kernel cruft
       + */
       +extern void                rdb(void);
       +extern int                iprint(char*, ...);
       +extern int                drawdebug;
       +
       +/*
       + * For other implementations, like x11.
       + */
       +extern void                _memfillcolor(Memimage*, u32int);
       +extern Memimage*        _allocmemimage(Rectangle, u32int);
       +extern int                _cloadmemimage(Memimage*, Rectangle, uchar*, int);
       +extern int                _loadmemimage(Memimage*, Rectangle, uchar*, int);
       +extern void                _freememimage(Memimage*);
       +extern u32int                _rgbatoimg(Memimage*, u32int);
       +extern u32int                _imgtorgba(Memimage*, u32int);
       +extern u32int                _pixelbits(Memimage*, Point);
       +extern int                _unloadmemimage(Memimage*, Rectangle, uchar*, int);
       +extern Memdrawparam*        _memimagedrawsetup(Memimage*,
       +                                Rectangle, Memimage*, Point, Memimage*,
       +                                Point, int);
       +extern void                _memimagedraw(Memdrawparam*);
       +extern void                _drawreplacescreenimage(Memimage*);
 (DIR) diff --git a/src/libdraw/memlayer.h b/src/libdraw/memlayer.h
       t@@ -0,0 +1,48 @@
       +typedef struct Memscreen Memscreen;
       +typedef void (*Refreshfn)(Memimage*, Rectangle, void*);
       +
       +struct Memscreen
       +{
       +        Memimage        *frontmost;        /* frontmost layer on screen */
       +        Memimage        *rearmost;        /* rearmost layer on screen */
       +        Memimage        *image;                /* upon which all layers are drawn */
       +        Memimage        *fill;                        /* if non-zero, picture to use when repainting */
       +};
       +
       +struct Memlayer
       +{
       +        Rectangle                screenr;        /* true position of layer on screen */
       +        Point                        delta;        /* add delta to go from image coords to screen */
       +        Memscreen        *screen;        /* screen this layer belongs to */
       +        Memimage        *front;        /* window in front of this one */
       +        Memimage        *rear;        /* window behind this one*/
       +        int                clear;        /* layer is fully visible */
       +        Memimage        *save;        /* save area for obscured parts */
       +        Refreshfn        refreshfn;                /* function to call to refresh obscured parts if save==nil */
       +        void                *refreshptr;        /* argument to refreshfn */
       +};
       +
       +/*
       + * These functions accept local coordinates
       + */
       +int                        memload(Memimage*, Rectangle, uchar*, int, int);
       +int                        memunload(Memimage*, Rectangle, uchar*, int);
       +
       +/*
       + * All these functions accept screen coordinates, not local ones.
       + */
       +void                        _memlayerop(void (*fn)(Memimage*, Rectangle, Rectangle, void*, int), Memimage*, Rectangle, Rectangle, void*);
       +Memimage*        memlalloc(Memscreen*, Rectangle, Refreshfn, void*, u32int);
       +void                        memldelete(Memimage*);
       +void                        memlfree(Memimage*);
       +void                        memltofront(Memimage*);
       +void                        memltofrontn(Memimage**, int);
       +void                        _memltofrontfill(Memimage*, int);
       +void                        memltorear(Memimage*);
       +void                        memltorearn(Memimage**, int);
       +int                        memlsetrefresh(Memimage*, Refreshfn, void*);
       +void                        memlhide(Memimage*, Rectangle);
       +void                        memlexpose(Memimage*, Rectangle);
       +void                        _memlsetclear(Memscreen*);
       +int                        memlorigin(Memimage*, Point, Point);
       +void                        memlnorefresh(Memimage*, Rectangle, void*);
 (DIR) diff --git a/src/libdraw/menuhit.c b/src/libdraw/menuhit.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/mkfile b/src/libdraw/mkfile
       t@@ -0,0 +1 @@
       +<../libutf/mkfile
 (DIR) diff --git a/src/libdraw/ml-draw.c b/src/libdraw/ml-draw.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/ml-lalloc.c b/src/libdraw/ml-lalloc.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/ml-layerop.c b/src/libdraw/ml-layerop.c
       t@@ -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);
       +        }
       +}
 (DIR) diff --git a/src/libdraw/ml-ldelete.c b/src/libdraw/ml-ldelete.c
       t@@ -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;
       +                                }
       +        }
       +}
 (DIR) diff --git a/src/libdraw/ml-lhide.c b/src/libdraw/ml-lhide.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/ml-line.c b/src/libdraw/ml-line.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/ml-load.c b/src/libdraw/ml-load.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/ml-lorigin.c b/src/libdraw/ml-lorigin.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/ml-lsetrefresh.c b/src/libdraw/ml-lsetrefresh.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/ml-ltofront.c b/src/libdraw/ml-ltofront.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/ml-ltorear.c b/src/libdraw/ml-ltorear.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/ml-unload.c b/src/libdraw/ml-unload.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/mouse.c b/src/libdraw/mouse.c
       t@@ -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);
       +        }
       +}
 (DIR) diff --git a/src/libdraw/mouse.h b/src/libdraw/mouse.h
       t@@ -0,0 +1,44 @@
       +typedef struct        Menu Menu;
       +typedef struct         Mousectl Mousectl;
       +
       +struct        Mouse
       +{
       +        int        buttons;        /* bit array: LMR=124 */
       +        Point        xy;
       +        ulong        msec;
       +};
       +
       +struct Mousectl
       +{
       +        Mouse        m;
       +        struct Channel        *c;        /* chan(Mouse) */
       +        struct Channel        *resizec;        /* chan(int)[2] */
       +                        /* buffered in case client is waiting for a mouse action before handling resize */
       +
       +        char                *file;
       +        int                mfd;                /* to mouse file */
       +        int                cfd;                /* to cursor file */
       +        int                pid;                /* of slave proc */
       +        Display                *display;
       +        /*Image*        image;        / * of associated window/display */
       +};
       +
       +struct Menu
       +{
       +        char        **item;
       +        char        *(*gen)(int);
       +        int        lasthit;
       +};
       +
       +/*
       + * Mouse
       + */
       +extern Mousectl*        initmouse(char*, Image*);
       +extern void                moveto(Mousectl*, Point);
       +extern int                        readmouse(Mousectl*);
       +extern void                closemouse(Mousectl*);
       +struct Cursor;
       +extern void                setcursor(Mousectl*, struct Cursor*);
       +extern void                drawgetrect(Rectangle, int);
       +extern Rectangle        getrect(int, Mousectl*);
       +extern int                         menuhit(int, Mousectl*, Menu*, Screen*);
 (DIR) diff --git a/src/libdraw/openfont.c b/src/libdraw/openfont.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/readcolmap.c b/src/libdraw/readcolmap.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/readimage.c b/src/libdraw/readimage.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/readsubfont.c b/src/libdraw/readsubfont.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/string.c b/src/libdraw/string.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/stringwidth.c b/src/libdraw/stringwidth.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libdraw/subfont.c b/src/libdraw/subfont.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/subfontcache.c b/src/libdraw/subfontcache.c
       t@@ -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;
       +        }
       +}
 (DIR) diff --git a/src/libdraw/subfontname.c b/src/libdraw/subfontname.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/unix.c b/src/libdraw/unix.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/libdraw/unloadimage.c b/src/libdraw/unloadimage.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libdraw/writecolmap.c b/src/libdraw/writecolmap.c
       t@@ -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");
       +}
 (DIR) diff --git a/src/libdraw/x11-alloc.c b/src/libdraw/x11-alloc.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-cload.c b/src/libdraw/x11-cload.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-draw.c b/src/libdraw/x11-draw.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-event.c b/src/libdraw/x11-event.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-fill.c b/src/libdraw/x11-fill.c
       t@@ -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));
       +}
       +
       +
 (DIR) diff --git a/src/libdraw/x11-get.c b/src/libdraw/x11-get.c
       t@@ -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);
       +}
       +
       +
       +
 (DIR) diff --git a/src/libdraw/x11-inc.h b/src/libdraw/x11-inc.h
       t@@ -0,0 +1,31 @@
       +#define Colormap        XColormap
       +#define Cursor                XCursor
       +#define Display                XDisplay
       +#define Drawable        XDrawable
       +#define Font                XFont
       +#define GC                XGC
       +#define Point                XPoint
       +#define Rectangle        XRectangle
       +#define Screen                XScreen
       +#define Visual                XVisual
       +#define Window                XWindow
       +
       +#include <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
       +
 (DIR) diff --git a/src/libdraw/x11-init.c b/src/libdraw/x11-init.c
       t@@ -0,0 +1,584 @@
       +/*
       + * Some of the stuff in this file is not X-dependent and should be elsewhere.
       + */
       +#include "x11-inc.h"
       +#include <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;
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-itrans.c b/src/libdraw/x11-itrans.c
       t@@ -0,0 +1,258 @@
       +/* input event and data structure translation */
       +
       +#include "x11-inc.h"
       +
       +#include <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);
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-keyboard.c b/src/libdraw/x11-keyboard.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-load.c b/src/libdraw/x11-load.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-memdraw.h b/src/libdraw/x11-memdraw.h
       t@@ -0,0 +1,93 @@
       +/*
       + * Structure pointed to by X field of Memimage
       + */
       +
       +typedef struct Xmem Xmem;
       +typedef struct Xprivate Xprivate;
       +
       +enum
       +{
       +        PMundef = ~0
       +};
       +
       +struct Xmem
       +{
       +        int                pixmap;        /* pixmap id */
       +        XImage                *xi;        /* local image */
       +        int                dirty;        /* is the X server ahead of us?  */
       +        Rectangle        dirtyr;        /* which pixels? */
       +        Rectangle        r;        /* size of image */
       +};
       +
       +struct Xprivate {
       +        u32int                black;
       +        u32int                chan;
       +        XColormap        cmap;
       +        XCursor                cursor;
       +        XDisplay        *display;
       +        int                depth;                                /* of screen */
       +        XDrawable        drawable;
       +        XColor                map[256];
       +        XColor                map7[128];
       +        uchar                map7to8[128][2];
       +        XGC                gccopy;
       +        XGC                gccopy0;
       +        XGC                gcfill;
       +        u32int                gcfillcolor;
       +        XGC                gcfill0;
       +        u32int                gcfill0color;
       +        XGC                gcreplsrc;
       +        u32int                gcreplsrctile;
       +        XGC                gcreplsrc0;
       +        u32int                gcreplsrc0tile;
       +        XGC                gcsimplesrc;
       +        u32int                gcsimplesrccolor;
       +        u32int                gcsimplesrcpixmap;
       +        XGC                gcsimplesrc0;
       +        u32int                gcsimplesrc0color;
       +        u32int                gcsimplesrc0pixmap;
       +        XGC                gczero;
       +        u32int                gczeropixmap;
       +        XGC                gczero0;
       +        u32int                gczero0pixmap;
       +        XDisplay        *kbdcon;
       +        XDisplay        *mousecon;
       +        Memimage*        screenimage;
       +        XDrawable        screenpm;
       +        Rectangle        screenr;
       +        XDisplay        *snarfcon;
       +        int                toplan9[256];
       +        int                tox11[256];
       +        int                usetable;
       +        XVisual                *vis;
       +        u32int                white;
       +};
       +
       +extern Xprivate _x;
       +
       +extern Memimage *xallocmemimage(Rectangle, u32int, int);
       +extern XImage        *xallocxdata(Memimage*, Rectangle);
       +extern void        xdirtyxdata(Memimage*, Rectangle);
       +extern void        xfillcolor(Memimage*, Rectangle, u32int);
       +extern void        xfreexdata(Memimage*);
       +extern XImage        *xgetxdata(Memimage*, Rectangle);
       +extern void        xputxdata(Memimage*, Rectangle);
       +
       +struct Mouse;
       +extern int        xtoplan9mouse(XEvent*, struct Mouse*);
       +extern int        xtoplan9kbd(XEvent*);
       +extern void        xexpose(XEvent*, XDisplay*);
       +extern int        xconfigure(XEvent*, XDisplay*);
       +extern void        flushmemscreen(Rectangle);
       +extern void        xmoveto(Point);
       +struct Cursor;
       +extern void        xsetcursor(struct Cursor*);
       +
       +#define MouseMask (\
       +        ButtonPressMask|\
       +        ButtonReleaseMask|\
       +        PointerMotionMask|\
       +        Button1MotionMask|\
       +        Button2MotionMask|\
       +        Button3MotionMask)
       +
 (DIR) diff --git a/src/libdraw/x11-mouse.c b/src/libdraw/x11-mouse.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/libdraw/x11-pixelbits.c b/src/libdraw/x11-pixelbits.c
       t@@ -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);
       +}
       +
       +
 (DIR) diff --git a/src/libdraw/x11-unload.c b/src/libdraw/x11-unload.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/libthread/386.c b/src/libthread/386.c
       t@@ -0,0 +1,21 @@
       +#include "threadimpl.h"
       +
       +static void
       +launcher386(void (*f)(void *arg), void *arg)
       +{
       +        (*f)(arg);
       +        threadexits(nil);
       +}
       +
       +void
       +_threadinitstack(Thread *t, void (*f)(void*), void *arg)
       +{
       +        ulong *tos;
       +
       +        tos = (ulong*)&t->stk[t->stksize&~7];
       +        *--tos = (ulong)arg;
       +        *--tos = (ulong)f;
       +        t->sched.pc = (ulong)launcher386;
       +        t->sched.sp = (ulong)tos - 8;                /* old PC and new PC */
       +}
       +
 (DIR) diff --git a/src/libthread/FreeBSD-386.s b/src/libthread/FreeBSD-386.s
       t@@ -0,0 +1,18 @@
       +
       +.globl        _xinc
       +_xinc:
       +        movl 4(%esp), %eax
       +        lock incl 0(%eax)
       +        ret
       +
       +.globl        _xdec
       +_xdec:
       +        movl 4(%esp), %eax
       +        lock decl 0(%eax)
       +        jz iszero
       +        movl %eax, 1
       +        ret
       +iszero:
       +        movl %eax, 0
       +        ret
       +
 (DIR) diff --git a/src/libthread/LICENSE b/src/libthread/LICENSE
       t@@ -0,0 +1,258 @@
       +The Plan 9 software is provided under the terms of the
       +Lucent Public License, Version 1.02, reproduced below,
       +with the following exceptions:
       +
       +1. No right is granted to create derivative works of or
       +   to redistribute (other than with the Plan 9 Operating System)
       +   the screen imprinter fonts identified in subdirectory
       +   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
       +   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
       +   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
       +   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
       +
       +2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
       +   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
       +
       +3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
       +   covered by the Aladdin Free Public License, reproduced in the file
       +   /LICENSE.afpl.
       +
       +===================================================================
       +
       +Lucent Public License Version 1.02
       +
       +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
       +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
       +PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
       +
       +1. DEFINITIONS
       +
       +"Contribution" means:
       +
       +  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
       +     Program, and
       +  b. in the case of each Contributor,
       +
       +     i. changes to the Program, and
       +    ii. additions to the Program;
       +
       +    where such changes and/or additions to the Program were added to the
       +    Program by such Contributor itself or anyone acting on such
       +    Contributor's behalf, and the Contributor explicitly consents, in
       +    accordance with Section 3C, to characterization of the changes and/or
       +    additions as Contributions.
       +
       +"Contributor" means LUCENT and any other entity that has Contributed a
       +Contribution to the Program.
       +
       +"Distributor" means a Recipient that distributes the Program,
       +modifications to the Program, or any part thereof.
       +
       +"Licensed Patents" mean patent claims licensable by a Contributor
       +which are necessarily infringed by the use or sale of its Contribution
       +alone or when combined with the Program.
       +
       +"Original Program" means the original version of the software
       +accompanying this Agreement as released by LUCENT, including source
       +code, object code and documentation, if any.
       +
       +"Program" means the Original Program and Contributions or any part
       +thereof
       +
       +"Recipient" means anyone who receives the Program under this
       +Agreement, including all Contributors.
       +
       +2. GRANT OF RIGHTS
       +
       + a. Subject to the terms of this Agreement, each Contributor hereby
       +    grants Recipient a non-exclusive, worldwide, royalty-free copyright
       +    license to reproduce, prepare derivative works of, publicly display,
       +    publicly perform, distribute and sublicense the Contribution of such
       +    Contributor, if any, and such derivative works, in source code and
       +    object code form.
       +    
       + b. Subject to the terms of this Agreement, each Contributor hereby
       +    grants Recipient a non-exclusive, worldwide, royalty-free patent
       +    license under Licensed Patents to make, use, sell, offer to sell,
       +    import and otherwise transfer the Contribution of such Contributor, if
       +    any, in source code and object code form. The patent license granted
       +    by a Contributor shall also apply to the combination of the
       +    Contribution of that Contributor and the Program if, at the time the
       +    Contribution is added by the Contributor, such addition of the
       +    Contribution causes such combination to be covered by the Licensed
       +    Patents. The patent license granted by a Contributor shall not apply
       +    to (i) any other combinations which include the Contribution, nor to
       +    (ii) Contributions of other Contributors. No hardware per se is
       +    licensed hereunder.
       +    
       + c. Recipient understands that although each Contributor grants the
       +    licenses to its Contributions set forth herein, no assurances are
       +    provided by any Contributor that the Program does not infringe the
       +    patent or other intellectual property rights of any other entity. Each
       +    Contributor disclaims any liability to Recipient for claims brought by
       +    any other entity based on infringement of intellectual property rights
       +    or otherwise. As a condition to exercising the rights and licenses
       +    granted hereunder, each Recipient hereby assumes sole responsibility
       +    to secure any other intellectual property rights needed, if any. For
       +    example, if a third party patent license is required to allow
       +    Recipient to distribute the Program, it is Recipient's responsibility
       +    to acquire that license before distributing the Program.
       +
       + d. Each Contributor represents that to its knowledge it has sufficient
       +    copyright rights in its Contribution, if any, to grant the copyright
       +    license set forth in this Agreement.
       +
       +3. REQUIREMENTS
       +
       +A. Distributor may choose to distribute the Program in any form under
       +this Agreement or under its own license agreement, provided that:
       +
       + a. it complies with the terms and conditions of this Agreement;
       +
       + b. if the Program is distributed in source code or other tangible
       +    form, a copy of this Agreement or Distributor's own license agreement
       +    is included with each copy of the Program; and
       +
       + c. if distributed under Distributor's own license agreement, such
       +    license agreement:
       +
       +      i. effectively disclaims on behalf of all Contributors all warranties
       +         and conditions, express and implied, including warranties or
       +         conditions of title and non-infringement, and implied warranties or
       +         conditions of merchantability and fitness for a particular purpose;
       +     ii. effectively excludes on behalf of all Contributors all liability
       +         for damages, including direct, indirect, special, incidental and
       +         consequential damages, such as lost profits; and
       +    iii. states that any provisions which differ from this Agreement are
       +         offered by that Contributor alone and not by any other party.
       +
       +B. Each Distributor must include the following in a conspicuous
       +   location in the Program:
       +
       +   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
       +   Reserved.
       +
       +C. In addition, each Contributor must identify itself as the
       +originator of its Contribution in a manner that reasonably allows
       +subsequent Recipients to identify the originator of the Contribution.
       +Also, each Contributor must agree that the additions and/or changes
       +are intended to be a Contribution. Once a Contribution is contributed,
       +it may not thereafter be revoked.
       +
       +4. COMMERCIAL DISTRIBUTION
       +
       +Commercial distributors of software may accept certain
       +responsibilities with respect to end users, business partners and the
       +like. While this license is intended to facilitate the commercial use
       +of the Program, the Distributor who includes the Program in a
       +commercial product offering should do so in a manner which does not
       +create potential liability for Contributors. Therefore, if a
       +Distributor includes the Program in a commercial product offering,
       +such Distributor ("Commercial Distributor") hereby agrees to defend
       +and indemnify every Contributor ("Indemnified Contributor") against
       +any losses, damages and costs (collectively"Losses") arising from
       +claims, lawsuits and other legal actions brought by a third party
       +against the Indemnified Contributor to the extent caused by the acts
       +or omissions of such Commercial Distributor in connection with its
       +distribution of the Program in a commercial product offering. The
       +obligations in this section do not apply to any claims or Losses
       +relating to any actual or alleged intellectual property infringement.
       +In order to qualify, an Indemnified Contributor must: a) promptly
       +notify the Commercial Distributor in writing of such claim, and b)
       +allow the Commercial Distributor to control, and cooperate with the
       +Commercial Distributor in, the defense and any related settlement
       +negotiations. The Indemnified Contributor may participate in any such
       +claim at its own expense.
       +
       +For example, a Distributor might include the Program in a commercial
       +product offering, Product X. That Distributor is then a Commercial
       +Distributor. If that Commercial Distributor then makes performance
       +claims, or offers warranties related to Product X, those performance
       +claims and warranties are such Commercial Distributor's responsibility
       +alone. Under this section, the Commercial Distributor would have to
       +defend claims against the Contributors related to those performance
       +claims and warranties, and if a court requires any Contributor to pay
       +any damages as a result, the Commercial Distributor must pay those
       +damages.
       +
       +5. NO WARRANTY
       +
       +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
       +PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       +KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
       +WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
       +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
       +responsible for determining the appropriateness of using and
       +distributing the Program and assumes all risks associated with its
       +exercise of rights under this Agreement, including but not limited to
       +the risks and costs of program errors, compliance with applicable
       +laws, damage to or loss of data, programs or equipment, and
       +unavailability or interruption of operations.
       +
       +6. DISCLAIMER OF LIABILITY
       +
       +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
       +ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
       +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
       +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
       +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
       +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
       +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
       +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
       +
       +7. EXPORT CONTROL
       +
       +Recipient agrees that Recipient alone is responsible for compliance
       +with the United States export administration regulations (and the
       +export control laws and regulation of any other countries).
       +
       +8. GENERAL
       +
       +If any provision of this Agreement is invalid or unenforceable under
       +applicable law, it shall not affect the validity or enforceability of
       +the remainder of the terms of this Agreement, and without further
       +action by the parties hereto, such provision shall be reformed to the
       +minimum extent necessary to make such provision valid and enforceable.
       +
       +If Recipient institutes patent litigation against a Contributor with
       +respect to a patent applicable to software (including a cross-claim or
       +counterclaim in a lawsuit), then any patent licenses granted by that
       +Contributor to such Recipient under this Agreement shall terminate as
       +of the date such litigation is filed. In addition, if Recipient
       +institutes patent litigation against any entity (including a
       +cross-claim or counterclaim in a lawsuit) alleging that the Program
       +itself (excluding combinations of the Program with other software or
       +hardware) infringes such Recipient's patent(s), then such Recipient's
       +rights granted under Section 2(b) shall terminate as of the date such
       +litigation is filed.
       +
       +All Recipient's rights under this Agreement shall terminate if it
       +fails to comply with any of the material terms or conditions of this
       +Agreement and does not cure such failure in a reasonable period of
       +time after becoming aware of such noncompliance. If all Recipient's
       +rights under this Agreement terminate, Recipient agrees to cease use
       +and distribution of the Program as soon as reasonably practicable.
       +However, Recipient's obligations under this Agreement and any licenses
       +granted by Recipient relating to the Program shall continue and
       +survive.
       +
       +LUCENT may publish new versions (including revisions) of this
       +Agreement from time to time. Each new version of the Agreement will be
       +given a distinguishing version number. The Program (including
       +Contributions) may always be distributed subject to the version of the
       +Agreement under which it was received. In addition, after a new
       +version of the Agreement is published, Contributor may elect to
       +distribute the Program (including its Contributions) under the new
       +version. No one other than LUCENT has the right to modify this
       +Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
       +Recipient receives no rights or licenses to the intellectual property
       +of any Contributor under this Agreement, whether expressly, by
       +implication, estoppel or otherwise. All rights in the Program not
       +expressly granted under this Agreement are reserved.
       +
       +This Agreement is governed by the laws of the State of New York and
       +the intellectual property laws of the United States of America. No
       +party to this Agreement will bring a legal action under this Agreement
       +more than one year after the cause of action arose. Each party waives
       +its rights to a jury trial in any resulting litigation.
       +
 (DIR) diff --git a/src/libthread/Make.Darwin-PowerMacintosh b/src/libthread/Make.Darwin-PowerMacintosh
       t@@ -0,0 +1,6 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.FreeBSD-386 b/src/libthread/Make.FreeBSD-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.HP-UX-9000 b/src/libthread/Make.HP-UX-9000
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS=-O -c -Ae -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.Linux-386 b/src/libthread/Make.Linux-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.NetBSD-386 b/src/libthread/Make.NetBSD-386
       t@@ -0,0 +1,7 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O        # default, can be overriden by Make.$(SYSNAME)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.OSF1-alpha b/src/libthread/Make.OSF1-alpha
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS+=-g -c -I.
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.SunOS-sun4u b/src/libthread/Make.SunOS-sun4u
       t@@ -0,0 +1,2 @@
       +include Make.SunOS-sun4u-$(CC)
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.SunOS-sun4u-cc b/src/libthread/Make.SunOS-sun4u-cc
       t@@ -0,0 +1,6 @@
       +CC=cc
       +CFLAGS+=-g -c -I. -O
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Make.SunOS-sun4u-gcc b/src/libthread/Make.SunOS-sun4u-gcc
       t@@ -0,0 +1,6 @@
       +CC=gcc
       +CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
       +O=o
       +AR=ar
       +ARFLAGS=rvc
       +NAN=nan64.$O
 (DIR) diff --git a/src/libthread/Makefile b/src/libthread/Makefile
       t@@ -0,0 +1,125 @@
       +
       +# this works in gnu make
       +SYSNAME:=${shell uname}
       +OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
       +
       +# this works in bsd make
       +SYSNAME!=uname
       +OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
       +
       +# the gnu rules will mess up bsd but not vice versa,
       +# hence the gnu rules come first.
       +
       +include Make.$(SYSNAME)-$(OBJTYPE)
       +
       +PREFIX=/usr/local
       +
       +NUKEFILES=
       +
       +TGZFILES=
       +
       +LIB=libthread.a
       +VERSION=2.0
       +PORTPLACE=devel/libthread
       +NAME=libthread
       +
       +OFILES=\
       +        $(OBJTYPE).$O\
       +        asm-$(SYSNAME)-$(OBJTYPE).$O\
       +        channel.$O\
       +        chanprint.$O\
       +        create.$O\
       +        debug.$O\
       +        exec-unix.$O\
       +        exit.$O\
       +        getpid.$O\
       +        id.$O\
       +        iocall.$O\
       +        ioclose.$O\
       +        ioopen.$O\
       +        ioproc.$O\
       +        ioread.$O\
       +        ioreadn.$O\
       +        iowrite.$O\
       +        kill.$O\
       +        lib.$O\
       +        main.$O\
       +        memset.$O\
       +        memsetd.$O\
       +        note.$O\
       +        proctab.$O\
       +        ref.$O\
       +        rendez.$O\
       +        sched.$O\
       +
       +HFILES=\
       +        thread.h\
       +        label.h\
       +        threadimpl.h\
       +
       +all: $(LIB)
       +
       +install: $(LIB)
       +        test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
       +        install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
       +        install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
       +        install -m 0644 thread.h $(PREFIX)/include/thread.h
       +        install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
       +
       +tprimes: $(LIB) tprimes.$O
       +        $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
       +
       +texec: $(LIB) texec.$O
       +        $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
       +
       +$(LIB): $(OFILES)
       +        $(AR) $(ARFLAGS) $(LIB) $(OFILES)
       +
       +NUKEFILES+=$(LIB)
       +.c.$O:
       +        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
       +
       +%.$O: %.c
       +        $(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
       +
       +
       +$(OFILES): $(HFILES)
       +
       +tgz:
       +        rm -rf $(NAME)-$(VERSION)
       +        mkdir $(NAME)-$(VERSION)
       +        cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
       +        tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
       +        rm -rf $(NAME)-$(VERSION)
       +
       +clean:
       +        rm -f $(OFILES) $(LIB)
       +
       +nuke:
       +        rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
       +
       +rpm:
       +        make tgz
       +        cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
       +        rpm -ba rpm.spec
       +        cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
       +        cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
       +        scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
       +
       +PORTDIR=/usr/ports/$(PORTPLACE)
       +
       +ports:
       +        make tgz
       +        rm -rf $(PORTDIR)
       +        mkdir $(PORTDIR)
       +        cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
       +        cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
       +        (cd $(PORTDIR); make makesum)
       +        (cd $(PORTDIR); make)
       +        (cd $(PORTDIR); /usr/local/bin/portlint)
       +        rm -rf $(PORTDIR)/work
       +        shar `find $(PORTDIR)` > ports.shar
       +        (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
       +        scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
       +
       +.phony: all clean nuke install tgz rpm ports
 (DIR) diff --git a/src/libthread/Makefile.MID b/src/libthread/Makefile.MID
       t@@ -0,0 +1,54 @@
       +LIB=libthread.a
       +VERSION=2.0
       +PORTPLACE=devel/libthread
       +NAME=libthread
       +
       +OFILES=\
       +        $(OBJTYPE).$O\
       +        asm-$(SYSNAME)-$(OBJTYPE).$O\
       +        channel.$O\
       +        chanprint.$O\
       +        create.$O\
       +        debug.$O\
       +        exec-unix.$O\
       +        exit.$O\
       +        getpid.$O\
       +        id.$O\
       +        iocall.$O\
       +        ioclose.$O\
       +        ioopen.$O\
       +        ioproc.$O\
       +        ioread.$O\
       +        ioreadn.$O\
       +        iowrite.$O\
       +        kill.$O\
       +        lib.$O\
       +        main.$O\
       +        memset.$O\
       +        memsetd.$O\
       +        note.$O\
       +        proctab.$O\
       +        ref.$O\
       +        rendez.$O\
       +        sched.$O\
       +
       +HFILES=\
       +        thread.h\
       +        label.h\
       +        threadimpl.h\
       +
       +all: $(LIB)
       +
       +install: $(LIB)
       +        test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
       +        install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
       +        install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
       +        install -m 0644 thread.h $(PREFIX)/include/thread.h
       +        install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
       +
       +tprimes: $(LIB) tprimes.$O
       +        $(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
       +
       +texec: $(LIB) texec.$O
       +        $(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
       +
 (DIR) diff --git a/src/libthread/NOTICE b/src/libthread/NOTICE
       t@@ -0,0 +1,19 @@
       +/*
       + * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
       + *                Copyright (c) 2003 by Lucent Technologies.
       + * Permission to use, copy, modify, and distribute this software for any
       + * purpose without fee is hereby granted, provided that this entire notice
       + * is included in all copies of any software which is or includes a copy
       + * or modification of this software and in all copies of the supporting
       + * documentation for such software.
       + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
       + * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
       + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
       + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
       +*/
       +
       +This is a Unix port of the Plan 9 thread library.
       +
       +Please send comments about the packaging
       +to Russ Cox <rsc@post.harvard.edu>.
       +
 (DIR) diff --git a/src/libthread/README b/src/libthread/README
       t@@ -0,0 +1,19 @@
       +/*
       + * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
       + *                Copyright (c) 2003 by Lucent Technologies.
       + * Permission to use, copy, modify, and distribute this software for any
       + * purpose without fee is hereby granted, provided that this entire notice
       + * is included in all copies of any software which is or includes a copy
       + * or modification of this software and in all copies of the supporting
       + * documentation for such software.
       + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
       + * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
       + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
       + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
       +*/
       +
       +This is a Unix port of the Plan 9 thread library.
       +
       +Please send comments about the packaging
       +to Russ Cox <rsc@post.harvard.edu>.
       +
 (DIR) diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s
       t@@ -0,0 +1,49 @@
       +.globl        _setlabel
       +.type        _setlabel,@function
       +
       +_setlabel:
       +        movl        4(%esp), %eax
       +        movl        0(%esp), %edx
       +        movl        %edx, 0(%eax)
       +        movl        %ebx, 4(%eax)
       +        movl        %esp, 8(%eax)
       +        movl        %ebp, 12(%eax)
       +        movl        %esi, 16(%eax)
       +        movl        %edi, 20(%eax)
       +        xorl        %eax, %eax
       +        ret
       +
       +.globl        _gotolabel
       +.type        _gotolabel,@function
       +
       +_gotolabel:
       +        movl        4(%esp), %edx
       +        movl        0(%edx), %ecx
       +        movl        4(%edx), %ebx
       +        movl        8(%edx), %esp
       +        movl        12(%edx), %ebp
       +        movl        16(%edx), %esi
       +        movl        20(%edx), %edi
       +        xorl        %eax, %eax
       +        incl        %eax
       +        movl        %ecx, 0(%esp)
       +        ret
       +
       +
       +.globl        _xinc
       +_xinc:
       +        movl 4(%esp), %eax
       +        lock incl 0(%eax)
       +        ret
       +
       +.globl        _xdec
       +_xdec:
       +        movl 4(%esp), %eax
       +        lock decl 0(%eax)
       +        jz iszero
       +        movl %eax, 1
       +        ret
       +iszero:
       +        movl %eax, 0
       +        ret
       +
 (DIR) diff --git a/src/libthread/asm-Linux-386.s b/src/libthread/asm-Linux-386.s
       t@@ -0,0 +1 @@
       +.include "asm-FreeBSD-386.s"
 (DIR) diff --git a/src/libthread/bundle.ports b/src/libthread/bundle.ports
       t@@ -0,0 +1,42 @@
       +--- Makefile ---
       +# New ports collection makefile for: libthread
       +# Date Created:                11 Feb 2003
       +# Whom:                        rsc
       +#
       +
       +PORTNAME=        libthread
       +PORTVERSION=        1.0
       +CATEGORIES=        devel
       +MASTER_SITES=        http://pdos.lcs.mit.edu/~rsc/software/
       +DISTNAME=        libthread
       +EXTRACT_SUFX=        .tgz
       +
       +MAINTAINER=        rsc@post.harvard.edu
       +
       +MAN3=                print.3 fmtinstall.3
       +MLINKS=                XXX
       +USE_REINPLACE=        XXX (wkj says yes)
       +
       +.include <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.
 (DIR) diff --git a/src/libthread/channel.c b/src/libthread/channel.c
       t@@ -0,0 +1,485 @@
       +#include "threadimpl.h"
       +
       +static Lock chanlock;                /* central channel access lock */
       +
       +static void enqueue(Alt*, Channel**);
       +static void dequeue(Alt*);
       +static int altexec(Alt*, int);
       +
       +int _threadhighnentry;
       +int _threadnalt;
       +
       +static int
       +canexec(Alt *a)
       +{
       +        int i, otherop;
       +        Channel *c;
       +
       +        c = a->c;
       +        /* are there senders or receivers blocked? */
       +        otherop = (CHANSND+CHANRCV) - a->op;
       +        for(i=0; 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;
       +}
 (DIR) diff --git a/src/libthread/chanprint.c b/src/libthread/chanprint.c
       t@@ -0,0 +1,18 @@
       +#include "threadimpl.h"
       +
       +int
       +chanprint(Channel *c, char *fmt, ...)
       +{
       +        va_list arg;
       +        char *p;
       +        int n;
       +
       +        va_start(arg, fmt);
       +        p = vsmprint(fmt, arg);
       +        va_end(arg);
       +        if(p == nil)
       +                sysfatal("vsmprint failed: %r");
       +        n = sendp(c, p);
       +        yield();        /* let recipient handle message immediately */
       +        return n;
       +}
 (DIR) diff --git a/src/libthread/create.c b/src/libthread/create.c
       t@@ -0,0 +1,182 @@
       +#include "threadimpl.h"
       +
       +#define free
       +Pqueue _threadpq;
       +
       +static int nextID(void);
       +
       +/*
       + * Create and initialize a new Thread structure attached to a given proc.
       + */
       +
       +typedef struct Stack Stack;
       +struct Stack {
       +        ulong magic;
       +        Thread *thr;
       +        Stack *next;
       +        uchar buf[STKSIZE-12];
       +};
       +
       +static Stack *stkfree;
       +static Lock stklock;
       +
       +void
       +_stackfree(void *v)
       +{
       +        Stack *s;
       +
       +        s = v;
       +        lock(&stklock);
       +        s->thr = nil;
       +        s->magic = 0;
       +        s->next = stkfree;
       +        stkfree = s;
       +        unlock(&stklock);
       +}
       +
       +static Stack*
       +stackalloc(void)
       +{
       +        char *buf;
       +        Stack *s;
       +        int i;
       +
       +        lock(&stklock);
       +        while(stkfree == nil){
       +                unlock(&stklock);
       +                assert(STKSIZE == sizeof(Stack));
       +                buf = malloc(STKSIZE+128*STKSIZE);
       +                s = (Stack*)(((ulong)buf+STKSIZE)&~(STKSIZE-1));
       +                for(i=0; i<128; i++)
       +                        _stackfree(&s[i]);
       +                lock(&stklock);
       +        }
       +        s = stkfree;
       +        stkfree = stkfree->next;
       +        unlock(&stklock);
       +        s->magic = STKMAGIC;
       +        return s;
       +}
       +
       +static int
       +newthread(Proc *p, void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp)
       +{
       +        int id;
       +        Thread *t;
       +        Stack *s;
       +
       +        if(stacksize < 32)
       +                sysfatal("bad stacksize %d", stacksize);
       +        t = _threadmalloc(sizeof(Thread), 1);
       +        s = stackalloc();
       +        s->thr = t;
       +        t->stk = (char*)s;
       +        t->stksize = STKSIZE;
       +        _threaddebugmemset(s->buf, 0xFE, sizeof s->buf);
       +        _threadinitstack(t, f, arg);
       +        t->proc = p;
       +        t->grp = grp;
       +        if(name)
       +                t->cmdname = strdup(name);
       +        t->id = nextID();
       +        id = t->id;
       +        t->next = (Thread*)~0;
       +        _threaddebug(DBGSCHED, "create thread %d.%d name %s", p->pid, t->id, name);
       +        lock(&p->lock);
       +        p->nthreads++;
       +        if(p->threads.head == nil)
       +                p->threads.head = t;
       +        else{
       +                t->prevt = p->threads.tail;
       +                t->prevt->nextt = t;
       +        }
       +        p->threads.tail = t;
       +        t->state = Ready;
       +        _threadready(t);
       +        unlock(&p->lock);
       +        return id;
       +}
       +
       +static int
       +nextID(void)
       +{
       +        static Lock l;
       +        static int id;
       +        int i;
       +
       +        lock(&l);
       +        i = ++id;
       +        unlock(&l);
       +        return i;
       +}
       +        
       +int
       +procrfork(void (*f)(void *), void *arg, uint stacksize, int rforkflag)
       +{
       +        Proc *p;
       +        int id;
       +
       +        p = _threadgetproc();
       +        assert(p->newproc == nil);
       +        p->newproc = _newproc(f, arg, stacksize, nil, p->thread->grp, rforkflag);
       +        id = p->newproc->threads.head->id;
       +        _sched();
       +        return id;
       +}
       +
       +int
       +proccreate(void (*f)(void*), void *arg, uint stacksize)
       +{
       +        return procrfork(f, arg, stacksize, 0);
       +}
       +
       +void
       +_freeproc(Proc *p)
       +{
       +        Thread *t, *nextt;
       +
       +        for(t = p->threads.head; t; t = nextt){
       +                if(t->cmdname)
       +                        free(t->cmdname);
       +                assert(t->stk != nil);
       +                _stackfree((Stack*)t->stk);
       +                nextt = t->nextt;
       +                free(t);
       +        }
       +        free(p);
       +}
       +
       +/* 
       + * Create a new thread and schedule it to run.
       + * The thread grp is inherited from the currently running thread.
       + */
       +int
       +threadcreate(void (*f)(void *arg), void *arg, uint stacksize)
       +{
       +        return newthread(_threadgetproc(), f, arg, stacksize, nil, threadgetgrp());
       +}
       +
       +/*
       + * Create and initialize a new Proc structure with a single Thread
       + * running inside it.  Add the Proc to the global process list.
       + */
       +Proc*
       +_newproc(void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp, int rforkflag)
       +{
       +        Proc *p;
       +
       +        p = _threadmalloc(sizeof *p, 1);
       +        p->pid = -1;
       +        p->rforkflag = rforkflag;
       +        newthread(p, f, arg, stacksize, name, grp);
       +
       +        lock(&_threadpq.lock);
       +        if(_threadpq.head == nil)
       +                _threadpq.head = p;
       +        else
       +                *_threadpq.tail = p;
       +        _threadpq.tail = &p->next;
       +        unlock(&_threadpq.lock);
       +        return p;
       +}
       +
 (DIR) diff --git a/src/libthread/debug.c b/src/libthread/debug.c
       t@@ -0,0 +1,48 @@
       +#include "threadimpl.h"
       +
       +int _threaddebuglevel;
       +
       +void
       +__threaddebug(ulong flag, char *fmt, ...)
       +{
       +        char buf[128];
       +        va_list arg;
       +        Fmt f;
       +        Proc *p;
       +
       +        if((_threaddebuglevel&flag) == 0)
       +                return;
       +
       +        fmtfdinit(&f, 2, buf, sizeof buf);
       +
       +        p = _threadgetproc();
       +        if(p==nil)
       +                fmtprint(&f, "noproc ");
       +        else if(p->thread)
       +                fmtprint(&f, "%d.%d ", p->pid, p->thread->id);
       +        else
       +                fmtprint(&f, "%d._ ", p->pid);
       +
       +        va_start(arg, fmt);
       +        fmtvprint(&f, fmt, arg);
       +        va_end(arg);
       +        fmtprint(&f, "\n");
       +        fmtfdflush(&f);
       +}
       +
       +void
       +_threadassert(char *s)
       +{
       +        char buf[256];
       +        int n;
       +        Proc *p;
       +
       +        p = _threadgetproc();
       +        if(p && p->thread)
       +                n = sprint(buf, "%d.%d ", p->pid, p->thread->id);
       +        else
       +                n = 0;
       +        snprint(buf+n, sizeof(buf)-n, "%s: assertion failed\n", s);
       +        write(2, buf, strlen(buf));
       +        abort();
       +}
 (DIR) diff --git a/src/libthread/exec-unix.c b/src/libthread/exec-unix.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libthread/exec.c b/src/libthread/exec.c
       t@@ -0,0 +1,77 @@
       +#include "threadimpl.h"
       +
       +#define PIPEMNT        "/mnt/temp"
       +
       +void
       +procexec(Channel *pidc, char *prog, char *args[])
       +{
       +        int n;
       +        Proc *p;
       +        Thread *t;
       +
       +        _threaddebug(DBGEXEC, "procexec %s", prog);
       +        /* must be only thread in proc */
       +        p = _threadgetproc();
       +        t = p->thread;
       +        if(p->threads.head != t || p->threads.head->nextt != nil){
       +                werrstr("not only thread in proc");
       +        Bad:
       +                if(pidc)
       +                        sendul(pidc, ~0);
       +                return;
       +        }
       +
       +        /*
       +         * We want procexec to behave like exec; if exec succeeds,
       +         * never return, and if it fails, return with errstr set.
       +         * Unfortunately, the exec happens in another proc since
       +         * we have to wait for the exec'ed process to finish.
       +         * To provide the semantics, we open a pipe with the 
       +         * write end close-on-exec and hand it to the proc that
       +         * is doing the exec.  If the exec succeeds, the pipe will
       +         * close so that our read below fails.  If the exec fails,
       +         * then the proc doing the exec sends the errstr down the
       +         * pipe to us.
       +         */
       +        if(bind("#|", PIPEMNT, MREPL) < 0)
       +                goto Bad;
       +        if((p->exec.fd[0] = open(PIPEMNT "/data", OREAD)) < 0){
       +                unmount(nil, PIPEMNT);
       +                goto Bad;
       +        }
       +        if((p->exec.fd[1] = open(PIPEMNT "/data1", OWRITE|OCEXEC)) < 0){
       +                close(p->exec.fd[0]);
       +                unmount(nil, PIPEMNT);
       +                goto Bad;
       +        }
       +        unmount(nil, PIPEMNT);
       +
       +        /* exec in parallel via the scheduler */
       +        assert(p->needexec==0);
       +        p->exec.prog = prog;
       +        p->exec.args = args;
       +        p->needexec = 1;
       +        _sched();
       +
       +        close(p->exec.fd[1]);
       +        if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){        /* exec failed */
       +                p->exitstr[n] = '\0';
       +                errstr(p->exitstr, ERRMAX);
       +                close(p->exec.fd[0]);
       +                goto Bad;
       +        }
       +        close(p->exec.fd[0]);
       +
       +        if(pidc)
       +                sendul(pidc, t->ret);
       +
       +        /* wait for exec'ed program, then exit */
       +        _schedexecwait();
       +}
       +
       +void
       +procexecl(Channel *pidc, char *f, ...)
       +{
       +        procexec(pidc, f, &f+1);
       +}
       +
 (DIR) diff --git a/src/libthread/exit.c b/src/libthread/exit.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/libthread/getpid.c b/src/libthread/getpid.c
       t@@ -0,0 +1,8 @@
       +#include "threadimpl.h"
       +#include <unistd.h>
       +
       +int
       +_threadgetpid(void)
       +{
       +        return getpid();
       +}
 (DIR) diff --git a/src/libthread/id.c b/src/libthread/id.c
       t@@ -0,0 +1,135 @@
       +#include "threadimpl.h"
       +
       +int
       +threadid(void)
       +{
       +        return _threadgetproc()->thread->id;
       +}
       +
       +int
       +threadpid(int id)
       +{
       +        int pid;
       +        Proc *p;
       +        Thread *t;
       +
       +        if (id < 0)
       +                return -1;
       +        if (id == 0)
       +                return _threadgetproc()->pid;
       +        lock(&_threadpq.lock);
       +        for (p = _threadpq.head; p->next; p = p->next){
       +                lock(&p->lock);
       +                for (t = p->threads.head; t; t = t->nextt)
       +                        if (t->id == id){
       +                                pid = p->pid;
       +                                unlock(&p->lock);
       +                                unlock(&_threadpq.lock);
       +                                return pid;
       +                        }
       +                unlock(&p->lock);
       +        }
       +        unlock(&_threadpq.lock);
       +        return -1;
       +}
       +
       +int
       +threadsetgrp(int ng)
       +{
       +        int og;
       +        Thread *t;
       +
       +        t = _threadgetproc()->thread;
       +        og = t->grp;
       +        t->grp = ng;
       +        return og;
       +}
       +
       +int
       +threadgetgrp(void)
       +{
       +        return _threadgetproc()->thread->grp;
       +}
       +
       +void
       +threadsetname(char *name)
       +{
       +/*
       +        int fd, n;
       +        char buf[128], *s;
       +*/
       +        Proc *p;
       +        Thread *t;
       +
       +        p = _threadgetproc();
       +        t = p->thread;
       +        if (t->cmdname)
       +                free(t->cmdname);
       +        t->cmdname = strdup(name);
       +/* Plan 9 only 
       +        if(p->nthreads == 1){
       +                snprint(buf, sizeof buf, "#p/%d/args", getpid());
       +                if((fd = open(buf, OWRITE)) >= 0){
       +                        snprint(buf, sizeof buf, "%s [%s]", argv0, name);
       +                        n = strlen(buf)+1;
       +                        s = strchr(buf, ' ');
       +                        if(s)
       +                                *s = '\0';
       +                        write(fd, buf, n);
       +                        close(fd);
       +                }
       +        }
       +*/
       +}
       +
       +char*
       +threadgetname(void)
       +{
       +        return _threadgetproc()->thread->cmdname;
       +}
       +
       +void**
       +threaddata(void)
       +{
       +        return &_threadgetproc()->thread->udata[0];
       +}
       +
       +void**
       +procdata(void)
       +{
       +        return &_threadgetproc()->udata;
       +}
       +
       +static Lock privlock;
       +static int privmask = 1;
       +
       +int
       +tprivalloc(void)
       +{
       +        int i;
       +
       +        lock(&privlock);
       +        for(i=0; i<NPRIV; 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];
       +}
 (DIR) diff --git a/src/libthread/iocall.c b/src/libthread/iocall.c
       t@@ -0,0 +1,49 @@
       +#include "threadimpl.h"
       +
       +long
       +iocall(Ioproc *io, long (*op)(va_list*), ...)
       +{
       +        int ret, inted;
       +        Ioproc *msg;
       +
       +        if(send(io->c, &io) == -1){
       +                werrstr("interrupted");
       +                return -1;
       +        }
       +        assert(!io->inuse);
       +        io->inuse = 1;
       +        io->op = op;
       +        va_start(io->arg, op);
       +        msg = io;
       +        inted = 0;
       +        while(send(io->creply, &msg) == -1){
       +                msg = nil;
       +                inted = 1;
       +        }
       +        if(inted){
       +                werrstr("interrupted");
       +                return -1;
       +        }
       +
       +        /*
       +         * If we get interrupted, we have stick around so that
       +         * the IO proc has someone to talk to.  Send it an interrupt
       +         * and try again.
       +         */
       +        inted = 0;
       +        while(recv(io->creply, nil) == -1){
       +                inted = 1;
       +                iointerrupt(io);
       +        }
       +        USED(inted);
       +        va_end(io->arg);
       +        ret = io->ret;
       +        if(ret < 0)
       +                errstr(io->err, sizeof io->err);
       +        io->inuse = 0;
       +
       +        /* release resources */
       +        while(send(io->creply, &io) == -1)
       +                ;
       +        return ret;
       +}
 (DIR) diff --git a/src/libthread/ioclose.c b/src/libthread/ioclose.c
       t@@ -0,0 +1,16 @@
       +#include "threadimpl.h"
       +
       +static long
       +_ioclose(va_list *arg)
       +{
       +        int fd;
       +
       +        fd = va_arg(*arg, int);
       +        return close(fd);
       +}
       +
       +int
       +ioclose(Ioproc *io, int fd)
       +{
       +        return iocall(io, _ioclose, fd);
       +}
 (DIR) diff --git a/src/libthread/iodial.c b/src/libthread/iodial.c
       t@@ -0,0 +1,21 @@
       +#include "threadimpl.h"
       +
       +static long
       +_iodial(va_list *arg)
       +{
       +        char *addr, *local, *dir;
       +        int *cdfp;
       +
       +        addr = va_arg(*arg, char*);
       +        local = va_arg(*arg, char*);
       +        dir = va_arg(*arg, char*);
       +        cdfp = va_arg(*arg, int*);
       +
       +        return dial(addr, local, dir, cdfp);
       +}
       +
       +int
       +iodial(Ioproc *io, char *addr, char *local, char *dir, int *cdfp)
       +{
       +        return iocall(io, _iodial, addr, local, dir, cdfp);
       +}
 (DIR) diff --git a/src/libthread/ioopen.c b/src/libthread/ioopen.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libthread/ioproc.3 b/src/libthread/ioproc.3
       t@@ -0,0 +1,179 @@
       +.TH IOPROC 2
       +.SH NAME
       +closeioproc,
       +iocall,
       +ioclose,
       +iointerrupt,
       +iodial,
       +ioopen,
       +ioproc,
       +ioread,
       +ioreadn,
       +iowrite \- slave I/O processes for threaded programs
       +.SH SYNOPSIS
       +.PP
       +.de XX
       +.ift .sp 0.5
       +.ifn .sp
       +..
       +.EX
       +.ta \w'Ioproc* 'u
       +#include <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)
       +
 (DIR) diff --git a/src/libthread/ioproc.c b/src/libthread/ioproc.c
       t@@ -0,0 +1,74 @@
       +#include "threadimpl.h"
       +
       +enum
       +{
       +        STACK = 8192,
       +};
       +
       +void
       +iointerrupt(Ioproc *io)
       +{
       +        if(!io->inuse)
       +                return;
       +        threadint(io->tid);
       +}
       +
       +static void
       +xioproc(void *a)
       +{
       +        Ioproc *io, *x;
       +        io = a;
       +        /*
       +         * first recvp acquires the ioproc.
       +         * second tells us that the data is ready.
       +         */
       +        for(;;){
       +                while(recv(io->c, &x) == -1)
       +                        ;
       +                if(x == 0)        /* our cue to leave */
       +                        break;
       +                assert(x == io);
       +
       +                /* caller is now committed -- even if interrupted he'll return */
       +                while(recv(io->creply, &x) == -1)
       +                        ;
       +                if(x == 0)        /* caller backed out */
       +                        continue;
       +                assert(x == io);
       +
       +                io->ret = io->op(&io->arg);
       +                if(io->ret < 0)
       +                        rerrstr(io->err, sizeof io->err);
       +                while(send(io->creply, &io) == -1)
       +                        ;
       +                while(recv(io->creply, &x) == -1)
       +                        ;
       +        }
       +}
       +
       +Ioproc*
       +ioproc(void)
       +{
       +        Ioproc *io;
       +
       +        io = mallocz(sizeof(*io), 1);
       +        if(io == nil)
       +                sysfatal("ioproc malloc: %r");
       +        io->c = chancreate(sizeof(void*), 0);
       +        io->creply = chancreate(sizeof(void*), 0);
       +        io->tid = proccreate(xioproc, io, STACK);
       +        return io;
       +}
       +
       +void
       +closeioproc(Ioproc *io)
       +{
       +        if(io == nil)
       +                return;
       +        iointerrupt(io);
       +        while(send(io->c, 0) == -1)
       +                ;
       +        chanfree(io->c);
       +        chanfree(io->creply);
       +        free(io);
       +}
 (DIR) diff --git a/src/libthread/ioread.c b/src/libthread/ioread.c
       t@@ -0,0 +1,20 @@
       +#include "threadimpl.h"
       +
       +static long
       +_ioread(va_list *arg)
       +{
       +        int fd;
       +        void *a;
       +        long n;
       +
       +        fd = va_arg(*arg, int);
       +        a = va_arg(*arg, void*);
       +        n = va_arg(*arg, long);
       +        return read(fd, a, n);
       +}
       +
       +long
       +ioread(Ioproc *io, int fd, void *a, long n)
       +{
       +        return iocall(io, _ioread, fd, a, n);
       +}
 (DIR) diff --git a/src/libthread/ioreadn.c b/src/libthread/ioreadn.c
       t@@ -0,0 +1,21 @@
       +#include "threadimpl.h"
       +
       +static long
       +_ioreadn(va_list *arg)
       +{
       +        int fd;
       +        void *a;
       +        long n;
       +
       +        fd = va_arg(*arg, int);
       +        a = va_arg(*arg, void*);
       +        n = va_arg(*arg, long);
       +        n = readn(fd, a, n);
       +        return n;
       +}
       +
       +long
       +ioreadn(Ioproc *io, int fd, void *a, long n)
       +{
       +        return iocall(io, _ioreadn, fd, a, n);
       +}
 (DIR) diff --git a/src/libthread/iosleep.c b/src/libthread/iosleep.c
       t@@ -0,0 +1,16 @@
       +#include "threadimpl.h"
       +
       +static long
       +_iosleep(va_list *arg)
       +{
       +        long n;
       +
       +        n = va_arg(*arg, long);
       +        return sleep(n);
       +}
       +
       +int
       +iosleep(Ioproc *io, long n)
       +{
       +        return iocall(io, _iosleep, n);
       +}
 (DIR) diff --git a/src/libthread/iowrite.c b/src/libthread/iowrite.c
       t@@ -0,0 +1,21 @@
       +#include "threadimpl.h"
       +
       +static long
       +_iowrite(va_list *arg)
       +{
       +        int fd;
       +        void *a;
       +        long n;
       +
       +        fd = va_arg(*arg, int);
       +        a = va_arg(*arg, void*);
       +        n = va_arg(*arg, long);
       +        n = write(fd, a, n);
       +        return n;
       +}
       +
       +long
       +iowrite(Ioproc *io, int fd, void *a, long n)
       +{
       +        return iocall(io, _iowrite, fd, a, n);
       +}
 (DIR) diff --git a/src/libthread/kill.c b/src/libthread/kill.c
       t@@ -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;
       +        }
       +}
 (DIR) diff --git a/src/libthread/label.h b/src/libthread/label.h
       t@@ -0,0 +1,24 @@
       +/*
       + * setjmp and longjmp, but our own because some (stupid) c libraries
       + * assume longjmp is only used to move up the stack, and error out
       + * if you do otherwise.
       + */
       +
       +typedef struct Label Label;
       +#define LABELDPC 0
       +
       +#if defined (__i386__) && (defined(__FreeBSD__) || defined(__linux__))
       +struct Label
       +{
       +        ulong pc;
       +        ulong bx;
       +        ulong sp;
       +        ulong bp;
       +        ulong si;
       +        ulong di;
       +};
       +#else
       +#error "Unknown or unsupported architecture"
       +#endif
       +
       +
 (DIR) diff --git a/src/libthread/lib.c b/src/libthread/lib.c
       t@@ -0,0 +1,35 @@
       +#include "threadimpl.h"
       +
       +static long totalmalloc;
       +
       +void*
       +_threadmalloc(long size, int z)
       +{
       +        void *m;
       +
       +        m = malloc(size);
       +        if (m == nil)
       +                sysfatal("Malloc of size %ld failed: %r\n", size);
       +        setmalloctag(m, getcallerpc(&size));
       +        totalmalloc += size;
       +        if (size > 1000000) {
       +                fprint(2, "Malloc of size %ld, total %ld\n", size, totalmalloc);
       +                abort();
       +        }
       +        if (z)
       +                _threadmemset(m, 0, size);
       +        return m;
       +}
       +
       +void
       +_threadsysfatal(char *fmt, va_list arg)
       +{
       +        char buf[1024];        /* size doesn't matter; we're about to exit */
       +
       +        vseprint(buf, buf+sizeof(buf), fmt, arg);
       +        if(argv0)
       +                fprint(2, "%s: %s\n", argv0, buf);
       +        else
       +                fprint(2, "%s\n", buf);
       +        threadexitsall(buf);
       +}
 (DIR) diff --git a/src/libthread/main.c b/src/libthread/main.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libthread/memset.c b/src/libthread/memset.c
       t@@ -0,0 +1,8 @@
       +#include "threadimpl.h"
       +#include <string.h>
       +
       +void
       +_threadmemset(void *v, int c, int n)
       +{
       +        memset(v, c, n);
       +}
 (DIR) diff --git a/src/libthread/memsetd.c b/src/libthread/memsetd.c
       t@@ -0,0 +1,8 @@
       +#include "threadimpl.h"
       +#include <string.h>
       +
       +void
       +_threaddebugmemset(void *v, int c, int n)
       +{
       +        memset(v, c, n);
       +}
 (DIR) diff --git a/src/libthread/mkfile b/src/libthread/mkfile
       t@@ -0,0 +1,2 @@
       +<../libutf/mkfile
       +
 (DIR) diff --git a/src/libthread/note.c b/src/libthread/note.c
       t@@ -0,0 +1,143 @@
       +#include "threadimpl.h"
       +
       +int        _threadnopasser;
       +
       +#ifdef NOTDEF
       +#define        NFN                33
       +#define        ERRLEN        48
       +typedef struct Note Note;
       +struct Note
       +{
       +        Lock                inuse;
       +        Proc                *proc;                /* recipient */
       +        char                s[ERRMAX];        /* arg2 */
       +};
       +
       +static Note        notes[128];
       +static Note        *enotes = notes+nelem(notes);
       +static int                (*onnote[NFN])(void*, char*);
       +static int                onnotepid[NFN];
       +static Lock        onnotelock;
       +
       +int
       +threadnotify(int (*f)(void*, char*), int in)
       +{
       +        int i, topid;
       +        int (*from)(void*, char*), (*to)(void*, char*);
       +
       +        if(in){
       +                from = nil;
       +                to = f;
       +                topid = _threadgetproc()->pid;
       +        }else{
       +                from = f;
       +                to = nil;
       +                topid = 0;
       +        }
       +        lock(&onnotelock);
       +        for(i=0; 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);
       +*/
       +}
       +
 (DIR) diff --git a/src/libthread/proctab.c b/src/libthread/proctab.c
       t@@ -0,0 +1,64 @@
       +#include "threadimpl.h"
       +
       +/* this will need work */
       +enum
       +{
       +        PTABHASH = 257,
       +};
       +
       +static Lock ptablock;
       +Proc *ptab[PTABHASH];
       +
       +void
       +_threadsetproc(Proc *p)
       +{
       +        int h;
       +
       +        lock(&ptablock);
       +        h = ((unsigned)p->pid)%PTABHASH;
       +        p->link = ptab[h];
       +        unlock(&ptablock);
       +        ptab[h] = p;
       +}
       +
       +static Proc*
       +__threadgetproc(int rm)
       +{
       +        Proc **l, *p;
       +        int h, pid;
       +        Thread *t;
       +        ulong *s;
       +
       +        s = (ulong*)((ulong)&pid & ~(STKSIZE-1));
       +        if(s[0] == STKMAGIC){
       +                t = (Thread*)s[1];
       +                return t->proc;
       +        }
       +
       +        pid = _threadgetpid();
       +
       +        lock(&ptablock);
       +        h = ((unsigned)pid)%PTABHASH;
       +        for(l=&ptab[h]; p=*l; l=&p->link){
       +                if(p->pid == pid){
       +                        if(rm)
       +                                *l = p->link;
       +                        unlock(&ptablock);
       +                        return p;
       +                }
       +        }
       +        unlock(&ptablock);
       +        return nil;
       +}
       +
       +Proc*
       +_threadgetproc(void)
       +{
       +        return __threadgetproc(0);
       +}
       +
       +Proc*
       +_threaddelproc(void)
       +{
       +        return __threadgetproc(1);
       +}
 (DIR) diff --git a/src/libthread/ref.c b/src/libthread/ref.c
       t@@ -0,0 +1,13 @@
       +#include "threadimpl.h"
       +
       +void
       +incref(Ref *r)
       +{
       +        _xinc(&r->ref);
       +}
       +
       +long
       +decref(Ref *r)
       +{
       +        return _xdec(&r->ref);
       +}
 (DIR) diff --git a/src/libthread/rendez.c b/src/libthread/rendez.c
       t@@ -0,0 +1,104 @@
       +#include "threadimpl.h"
       +
       +Rgrp _threadrgrp;
       +static int isdirty;
       +int _threadhighnrendez;
       +int _threadnrendez;
       +static int nrendez;
       +
       +static ulong
       +finish(Thread *t, ulong val)
       +{
       +        ulong ret;
       +
       +        ret = t->rendval;
       +        t->rendval = val;
       +        while(t->state == Running)
       +                sleep(0);
       +        lock(&t->proc->lock);
       +        if(t->state == Rendezvous){        /* not always true: might be Dead */
       +                t->state = Ready;
       +                _threadready(t);
       +        }
       +        unlock(&t->proc->lock);
       +        return ret;
       +}
       +
       +ulong
       +_threadrendezvous(ulong tag, ulong val)
       +{
       +        ulong ret;
       +        Thread *t, **l;
       +
       +        lock(&_threadrgrp.lock);
       +_threadnrendez++;
       +        l = &_threadrgrp.hash[tag%nelem(_threadrgrp.hash)];
       +        for(t=*l; t; l=&t->rendhash, t=*l){
       +                if(t->rendtag==tag){
       +                        _threaddebug(DBGREND, "Rendezvous with thread %d.%d", t->proc->pid, t->id);
       +                        *l = t->rendhash;
       +                        ret = finish(t, val);
       +                        --nrendez;
       +                        unlock(&_threadrgrp.lock);
       +                        return ret;
       +                }
       +        }
       +
       +        /* Going to sleep here. */
       +        t = _threadgetproc()->thread;
       +        t->rendbreak = 0;
       +        t->inrendez = 1;
       +        t->rendtag = tag;
       +        t->rendval = val;
       +        t->rendhash = *l;
       +        *l = t;
       +        t->nextstate = Rendezvous;
       +        ++nrendez;
       +        if(nrendez > _threadhighnrendez)
       +                _threadhighnrendez = nrendez;
       +        _threaddebug(DBGREND, "Rendezvous for tag %lud", t->rendtag);
       +        unlock(&_threadrgrp.lock);
       +        _sched();
       +        t->inrendez = 0;
       +        _threaddebug(DBGREND, "Woke after rendezvous; val is %lud", t->rendval);
       +        return t->rendval;
       +}
       +
       +/*
       + * This is called while holding _threadpq.lock and p->lock,
       + * so we can't lock _threadrgrp.lock.  Instead our caller has 
       + * to call _threadbreakrendez after dropping those locks.
       + */
       +void
       +_threadflagrendez(Thread *t)
       +{
       +        t->rendbreak = 1;
       +        isdirty = 1;
       +}
       +
       +void
       +_threadbreakrendez(void)
       +{
       +        int i;
       +        Thread *t, **l;
       +
       +        if(isdirty == 0)
       +                return;
       +        lock(&_threadrgrp.lock);
       +        if(isdirty == 0){
       +                unlock(&_threadrgrp.lock);
       +                return;
       +        }
       +        isdirty = 0;
       +        for(i=0; 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);
       +}
 (DIR) diff --git a/src/libthread/rpm.spec b/src/libthread/rpm.spec
       t@@ -0,0 +1,26 @@
       +Summary: Port of Plan 9's thread library
       +Name: libthread
       +Version: 2.0
       +Release: 1
       +Group: Development/C
       +Copyright: BSD-like
       +Packager: Russ Cox <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
 (DIR) diff --git a/src/libthread/sched.c b/src/libthread/sched.c
       t@@ -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();
       +}
       +
 (DIR) diff --git a/src/libthread/texec.c b/src/libthread/texec.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/libthread/thread.3 b/src/libthread/thread.3
       t@@ -0,0 +1,576 @@
       +.TH THREAD 2
       +.SH NAME
       +alt,
       +chancreate,
       +chanfree,
       +chaninit,
       +chanprint,
       +mainstacksize,
       +proccreate,
       +procdata,
       +procexec,
       +procexecl,
       +procrfork,
       +recv,
       +recvp,
       +recvul,
       +send,
       +sendp,
       +sendul,
       +nbrecv,
       +nbrecvp,
       +nbrecvul,
       +nbsend,
       +nbsendp,
       +nbsendul,
       +threadcreate,
       +threaddata,
       +threadexits,
       +threadexitsall,
       +threadgetgrp,
       +threadgetname,
       +threadint,
       +threadintgrp,
       +threadkill,
       +threadkillgrp,
       +threadmain,
       +threadnotify,
       +threadid,
       +threadpid,
       +threadsetgrp,
       +threadsetname,
       +threadwaitchan,
       +yield \- thread and proc management
       +.SH SYNOPSIS
       +.PP
       +.EX
       +.ta 4n +4n +4n +4n +4n +4n +4n
       +#include <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)
 (DIR) diff --git a/src/libthread/thread.h b/src/libthread/thread.h
       t@@ -0,0 +1,132 @@
       +#ifndef _THREADH_
       +#define _THREADH_ 1
       +
       +/* avoid conflicts with socket library */
       +#undef send
       +#define send _threadsend
       +#undef recv
       +#define recv _threadrecv
       +
       +typedef struct Alt        Alt;
       +typedef struct Channel        Channel;
       +typedef struct Ref        Ref;
       +
       +/* Channel structure.  S is the size of the buffer.  For unbuffered channels
       + * s is zero.  v is an array of s values.  If s is zero, v is unused.
       + * f and n represent the state of the queue pointed to by v.
       + */
       +
       +enum {
       +        Nqwds = 2,
       +        Nqshift = 5,        // 2log #of bits in long
       +        Nqmask =  - 1,
       +        Nqbits = (1 << Nqshift) * 2,
       +};
       +
       +struct Channel {
       +        int                        s;                // Size of the channel (may be zero)
       +        unsigned int        f;                // Extraction point (insertion pt: (f + n) % s)
       +        unsigned int        n;                // Number of values in the channel
       +        int                        e;                // Element size
       +        int                        freed;        // Set when channel is being deleted
       +        volatile Alt        **qentry;        // Receivers/senders waiting (malloc)
       +        volatile int        nentry;        // # of entries malloc-ed
       +        unsigned char                v[1];                // Array of s values in the channel
       +};
       +
       +
       +/* Channel operations for alt: */
       +typedef enum {
       +        CHANEND,
       +        CHANSND,
       +        CHANRCV,
       +        CHANNOP,
       +        CHANNOBLK,
       +} ChanOp;
       +
       +struct Alt {
       +        Channel        *c;                /* channel */
       +        void                *v;                /* pointer to value */
       +        ChanOp        op;                /* operation */
       +
       +        /* the next variables are used internally to alt
       +         * they need not be initialized
       +         */
       +        Channel        **tag;        /* pointer to rendez-vous tag */
       +        int                entryno;        /* entry number */
       +};
       +
       +struct Ref {
       +        long ref;
       +};
       +
       +int                alt(Alt alts[]);
       +Channel*        chancreate(int elemsize, int bufsize);
       +int                chaninit(Channel *c, int elemsize, int elemcnt);
       +void                chanfree(Channel *c);
       +int                chanprint(Channel *, char *, ...);
       +long                decref(Ref *r);                /* returns 0 iff value is now zero */
       +void                incref(Ref *r);
       +int                nbrecv(Channel *c, void *v);
       +void*                nbrecvp(Channel *c);
       +unsigned long                nbrecvul(Channel *c);
       +int                nbsend(Channel *c, void *v);
       +int                nbsendp(Channel *c, void *v);
       +int                nbsendul(Channel *c, unsigned long v);
       +int                proccreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
       +int                procrfork(void (*f)(void *arg), void *arg, unsigned int stacksize, int flag);
       +void**                procdata(void);
       +void                procexec(Channel *, char *, char *[]);
       +void                procexecl(Channel *, char *, ...);
       +int                recv(Channel *c, void *v);
       +void*                recvp(Channel *c);
       +unsigned long                recvul(Channel *c);
       +int                send(Channel *c, void *v);
       +int                sendp(Channel *c, void *v);
       +int                sendul(Channel *c, unsigned long v);
       +int                threadcreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
       +void**                threaddata(void);
       +void                threadexits(char *);
       +void                threadexitsall(char *);
       +int                threadgetgrp(void);        /* return thread group of current thread */
       +char*                threadgetname(void);
       +void                threadint(int);        /* interrupt thread */
       +void                threadintgrp(int);        /* interrupt threads in grp */
       +void                threadkill(int);        /* kill thread */
       +void                threadkillgrp(int);        /* kill threads in group */
       +void                threadmain(int argc, char *argv[]);
       +void                threadnonotes(void);
       +int                threadnotify(int (*f)(void*, char*), int in);
       +int                threadid(void);
       +int                threadpid(int);
       +int                threadsetgrp(int);        /* set thread group, return old */
       +void                threadsetname(char *name);
       +Channel*        threadwaitchan(void);
       +int        tprivalloc(void);
       +void        tprivfree(int);
       +void        **tprivaddr(int);
       +void                yield(void);
       +
       +long                threadstack(void);
       +
       +extern        int                mainstacksize;
       +
       +/* slave I/O processes */
       +typedef struct Ioproc Ioproc;
       +
       +Ioproc*        ioproc(void);
       +void                closeioproc(Ioproc*);
       +void                iointerrupt(Ioproc*);
       +
       +int                ioclose(Ioproc*, int);
       +int                iodial(Ioproc*, char*, char*, char*, int*);
       +int                ioopen(Ioproc*, char*, int);
       +long                ioread(Ioproc*, int, void*, long);
       +long                ioreadn(Ioproc*, int, void*, long);
       +long                iowrite(Ioproc*, int, void*, long);
       +int                iosleep(Ioproc*, long);
       +
       +long                iocall(Ioproc*, long (*)(va_list*), ...);
       +void                ioret(Ioproc*, int);
       +
       +#endif        /* _THREADH_ */
 (DIR) diff --git a/src/libthread/threadimpl.h b/src/libthread/threadimpl.h
       t@@ -0,0 +1,219 @@
       +/* 
       + * Some notes on locking:
       + *
       + *        All the locking woes come from implementing
       + *        threadinterrupt (and threadkill).
       + *
       + *        _threadgetproc()->thread is always a live pointer.
       + *        p->threads, p->ready, and _threadrgrp also contain
       + *         live thread pointers.  These may only be consulted
       + *        while holding p->lock or _threadrgrp.lock; in procs
       + *        other than p, the pointers are only guaranteed to be live
       + *        while the lock is still being held.
       + *
       + *        Thread structures can only be freed by the proc
       + *        they belong to.  Threads marked with t->inrendez
       + *         need to be extracted from the _threadrgrp before
       + *        being freed.
       + *
       + *        _threadrgrp.lock cannot be acquired while holding p->lock.
       + */
       +
       +#include <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);
       +
 (DIR) diff --git a/src/libthread/tprimes b/src/libthread/tprimes
       Binary files differ.
 (DIR) diff --git a/src/libthread/tprimes.c b/src/libthread/tprimes.c
       t@@ -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);
       +}