Initial revision
diff --git a/src/libthread/386.c b/src/libthread/386.c
new file mode 100644
index 0000000..2f391bf
--- /dev/null
+++ b/src/libthread/386.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static void
+launcher386(void (*f)(void *arg), void *arg)
+{
+	(*f)(arg);
+	threadexits(nil);
+}
+
+void
+_threadinitstack(Thread *t, void (*f)(void*), void *arg)
+{
+	ulong *tos;
+
+	tos = (ulong*)&t->stk[t->stksize&~7];
+	*--tos = (ulong)arg;
+	*--tos = (ulong)f;
+	t->sched.pc = (ulong)launcher386;
+	t->sched.sp = (ulong)tos - 8;		/* old PC and new PC */
+}
+
diff --git a/src/libthread/FreeBSD-386.s b/src/libthread/FreeBSD-386.s
new file mode 100644
index 0000000..624518e
--- /dev/null
+++ b/src/libthread/FreeBSD-386.s
@@ -0,0 +1,18 @@
+
+.globl	_xinc
+_xinc:
+	movl 4(%esp), %eax
+	lock incl 0(%eax)
+	ret
+
+.globl	_xdec
+_xdec:
+	movl 4(%esp), %eax
+	lock decl 0(%eax)
+	jz iszero
+	movl %eax, 1
+	ret
+iszero:
+	movl %eax, 0
+	ret
+
diff --git a/src/libthread/LICENSE b/src/libthread/LICENSE
new file mode 100644
index 0000000..a5d7d87
--- /dev/null
+++ b/src/libthread/LICENSE
@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+   to redistribute (other than with the Plan 9 Operating System)
+   the screen imprinter fonts identified in subdirectory
+   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+   covered by the Aladdin Free Public License, reproduced in the file
+   /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+     Program, and
+  b. in the case of each Contributor,
+
+     i. changes to the Program, and
+    ii. additions to the Program;
+
+    where such changes and/or additions to the Program were added to the
+    Program by such Contributor itself or anyone acting on such
+    Contributor's behalf, and the Contributor explicitly consents, in
+    accordance with Section 3C, to characterization of the changes and/or
+    additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free copyright
+    license to reproduce, prepare derivative works of, publicly display,
+    publicly perform, distribute and sublicense the Contribution of such
+    Contributor, if any, and such derivative works, in source code and
+    object code form.
+    
+ b. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free patent
+    license under Licensed Patents to make, use, sell, offer to sell,
+    import and otherwise transfer the Contribution of such Contributor, if
+    any, in source code and object code form. The patent license granted
+    by a Contributor shall also apply to the combination of the
+    Contribution of that Contributor and the Program if, at the time the
+    Contribution is added by the Contributor, such addition of the
+    Contribution causes such combination to be covered by the Licensed
+    Patents. The patent license granted by a Contributor shall not apply
+    to (i) any other combinations which include the Contribution, nor to
+    (ii) Contributions of other Contributors. No hardware per se is
+    licensed hereunder.
+    
+ c. Recipient understands that although each Contributor grants the
+    licenses to its Contributions set forth herein, no assurances are
+    provided by any Contributor that the Program does not infringe the
+    patent or other intellectual property rights of any other entity. Each
+    Contributor disclaims any liability to Recipient for claims brought by
+    any other entity based on infringement of intellectual property rights
+    or otherwise. As a condition to exercising the rights and licenses
+    granted hereunder, each Recipient hereby assumes sole responsibility
+    to secure any other intellectual property rights needed, if any. For
+    example, if a third party patent license is required to allow
+    Recipient to distribute the Program, it is Recipient's responsibility
+    to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+    copyright rights in its Contribution, if any, to grant the copyright
+    license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+    form, a copy of this Agreement or Distributor's own license agreement
+    is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+    license agreement:
+
+      i. effectively disclaims on behalf of all Contributors all warranties
+         and conditions, express and implied, including warranties or
+         conditions of title and non-infringement, and implied warranties or
+         conditions of merchantability and fitness for a particular purpose;
+     ii. effectively excludes on behalf of all Contributors all liability
+         for damages, including direct, indirect, special, incidental and
+         consequential damages, such as lost profits; and
+    iii. states that any provisions which differ from this Agreement are
+         offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+   location in the Program:
+
+   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+   Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/libthread/Make.Darwin-PowerMacintosh b/src/libthread/Make.Darwin-PowerMacintosh
new file mode 100644
index 0000000..14b8d4e
--- /dev/null
+++ b/src/libthread/Make.Darwin-PowerMacintosh
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.FreeBSD-386 b/src/libthread/Make.FreeBSD-386
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/libthread/Make.FreeBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.HP-UX-9000 b/src/libthread/Make.HP-UX-9000
new file mode 100644
index 0000000..edbdc11
--- /dev/null
+++ b/src/libthread/Make.HP-UX-9000
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.Linux-386 b/src/libthread/Make.Linux-386
new file mode 100644
index 0000000..74b0252
--- /dev/null
+++ b/src/libthread/Make.Linux-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.NetBSD-386 b/src/libthread/Make.NetBSD-386
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/libthread/Make.NetBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/libthread/Make.OSF1-alpha b/src/libthread/Make.OSF1-alpha
new file mode 100644
index 0000000..3d45279
--- /dev/null
+++ b/src/libthread/Make.OSF1-alpha
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u b/src/libthread/Make.SunOS-sun4u
new file mode 100644
index 0000000..c5fe67b
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u
@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u-cc b/src/libthread/Make.SunOS-sun4u-cc
new file mode 100644
index 0000000..829301d
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u-cc
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Make.SunOS-sun4u-gcc b/src/libthread/Make.SunOS-sun4u-gcc
new file mode 100644
index 0000000..5c41594
--- /dev/null
+++ b/src/libthread/Make.SunOS-sun4u-gcc
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/libthread/Makefile b/src/libthread/Makefile
new file mode 100644
index 0000000..ebccc1a
--- /dev/null
+++ b/src/libthread/Makefile
@@ -0,0 +1,125 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+LIB=libthread.a
+VERSION=2.0
+PORTPLACE=devel/libthread
+NAME=libthread
+
+OFILES=\
+	$(OBJTYPE).$O\
+	asm-$(SYSNAME)-$(OBJTYPE).$O\
+	channel.$O\
+	chanprint.$O\
+	create.$O\
+	debug.$O\
+	exec-unix.$O\
+	exit.$O\
+	getpid.$O\
+	id.$O\
+	iocall.$O\
+	ioclose.$O\
+	ioopen.$O\
+	ioproc.$O\
+	ioread.$O\
+	ioreadn.$O\
+	iowrite.$O\
+	kill.$O\
+	lib.$O\
+	main.$O\
+	memset.$O\
+	memsetd.$O\
+	note.$O\
+	proctab.$O\
+	ref.$O\
+	rendez.$O\
+	sched.$O\
+
+HFILES=\
+	thread.h\
+	label.h\
+	threadimpl.h\
+
+all: $(LIB)
+
+install: $(LIB)
+	test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+	install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
+	install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
+	install -m 0644 thread.h $(PREFIX)/include/thread.h
+	install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+tprimes: $(LIB) tprimes.$O
+	$(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+texec: $(LIB) texec.$O
+	$(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+$(LIB): $(OFILES)
+	$(AR) $(ARFLAGS) $(LIB) $(OFILES)
+
+NUKEFILES+=$(LIB)
+.c.$O:
+	$(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+	$(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+	rm -rf $(NAME)-$(VERSION)
+	mkdir $(NAME)-$(VERSION)
+	cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+	tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+	rm -rf $(NAME)-$(VERSION)
+
+clean:
+	rm -f $(OFILES) $(LIB)
+
+nuke:
+	rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+	make tgz
+	cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+	rpm -ba rpm.spec
+	cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+	cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+	scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+	make tgz
+	rm -rf $(PORTDIR)
+	mkdir $(PORTDIR)
+	cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+	cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+	(cd $(PORTDIR); make makesum)
+	(cd $(PORTDIR); make)
+	(cd $(PORTDIR); /usr/local/bin/portlint)
+	rm -rf $(PORTDIR)/work
+	shar `find $(PORTDIR)` > ports.shar
+	(cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+	scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/libthread/Makefile.MID b/src/libthread/Makefile.MID
new file mode 100644
index 0000000..05c3416
--- /dev/null
+++ b/src/libthread/Makefile.MID
@@ -0,0 +1,54 @@
+LIB=libthread.a
+VERSION=2.0
+PORTPLACE=devel/libthread
+NAME=libthread
+
+OFILES=\
+	$(OBJTYPE).$O\
+	asm-$(SYSNAME)-$(OBJTYPE).$O\
+	channel.$O\
+	chanprint.$O\
+	create.$O\
+	debug.$O\
+	exec-unix.$O\
+	exit.$O\
+	getpid.$O\
+	id.$O\
+	iocall.$O\
+	ioclose.$O\
+	ioopen.$O\
+	ioproc.$O\
+	ioread.$O\
+	ioreadn.$O\
+	iowrite.$O\
+	kill.$O\
+	lib.$O\
+	main.$O\
+	memset.$O\
+	memsetd.$O\
+	note.$O\
+	proctab.$O\
+	ref.$O\
+	rendez.$O\
+	sched.$O\
+
+HFILES=\
+	thread.h\
+	label.h\
+	threadimpl.h\
+
+all: $(LIB)
+
+install: $(LIB)
+	test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+	install -m 0644 thread.3 $(PREFIX)/man/man3/thread.3
+	install -m 0644 ioproc.3 $(PREFIX)/man/man3/ioproc.3
+	install -m 0644 thread.h $(PREFIX)/include/thread.h
+	install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+tprimes: $(LIB) tprimes.$O
+	$(CC) -o tprimes tprimes.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
+texec: $(LIB) texec.$O
+	$(CC) -o texec texec.$O $(LIB) -L$(PREFIX)/lib -l9 -lfmt -lutf
+
diff --git a/src/libthread/NOTICE b/src/libthread/NOTICE
new file mode 100644
index 0000000..8bf69d6
--- /dev/null
+++ b/src/libthread/NOTICE
@@ -0,0 +1,19 @@
+/*
+ * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
+ *		Copyright (c) 2003 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+This is a Unix port of the Plan 9 thread library.
+
+Please send comments about the packaging
+to Russ Cox <rsc@post.harvard.edu>.
+
diff --git a/src/libthread/README b/src/libthread/README
new file mode 100644
index 0000000..8bf69d6
--- /dev/null
+++ b/src/libthread/README
@@ -0,0 +1,19 @@
+/*
+ * The authors of this software are Russ Cox, Sape Mullender, and Rob Pike.
+ *		Copyright (c) 2003 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+This is a Unix port of the Plan 9 thread library.
+
+Please send comments about the packaging
+to Russ Cox <rsc@post.harvard.edu>.
+
diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s
new file mode 100644
index 0000000..7cad85c
--- /dev/null
+++ b/src/libthread/asm-FreeBSD-386.s
@@ -0,0 +1,49 @@
+.globl	_setlabel
+.type	_setlabel,@function
+
+_setlabel:
+	movl	4(%esp), %eax
+	movl	0(%esp), %edx
+	movl	%edx, 0(%eax)
+	movl	%ebx, 4(%eax)
+	movl	%esp, 8(%eax)
+	movl	%ebp, 12(%eax)
+	movl	%esi, 16(%eax)
+	movl	%edi, 20(%eax)
+	xorl	%eax, %eax
+	ret
+
+.globl	_gotolabel
+.type	_gotolabel,@function
+
+_gotolabel:
+	movl	4(%esp), %edx
+	movl	0(%edx), %ecx
+	movl	4(%edx), %ebx
+	movl	8(%edx), %esp
+	movl	12(%edx), %ebp
+	movl	16(%edx), %esi
+	movl	20(%edx), %edi
+	xorl	%eax, %eax
+	incl	%eax
+	movl	%ecx, 0(%esp)
+	ret
+
+
+.globl	_xinc
+_xinc:
+	movl 4(%esp), %eax
+	lock incl 0(%eax)
+	ret
+
+.globl	_xdec
+_xdec:
+	movl 4(%esp), %eax
+	lock decl 0(%eax)
+	jz iszero
+	movl %eax, 1
+	ret
+iszero:
+	movl %eax, 0
+	ret
+
diff --git a/src/libthread/asm-Linux-386.s b/src/libthread/asm-Linux-386.s
new file mode 100644
index 0000000..75d965b
--- /dev/null
+++ b/src/libthread/asm-Linux-386.s
@@ -0,0 +1 @@
+.include "asm-FreeBSD-386.s"
diff --git a/src/libthread/bundle.ports b/src/libthread/bundle.ports
new file mode 100644
index 0000000..adfeb90
--- /dev/null
+++ b/src/libthread/bundle.ports
@@ -0,0 +1,42 @@
+--- Makefile ---
+# New ports collection makefile for: libthread
+# Date Created:		11 Feb 2003
+# Whom:			rsc
+#
+
+PORTNAME=	libthread
+PORTVERSION=	1.0
+CATEGORIES=	devel
+MASTER_SITES=	http://pdos.lcs.mit.edu/~rsc/software/
+DISTNAME=	libthread
+EXTRACT_SUFX=	.tgz
+
+MAINTAINER=	rsc@post.harvard.edu
+
+MAN3=		print.3 fmtinstall.3
+MLINKS=		XXX
+USE_REINPLACE=	XXX (wkj says yes)
+
+.include <bsd.port.pre.mk>
+
+post-patch:
+	${REINPLACE_CMD} -e 's,@@LOCAL@@,${PREFIX},g' ${WRKSRC}/Makefile
+
+.include <bsd.port.post.mk>
+
+--- pkg-comment ---
+Plan 9 thread library
+--- pkg-descr ---
+Libthread is a port of Plan 9's thread library.
+
+WWW: http://pdos.lcs.mit.edu/~rsc/software/
+WWW: http://plan9.bell-labs.com/magic/man2html/2/thread
+
+Russ Cox
+rsc@post.harvard.edu
+--- pkg-plist ---
+lib/libthread.a
+include/thread.h
+--- /dev/null ---
+This is just a way to make sure blank lines don't
+creep into pkg-plist.
diff --git a/src/libthread/channel.c b/src/libthread/channel.c
new file mode 100644
index 0000000..384f23f
--- /dev/null
+++ b/src/libthread/channel.c
@@ -0,0 +1,485 @@
+#include "threadimpl.h"
+
+static Lock chanlock;		/* central channel access lock */
+
+static void enqueue(Alt*, Channel**);
+static void dequeue(Alt*);
+static int altexec(Alt*, int);
+
+int _threadhighnentry;
+int _threadnalt;
+
+static int
+canexec(Alt *a)
+{
+	int i, otherop;
+	Channel *c;
+
+	c = a->c;
+	/* are there senders or receivers blocked? */
+	otherop = (CHANSND+CHANRCV) - a->op;
+	for(i=0; i<c->nentry; i++)
+		if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil){
+			_threaddebug(DBGCHAN, "can rendez alt %p chan %p", a, c);
+			return 1;
+		}
+
+	/* is there room in the channel? */
+	if((a->op==CHANSND && c->n < c->s)
+	|| (a->op==CHANRCV && c->n > 0)){
+		_threaddebug(DBGCHAN, "can buffer alt %p chan %p", a, c);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void
+_chanfree(Channel *c)
+{
+	int i, inuse;
+
+	inuse = 0;
+	for(i = 0; i < c->nentry; i++)
+		if(c->qentry[i])
+			inuse = 1;
+	if(inuse)
+		c->freed = 1;
+	else{
+		if(c->qentry)
+			free(c->qentry);
+		free(c);
+	}
+}
+
+void
+chanfree(Channel *c)
+{
+	lock(&chanlock);
+	_chanfree(c);
+	unlock(&chanlock);
+}
+
+int
+chaninit(Channel *c, int elemsize, int elemcnt)
+{
+	if(elemcnt < 0 || elemsize <= 0 || c == nil)
+		return -1;
+	c->f = 0;
+	c->n = 0;
+	c->freed = 0;
+	c->e = elemsize;
+	c->s = elemcnt;
+	_threaddebug(DBGCHAN, "chaninit %p", c);
+	return 1;
+}
+
+Channel*
+chancreate(int elemsize, int elemcnt)
+{
+	Channel *c;
+
+	if(elemcnt < 0 || elemsize <= 0)
+		return nil;
+	c = _threadmalloc(sizeof(Channel)+elemsize*elemcnt, 1);
+	c->e = elemsize;
+	c->s = elemcnt;
+	_threaddebug(DBGCHAN, "chancreate %p", c);
+	return c;
+}
+
+int
+alt(Alt *alts)
+{
+	Alt *a, *xa;
+	Channel *volatile c;
+	int n, s;
+	ulong r;
+	Thread *t;
+
+	/*
+	 * The point of going splhi here is that note handlers
+	 * might reasonably want to use channel operations,
+	 * but that will hang if the note comes while we hold the
+	 * chanlock.  Instead, we delay the note until we've dropped
+	 * the lock.
+	 */
+	t = _threadgetproc()->thread;
+	if(t->moribund || _threadexitsallstatus)
+		yield();	/* won't return */
+	s = _procsplhi();
+	lock(&chanlock);
+	t->alt = alts;
+	t->chan = Chanalt;
+
+	/* test whether any channels can proceed */
+	n = 0;
+	a = nil;
+
+	for(xa=alts; xa->op!=CHANEND && xa->op!=CHANNOBLK; xa++){
+		xa->entryno = -1;
+		if(xa->op == CHANNOP)
+			continue;
+		
+		c = xa->c;
+		if(c==nil){
+			unlock(&chanlock);
+			_procsplx(s);
+			t->chan = Channone;
+			return -1;
+		}
+		if(canexec(xa))
+			if(nrand(++n) == 0)
+				a = xa;
+	}
+
+	if(a==nil){
+		/* nothing can proceed */
+		if(xa->op == CHANNOBLK){
+			unlock(&chanlock);
+			_procsplx(s);
+			t->chan = Channone;
+_threadnalt++;
+			return xa - alts;
+		}
+
+		/* enqueue on all channels. */
+		c = nil;
+		for(xa=alts; xa->op!=CHANEND; xa++){
+			if(xa->op==CHANNOP)
+				continue;
+			enqueue(xa, (Channel**)&c);
+		}
+
+		/*
+		 * wait for successful rendezvous.
+		 * we can't just give up if the rendezvous
+		 * is interrupted -- someone else might come
+		 * along and try to rendezvous with us, so
+		 * we need to be here.
+		 */
+	    Again:
+		unlock(&chanlock);
+		_procsplx(s);
+		r = _threadrendezvous((ulong)&c, 0);
+		s = _procsplhi();
+		lock(&chanlock);
+
+		if(r==~0){		/* interrupted */
+			if(c!=nil)		/* someone will meet us; go back */
+				goto Again;
+			c = (Channel*)~0;	/* so no one tries to meet us */
+		}
+
+		/* dequeue from channels, find selected one */
+		a = nil;
+		for(xa=alts; xa->op!=CHANEND; xa++){
+			if(xa->op==CHANNOP)
+				continue;
+			if(xa->c == c)
+				a = xa;
+			dequeue(xa);
+		}
+		unlock(&chanlock);
+		_procsplx(s);
+		if(a == nil){	/* we were interrupted */
+			assert(c==(Channel*)~0);
+			return -1;
+		}
+	}else{
+		altexec(a, s);	/* unlocks chanlock, does splx */
+	}
+	_sched();
+	t->chan = Channone;
+_threadnalt++;
+	return a - alts;
+}
+
+static int
+runop(int op, Channel *c, void *v, int nb)
+{
+	int r;
+	Alt a[2];
+
+	/*
+	 * we could do this without calling alt,
+	 * but the only reason would be performance,
+	 * and i'm not convinced it matters.
+	 */
+	a[0].op = op;
+	a[0].c = c;
+	a[0].v = v;
+	a[1].op = CHANEND;
+	if(nb)
+		a[1].op = CHANNOBLK;
+	switch(r=alt(a)){
+	case -1:	/* interrupted */
+		return -1;
+	case 1:	/* nonblocking, didn't accomplish anything */
+		assert(nb);
+		return 0;
+	case 0:
+		return 1;
+	default:
+		fprint(2, "ERROR: channel alt returned %d\n", r);
+		abort();
+		return -1;
+	}
+}
+
+int
+recv(Channel *c, void *v)
+{
+	return runop(CHANRCV, c, v, 0);
+}
+
+int
+nbrecv(Channel *c, void *v)
+{
+	return runop(CHANRCV, c, v, 1);
+}
+
+int
+send(Channel *c, void *v)
+{
+	return runop(CHANSND, c, v, 0);
+}
+
+int
+nbsend(Channel *c, void *v)
+{
+	return runop(CHANSND, c, v, 1);
+}
+
+static void
+channelsize(Channel *c, int sz)
+{
+	if(c->e != sz){
+		fprint(2, "expected channel with elements of size %d, got size %d",
+			sz, c->e);
+		abort();
+	}
+}
+
+int
+sendul(Channel *c, ulong v)
+{
+	channelsize(c, sizeof(ulong));
+	return send(c, &v);
+}
+
+ulong
+recvul(Channel *c)
+{
+	ulong v;
+
+	channelsize(c, sizeof(ulong));
+	if(recv(c, &v) < 0)
+		return ~0;
+	return v;
+}
+
+int
+sendp(Channel *c, void *v)
+{
+	channelsize(c, sizeof(void*));
+	return send(c, &v);
+}
+
+void*
+recvp(Channel *c)
+{
+	void *v;
+
+	channelsize(c, sizeof(void*));
+	if(recv(c, &v) < 0)
+		return nil;
+	return v;
+}
+
+int
+nbsendul(Channel *c, ulong v)
+{
+	channelsize(c, sizeof(ulong));
+	return nbsend(c, &v);
+}
+
+ulong
+nbrecvul(Channel *c)
+{
+	ulong v;
+
+	channelsize(c, sizeof(ulong));
+	if(nbrecv(c, &v) == 0)
+		return 0;
+	return v;
+}
+
+int
+nbsendp(Channel *c, void *v)
+{
+	channelsize(c, sizeof(void*));
+	return nbsend(c, &v);
+}
+
+void*
+nbrecvp(Channel *c)
+{
+	void *v;
+
+	channelsize(c, sizeof(void*));
+	if(nbrecv(c, &v) == 0)
+		return nil;
+	return v;
+}
+
+static int
+emptyentry(Channel *c)
+{
+	int i, extra;
+
+	assert((c->nentry==0 && c->qentry==nil) || (c->nentry && c->qentry));
+
+	for(i=0; i<c->nentry; i++)
+		if(c->qentry[i]==nil)
+			return i;
+
+	extra = 16;
+	c->nentry += extra;
+if(c->nentry > _threadhighnentry) _threadhighnentry = c->nentry;
+	c->qentry = realloc((void*)c->qentry, c->nentry*sizeof(c->qentry[0]));
+	if(c->qentry == nil)
+		sysfatal("realloc channel entries: %r");
+	_threadmemset(&c->qentry[i], 0, extra*sizeof(c->qentry[0]));
+	return i;
+}
+
+static void
+enqueue(Alt *a, Channel **c)
+{
+	int i;
+
+	_threaddebug(DBGCHAN, "Queuing alt %p on channel %p", a, a->c);
+	a->tag = c;
+	i = emptyentry(a->c);
+	a->c->qentry[i] = a;
+}
+
+static void
+dequeue(Alt *a)
+{
+	int i;
+	Channel *c;
+
+	c = a->c;
+	for(i=0; i<c->nentry; i++)
+		if(c->qentry[i]==a){
+			_threaddebug(DBGCHAN, "Dequeuing alt %p from channel %p", a, a->c);
+			c->qentry[i] = nil;
+			if(c->freed)
+				_chanfree(c);
+			return;
+		}
+}
+
+static void*
+altexecbuffered(Alt *a, int willreplace)
+{
+	uchar *v;
+	Channel *c;
+
+	c = a->c;
+	/* use buffered channel queue */
+	if(a->op==CHANRCV && c->n > 0){
+		_threaddebug(DBGCHAN, "buffer recv alt %p chan %p", a, c);
+		v = c->v + c->e*(c->f%c->s);
+		if(!willreplace)
+			c->n--;
+		c->f++;
+		return v;
+	}
+	if(a->op==CHANSND && c->n < c->s){
+		_threaddebug(DBGCHAN, "buffer send alt %p chan %p", a, c);
+		v = c->v + c->e*((c->f+c->n)%c->s);
+		if(!willreplace)
+			c->n++;
+		return v;
+	}
+	abort();
+	return nil;
+}
+
+static void
+altcopy(void *dst, void *src, int sz)
+{
+	if(dst){
+		if(src)
+			memmove(dst, src, sz);
+		else
+			_threadmemset(dst, 0, sz);
+	}
+}
+
+static int
+altexec(Alt *a, int spl)
+{
+	volatile Alt *b;
+	int i, n, otherop;
+	Channel *c;
+	void *me, *waiter, *buf;
+
+	c = a->c;
+
+	/* rendezvous with others */
+	otherop = (CHANSND+CHANRCV) - a->op;
+	n = 0;
+	b = nil;
+	me = a->v;
+	for(i=0; i<c->nentry; i++)
+		if(c->qentry[i] && c->qentry[i]->op==otherop && *c->qentry[i]->tag==nil)
+			if(nrand(++n) == 0)
+				b = c->qentry[i];
+	if(b != nil){
+		_threaddebug(DBGCHAN, "rendez %s alt %p chan %p alt %p", a->op==CHANRCV?"recv":"send", a, c, b);
+		waiter = b->v;
+		if(c->s && c->n){
+			/*
+			 * if buffer is full and there are waiters
+			 * and we're meeting a waiter,
+			 * we must be receiving.
+			 *
+			 * we use the value in the channel buffer,
+			 * copy the waiter's value into the channel buffer
+			 * on behalf of the waiter, and then wake the waiter.
+			 */
+			if(a->op!=CHANRCV)
+				abort();
+			buf = altexecbuffered(a, 1);
+			altcopy(me, buf, c->e);
+			altcopy(buf, waiter, c->e);
+		}else{
+			if(a->op==CHANRCV)
+				altcopy(me, waiter, c->e);
+			else
+				altcopy(waiter, me, c->e);
+		}
+		*b->tag = c;	/* commits us to rendezvous */
+		_threaddebug(DBGCHAN, "unlocking the chanlock");
+		unlock(&chanlock);
+		_procsplx(spl);
+		_threaddebug(DBGCHAN, "chanlock is %lud", *(ulong*)&chanlock);
+		while(_threadrendezvous((ulong)b->tag, 0) == ~0)
+			;
+		return 1;
+	}
+
+	buf = altexecbuffered(a, 0);
+	if(a->op==CHANRCV)
+		altcopy(me, buf, c->e);
+	else
+		altcopy(buf, me, c->e);
+
+	unlock(&chanlock);
+	_procsplx(spl);
+	return 1;
+}
diff --git a/src/libthread/chanprint.c b/src/libthread/chanprint.c
new file mode 100644
index 0000000..af9e810
--- /dev/null
+++ b/src/libthread/chanprint.c
@@ -0,0 +1,18 @@
+#include "threadimpl.h"
+
+int
+chanprint(Channel *c, char *fmt, ...)
+{
+	va_list arg;
+	char *p;
+	int n;
+
+	va_start(arg, fmt);
+	p = vsmprint(fmt, arg);
+	va_end(arg);
+	if(p == nil)
+		sysfatal("vsmprint failed: %r");
+	n = sendp(c, p);
+	yield();	/* let recipient handle message immediately */
+	return n;
+}
diff --git a/src/libthread/create.c b/src/libthread/create.c
new file mode 100644
index 0000000..ab803a2
--- /dev/null
+++ b/src/libthread/create.c
@@ -0,0 +1,182 @@
+#include "threadimpl.h"
+
+#define free
+Pqueue _threadpq;
+
+static int nextID(void);
+
+/*
+ * Create and initialize a new Thread structure attached to a given proc.
+ */
+
+typedef struct Stack Stack;
+struct Stack {
+	ulong magic;
+	Thread *thr;
+	Stack *next;
+	uchar buf[STKSIZE-12];
+};
+
+static Stack *stkfree;
+static Lock stklock;
+
+void
+_stackfree(void *v)
+{
+	Stack *s;
+
+	s = v;
+	lock(&stklock);
+	s->thr = nil;
+	s->magic = 0;
+	s->next = stkfree;
+	stkfree = s;
+	unlock(&stklock);
+}
+
+static Stack*
+stackalloc(void)
+{
+	char *buf;
+	Stack *s;
+	int i;
+
+	lock(&stklock);
+	while(stkfree == nil){
+		unlock(&stklock);
+		assert(STKSIZE == sizeof(Stack));
+		buf = malloc(STKSIZE+128*STKSIZE);
+		s = (Stack*)(((ulong)buf+STKSIZE)&~(STKSIZE-1));
+		for(i=0; i<128; i++)
+			_stackfree(&s[i]);
+		lock(&stklock);
+	}
+	s = stkfree;
+	stkfree = stkfree->next;
+	unlock(&stklock);
+	s->magic = STKMAGIC;
+	return s;
+}
+
+static int
+newthread(Proc *p, void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp)
+{
+	int id;
+	Thread *t;
+	Stack *s;
+
+	if(stacksize < 32)
+		sysfatal("bad stacksize %d", stacksize);
+	t = _threadmalloc(sizeof(Thread), 1);
+	s = stackalloc();
+	s->thr = t;
+	t->stk = (char*)s;
+	t->stksize = STKSIZE;
+	_threaddebugmemset(s->buf, 0xFE, sizeof s->buf);
+	_threadinitstack(t, f, arg);
+	t->proc = p;
+	t->grp = grp;
+	if(name)
+		t->cmdname = strdup(name);
+	t->id = nextID();
+	id = t->id;
+	t->next = (Thread*)~0;
+	_threaddebug(DBGSCHED, "create thread %d.%d name %s", p->pid, t->id, name);
+	lock(&p->lock);
+	p->nthreads++;
+	if(p->threads.head == nil)
+		p->threads.head = t;
+	else{
+		t->prevt = p->threads.tail;
+		t->prevt->nextt = t;
+	}
+	p->threads.tail = t;
+	t->state = Ready;
+	_threadready(t);
+	unlock(&p->lock);
+	return id;
+}
+
+static int
+nextID(void)
+{
+	static Lock l;
+	static int id;
+	int i;
+
+	lock(&l);
+	i = ++id;
+	unlock(&l);
+	return i;
+}
+	
+int
+procrfork(void (*f)(void *), void *arg, uint stacksize, int rforkflag)
+{
+	Proc *p;
+	int id;
+
+	p = _threadgetproc();
+	assert(p->newproc == nil);
+	p->newproc = _newproc(f, arg, stacksize, nil, p->thread->grp, rforkflag);
+	id = p->newproc->threads.head->id;
+	_sched();
+	return id;
+}
+
+int
+proccreate(void (*f)(void*), void *arg, uint stacksize)
+{
+	return procrfork(f, arg, stacksize, 0);
+}
+
+void
+_freeproc(Proc *p)
+{
+	Thread *t, *nextt;
+
+	for(t = p->threads.head; t; t = nextt){
+		if(t->cmdname)
+			free(t->cmdname);
+		assert(t->stk != nil);
+		_stackfree((Stack*)t->stk);
+		nextt = t->nextt;
+		free(t);
+	}
+	free(p);
+}
+
+/* 
+ * Create a new thread and schedule it to run.
+ * The thread grp is inherited from the currently running thread.
+ */
+int
+threadcreate(void (*f)(void *arg), void *arg, uint stacksize)
+{
+	return newthread(_threadgetproc(), f, arg, stacksize, nil, threadgetgrp());
+}
+
+/*
+ * Create and initialize a new Proc structure with a single Thread
+ * running inside it.  Add the Proc to the global process list.
+ */
+Proc*
+_newproc(void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp, int rforkflag)
+{
+	Proc *p;
+
+	p = _threadmalloc(sizeof *p, 1);
+	p->pid = -1;
+	p->rforkflag = rforkflag;
+	newthread(p, f, arg, stacksize, name, grp);
+
+	lock(&_threadpq.lock);
+	if(_threadpq.head == nil)
+		_threadpq.head = p;
+	else
+		*_threadpq.tail = p;
+	_threadpq.tail = &p->next;
+	unlock(&_threadpq.lock);
+	return p;
+}
+
diff --git a/src/libthread/debug.c b/src/libthread/debug.c
new file mode 100644
index 0000000..63e2e1b
--- /dev/null
+++ b/src/libthread/debug.c
@@ -0,0 +1,48 @@
+#include "threadimpl.h"
+
+int _threaddebuglevel;
+
+void
+__threaddebug(ulong flag, char *fmt, ...)
+{
+	char buf[128];
+	va_list arg;
+	Fmt f;
+	Proc *p;
+
+	if((_threaddebuglevel&flag) == 0)
+		return;
+
+	fmtfdinit(&f, 2, buf, sizeof buf);
+
+	p = _threadgetproc();
+	if(p==nil)
+		fmtprint(&f, "noproc ");
+	else if(p->thread)
+		fmtprint(&f, "%d.%d ", p->pid, p->thread->id);
+	else
+		fmtprint(&f, "%d._ ", p->pid);
+
+	va_start(arg, fmt);
+	fmtvprint(&f, fmt, arg);
+	va_end(arg);
+	fmtprint(&f, "\n");
+	fmtfdflush(&f);
+}
+
+void
+_threadassert(char *s)
+{
+	char buf[256];
+	int n;
+	Proc *p;
+
+	p = _threadgetproc();
+	if(p && p->thread)
+		n = sprint(buf, "%d.%d ", p->pid, p->thread->id);
+	else
+		n = 0;
+	snprint(buf+n, sizeof(buf)-n, "%s: assertion failed\n", s);
+	write(2, buf, strlen(buf));
+	abort();
+}
diff --git a/src/libthread/exec-unix.c b/src/libthread/exec-unix.c
new file mode 100644
index 0000000..5a37e34
--- /dev/null
+++ b/src/libthread/exec-unix.c
@@ -0,0 +1,124 @@
+#include <fcntl.h>
+#include <unistd.h>
+#include "threadimpl.h"
+
+void
+procexec(Channel *pidc, char *prog, char *args[])
+{
+	int n;
+	Proc *p;
+	Thread *t;
+
+	_threaddebug(DBGEXEC, "procexec %s", prog);
+	/* must be only thread in proc */
+	p = _threadgetproc();
+	t = p->thread;
+	if(p->threads.head != t || p->threads.head->nextt != nil){
+		werrstr("not only thread in proc");
+	Bad:
+		if(pidc)
+			sendul(pidc, ~0);
+		return;
+	}
+
+	/*
+	 * We want procexec to behave like exec; if exec succeeds,
+	 * never return, and if it fails, return with errstr set.
+	 * Unfortunately, the exec happens in another proc since
+	 * we have to wait for the exec'ed process to finish.
+	 * To provide the semantics, we open a pipe with the 
+	 * write end close-on-exec and hand it to the proc that
+	 * is doing the exec.  If the exec succeeds, the pipe will
+	 * close so that our read below fails.  If the exec fails,
+	 * then the proc doing the exec sends the errstr down the
+	 * pipe to us.
+	 */
+	if(pipe(p->exec.fd) < 0)
+		goto Bad;
+	if(fcntl(p->exec.fd[1], F_SETFD, 1) < 0)
+		goto Bad;
+
+	/* exec in parallel via the scheduler */
+	assert(p->needexec==0);
+	p->exec.prog = prog;
+	p->exec.args = args;
+	p->needexec = 1;
+	_sched();
+
+	close(p->exec.fd[1]);
+	if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){	/* exec failed */
+		p->exitstr[n] = '\0';
+		errstr(p->exitstr, ERRMAX);
+		close(p->exec.fd[0]);
+		goto Bad;
+	}
+	close(p->exec.fd[0]);
+
+	if(pidc)
+		sendul(pidc, t->ret);
+
+	/* wait for exec'ed program, then exit */
+	_schedexecwait();
+}
+
+void
+procexecl(Channel *pidc, char *f, ...)
+{
+	procexec(pidc, f, &f+1);
+}
+
+void
+_schedexecwait(void)
+{
+	int pid;
+	Channel *c;
+	Proc *p;
+	Thread *t;
+	Waitmsg *w;
+
+	p = _threadgetproc();
+	t = p->thread;
+	pid = t->ret;
+	_threaddebug(DBGEXEC, "_schedexecwait %d", t->ret);
+
+	for(;;){
+		w = wait();
+		if(w == nil)
+			break;
+		if(w->pid == pid)
+			break;
+		free(w);
+	}
+	if(w != nil){
+		if((c = _threadwaitchan) != nil)
+			sendp(c, w);
+		else
+			free(w);
+	}
+	threadexits("procexec");
+}
+
+static void
+efork(void *ve)
+{
+	char buf[ERRMAX];
+	Execargs *e;
+
+	e = ve;
+	_threaddebug(DBGEXEC, "_schedexec %s", e->prog);
+	close(e->fd[0]);
+	execv(e->prog, e->args);
+	_threaddebug(DBGEXEC, "_schedexec failed: %r");
+	rerrstr(buf, sizeof buf);
+	if(buf[0]=='\0')
+		strcpy(buf, "exec failed");
+	write(e->fd[1], buf, strlen(buf));
+	close(e->fd[1]);
+	_exits(buf);
+}
+
+int
+_schedexec(Execargs *e)
+{
+	return ffork(RFFDG|RFPROC|RFMEM, efork, e);
+}
diff --git a/src/libthread/exec.c b/src/libthread/exec.c
new file mode 100644
index 0000000..bcf2080
--- /dev/null
+++ b/src/libthread/exec.c
@@ -0,0 +1,77 @@
+#include "threadimpl.h"
+
+#define PIPEMNT	"/mnt/temp"
+
+void
+procexec(Channel *pidc, char *prog, char *args[])
+{
+	int n;
+	Proc *p;
+	Thread *t;
+
+	_threaddebug(DBGEXEC, "procexec %s", prog);
+	/* must be only thread in proc */
+	p = _threadgetproc();
+	t = p->thread;
+	if(p->threads.head != t || p->threads.head->nextt != nil){
+		werrstr("not only thread in proc");
+	Bad:
+		if(pidc)
+			sendul(pidc, ~0);
+		return;
+	}
+
+	/*
+	 * We want procexec to behave like exec; if exec succeeds,
+	 * never return, and if it fails, return with errstr set.
+	 * Unfortunately, the exec happens in another proc since
+	 * we have to wait for the exec'ed process to finish.
+	 * To provide the semantics, we open a pipe with the 
+	 * write end close-on-exec and hand it to the proc that
+	 * is doing the exec.  If the exec succeeds, the pipe will
+	 * close so that our read below fails.  If the exec fails,
+	 * then the proc doing the exec sends the errstr down the
+	 * pipe to us.
+	 */
+	if(bind("#|", PIPEMNT, MREPL) < 0)
+		goto Bad;
+	if((p->exec.fd[0] = open(PIPEMNT "/data", OREAD)) < 0){
+		unmount(nil, PIPEMNT);
+		goto Bad;
+	}
+	if((p->exec.fd[1] = open(PIPEMNT "/data1", OWRITE|OCEXEC)) < 0){
+		close(p->exec.fd[0]);
+		unmount(nil, PIPEMNT);
+		goto Bad;
+	}
+	unmount(nil, PIPEMNT);
+
+	/* exec in parallel via the scheduler */
+	assert(p->needexec==0);
+	p->exec.prog = prog;
+	p->exec.args = args;
+	p->needexec = 1;
+	_sched();
+
+	close(p->exec.fd[1]);
+	if((n = read(p->exec.fd[0], p->exitstr, ERRMAX-1)) > 0){	/* exec failed */
+		p->exitstr[n] = '\0';
+		errstr(p->exitstr, ERRMAX);
+		close(p->exec.fd[0]);
+		goto Bad;
+	}
+	close(p->exec.fd[0]);
+
+	if(pidc)
+		sendul(pidc, t->ret);
+
+	/* wait for exec'ed program, then exit */
+	_schedexecwait();
+}
+
+void
+procexecl(Channel *pidc, char *f, ...)
+{
+	procexec(pidc, f, &f+1);
+}
+
diff --git a/src/libthread/exit.c b/src/libthread/exit.c
new file mode 100644
index 0000000..ddf7001
--- /dev/null
+++ b/src/libthread/exit.c
@@ -0,0 +1,63 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+char *_threadexitsallstatus;
+Channel *_threadwaitchan;
+
+void
+threadexits(char *exitstr)
+{
+	Proc *p;
+	Thread *t;
+
+	p = _threadgetproc();
+	t = p->thread;
+	t->moribund = 1;
+	if(exitstr==nil)
+		exitstr="";
+	utfecpy(p->exitstr, p->exitstr+ERRMAX, exitstr);
+	_sched();
+}
+
+void
+threadexitsall(char *exitstr)
+{
+	Proc *p;
+	int *pid;
+	int i, npid, mypid;
+
+	if(exitstr == nil)
+		exitstr = "";
+	_threadexitsallstatus = exitstr;
+	_threaddebug(DBGSCHED, "_threadexitsallstatus set to %p", _threadexitsallstatus);
+	mypid = _threadgetpid();
+
+	/*
+	 * signal others.
+	 * copying all the pids first avoids other threads
+	 * teardown procedures getting in the way.
+	 */
+	lock(&_threadpq.lock);
+	npid = 0;
+	for(p=_threadpq.head; p; p=p->next)
+		npid++;
+	pid = _threadmalloc(npid*sizeof(pid[0]), 0);
+	npid = 0;
+	for(p = _threadpq.head; p; p=p->next)
+		pid[npid++] = p->pid;
+	unlock(&_threadpq.lock);
+	for(i=0; i<npid; i++)
+		if(pid[i] != mypid)
+			kill(pid[i], SIGTERM);
+
+	/* leave */
+	exit(0);
+}
+
+Channel*
+threadwaitchan(void)
+{
+	if(_threadwaitchan==nil)
+		_threadwaitchan = chancreate(sizeof(Waitmsg*), 16);
+	return _threadwaitchan;
+}
diff --git a/src/libthread/getpid.c b/src/libthread/getpid.c
new file mode 100644
index 0000000..da03bd3
--- /dev/null
+++ b/src/libthread/getpid.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <unistd.h>
+
+int
+_threadgetpid(void)
+{
+	return getpid();
+}
diff --git a/src/libthread/id.c b/src/libthread/id.c
new file mode 100644
index 0000000..727798d
--- /dev/null
+++ b/src/libthread/id.c
@@ -0,0 +1,135 @@
+#include "threadimpl.h"
+
+int
+threadid(void)
+{
+	return _threadgetproc()->thread->id;
+}
+
+int
+threadpid(int id)
+{
+	int pid;
+	Proc *p;
+	Thread *t;
+
+	if (id < 0)
+		return -1;
+	if (id == 0)
+		return _threadgetproc()->pid;
+	lock(&_threadpq.lock);
+	for (p = _threadpq.head; p->next; p = p->next){
+		lock(&p->lock);
+		for (t = p->threads.head; t; t = t->nextt)
+			if (t->id == id){
+				pid = p->pid;
+				unlock(&p->lock);
+				unlock(&_threadpq.lock);
+				return pid;
+			}
+		unlock(&p->lock);
+	}
+	unlock(&_threadpq.lock);
+	return -1;
+}
+
+int
+threadsetgrp(int ng)
+{
+	int og;
+	Thread *t;
+
+	t = _threadgetproc()->thread;
+	og = t->grp;
+	t->grp = ng;
+	return og;
+}
+
+int
+threadgetgrp(void)
+{
+	return _threadgetproc()->thread->grp;
+}
+
+void
+threadsetname(char *name)
+{
+/*
+	int fd, n;
+	char buf[128], *s;
+*/
+	Proc *p;
+	Thread *t;
+
+	p = _threadgetproc();
+	t = p->thread;
+	if (t->cmdname)
+		free(t->cmdname);
+	t->cmdname = strdup(name);
+/* Plan 9 only 
+	if(p->nthreads == 1){
+		snprint(buf, sizeof buf, "#p/%d/args", getpid());
+		if((fd = open(buf, OWRITE)) >= 0){
+			snprint(buf, sizeof buf, "%s [%s]", argv0, name);
+			n = strlen(buf)+1;
+			s = strchr(buf, ' ');
+			if(s)
+				*s = '\0';
+			write(fd, buf, n);
+			close(fd);
+		}
+	}
+*/
+}
+
+char*
+threadgetname(void)
+{
+	return _threadgetproc()->thread->cmdname;
+}
+
+void**
+threaddata(void)
+{
+	return &_threadgetproc()->thread->udata[0];
+}
+
+void**
+procdata(void)
+{
+	return &_threadgetproc()->udata;
+}
+
+static Lock privlock;
+static int privmask = 1;
+
+int
+tprivalloc(void)
+{
+	int i;
+
+	lock(&privlock);
+	for(i=0; i<NPRIV; i++)
+		if(!(privmask&(1<<i))){
+			privmask |= 1<<i;
+			unlock(&privlock);
+			return i;
+		}
+	unlock(&privlock);
+	return -1;
+}
+
+void
+tprivfree(int i)
+{
+	if(i < 0 || i >= NPRIV)
+		abort();
+	lock(&privlock);
+	privmask &= ~(1<<i);
+}
+
+void**
+tprivaddr(int i)
+{
+	return &_threadgetproc()->thread->udata[i];
+}
diff --git a/src/libthread/iocall.c b/src/libthread/iocall.c
new file mode 100644
index 0000000..d9cf9d0
--- /dev/null
+++ b/src/libthread/iocall.c
@@ -0,0 +1,49 @@
+#include "threadimpl.h"
+
+long
+iocall(Ioproc *io, long (*op)(va_list*), ...)
+{
+	int ret, inted;
+	Ioproc *msg;
+
+	if(send(io->c, &io) == -1){
+		werrstr("interrupted");
+		return -1;
+	}
+	assert(!io->inuse);
+	io->inuse = 1;
+	io->op = op;
+	va_start(io->arg, op);
+	msg = io;
+	inted = 0;
+	while(send(io->creply, &msg) == -1){
+		msg = nil;
+		inted = 1;
+	}
+	if(inted){
+		werrstr("interrupted");
+		return -1;
+	}
+
+	/*
+	 * If we get interrupted, we have stick around so that
+	 * the IO proc has someone to talk to.  Send it an interrupt
+	 * and try again.
+	 */
+	inted = 0;
+	while(recv(io->creply, nil) == -1){
+		inted = 1;
+		iointerrupt(io);
+	}
+	USED(inted);
+	va_end(io->arg);
+	ret = io->ret;
+	if(ret < 0)
+		errstr(io->err, sizeof io->err);
+	io->inuse = 0;
+
+	/* release resources */
+	while(send(io->creply, &io) == -1)
+		;
+	return ret;
+}
diff --git a/src/libthread/ioclose.c b/src/libthread/ioclose.c
new file mode 100644
index 0000000..fbaabb7
--- /dev/null
+++ b/src/libthread/ioclose.c
@@ -0,0 +1,16 @@
+#include "threadimpl.h"
+
+static long
+_ioclose(va_list *arg)
+{
+	int fd;
+
+	fd = va_arg(*arg, int);
+	return close(fd);
+}
+
+int
+ioclose(Ioproc *io, int fd)
+{
+	return iocall(io, _ioclose, fd);
+}
diff --git a/src/libthread/iodial.c b/src/libthread/iodial.c
new file mode 100644
index 0000000..8171156
--- /dev/null
+++ b/src/libthread/iodial.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_iodial(va_list *arg)
+{
+	char *addr, *local, *dir;
+	int *cdfp;
+
+	addr = va_arg(*arg, char*);
+	local = va_arg(*arg, char*);
+	dir = va_arg(*arg, char*);
+	cdfp = va_arg(*arg, int*);
+
+	return dial(addr, local, dir, cdfp);
+}
+
+int
+iodial(Ioproc *io, char *addr, char *local, char *dir, int *cdfp)
+{
+	return iocall(io, _iodial, addr, local, dir, cdfp);
+}
diff --git a/src/libthread/ioopen.c b/src/libthread/ioopen.c
new file mode 100644
index 0000000..efca4ca
--- /dev/null
+++ b/src/libthread/ioopen.c
@@ -0,0 +1,20 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include "threadimpl.h"
+
+static long
+_ioopen(va_list *arg)
+{
+	char *path;
+	int mode;
+
+	path = va_arg(*arg, char*);
+	mode = va_arg(*arg, int);
+	return open(path, mode);
+}
+
+int
+ioopen(Ioproc *io, char *path, int mode)
+{
+	return iocall(io, _ioopen, path, mode);
+}
diff --git a/src/libthread/ioproc.3 b/src/libthread/ioproc.3
new file mode 100644
index 0000000..1cada2d
--- /dev/null
+++ b/src/libthread/ioproc.3
@@ -0,0 +1,179 @@
+.TH IOPROC 2
+.SH NAME
+closeioproc,
+iocall,
+ioclose,
+iointerrupt,
+iodial,
+ioopen,
+ioproc,
+ioread,
+ioreadn,
+iowrite \- slave I/O processes for threaded programs
+.SH SYNOPSIS
+.PP
+.de XX
+.ift .sp 0.5
+.ifn .sp
+..
+.EX
+.ta \w'Ioproc* 'u
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+.sp
+typedef struct Ioproc Ioproc;
+.sp
+Ioproc*	ioproc(void);
+.XX
+int	ioopen(Ioproc *io, char *file, int omode);
+int	ioclose(Ioproc *io, int fd);
+long	ioread(Ioproc *io, int fd, void *a, long n);
+long	ioreadn(Ioproc *io, int fd, void *a, long n);
+long	iowrite(Ioproc *io, int fd, void *a, long n);
+int	iodial(Ioproc *io, char *addr, char *local, char *dir, char *cdfp);
+.XX
+void	iointerrupt(Ioproc *io);
+void	closeioproc(Ioproc *io);
+.XX
+long	iocall(Ioproc *io, long (*op)(va_list *arg), ...);
+.EE
+.SH DESCRIPTION
+.PP
+These routines provide access to I/O in slave procs.
+Since the I/O itself is done in a slave proc, other threads
+in the calling proc can run while the calling thread
+waits for the I/O to complete.
+.PP
+.I Ioproc
+forks a new slave proc and returns a pointer to the
+.B Ioproc
+associated with it.
+.I Ioproc
+uses
+.I mallocz
+and
+.IR proccreate ;
+if either fails, it calls
+.I sysfatal
+rather than return an error.
+.PP
+.IR Ioopen ,
+.IR ioclose ,
+.IR ioread ,
+.IR ioreadn ,
+.IR iowrite ,
+and
+.IR iodial
+are execute the
+similarly named library or system calls
+(see
+.IR open (2),
+.IR read (2),
+and
+.IR dial (2))
+in the slave process associated with
+.IR io .
+It is an error to execute more than one call
+at a time in an I/O proc.
+.PP
+.I Iointerrupt
+interrupts the call currently executing in the I/O proc.
+If no call is executing,
+.IR iointerrupt
+is a no-op.
+.PP
+.I Closeioproc
+terminates the I/O proc and frees the associated
+.B Ioproc .
+.PP
+.I Iocall
+is a primitive that may be used to implement
+more slave I/O routines.
+.I Iocall
+arranges for
+.I op
+to be called in
+.IR io 's
+proc, with
+.I arg
+set to the variable parameter list,
+returning the value that
+.I op
+returns.
+.SH EXAMPLE
+Relay messages between two file descriptors,
+counting the total number of bytes seen:
+.IP
+.EX
+.ta +\w'xxxx'u +\w'xxxx'u +\w'xxxx'u
+int tot;
+
+void
+relaythread(void *v)
+{
+	int *fd, n;
+	char buf[1024];
+	Ioproc *io;
+
+	fd = v;
+	io = ioproc();
+	while((n = ioread(io, fd[0], buf, sizeof buf)) > 0){
+		if(iowrite(io, fd[1], buf, n) != n)
+			sysfatal("iowrite: %r");
+		tot += n;
+	}
+	closeioproc(io);
+}
+
+void
+relay(int fd0, int fd1)
+{
+	int fd[4];
+
+	fd[0] = fd[3] = fd0;
+	fd[1] = fd[2] = fd1;
+	threadcreate(relaythread, fd, 8192);
+	threadcreate(relaythread, fd+2, 8192);
+}
+.EE
+.LP
+If the two
+.I relaythread
+instances were running in different procs, the
+common access to
+.I tot
+would be unsafe.
+.EE
+.PP
+Implement
+.IR ioread :
+.IP
+.EX
+static long
+_ioread(va_list *arg)
+{
+	int fd;
+	void *a;
+	long n;
+
+	fd = va_arg(*arg, int);
+	a = va_arg(*arg, void*);
+	n = va_arg(*arg, long);
+	return read(fd, a, n);
+}
+
+long
+ioread(Ioproc *io, int fd, void *a, long n)
+{
+	return iocall(io, _ioread, fd, a, n);
+}
+.EE
+.SH SOURCE
+.B /sys/src/libthread/io*.c
+.SH SEE ALSO
+.IR dial (2),
+.IR open (2),
+.IR read (2),
+.IR thread (2)
+
diff --git a/src/libthread/ioproc.c b/src/libthread/ioproc.c
new file mode 100644
index 0000000..2b2a602
--- /dev/null
+++ b/src/libthread/ioproc.c
@@ -0,0 +1,74 @@
+#include "threadimpl.h"
+
+enum
+{
+	STACK = 8192,
+};
+
+void
+iointerrupt(Ioproc *io)
+{
+	if(!io->inuse)
+		return;
+	threadint(io->tid);
+}
+
+static void
+xioproc(void *a)
+{
+	Ioproc *io, *x;
+	io = a;
+	/*
+	 * first recvp acquires the ioproc.
+	 * second tells us that the data is ready.
+	 */
+	for(;;){
+		while(recv(io->c, &x) == -1)
+			;
+		if(x == 0)	/* our cue to leave */
+			break;
+		assert(x == io);
+
+		/* caller is now committed -- even if interrupted he'll return */
+		while(recv(io->creply, &x) == -1)
+			;
+		if(x == 0)	/* caller backed out */
+			continue;
+		assert(x == io);
+
+		io->ret = io->op(&io->arg);
+		if(io->ret < 0)
+			rerrstr(io->err, sizeof io->err);
+		while(send(io->creply, &io) == -1)
+			;
+		while(recv(io->creply, &x) == -1)
+			;
+	}
+}
+
+Ioproc*
+ioproc(void)
+{
+	Ioproc *io;
+
+	io = mallocz(sizeof(*io), 1);
+	if(io == nil)
+		sysfatal("ioproc malloc: %r");
+	io->c = chancreate(sizeof(void*), 0);
+	io->creply = chancreate(sizeof(void*), 0);
+	io->tid = proccreate(xioproc, io, STACK);
+	return io;
+}
+
+void
+closeioproc(Ioproc *io)
+{
+	if(io == nil)
+		return;
+	iointerrupt(io);
+	while(send(io->c, 0) == -1)
+		;
+	chanfree(io->c);
+	chanfree(io->creply);
+	free(io);
+}
diff --git a/src/libthread/ioread.c b/src/libthread/ioread.c
new file mode 100644
index 0000000..62b1be0
--- /dev/null
+++ b/src/libthread/ioread.c
@@ -0,0 +1,20 @@
+#include "threadimpl.h"
+
+static long
+_ioread(va_list *arg)
+{
+	int fd;
+	void *a;
+	long n;
+
+	fd = va_arg(*arg, int);
+	a = va_arg(*arg, void*);
+	n = va_arg(*arg, long);
+	return read(fd, a, n);
+}
+
+long
+ioread(Ioproc *io, int fd, void *a, long n)
+{
+	return iocall(io, _ioread, fd, a, n);
+}
diff --git a/src/libthread/ioreadn.c b/src/libthread/ioreadn.c
new file mode 100644
index 0000000..b843f60
--- /dev/null
+++ b/src/libthread/ioreadn.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_ioreadn(va_list *arg)
+{
+	int fd;
+	void *a;
+	long n;
+
+	fd = va_arg(*arg, int);
+	a = va_arg(*arg, void*);
+	n = va_arg(*arg, long);
+	n = readn(fd, a, n);
+	return n;
+}
+
+long
+ioreadn(Ioproc *io, int fd, void *a, long n)
+{
+	return iocall(io, _ioreadn, fd, a, n);
+}
diff --git a/src/libthread/iosleep.c b/src/libthread/iosleep.c
new file mode 100644
index 0000000..5575645
--- /dev/null
+++ b/src/libthread/iosleep.c
@@ -0,0 +1,16 @@
+#include "threadimpl.h"
+
+static long
+_iosleep(va_list *arg)
+{
+	long n;
+
+	n = va_arg(*arg, long);
+	return sleep(n);
+}
+
+int
+iosleep(Ioproc *io, long n)
+{
+	return iocall(io, _iosleep, n);
+}
diff --git a/src/libthread/iowrite.c b/src/libthread/iowrite.c
new file mode 100644
index 0000000..664a84b
--- /dev/null
+++ b/src/libthread/iowrite.c
@@ -0,0 +1,21 @@
+#include "threadimpl.h"
+
+static long
+_iowrite(va_list *arg)
+{
+	int fd;
+	void *a;
+	long n;
+
+	fd = va_arg(*arg, int);
+	a = va_arg(*arg, void*);
+	n = va_arg(*arg, long);
+	n = write(fd, a, n);
+	return n;
+}
+
+long
+iowrite(Ioproc *io, int fd, void *a, long n)
+{
+	return iocall(io, _iowrite, fd, a, n);
+}
diff --git a/src/libthread/kill.c b/src/libthread/kill.c
new file mode 100644
index 0000000..a9392ea
--- /dev/null
+++ b/src/libthread/kill.c
@@ -0,0 +1,89 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+static void tinterrupt(Proc*, Thread*);
+
+static void
+threadxxxgrp(int grp, int dokill)
+{
+	Proc *p;
+	Thread *t;
+
+	lock(&_threadpq.lock);
+	for(p=_threadpq.head; p; p=p->next){
+		lock(&p->lock);
+		for(t=p->threads.head; t; t=t->nextt)
+			if(t->grp == grp){
+				if(dokill)
+					t->moribund = 1;
+				tinterrupt(p, t);
+			}
+		unlock(&p->lock);
+	}
+	unlock(&_threadpq.lock);
+	_threadbreakrendez();
+}
+
+static void
+threadxxx(int id, int dokill)
+{
+	Proc *p;
+	Thread *t;
+
+	lock(&_threadpq.lock);
+	for(p=_threadpq.head; p; p=p->next){
+		lock(&p->lock);
+		for(t=p->threads.head; t; t=t->nextt)
+			if(t->id == id){
+				if(dokill)
+					t->moribund = 1;
+				tinterrupt(p, t);
+				unlock(&p->lock);
+				unlock(&_threadpq.lock);
+				_threadbreakrendez();
+				return;
+			}
+		unlock(&p->lock);
+	}
+	unlock(&_threadpq.lock);
+	_threaddebug(DBGNOTE, "Can't find thread to kill");
+	return;
+}
+
+void
+threadkillgrp(int grp)
+{
+	threadxxxgrp(grp, 1);
+}
+
+void
+threadkill(int id)
+{
+	threadxxx(id, 1);
+}
+
+void
+threadintgrp(int grp)
+{
+	threadxxxgrp(grp, 0);
+}
+
+void
+threadint(int id)
+{
+	threadxxx(id, 0);
+}
+
+static void
+tinterrupt(Proc *p, Thread *t)
+{
+	switch(t->state){
+	case Running:
+		kill(p->pid, SIGINT);
+	//	postnote(PNPROC, p->pid, "threadint");
+		break;
+	case Rendezvous:
+		_threadflagrendez(t);
+		break;
+	}
+}
diff --git a/src/libthread/label.h b/src/libthread/label.h
new file mode 100644
index 0000000..0c9f303
--- /dev/null
+++ b/src/libthread/label.h
@@ -0,0 +1,24 @@
+/*
+ * setjmp and longjmp, but our own because some (stupid) c libraries
+ * assume longjmp is only used to move up the stack, and error out
+ * if you do otherwise.
+ */
+
+typedef struct Label Label;
+#define LABELDPC 0
+
+#if defined (__i386__) && (defined(__FreeBSD__) || defined(__linux__))
+struct Label
+{
+	ulong pc;
+	ulong bx;
+	ulong sp;
+	ulong bp;
+	ulong si;
+	ulong di;
+};
+#else
+#error "Unknown or unsupported architecture"
+#endif
+
+
diff --git a/src/libthread/lib.c b/src/libthread/lib.c
new file mode 100644
index 0000000..86e7506
--- /dev/null
+++ b/src/libthread/lib.c
@@ -0,0 +1,35 @@
+#include "threadimpl.h"
+
+static long totalmalloc;
+
+void*
+_threadmalloc(long size, int z)
+{
+	void *m;
+
+	m = malloc(size);
+	if (m == nil)
+		sysfatal("Malloc of size %ld failed: %r\n", size);
+	setmalloctag(m, getcallerpc(&size));
+	totalmalloc += size;
+	if (size > 1000000) {
+		fprint(2, "Malloc of size %ld, total %ld\n", size, totalmalloc);
+		abort();
+	}
+	if (z)
+		_threadmemset(m, 0, size);
+	return m;
+}
+
+void
+_threadsysfatal(char *fmt, va_list arg)
+{
+	char buf[1024];	/* size doesn't matter; we're about to exit */
+
+	vseprint(buf, buf+sizeof(buf), fmt, arg);
+	if(argv0)
+		fprint(2, "%s: %s\n", argv0, buf);
+	else
+		fprint(2, "%s\n", buf);
+	threadexitsall(buf);
+}
diff --git a/src/libthread/main.c b/src/libthread/main.c
new file mode 100644
index 0000000..1acd834
--- /dev/null
+++ b/src/libthread/main.c
@@ -0,0 +1,124 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+typedef struct Mainarg Mainarg;
+struct Mainarg
+{
+	int	argc;
+	char	**argv;
+};
+
+int	mainstacksize;
+int	_threadnotefd;
+int	_threadpasserpid;
+static void mainlauncher(void*);
+extern void (*_sysfatal)(char*, va_list);
+
+void
+_threaddie(int x)
+{
+	extern char *_threadexitsallstatus;
+	USED(x);
+
+	if(_threadexitsallstatus)
+		exit(_threadexitsallstatus[0] ? 1 : 0);
+}
+
+int
+main(int argc, char **argv)
+{
+	Mainarg *a;
+	Proc *p;
+
+	signal(SIGTERM, _threaddie);
+//	rfork(RFREND);
+
+//_threaddebuglevel = (DBGSCHED|DBGCHAN|DBGREND)^~0;
+	_systhreadinit();
+	_qlockinit(_threadrendezvous);
+	_sysfatal = _threadsysfatal;
+//	notify(_threadnote);
+	if(mainstacksize == 0)
+		mainstacksize = 32*1024;
+
+	a = _threadmalloc(sizeof *a, 1);
+	a->argc = argc;
+	a->argv = argv;
+
+	p = _newproc(mainlauncher, a, mainstacksize, "threadmain", 0, 0);
+	_schedinit(p);
+	abort();	/* not reached */
+	return 0;
+}
+
+static void
+mainlauncher(void *arg)
+{
+	Mainarg *a;
+
+	a = arg;
+	threadmain(a->argc, a->argv);
+	threadexits("threadmain");
+}
+
+void
+_threadsignal(void)
+{
+}
+
+void
+_threadsignalpasser(void)
+{
+}
+
+int
+_schedfork(Proc *p)
+{
+	return ffork(RFMEM|RFNOWAIT, _schedinit, p);
+}
+
+void
+_schedexit(Proc *p)
+{
+	char ex[ERRMAX];
+	Proc **l;
+
+	lock(&_threadpq.lock);
+	for(l=&_threadpq.head; *l; l=&(*l)->next){
+		if(*l == p){
+			*l = p->next;
+			if(*l == nil)
+				_threadpq.tail = l;
+			break;
+		}
+	}
+	unlock(&_threadpq.lock);
+
+	strncpy(ex, p->exitstr, sizeof ex);
+	ex[sizeof ex-1] = '\0';
+	free(p);
+	_exit(ex[0]);
+}
+
+int
+nrand(int n)
+{
+	return random()%n;
+}
+
+void
+_systhreadinit(void)
+{
+}
+
+void
+threadstats(void)
+{
+	extern int _threadnrendez, _threadhighnrendez,
+		_threadnalt, _threadhighnentry;
+	fprint(2, "*** THREAD LIBRARY STATS ***\n");
+	fprint(2, "nrendez %d high simultaneous %d\n", 
+		_threadnrendez, _threadhighnrendez);
+	fprint(2, "nalt %d high simultaneous entry %d\n",
+		_threadnalt, _threadhighnentry);
+}
diff --git a/src/libthread/memset.c b/src/libthread/memset.c
new file mode 100644
index 0000000..dd74fb6
--- /dev/null
+++ b/src/libthread/memset.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <string.h>
+
+void
+_threadmemset(void *v, int c, int n)
+{
+	memset(v, c, n);
+}
diff --git a/src/libthread/memsetd.c b/src/libthread/memsetd.c
new file mode 100644
index 0000000..ef3f960
--- /dev/null
+++ b/src/libthread/memsetd.c
@@ -0,0 +1,8 @@
+#include "threadimpl.h"
+#include <string.h>
+
+void
+_threaddebugmemset(void *v, int c, int n)
+{
+	memset(v, c, n);
+}
diff --git a/src/libthread/mkfile b/src/libthread/mkfile
new file mode 100644
index 0000000..703f6b0
--- /dev/null
+++ b/src/libthread/mkfile
@@ -0,0 +1,2 @@
+<../libutf/mkfile
+
diff --git a/src/libthread/note.c b/src/libthread/note.c
new file mode 100644
index 0000000..b7f4b13
--- /dev/null
+++ b/src/libthread/note.c
@@ -0,0 +1,143 @@
+#include "threadimpl.h"
+
+int	_threadnopasser;
+
+#ifdef NOTDEF
+#define	NFN		33
+#define	ERRLEN	48
+typedef struct Note Note;
+struct Note
+{
+	Lock		inuse;
+	Proc		*proc;		/* recipient */
+	char		s[ERRMAX];	/* arg2 */
+};
+
+static Note	notes[128];
+static Note	*enotes = notes+nelem(notes);
+static int		(*onnote[NFN])(void*, char*);
+static int		onnotepid[NFN];
+static Lock	onnotelock;
+
+int
+threadnotify(int (*f)(void*, char*), int in)
+{
+	int i, topid;
+	int (*from)(void*, char*), (*to)(void*, char*);
+
+	if(in){
+		from = nil;
+		to = f;
+		topid = _threadgetproc()->pid;
+	}else{
+		from = f;
+		to = nil;
+		topid = 0;
+	}
+	lock(&onnotelock);
+	for(i=0; i<NFN; i++)
+		if(onnote[i]==from){
+			onnote[i] = to;
+			onnotepid[i] = topid;
+			break;
+		}
+	unlock(&onnotelock);
+	return i<NFN;
+}
+
+static void
+delayednotes(Proc *p, void *v)
+{
+	int i;
+	Note *n;
+	int (*fn)(void*, char*);
+
+	if(!p->pending)
+		return;
+
+	p->pending = 0;
+	for(n=notes; n<enotes; n++){
+		if(n->proc == p){
+			for(i=0; i<NFN; i++){
+				if(onnotepid[i]!=p->pid || (fn = onnote[i])==nil)
+					continue;
+				if((*fn)(v, n->s))
+					break;
+			}
+			if(i==NFN){
+				_threaddebug(DBGNOTE, "Unhandled note %s, proc %p\n", n->s, p);
+				if(v != nil)
+					noted(NDFLT);
+				else if(strncmp(n->s, "sys:", 4)==0)
+					abort();
+				threadexitsall(n->s);
+			}
+			n->proc = nil;
+			unlock(&n->inuse);
+		}
+	}
+}
+
+void
+_threadnote(void *v, char *s)
+{
+	Proc *p;
+	Note *n;
+
+	_threaddebug(DBGNOTE, "Got note %s", s);
+	if(strncmp(s, "sys:", 4) == 0)
+		noted(NDFLT);
+
+//	if(_threadexitsallstatus){
+//		_threaddebug(DBGNOTE, "Threadexitsallstatus = '%s'\n", _threadexitsallstatus);
+//		_exits(_threadexitsallstatus);
+//	}
+
+	if(strcmp(s, "threadint")==0)
+		noted(NCONT);
+
+	p = _threadgetproc();
+	if(p == nil)
+		noted(NDFLT);
+
+	for(n=notes; n<enotes; n++)
+		if(canlock(&n->inuse))
+			break;
+	if(n==enotes)
+		sysfatal("libthread: too many delayed notes");
+	utfecpy(n->s, n->s+ERRMAX, s);
+	n->proc = p;
+	p->pending = 1;
+	if(!p->splhi)
+		delayednotes(p, v);
+	noted(NCONT);
+}
+#endif
+
+int
+_procsplhi(void)
+{
+	int s;
+	Proc *p;
+
+	p = _threadgetproc();
+	s = p->splhi;
+	p->splhi = 1;
+	return s;
+}
+
+void
+_procsplx(int s)
+{
+	Proc *p;
+
+	p = _threadgetproc();
+	p->splhi = s;
+	if(s)
+		return;
+/*
+	if(p->pending)
+		delayednotes(p, nil);
+*/
+}
+
diff --git a/src/libthread/proctab.c b/src/libthread/proctab.c
new file mode 100644
index 0000000..4029652
--- /dev/null
+++ b/src/libthread/proctab.c
@@ -0,0 +1,64 @@
+#include "threadimpl.h"
+
+/* this will need work */
+enum
+{
+	PTABHASH = 257,
+};
+
+static Lock ptablock;
+Proc *ptab[PTABHASH];
+
+void
+_threadsetproc(Proc *p)
+{
+	int h;
+
+	lock(&ptablock);
+	h = ((unsigned)p->pid)%PTABHASH;
+	p->link = ptab[h];
+	unlock(&ptablock);
+	ptab[h] = p;
+}
+
+static Proc*
+__threadgetproc(int rm)
+{
+	Proc **l, *p;
+	int h, pid;
+	Thread *t;
+	ulong *s;
+
+	s = (ulong*)((ulong)&pid & ~(STKSIZE-1));
+	if(s[0] == STKMAGIC){
+		t = (Thread*)s[1];
+		return t->proc;
+	}
+
+	pid = _threadgetpid();
+
+	lock(&ptablock);
+	h = ((unsigned)pid)%PTABHASH;
+	for(l=&ptab[h]; p=*l; l=&p->link){
+		if(p->pid == pid){
+			if(rm)
+				*l = p->link;
+			unlock(&ptablock);
+			return p;
+		}
+	}
+	unlock(&ptablock);
+	return nil;
+}
+
+Proc*
+_threadgetproc(void)
+{
+	return __threadgetproc(0);
+}
+
+Proc*
+_threaddelproc(void)
+{
+	return __threadgetproc(1);
+}
diff --git a/src/libthread/ref.c b/src/libthread/ref.c
new file mode 100644
index 0000000..9b78e63
--- /dev/null
+++ b/src/libthread/ref.c
@@ -0,0 +1,13 @@
+#include "threadimpl.h"
+
+void
+incref(Ref *r)
+{
+	_xinc(&r->ref);
+}
+
+long
+decref(Ref *r)
+{
+	return _xdec(&r->ref);
+}
diff --git a/src/libthread/rendez.c b/src/libthread/rendez.c
new file mode 100644
index 0000000..62b825b
--- /dev/null
+++ b/src/libthread/rendez.c
@@ -0,0 +1,104 @@
+#include "threadimpl.h"
+
+Rgrp _threadrgrp;
+static int isdirty;
+int _threadhighnrendez;
+int _threadnrendez;
+static int nrendez;
+
+static ulong
+finish(Thread *t, ulong val)
+{
+	ulong ret;
+
+	ret = t->rendval;
+	t->rendval = val;
+	while(t->state == Running)
+		sleep(0);
+	lock(&t->proc->lock);
+	if(t->state == Rendezvous){	/* not always true: might be Dead */
+		t->state = Ready;
+		_threadready(t);
+	}
+	unlock(&t->proc->lock);
+	return ret;
+}
+
+ulong
+_threadrendezvous(ulong tag, ulong val)
+{
+	ulong ret;
+	Thread *t, **l;
+
+	lock(&_threadrgrp.lock);
+_threadnrendez++;
+	l = &_threadrgrp.hash[tag%nelem(_threadrgrp.hash)];
+	for(t=*l; t; l=&t->rendhash, t=*l){
+		if(t->rendtag==tag){
+			_threaddebug(DBGREND, "Rendezvous with thread %d.%d", t->proc->pid, t->id);
+			*l = t->rendhash;
+			ret = finish(t, val);
+			--nrendez;
+			unlock(&_threadrgrp.lock);
+			return ret;
+		}
+	}
+
+	/* Going to sleep here. */
+	t = _threadgetproc()->thread;
+	t->rendbreak = 0;
+	t->inrendez = 1;
+	t->rendtag = tag;
+	t->rendval = val;
+	t->rendhash = *l;
+	*l = t;
+	t->nextstate = Rendezvous;
+	++nrendez;
+	if(nrendez > _threadhighnrendez)
+		_threadhighnrendez = nrendez;
+	_threaddebug(DBGREND, "Rendezvous for tag %lud", t->rendtag);
+	unlock(&_threadrgrp.lock);
+	_sched();
+	t->inrendez = 0;
+	_threaddebug(DBGREND, "Woke after rendezvous; val is %lud", t->rendval);
+	return t->rendval;
+}
+
+/*
+ * This is called while holding _threadpq.lock and p->lock,
+ * so we can't lock _threadrgrp.lock.  Instead our caller has 
+ * to call _threadbreakrendez after dropping those locks.
+ */
+void
+_threadflagrendez(Thread *t)
+{
+	t->rendbreak = 1;
+	isdirty = 1;
+}
+
+void
+_threadbreakrendez(void)
+{
+	int i;
+	Thread *t, **l;
+
+	if(isdirty == 0)
+		return;
+	lock(&_threadrgrp.lock);
+	if(isdirty == 0){
+		unlock(&_threadrgrp.lock);
+		return;
+	}
+	isdirty = 0;
+	for(i=0; i<nelem(_threadrgrp.hash); i++){
+		l = &_threadrgrp.hash[i];
+		for(t=*l; t; t=*l){
+			if(t->rendbreak){
+				*l = t->rendhash;
+				finish(t, ~0);
+			}else
+				 l=&t->rendhash;
+		}
+	}
+	unlock(&_threadrgrp.lock);
+}
diff --git a/src/libthread/rpm.spec b/src/libthread/rpm.spec
new file mode 100644
index 0000000..fdc72de
--- /dev/null
+++ b/src/libthread/rpm.spec
@@ -0,0 +1,26 @@
+Summary: Port of Plan 9's thread library
+Name: libthread
+Version: 2.0
+Release: 1
+Group: Development/C
+Copyright: BSD-like
+Packager: Russ Cox <rsc@post.harvard.edu>
+Source: http://pdos.lcs.mit.edu/~rsc/software/libthread-2.0.tgz
+URL: http://pdos.lcs.mit.edu/~rsc/software/#libthread
+
+%description
+Libthread is a port of Plan 9's thread library
+%prep
+%setup
+
+%build
+make
+
+%install
+make install
+
+%files
+/usr/local/include/thread.h
+/usr/local/lib/libthread.a
+/usr/local/man/man3/thread.3
+/usr/local/man/man3/ioproc.3
diff --git a/src/libthread/sched.c b/src/libthread/sched.c
new file mode 100644
index 0000000..8609833
--- /dev/null
+++ b/src/libthread/sched.c
@@ -0,0 +1,192 @@
+#include "threadimpl.h"
+#include <signal.h>
+
+//static Thread	*runthread(Proc*);
+
+static char *_psstate[] = {
+	"Dead",
+	"Running",
+	"Ready",
+	"Rendezvous",
+};
+
+static char*
+psstate(int s)
+{
+	if(s < 0 || s >= nelem(_psstate))
+		return "unknown";
+	return _psstate[s];
+}
+
+void
+_schedinit(void *arg)
+{
+	Proc *p;
+	Thread *t;
+	extern void ignusr1(void), _threaddie(int);
+	ignusr1();
+	signal(SIGTERM, _threaddie);
+  
+
+
+	p = arg;
+	p->pid = _threadgetpid();
+	_threadsetproc(p);
+	while(_setlabel(&p->sched))
+		;
+	_threaddebug(DBGSCHED, "top of schedinit, _threadexitsallstatus=%p", _threadexitsallstatus);
+	if(_threadexitsallstatus)
+		exits(_threadexitsallstatus);
+	lock(&p->lock);
+	if((t=p->thread) != nil){
+		p->thread = nil;
+		if(t->moribund){
+			assert(t->moribund == 1);
+			t->state = Dead;
+			if(t->prevt)
+				t->prevt->nextt = t->nextt;
+			else
+				p->threads.head = t->nextt;
+			if(t->nextt)
+				t->nextt->prevt = t->prevt;
+			else
+				p->threads.tail = t->prevt;
+			unlock(&p->lock);
+			if(t->inrendez){
+				_threadflagrendez(t);
+				_threadbreakrendez();
+			}
+			_stackfree(t->stk);
+			free(t->cmdname);
+			free(t);	/* XXX how do we know there are no references? */
+			t = nil;
+			_sched();
+		}
+		if(p->needexec){
+			t->ret = _schedexec(&p->exec);
+			p->needexec = 0;
+		}
+		if(p->newproc){
+			t->ret = _schedfork(p->newproc);
+			if(t->ret < 0){
+//fprint(2, "_schedfork: %r\n");
+				abort();
+}
+			p->newproc = nil;
+		}
+		t->state = t->nextstate;
+		if(t->state == Ready)
+			_threadready(t);
+	}
+	unlock(&p->lock);
+	_sched();
+}
+
+static inline Thread*
+runthread(Proc *p)
+{
+	Thread *t;
+	Tqueue *q;
+
+	if(p->nthreads==0)
+		return nil;
+	q = &p->ready;
+	lock(&p->readylock);
+	if(q->head == nil){
+		q->asleep = 1;
+		_threaddebug(DBGSCHED, "sleeping for more work");
+		unlock(&p->readylock);
+		while(rendezvous((ulong)q, 0) == ~0){
+			if(_threadexitsallstatus)
+				exits(_threadexitsallstatus);
+		}
+		/* lock picked up from _threadready */
+	}
+	t = q->head;
+	q->head = t->next;
+	unlock(&p->readylock);
+	return t;
+}
+
+void
+_sched(void)
+{
+	Proc *p;
+	Thread *t;
+
+Resched:
+	p = _threadgetproc();
+//fprint(2, "p %p\n", p);
+	if((t = p->thread) != nil){
+		if((ulong)&p < (ulong)t->stk){	/* stack overflow */
+			fprint(2, "stack overflow %lux %lux\n", (ulong)&p, (ulong)t->stk);
+			abort();
+		}
+	//	_threaddebug(DBGSCHED, "pausing, state=%s set %p goto %p",
+	//		psstate(t->state), &t->sched, &p->sched);
+		if(_setlabel(&t->sched)==0)
+			_gotolabel(&p->sched);
+		return;
+	}else{
+		t = runthread(p);
+		if(t == nil){
+			_threaddebug(DBGSCHED, "all threads gone; exiting");
+			_threaddelproc();
+			_schedexit(p);
+		}
+	//	_threaddebug(DBGSCHED, "running %d.%d", t->proc->pid, t->id);
+		p->thread = t;
+		if(t->moribund){
+			_threaddebug(DBGSCHED, "%d.%d marked to die");
+			goto Resched;
+		}
+		t->state = Running;
+		t->nextstate = Ready;
+		_gotolabel(&t->sched);
+	}
+}
+
+long
+threadstack(void)
+{
+	Proc *p;
+	Thread *t;
+
+	p = _threadgetproc();
+	t = p->thread;
+	return (ulong)&p - (ulong)t->stk;
+}
+
+void
+_threadready(Thread *t)
+{
+	Tqueue *q;
+
+	assert(t->state == Ready);
+	_threaddebug(DBGSCHED, "readying %d.%d", t->proc->pid, t->id);
+	q = &t->proc->ready;
+	lock(&t->proc->readylock);
+	t->next = nil;
+	if(q->head==nil)
+		q->head = t;
+	else
+		q->tail->next = t;
+	q->tail = t;
+	if(q->asleep){
+		q->asleep = 0;
+		/* lock passes to runthread */
+		_threaddebug(DBGSCHED, "waking process %d", t->proc->pid);
+		while(rendezvous((ulong)q, 0) == ~0){
+			if(_threadexitsallstatus)
+				exits(_threadexitsallstatus);
+		}
+	}else
+		unlock(&t->proc->readylock);
+}
+
+void
+yield(void)
+{
+	_sched();
+}
+
diff --git a/src/libthread/texec.c b/src/libthread/texec.c
new file mode 100644
index 0000000..9ba8682
--- /dev/null
+++ b/src/libthread/texec.c
@@ -0,0 +1,34 @@
+#include <lib9.h>
+#include <thread.h>
+extern int _threaddebuglevel;
+
+void
+doexec(void *v)
+{
+	char **argv = v;
+
+	procexec(nil, argv[0], argv);
+	sendp(threadwaitchan(), nil);
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	Channel *c;
+	Waitmsg *w;
+
+	ARGBEGIN{
+	case 'D':
+		_threaddebuglevel = ~0;
+		break;
+	}ARGEND
+
+	c = threadwaitchan();
+	proccreate(doexec, argv, 8192);
+	w = recvp(c);
+	if(w == nil)
+		print("exec failed\n");
+	else
+		print("%d %lu %lu %lu %s\n", w->pid, w->time[0], w->time[1], w->time[2], w->msg);
+	threadexits(nil);
+}
diff --git a/src/libthread/thread.3 b/src/libthread/thread.3
new file mode 100644
index 0000000..3009ac8
--- /dev/null
+++ b/src/libthread/thread.3
@@ -0,0 +1,576 @@
+.TH THREAD 2
+.SH NAME
+alt,
+chancreate,
+chanfree,
+chaninit,
+chanprint,
+mainstacksize,
+proccreate,
+procdata,
+procexec,
+procexecl,
+procrfork,
+recv,
+recvp,
+recvul,
+send,
+sendp,
+sendul,
+nbrecv,
+nbrecvp,
+nbrecvul,
+nbsend,
+nbsendp,
+nbsendul,
+threadcreate,
+threaddata,
+threadexits,
+threadexitsall,
+threadgetgrp,
+threadgetname,
+threadint,
+threadintgrp,
+threadkill,
+threadkillgrp,
+threadmain,
+threadnotify,
+threadid,
+threadpid,
+threadsetgrp,
+threadsetname,
+threadwaitchan,
+yield \- thread and proc management
+.SH SYNOPSIS
+.PP
+.EX
+.ta 4n +4n +4n +4n +4n +4n +4n
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+.sp
+#define	CHANEND		0
+#define	CHANSND		1
+#define	CHANRCV		2
+#define	CHANNOP		3
+#define	CHANNOBLK	4
+.sp
+.ta \w'    'u +\w'Channel 'u
+typedef struct Alt Alt;
+struct Alt {
+	Channel	*c;
+	void	*v;
+	int	op;
+	Channel	**tag;
+	int	entryno;
+};
+.fi
+.de XX
+.ift .sp 0.5
+.ifn .sp
+..
+.PP
+.nf
+.ft L
+.ta \w'\fLChannel* 'u +4n +4n +4n +4n
+void	threadmain(int argc, char *argv[])
+int	mainstacksize
+int	proccreate(void (*fn)(void*), void *arg, uint stacksize)
+int	procrfork(void (*fn)(void*), void *arg, uint stacksize,
+		int rforkflag)
+int	threadcreate(void (*fn)(void*), void *arg, uint stacksize)
+void	threadexits(char *status)
+void	threadexitsall(char *status)
+void	yield(void)
+.XX
+int	threadid(void)
+int	threadgrp(void)
+int	threadsetgrp(int group)
+int	threadpid(int id)
+.XX
+int	threadint(int id)
+int	threadintgrp(int group)
+int	threadkill(int id)
+int	threadkillgrp(int group)
+.XX
+void	threadsetname(char *name)
+char*	threadgetname(void)
+.XX
+void**	threaddata(void)
+void**	procdata(void)
+.XX
+int	chaninit(Channel *c, int elsize, int nel)
+Channel*	chancreate(int elsize, int nel)
+void	chanfree(Channel *c)
+.XX
+int	alt(Alt *alts)
+int	recv(Channel *c, void *v)
+void*	recvp(Channel *c)
+ulong	recvul(Channel *c)
+int	nbrecv(Channel *c, void *v)
+void*	nbrecvp(Channel *c)
+ulong	nbrecvul(Channel *c)
+int	send(Channel *c, void *v)
+int	sendp(Channel *c, void *v)
+int	sendul(Channel *c, ulong v)
+int	nbsend(Channel *c, void *v)
+int	nbsendp(Channel *c, void *v)
+int	nbsendul(Channel *c, ulong v)
+int	chanprint(Channel *c, char *fmt, ...)
+.XX
+int	procexecl(Channel *cpid, char *file, ...)
+int	procexec(Channel *cpid, char *file, char *args[])
+Channel*	threadwaitchan(void)
+.XX
+int	threadnotify(int (*f)(void*, char*), int in)
+.EE
+.SH DESCRIPTION
+.PP
+The thread library provides parallel programming support similar to that
+of the languages
+Alef and Newsqueak.
+Threads
+and
+procs
+occupy a shared address space,
+communicating and synchronizing through
+.I channels
+and shared variables.
+.PP
+A
+.I proc
+is a Plan 9 process that contains one or more cooperatively scheduled
+.IR threads .
+Programs using threads must replace
+.I main
+by
+.IR threadmain .
+The thread library provides a
+.I main
+function that sets up a proc with a single thread executing
+.I threadmain
+on a stack of size
+.I mainstacksize
+(default eight kilobytes).
+To set
+.IR mainstacksize ,
+declare a global variable
+initialized to the desired value
+.RI ( e.g. ,
+.B int
+.B mainstacksize
+.B =
+.BR 1024 ).
+.PP
+.I Threadcreate
+creates a new thread in the calling proc, returning a unique integer
+identifying the thread; the thread
+executes
+.I fn(arg)
+on a stack of size
+.IR stacksize .
+Thread stacks are allocated in shared memory, making it valid to pass 
+pointers to stack variables between threads and procs.
+.I Procrfork
+creates a new proc, and inside that proc creates
+a single thread as
+.I threadcreate
+would,
+returning the id of the created thread.
+.I Procrfork
+creates the new proc by calling
+.B rfork
+(see
+.IR fork (2))
+with flags
+.BR RFPROC|RFMEM|RFNOWAIT| \fIrforkflag\fR.
+(The thread library depends on all its procs
+running in the same rendezvous group.
+Do not include
+.B RFREND
+in
+.IR rforkflag .)
+.I Proccreate
+is identical to 
+.I procrfork
+with
+.I rforkflag
+set to zero.
+Be aware that the calling thread may continue
+execution before
+the newly created proc and thread
+are scheduled.
+Because of this,
+.I arg
+should not point to data on the stack of a function that could
+return before the new process is scheduled.
+.PP
+.I Threadexits
+terminates the calling thread.
+If the thread is the last in its proc,
+.I threadexits
+also terminates the proc, using
+.I status
+as the exit status.
+.I Threadexitsall
+terminates all procs in the program,
+using
+.I status
+as the exit status.
+.PP
+The threads in a proc are coroutines, scheduled nonpreemptively
+in a round-robin fashion.
+A thread must explicitly relinquish control of the processor
+before another thread in the same proc is run.
+Calls that do this are
+.IR yield ,
+.IR proccreate ,
+.IR procexec ,
+.IR procexecl ,
+.IR threadexits ,
+.IR alt ,
+.IR send ,
+and
+.I recv
+(and the calls related to
+.I send
+and
+.IR recv \(emsee
+their descriptions further on).
+Procs are scheduled by the operating system.
+Therefore, threads in different procs can preempt one another
+in arbitrary ways and should synchronize their
+actions using
+.B qlocks
+(see
+.IR lock (2))
+or channel communication.
+System calls such as
+.IR read (2)
+block the entire proc;
+all threads in a proc block until the system call finishes.
+.PP
+As mentioned above, each thread has a unique integer thread id.
+Thread ids are not reused; they are unique across the life of the program.
+.I Threadid
+returns the id for the current thread.
+Each thread also has a thread group id.
+The initial thread has a group id of zero.
+Each new thread inherits the group id of
+the thread that created it.
+.I Threadgrp
+returns the group id for the current thread;
+.I threadsetgrp
+sets it.
+.I Threadpid
+returns the pid of the Plan 9 process containing
+the thread identified by
+.IR id ,
+or \-1
+if no such thread is found.
+.PP
+.I Threadint
+interrupts a thread that is blocked in a channel operation
+or system call.
+.I Threadintgrp
+interrupts all threads with the given group id.
+.I Threadkill
+marks a thread to die when it next relinquishes the processor
+(via one of the calls listed above).
+If the thread is blocked in a channel operation or system call,
+it is also interrupted.
+.I Threadkillgrp
+kills all threads with the given group id.
+Note that
+.I threadkill
+and
+.I threadkillgrp
+will not terminate a thread that never relinquishes
+the processor.
+.PP
+Primarily for debugging,
+threads can have string names associated with them.
+.I Threadgetname
+returns the current thread's name;
+.I threadsetname
+sets it.
+The pointer returned by
+.I threadgetname
+is only valid until the next call to
+.IR threadsetname .
+.PP
+.I Threaddata
+returns a pointer to a per-thread pointer
+that may be modified by threaded programs for
+per-thread storage.
+Similarly, 
+.I procdata
+returns a pointer to a per-proc pointer.
+.PP
+.I Procexecl
+and
+.I procexec
+are threaded analogues of
+.I exec
+and
+.I execl
+(see
+.IR exec (2));
+on success,
+they replace the calling thread (which must be the only thread in its proc)
+and invoke the external program, never returning.
+On error, they return \-1.
+If
+.I cpid
+is not null, the pid of the invoked program
+will be sent along
+.I cpid
+once the program has been started, or \-1 will be sent if an
+error occurs.
+.I Procexec
+and
+.I procexecl
+will not access their arguments after sending a result
+along
+.IR cpid .
+Thus, programs that malloc the
+.I argv
+passed to
+.I procexec
+can safely free it once they have
+received the
+.I cpid
+response.
+.I Threadwaitchan
+returns a channel of pointers to
+.B Waitmsg
+structures (see
+.IR wait (2)).
+When an exec'ed process exits, a pointer to a
+.B Waitmsg
+is sent to this channel.
+These
+.B Waitmsg
+structures have been allocated with
+.IR malloc (2)
+and should be freed after use.
+.PP
+A
+.B Channel
+is a buffered or unbuffered queue for fixed-size messages.
+Procs and threads
+.I send
+messages into the channel and
+.I recv
+messages from the channel.  If the channel is unbuffered, a
+.I send
+operation blocks until the corresponding
+.I recv
+operation occurs and
+.IR "vice versa" .
+.I Chaninit
+initializes a 
+.B Channel
+for messages of size
+.I elsize 
+and with a buffer holding
+.I nel
+messages.
+If
+.I nel
+is zero, the channel is unbuffered.
+.IR Chancreate
+allocates a new channel and initializes it.
+.I Chanfree
+frees a channel that is no longer used.
+.I Chanfree
+can be called by either sender or receiver after the last item has been
+sent or received.  Freeing the channel will be delayed if there is a thread
+blocked on it until that thread unblocks (but
+.I chanfree
+returns immediately).
+.PP
+.I Send
+sends the element pointed at by
+.I v
+to the channel
+.IR c .
+If
+.I v
+is null, zeros are sent.
+.I Recv
+receives an element from
+.I c
+and stores it in
+.IR v .
+If
+.I v
+is null,
+the received value is discarded.
+.I Send
+and
+.I recv
+return 1 on success, \-1 if interrupted.
+.I Nbsend
+and
+.I nbrecv
+behave similarly, but return 0 rather than blocking.
+.PP
+.IR Sendp ,
+.IR nbsendp ,
+.IR sendul ,
+and
+.I nbsendul
+send a pointer or an unsigned long; the channel must
+have been initialized with the appropriate
+.IR elsize .
+.IR Recvp ,
+.IR nbrecvp ,
+.IR recvul ,
+and
+.I nbrecvul
+receive a pointer or an unsigned long;
+they return zero when a zero is received,
+when interrupted, or
+(for
+.I nbrecvp
+and
+.IR nbrecvul )
+when the operation would have blocked.
+To distinguish between these three cases,
+use
+.I recv
+or
+.IR nbrecv .
+.PP
+.I Alt
+can be used to recv from or send to one of a number of channels,
+as directed by an array of
+.B Alt
+structures,
+each of which describes a potential send or receive operation.
+In an
+.B Alt
+structure,
+.B c
+is the channel;
+.B v
+the value pointer (which may be null); and
+.B op
+the operation:
+.B CHANSND
+for a send operation,
+.B CHANRECV
+for a recv operation;
+.B CHANNOP
+for no operation
+(useful
+when
+.I alt
+is called with a varying set of operations).
+The array of
+.B Alt
+structures is terminated by an entry with
+.I op
+.B CHANEND
+or
+.BR CHANNOBLK .
+If at least one
+.B Alt
+structure can proceed, one of them is
+chosen at random to be executed.
+.I Alt
+returns the index of the chosen structure.
+If no operations can proceed and the list is terminated with
+.BR CHANNOBLK ,
+.I alt
+returns the index of the terminating
+.B CHANNOBLK
+structure.
+Otherwise,
+.I alt
+blocks until one of the operations can proceed,
+eventually returning the index of the structure executes.
+.I Alt
+returns \-1 when interrupted.
+The
+.B tag
+and
+.B entryno
+fields in the
+.B Alt
+structure are used internally by
+.I alt
+and need not be initialized.
+They are not used between
+.I alt
+calls.
+.PP
+.I Chanprint
+formats its arguments in the manner of
+.IR print (2)
+and sends the result to the channel
+.IR c.
+The string delivered by
+.I chanprint
+is allocated with
+.IR malloc (2)
+and should be freed upon receipt.
+.PP
+Thread library functions do not return on failure;
+if errors occur, the entire program is aborted.
+.PP
+Threaded programs should use
+.I threadnotify
+in place of
+.I atnotify
+(see
+.IR notify (2)).
+.PP
+It is safe to use
+.B sysfatal
+(see
+.IR perror (2))
+in threaded programs.
+.I Sysfatal
+will print the error string and call
+.IR threadexitsall .
+.PP
+It is safe to use 
+.IR rfork
+(see
+.IR fork (2))
+to manage the namespace, file descriptors, note group, and environment of a
+single process.
+That is, it is safe to call
+.I rfork
+with the flags
+.BR RFNAMEG ,
+.BR RFFDG ,
+.BR RFCFDG ,
+.BR RFNOTEG ,
+.BR RFENVG ,
+and
+.BR RFCENVG.
+(To create new processes, use
+.I proccreate
+and 
+.IR procrfork .)
+As mentioned above,
+the thread library depends on all procs being in the
+same rendezvous group; do not change the rendezvous
+group with
+.IR rfork .
+.SH FILES
+.B /sys/lib/acid/thread
+contains useful
+.IR acid (1)
+functions for debugging threaded programs.
+.PP
+.B /sys/src/libthread/example.c
+contains a full example program.
+.SH SOURCE
+.B /sys/src/libthread
+.SH SEE ALSO
+.IR intro (2),
+.IR ioproc (2)
diff --git a/src/libthread/thread.h b/src/libthread/thread.h
new file mode 100644
index 0000000..10aac28
--- /dev/null
+++ b/src/libthread/thread.h
@@ -0,0 +1,132 @@
+#ifndef _THREADH_
+#define _THREADH_ 1
+
+/* avoid conflicts with socket library */
+#undef send
+#define send _threadsend
+#undef recv
+#define recv _threadrecv
+
+typedef struct Alt	Alt;
+typedef struct Channel	Channel;
+typedef struct Ref	Ref;
+
+/* Channel structure.  S is the size of the buffer.  For unbuffered channels
+ * s is zero.  v is an array of s values.  If s is zero, v is unused.
+ * f and n represent the state of the queue pointed to by v.
+ */
+
+enum {
+	Nqwds = 2,
+	Nqshift = 5,	// 2log #of bits in long
+	Nqmask =  - 1,
+	Nqbits = (1 << Nqshift) * 2,
+};
+
+struct Channel {
+	int			s;		// Size of the channel (may be zero)
+	unsigned int	f;		// Extraction point (insertion pt: (f + n) % s)
+	unsigned int	n;		// Number of values in the channel
+	int			e;		// Element size
+	int			freed;	// Set when channel is being deleted
+	volatile Alt	**qentry;	// Receivers/senders waiting (malloc)
+	volatile int	nentry;	// # of entries malloc-ed
+	unsigned char		v[1];		// Array of s values in the channel
+};
+
+
+/* Channel operations for alt: */
+typedef enum {
+	CHANEND,
+	CHANSND,
+	CHANRCV,
+	CHANNOP,
+	CHANNOBLK,
+} ChanOp;
+
+struct Alt {
+	Channel	*c;		/* channel */
+	void		*v;		/* pointer to value */
+	ChanOp	op;		/* operation */
+
+	/* the next variables are used internally to alt
+	 * they need not be initialized
+	 */
+	Channel	**tag;	/* pointer to rendez-vous tag */
+	int		entryno;	/* entry number */
+};
+
+struct Ref {
+	long ref;
+};
+
+int		alt(Alt alts[]);
+Channel*	chancreate(int elemsize, int bufsize);
+int		chaninit(Channel *c, int elemsize, int elemcnt);
+void		chanfree(Channel *c);
+int		chanprint(Channel *, char *, ...);
+long		decref(Ref *r);		/* returns 0 iff value is now zero */
+void		incref(Ref *r);
+int		nbrecv(Channel *c, void *v);
+void*		nbrecvp(Channel *c);
+unsigned long		nbrecvul(Channel *c);
+int		nbsend(Channel *c, void *v);
+int		nbsendp(Channel *c, void *v);
+int		nbsendul(Channel *c, unsigned long v);
+int		proccreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
+int		procrfork(void (*f)(void *arg), void *arg, unsigned int stacksize, int flag);
+void**		procdata(void);
+void		procexec(Channel *, char *, char *[]);
+void		procexecl(Channel *, char *, ...);
+int		recv(Channel *c, void *v);
+void*		recvp(Channel *c);
+unsigned long		recvul(Channel *c);
+int		send(Channel *c, void *v);
+int		sendp(Channel *c, void *v);
+int		sendul(Channel *c, unsigned long v);
+int		threadcreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
+void**		threaddata(void);
+void		threadexits(char *);
+void		threadexitsall(char *);
+int		threadgetgrp(void);	/* return thread group of current thread */
+char*		threadgetname(void);
+void		threadint(int);	/* interrupt thread */
+void		threadintgrp(int);	/* interrupt threads in grp */
+void		threadkill(int);	/* kill thread */
+void		threadkillgrp(int);	/* kill threads in group */
+void		threadmain(int argc, char *argv[]);
+void		threadnonotes(void);
+int		threadnotify(int (*f)(void*, char*), int in);
+int		threadid(void);
+int		threadpid(int);
+int		threadsetgrp(int);	/* set thread group, return old */
+void		threadsetname(char *name);
+Channel*	threadwaitchan(void);
+int	tprivalloc(void);
+void	tprivfree(int);
+void	**tprivaddr(int);
+void		yield(void);
+
+long		threadstack(void);
+
+extern	int		mainstacksize;
+
+/* slave I/O processes */
+typedef struct Ioproc Ioproc;
+
+Ioproc*	ioproc(void);
+void		closeioproc(Ioproc*);
+void		iointerrupt(Ioproc*);
+
+int		ioclose(Ioproc*, int);
+int		iodial(Ioproc*, char*, char*, char*, int*);
+int		ioopen(Ioproc*, char*, int);
+long		ioread(Ioproc*, int, void*, long);
+long		ioreadn(Ioproc*, int, void*, long);
+long		iowrite(Ioproc*, int, void*, long);
+int		iosleep(Ioproc*, long);
+
+long		iocall(Ioproc*, long (*)(va_list*), ...);
+void		ioret(Ioproc*, int);
+
+#endif	/* _THREADH_ */
diff --git a/src/libthread/threadimpl.h b/src/libthread/threadimpl.h
new file mode 100644
index 0000000..24d9b21
--- /dev/null
+++ b/src/libthread/threadimpl.h
@@ -0,0 +1,219 @@
+/* 
+ * Some notes on locking:
+ *
+ *	All the locking woes come from implementing
+ *	threadinterrupt (and threadkill).
+ *
+ *	_threadgetproc()->thread is always a live pointer.
+ *	p->threads, p->ready, and _threadrgrp also contain
+ * 	live thread pointers.  These may only be consulted
+ *	while holding p->lock or _threadrgrp.lock; in procs
+ *	other than p, the pointers are only guaranteed to be live
+ *	while the lock is still being held.
+ *
+ *	Thread structures can only be freed by the proc
+ *	they belong to.  Threads marked with t->inrendez
+ * 	need to be extracted from the _threadrgrp before
+ *	being freed.
+ *
+ *	_threadrgrp.lock cannot be acquired while holding p->lock.
+ */
+
+#include <assert.h>
+#include <lib9.h>
+#include <thread.h>
+#include "label.h"
+
+enum{
+STKSIZE = 16384,
+STKMAGIC = 0xCAFEBEEF
+};
+
+typedef struct Thread	Thread;
+typedef struct Proc	Proc;
+typedef struct Tqueue	Tqueue;
+typedef struct Pqueue	Pqueue;
+typedef struct Rgrp		Rgrp;
+typedef struct Execargs	Execargs;
+
+/* must match list in sched.c */
+typedef enum
+{
+	Dead,
+	Running,
+	Ready,
+	Rendezvous,
+} State;
+	
+typedef enum
+{
+	Channone,
+	Chanalt,
+	Chansend,
+	Chanrecv,
+} Chanstate;
+
+enum
+{
+	RENDHASH = 10009,
+	Printsize = 2048,
+	NPRIV = 8,
+};
+
+struct Rgrp
+{
+	Lock		lock;
+	Thread	*hash[RENDHASH];
+};
+
+struct Tqueue		/* Thread queue */
+{
+	int		asleep;
+	Thread	*head;
+	Thread	*tail;
+};
+
+struct Thread
+{
+	Lock		lock;			/* protects thread data structure */
+	Label	sched;		/* for context switches */
+	int		id;			/* thread id */
+	int 		grp;			/* thread group */
+	int		moribund;	/* thread needs to die */
+	State		state;		/* run state */
+	State		nextstate;		/* next run state */
+	uchar	*stk;			/* top of stack (lowest address of stack) */
+	uint		stksize;		/* stack size */
+	Thread	*next;		/* next on ready queue */
+
+	Proc		*proc;		/* proc of this thread */
+	Thread	*nextt;		/* next on list of threads in this proc */
+	Thread	*prevt;		/* prev on list of threads in this proc */
+	int		ret;			/* return value for Exec, Fork */
+
+	char		*cmdname;	/* ptr to name of thread */
+
+	int		inrendez;
+	Thread	*rendhash;	/* Trgrp linked list */
+	ulong	rendtag;		/* rendezvous tag */
+	ulong	rendval;		/* rendezvous value */
+	int		rendbreak;	/* rendezvous has been taken */
+
+	Chanstate	chan;		/* which channel operation is current */
+	Alt		*alt;			/* pointer to current alt structure (debugging) */
+
+	void*	udata[NPRIV];	/* User per-thread data pointer */
+};
+
+struct Execargs
+{
+	char		*prog;
+	char		**args;
+	int		fd[2];
+};
+
+struct Proc
+{
+	Lock		lock;
+	Label	sched;		/* for context switches */
+	Proc		*link;		/* in proctab */
+	int		pid;			/* process id */
+	int		splhi;		/* delay notes */
+	Thread	*thread;		/* running thread */
+
+	int		needexec;
+	Execargs	exec;		/* exec argument */
+	Proc		*newproc;	/* fork argument */
+	char		exitstr[ERRMAX];	/* exit status */
+
+	int		rforkflag;
+	int		nthreads;
+	Tqueue	threads;		/* All threads of this proc */
+	Tqueue	ready;		/* Runnable threads */
+	Lock		readylock;
+
+	char		printbuf[Printsize];
+	int		blocked;		/* In a rendezvous */
+	int		pending;		/* delayed note pending */
+	int		nonotes;		/*  delay notes */
+	uint		nextID;		/* ID of most recently created thread */
+	Proc		*next;		/* linked list of Procs */
+
+	void		*arg;			/* passed between shared and unshared stk */
+	char		str[ERRMAX];	/* used by threadexits to avoid malloc */
+	char		errbuf[ERRMAX];	/* errstr */
+
+	void*	udata;		/* User per-proc data pointer */
+};
+
+struct Pqueue {		/* Proc queue */
+	Lock		lock;
+	Proc		*head;
+	Proc		**tail;
+};
+
+struct Ioproc
+{
+	int tid;
+	Channel *c, *creply;
+	int inuse;
+	long (*op)(va_list*);
+	va_list arg;
+	long ret;
+	char err[ERRMAX];
+	Ioproc *next;
+};
+
+void		_gotolabel(Label*);
+int		_setlabel(Label*);
+void		_freeproc(Proc*);
+Proc*	_newproc(void(*)(void*), void*, uint, char*, int, int);
+int		_procsplhi(void);
+void		_procsplx(int);
+void		_sched(void);
+int		_schedexec(Execargs*);
+void		_schedexecwait(void);
+void		_schedexit(Proc*);
+int		_schedfork(Proc*);
+void		_schedinit(void*);
+void		_systhreadinit(void);
+void		_threadassert(char*);
+void		_threadbreakrendez(void);
+void		__threaddebug(ulong, char*, ...);
+#define _threaddebug if(!_threaddebuglevel){}else __threaddebug
+void		_threadexitsall(char*);
+void		_threadflagrendez(Thread*);
+Proc*	_threadgetproc(void);
+Proc*	_threaddelproc(void);
+void		_threadsetproc(Proc*);
+void		_threadinitstack(Thread*, void(*)(void*), void*);
+void*	_threadmalloc(long, int);
+void		_threadnote(void*, char*);
+void		_threadready(Thread*);
+ulong	_threadrendezvous(ulong, ulong);
+void		_threadsignal(void);
+void		_threadsysfatal(char*, va_list);
+long		_xdec(long*);
+void		_xinc(long*);
+void		_threadremove(Proc*, Thread*);
+
+extern int			_threaddebuglevel;
+extern char*		_threadexitsallstatus;
+extern Pqueue		_threadpq;
+extern Channel*	_threadwaitchan;
+extern Rgrp		_threadrgrp;
+extern void _stackfree(void*);
+
+#define DBGAPPL	(1 << 0)
+#define DBGSCHED	(1 << 16)
+#define DBGCHAN	(1 << 17)
+#define DBGREND	(1 << 18)
+/* #define DBGKILL	(1 << 19) */
+#define DBGNOTE	(1 << 20)
+#define DBGEXEC	(1 << 21)
+
+#define ioproc_arg(io, type)	(va_arg((io)->arg, type))
+extern int _threadgetpid(void);
+extern void _threadmemset(void*, int, int);
+extern void _threaddebugmemset(void*, int, int);
+
diff --git a/src/libthread/tprimes b/src/libthread/tprimes
new file mode 100755
index 0000000..52d6f05
--- /dev/null
+++ b/src/libthread/tprimes
Binary files differ
diff --git a/src/libthread/tprimes.c b/src/libthread/tprimes.c
new file mode 100644
index 0000000..9426bdf
--- /dev/null
+++ b/src/libthread/tprimes.c
@@ -0,0 +1,62 @@
+#include <lib9.h>
+#include <thread.h>
+
+int quiet;
+int goal;
+int buffer;
+int (*fn)(void(*)(void*), void*, uint) = threadcreate;
+
+void
+primethread(void *arg)
+{
+	Channel *c, *nc;
+	int p, i;
+
+	c = arg;
+	p = recvul(c);
+	if(p > goal)
+		threadexitsall(nil);
+	if(!quiet)
+		print("%d\n", p);
+	nc = chancreate(sizeof(ulong), buffer);
+	(*fn)(primethread, nc, 8192);
+	for(;;){
+		i = recvul(c);
+		if(i%p)
+			sendul(nc, i);
+	}
+}
+
+extern int _threaddebuglevel;
+
+void
+threadmain(int argc, char **argv)
+{
+	int i;
+	Channel *c;
+
+	ARGBEGIN{
+	case 'D':
+		_threaddebuglevel = atoi(ARGF());
+		break;
+	case 'q':
+		quiet = 1;
+		break;
+	case 'b':
+		buffer = atoi(ARGF());
+		break;
+	case 'p':
+		fn=proccreate;
+		break;
+	}ARGEND
+
+	if(argc>0)
+		goal = atoi(argv[0]);
+	else
+		goal = 100;
+
+	c = chancreate(sizeof(ulong), buffer);
+	(*fn)(primethread, c, 8192);
+	for(i=2;; i++)
+		sendul(c, i);
+}