Initial revision
diff --git a/src/lib9/LICENSE b/src/lib9/LICENSE
new file mode 100644
index 0000000..a5d7d87
--- /dev/null
+++ b/src/lib9/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/lib9/Make.Darwin-PowerMacintosh b/src/lib9/Make.Darwin-PowerMacintosh
new file mode 100644
index 0000000..14b8d4e
--- /dev/null
+++ b/src/lib9/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/lib9/Make.FreeBSD-386 b/src/lib9/Make.FreeBSD-386
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/lib9/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/lib9/Make.HP-UX-9000 b/src/lib9/Make.HP-UX-9000
new file mode 100644
index 0000000..edbdc11
--- /dev/null
+++ b/src/lib9/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/lib9/Make.Linux-386 b/src/lib9/Make.Linux-386
new file mode 100644
index 0000000..74b0252
--- /dev/null
+++ b/src/lib9/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/lib9/Make.NetBSD-386 b/src/lib9/Make.NetBSD-386
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/lib9/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/lib9/Make.OSF1-alpha b/src/lib9/Make.OSF1-alpha
new file mode 100644
index 0000000..3d45279
--- /dev/null
+++ b/src/lib9/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/lib9/Make.SunOS-sun4u b/src/lib9/Make.SunOS-sun4u
new file mode 100644
index 0000000..c5fe67b
--- /dev/null
+++ b/src/lib9/Make.SunOS-sun4u
@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/lib9/Make.SunOS-sun4u-cc b/src/lib9/Make.SunOS-sun4u-cc
new file mode 100644
index 0000000..829301d
--- /dev/null
+++ b/src/lib9/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/lib9/Make.SunOS-sun4u-gcc b/src/lib9/Make.SunOS-sun4u-gcc
new file mode 100644
index 0000000..5c41594
--- /dev/null
+++ b/src/lib9/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/lib9/Makefile b/src/lib9/Makefile
new file mode 100644
index 0000000..595db78
--- /dev/null
+++ b/src/lib9/Makefile
@@ -0,0 +1,120 @@
+
+# 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=lib9.a
+VERSION=2.0
+PORTPLACE=devel/lib9
+NAME=lib9
+
+OFILES=\
+	_exits.$O\
+	argv0.$O\
+	await.$O\
+	encodefmt.$O\
+	errstr.$O\
+	exits.$O\
+	ffork-$(SYSNAME).$O\
+	getcallerpc-$(OBJTYPE).$O\
+	getfields.$O\
+	lock.$O\
+	malloctag.$O\
+	mallocz.$O\
+	nrand.$O\
+	qlock.$O\
+	readn.$O\
+	rendez.$O\
+	strecpy.$O\
+	sysfatal.$O\
+	tas-$(OBJTYPE).$O\
+	tokenize.$O\
+	u16.$O\
+	u32.$O\
+	u64.$O\
+	wait.$O\
+	werrstr.$O\
+
+HFILES=\
+	lib9.h\
+
+all: $(LIB)
+
+install: $(LIB)
+	test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+	# install -m 0644 lib9.3 $(PREFIX)/man/man3/lib9.3
+	install -m 0644 lib9.h $(PREFIX)/include/lib9.h
+	install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+test: $(LIB) test.$O
+	$(CC) -o test test.$O $(LIB) -L$(PREFIX)/lib -lfmt -lutf
+
+testfork: $(LIB) testfork.$O
+	$(CC) -o testfork testfork.$O $(LIB) -L$(PREFIX)/lib -lfmt -lutf
+
+$(LIB): $(OFILES)
+	$(AR) $(ARFLAGS) $(LIB) $(OFILES)
+
+NUKEFILES+=$(LIB)
+.c.$O:
+	$(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+	$(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+	rm -rf $(NAME)-$(VERSION)
+	mkdir $(NAME)-$(VERSION)
+	cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+	tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+	rm -rf $(NAME)-$(VERSION)
+
+clean:
+	rm -f $(OFILES) $(LIB)
+
+nuke:
+	rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+	make tgz
+	cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+	rpm -ba rpm.spec
+	cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+	cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+	scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+	make tgz
+	rm -rf $(PORTDIR)
+	mkdir $(PORTDIR)
+	cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+	cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+	(cd $(PORTDIR); make makesum)
+	(cd $(PORTDIR); make)
+	(cd $(PORTDIR); /usr/local/bin/portlint)
+	rm -rf $(PORTDIR)/work
+	shar `find $(PORTDIR)` > ports.shar
+	(cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+	scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/lib9/Makefile.MID b/src/lib9/Makefile.MID
new file mode 100644
index 0000000..8b3584c
--- /dev/null
+++ b/src/lib9/Makefile.MID
@@ -0,0 +1,49 @@
+LIB=lib9.a
+VERSION=2.0
+PORTPLACE=devel/lib9
+NAME=lib9
+
+OFILES=\
+	_exits.$O\
+	argv0.$O\
+	await.$O\
+	encodefmt.$O\
+	errstr.$O\
+	exits.$O\
+	ffork-$(SYSNAME).$O\
+	getcallerpc-$(OBJTYPE).$O\
+	getfields.$O\
+	lock.$O\
+	malloctag.$O\
+	mallocz.$O\
+	nrand.$O\
+	qlock.$O\
+	readn.$O\
+	rendez.$O\
+	strecpy.$O\
+	sysfatal.$O\
+	tas-$(OBJTYPE).$O\
+	tokenize.$O\
+	u16.$O\
+	u32.$O\
+	u64.$O\
+	wait.$O\
+	werrstr.$O\
+
+HFILES=\
+	lib9.h\
+
+all: $(LIB)
+
+install: $(LIB)
+	test -d $(PREFIX)/man/man3 || mkdir $(PREFIX)/man/man3
+	# install -m 0644 lib9.3 $(PREFIX)/man/man3/lib9.3
+	install -m 0644 lib9.h $(PREFIX)/include/lib9.h
+	install -m 0644 $(LIB) $(PREFIX)/lib/$(LIB)
+
+test: $(LIB) test.$O
+	$(CC) -o test test.$O $(LIB) -L$(PREFIX)/lib -lfmt -lutf
+
+testfork: $(LIB) testfork.$O
+	$(CC) -o testfork testfork.$O $(LIB) -L$(PREFIX)/lib -lfmt -lutf
+
diff --git a/src/lib9/_exits.c b/src/lib9/_exits.c
new file mode 100644
index 0000000..35ff4e6
--- /dev/null
+++ b/src/lib9/_exits.c
@@ -0,0 +1,9 @@
+#include <lib9.h>
+
+void
+_exits(char *s)
+{
+	if(s && *s)
+		_exit(1);
+	_exit(0);
+}
diff --git a/src/lib9/argv0.c b/src/lib9/argv0.c
new file mode 100644
index 0000000..2c846f4
--- /dev/null
+++ b/src/lib9/argv0.c
@@ -0,0 +1,4 @@
+#include <lib9.h>
+
+char *argv0;
+
diff --git a/src/lib9/await.c b/src/lib9/await.c
new file mode 100644
index 0000000..9df7faa
--- /dev/null
+++ b/src/lib9/await.c
@@ -0,0 +1,105 @@
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <string.h>
+#include <errno.h>
+#include <lib9.h>
+
+static struct {
+	int sig;
+	char *str;
+} tab[] = {
+	SIGHUP,		"hangup",
+	SIGINT,		"interrupt",
+	SIGQUIT,		"quit",
+	SIGILL,		"sys: trap: illegal instruction",
+	SIGTRAP,		"sys: trace trap",
+	SIGABRT,		"sys: abort",
+#ifdef SIGEMT
+	SIGEMT,		"sys: emulate instruction executed",
+#endif
+	SIGFPE,		"sys: fp: trap",
+	SIGKILL,		"sys: kill",
+	SIGBUS,		"sys: bus error",
+	SIGSEGV,		"sys: segmentation violation",
+	SIGALRM,		"alarm",
+	SIGTERM,		"kill",
+	SIGURG,		"sys: urgent condition on socket",
+	SIGSTOP,		"sys: stop",
+	SIGTSTP,		"sys: tstp",
+	SIGCONT,		"sys: cont",
+	SIGCHLD,		"sys: child",
+	SIGTTIN,		"sys: ttin",
+	SIGTTOU,		"sys: ttou",
+	SIGIO,		"sys: i/o possible on fd",
+	SIGXCPU,		"sys: cpu time limit exceeded",
+	SIGXFSZ,		"sys: file size limit exceeded",
+	SIGVTALRM,	"sys: virtual time alarm",
+	SIGPROF,		"sys: profiling timer alarm",
+	SIGWINCH,	"sys: window size change",
+#ifdef SIGINFO
+	SIGINFO,		"sys: status request",
+#endif
+	SIGUSR1,		"sys: usr1",
+	SIGUSR2,		"sys: usr2",
+};
+	
+static char*
+_p9sigstr(int sig, char *tmp)
+{
+	int i;
+
+	for(i=0; i<nelem(tab); i++)
+		if(tab[i].sig == sig)
+			return tab[i].str;
+	sprint(tmp, "sys: signal %d", sig);
+	return tmp;
+}
+
+/*
+static int
+_p9strsig(char *s)
+{
+	int i;
+
+	for(i=0; i<nelem(tab); i++)
+		if(strcmp(s, tab[i].str) == 0)
+			return tab[i].sig;
+	return 0;
+}
+*/
+
+int
+await(char *str, int n)
+{
+	int pid, status, cd;
+	struct rusage ru;
+	char buf[128], tmp[64];
+	ulong u, s;
+
+	for(;;){
+		pid = wait3(&status, 0, &ru);
+		if(pid < 0)
+			return -1;
+		u = ru.ru_utime.tv_sec*1000+((ru.ru_utime.tv_usec+500)/1000);
+		s = ru.ru_stime.tv_sec*1000+((ru.ru_stime.tv_usec+500)/1000);
+		if(WIFEXITED(status)){
+			status = WEXITSTATUS(status);
+			if(status)
+				snprint(buf, sizeof buf, "%d %lu %lu %lu %d", pid, u, s, u+s, status);
+			else
+				snprint(buf, sizeof buf, "%d %lu %lu %lu ''", pid, u, s, u+s);
+			strecpy(str, str+n, buf);
+			return strlen(str);
+		}
+		if(WIFSIGNALED(status)){
+			cd = WCOREDUMP(status);
+			USED(cd);
+			snprint(buf, sizeof buf, "%d %lu %lu %lu '%s'", pid, u, s, u+s, _p9sigstr(WTERMSIG(status), tmp));
+			strecpy(str, str+n, buf);
+			return strlen(str);
+		}
+	}
+}
diff --git a/src/lib9/encodefmt.c b/src/lib9/encodefmt.c
new file mode 100644
index 0000000..9a5cbfc
--- /dev/null
+++ b/src/lib9/encodefmt.c
@@ -0,0 +1,69 @@
+#include <lib9.h>
+
+int
+encodefmt(Fmt *f)
+{
+	char *out;
+	char *buf;
+	int len;
+	int ilen;
+	int rv;
+	uchar *b;
+	char obuf[64];	// rsc optimization
+
+	if(!(f->flags&FmtPrec) || f->prec < 1)
+		goto error;
+
+	b = va_arg(f->args, uchar*);
+
+	ilen = f->prec;
+	f->prec = 0;
+	f->flags &= ~FmtPrec;
+	switch(f->r){
+	case '<':
+		len = (8*ilen+4)/5 + 3;
+		break;
+	case '[':
+		len = (8*ilen+5)/6 + 4;
+		break;
+	case 'H':
+		len = 2*ilen + 1;
+		break;
+	default:
+		goto error;
+	}
+
+	if(len > sizeof(obuf)){
+		buf = malloc(len);
+		if(buf == nil)
+			goto error;
+	} else
+		buf = obuf;
+
+	// convert
+	out = buf;
+	switch(f->r){
+	case '<':
+		rv = enc32(out, len, b, ilen);
+		break;
+	case '[':
+		rv = enc64(out, len, b, ilen);
+		break;
+	case 'H':
+		rv = enc16(out, len, b, ilen);
+		break;
+	default:
+		rv = -1;
+		break;
+	}
+	if(rv < 0)
+		goto error;
+
+	fmtstrcpy(f, buf);
+	if(buf != obuf)
+		free(buf);
+	return 0;
+
+error:
+	return fmtstrcpy(f, "<encodefmt>");
+}
diff --git a/src/lib9/errstr.c b/src/lib9/errstr.c
new file mode 100644
index 0000000..e576b12
--- /dev/null
+++ b/src/lib9/errstr.c
@@ -0,0 +1,68 @@
+/*
+ * We assume there's only one error buffer for the whole system.
+ * If you use ffork, you need to provide a _syserrstr.  Since most
+ * people will use libthread (which provides a _syserrstr), this is
+ * okay.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <lib9.h>
+
+enum
+{
+	EPLAN9 = 0x19283745,
+};
+
+char *(*_syserrstr)(void);
+static char xsyserr[ERRMAX];
+static char*
+getsyserr(void)
+{
+	char *s;
+
+	s = nil;
+	if(_syserrstr)
+		s = (*_syserrstr)();
+	if(s == nil)
+		s = xsyserr;
+	return s;
+}
+
+int
+errstr(char *err, uint n)
+{
+	char tmp[ERRMAX];
+	char *syserr;
+
+	syserr = getsyserr();
+	if(errno != EPLAN9)
+		strcpy(syserr, strerror(errno));
+
+	strecpy(tmp, tmp+ERRMAX, syserr);
+	strecpy(syserr, syserr+ERRMAX, err);
+	strecpy(err, err+n, tmp);
+	errno = EPLAN9;
+	return 0;
+}
+
+void
+rerrstr(char *err, uint n)
+{
+	char *syserr;
+
+	syserr = getsyserr();
+	if(errno != EPLAN9)
+		strcpy(syserr, strerror(errno));
+	strecpy(err, err+n, syserr);
+}
+
+/* replaces __errfmt in libfmt */
+
+int
+__errfmt(Fmt *f)
+{
+	if(errno == EPLAN9)
+		return fmtstrcpy(f, getsyserr());
+	return fmtstrcpy(f, strerror(errno));
+}
diff --git a/src/lib9/exits.c b/src/lib9/exits.c
new file mode 100644
index 0000000..a449f68
--- /dev/null
+++ b/src/lib9/exits.c
@@ -0,0 +1,10 @@
+#include <lib9.h>
+
+void
+exits(char *s)
+{
+	if(s && *s)
+		exit(1);
+	exit(0);
+}
+
diff --git a/src/lib9/ffork-FreeBSD.c b/src/lib9/ffork-FreeBSD.c
new file mode 100644
index 0000000..a7c82e6
--- /dev/null
+++ b/src/lib9/ffork-FreeBSD.c
@@ -0,0 +1,33 @@
+#include <lib9.h>
+
+extern int __isthreaded;
+int
+ffork(int flags, void(*fn)(void*), void *arg)
+{
+	void *p;
+
+	__isthreaded = 1;
+	p = malloc(16384);
+	if(p == nil)
+		return -1;
+	memset(p, 0xFE, 16384);
+	return rfork_thread(RFPROC|flags, (char*)p+16000, (int(*)(void*))fn, arg);
+}
+
+/*
+ * For FreeBSD libc.
+ */
+
+typedef struct {
+	volatile long	access_lock;
+	volatile long	lock_owner;
+	volatile char	*fname;
+	volatile int	lineno;
+} spinlock_t;
+
+void
+_spinlock(spinlock_t *lk)
+{
+	lock((Lock*)&lk->access_lock);
+}
+
diff --git a/src/lib9/ffork-Linux.c b/src/lib9/ffork-Linux.c
new file mode 100644
index 0000000..aad8004
--- /dev/null
+++ b/src/lib9/ffork-Linux.c
@@ -0,0 +1,39 @@
+#include <sched.h>
+#include <signal.h>
+#include <lib9.h>
+
+int fforkstacksize = 16384;
+
+int
+ffork(int flags, void (*fn)(void*), void *arg)
+{
+	char *p;
+	int cloneflag, pid;
+
+	p = malloc(fforkstacksize);
+	if(p == nil)
+		return -1;
+	cloneflag = 0;
+	flags &= ~RFPROC;
+	if(flags&RFMEM){
+		cloneflag |= CLONE_VM;
+		flags &= ~RFMEM;
+	}
+	if(!(flags&RFFDG))
+		cloneflag |= CLONE_FILES;
+	else
+		flags &= ~RFFDG;
+	if(!(flags&RFNOWAIT))
+		cloneflag |= SIGCHLD;
+	else
+		flags &= ~RFNOWAIT;
+	if(flags){
+		fprint(2, "unknown rfork flags %x\n", flags);
+		return -1;
+	}
+	pid = clone((int(*)(void*))fn, p+fforkstacksize-16, cloneflag, arg);
+	if(pid < 0)
+		free(p);
+	return pid;
+}
+
diff --git a/src/lib9/getcallerpc-386.c b/src/lib9/getcallerpc-386.c
new file mode 100644
index 0000000..1367370
--- /dev/null
+++ b/src/lib9/getcallerpc-386.c
@@ -0,0 +1,7 @@
+#include <lib9.h>
+
+ulong
+getcallerpc(void *x)
+{
+	return (((ulong*)(x))[-1]);
+}
diff --git a/src/lib9/getfields.c b/src/lib9/getfields.c
new file mode 100644
index 0000000..79f7aba
--- /dev/null
+++ b/src/lib9/getfields.c
@@ -0,0 +1,36 @@
+#include <lib9.h>
+
+int
+getfields(char *str, char **args, int max, int mflag, char *set)
+{
+	Rune r;
+	int nr, intok, narg;
+
+	if(max <= 0)
+		return 0;
+
+	narg = 0;
+	args[narg] = str;
+	if(!mflag)
+		narg++;
+	intok = 0;
+	for(;; str += nr) {
+		nr = chartorune(&r, str);
+		if(r == 0)
+			break;
+		if(utfrune(set, r)) {
+			if(narg >= max)
+				break;
+			*str = 0;
+			intok = 0;
+			args[narg] = str + nr;
+			if(!mflag)
+				narg++;
+		} else {
+			if(!intok && mflag)
+				narg++;
+			intok = 1;
+		}
+	}
+	return narg;
+}
diff --git a/src/lib9/lib9.h b/src/lib9/lib9.h
new file mode 100644
index 0000000..bb7e540
--- /dev/null
+++ b/src/lib9/lib9.h
@@ -0,0 +1,246 @@
+/*
+ * Lib9 is miscellany from the Plan 9 C library that doesn't
+ * fit into libutf or into libfmt, but is still missing from traditional
+ * Unix C libraries.
+ */
+#ifndef _LIB9H_
+#define _LIB9H_ 1
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+                                                                                
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#ifndef _FMTH_
+#	include <fmt.h>
+#endif
+
+#define	nil	((void*)0)
+#define	nelem(x)	(sizeof(x)/sizeof((x)[0]))
+
+#define _NEEDUCHAR 1
+#define _NEEDUSHORT 1
+#define _NEEDUINT 1
+#define _NEEDULONG 1
+
+#if defined(__linux__)
+#	include <sys/types.h>
+#	if defined(__USE_MISC)
+#		undef _NEEDUSHORT
+#		undef _NEEDUINT
+#		undef _NEEDULONG
+#	endif
+#endif
+#if defined(__FreeBSD__)
+#	include <sys/types.h>
+#	if !defined(_POSIX_SOURCE)
+#		undef _NEEDUSHORT
+#		undef _NEEDUINT
+#	endif
+#endif
+
+typedef signed char schar;
+typedef unsigned int u32int;
+#ifdef _NEEDUCHAR
+	typedef unsigned char uchar;
+#endif
+#ifdef _NEEDUSHORT
+	typedef unsigned short ushort;
+#endif
+#ifdef _NEEDUINT
+	typedef unsigned int uint;
+#endif
+#ifdef _NEEDULONG
+	typedef unsigned long ulong;
+#endif
+typedef unsigned long long uvlong;
+typedef long long vlong;
+
+/* rfork to create new process running fn(arg) */
+
+#if defined(__FreeBSD__)
+#undef RFFDG
+#undef RFNOTEG
+#undef RFPROC
+#undef RFMEM
+#undef RFNOWAIT
+#undef RFCFDG
+#endif
+
+enum
+{
+/*	RFNAMEG		= (1<<0), */
+/*	RFENVG		= (1<<1), */
+	RFFDG		= (1<<2),
+	RFNOTEG		= (1<<3),
+	RFPROC		= (1<<4),
+	RFMEM		= (1<<5),
+	RFNOWAIT	= (1<<6),
+/*	RFCNAMEG	= (1<<10), */
+/*	RFCENVG		= (1<<11), */
+	RFCFDG		= (1<<12),
+/*	RFREND		= (1<<13), */
+/*	RFNOMNT		= (1<<14) */
+};
+extern int		ffork(int, void(*)(void*), void*);
+
+/* wait for processes */
+#define wait _p9wait
+typedef struct Waitmsg Waitmsg;
+struct Waitmsg
+{
+	int pid;	/* of loved one */
+	ulong time[3];	/* of loved one & descendants */
+	char	*msg;
+};
+extern	int		await(char*, int);
+extern	Waitmsg*	wait(void);
+
+/* synchronization */
+typedef struct Lock Lock;
+struct Lock
+{
+	int val;
+};
+
+extern int		_tas(void*);
+extern void	lock(Lock*);
+extern void	unlock(Lock*);
+extern int		canlock(Lock*);
+
+typedef struct QLp QLp;
+struct QLp
+{
+	int		inuse;
+	QLp		*next;
+	int		state;
+};
+
+typedef struct QLock QLock;
+struct QLock
+{
+	Lock	lock;
+	int		locked;
+	QLp		*head;
+	QLp		*tail;
+};
+
+extern void	qlock(QLock*);
+extern void	qunlock(QLock*);
+extern int		canqlock(QLock*);
+extern void	_qlockinit(ulong (*)(ulong, ulong));
+
+typedef struct RWLock RWLock;
+struct RWLock
+{
+	Lock	lock;
+	int		readers;
+	int		writer;
+	QLp		*head;
+	QLp		*tail;
+};
+
+extern void	rlock(RWLock*);
+extern void	runlock(RWLock*);
+extern int		canrlock(RWLock*);
+extern void	wlock(RWLock*);
+extern void	wunlock(RWLock*);
+extern int		canwlock(RWLock*);
+
+typedef struct Rendez Rendez;
+struct Rendez
+{
+	QLock	*l;
+	QLp		*head;
+	QLp		*tail;
+};
+
+extern void	rsleep(Rendez*);
+extern int		rwakeup(Rendez*);
+extern int		rwakeupall(Rendez*);
+
+extern ulong	rendezvous(ulong, ulong);
+
+/* one of a kind */
+extern void	sysfatal(char*, ...);
+extern int		nrand(int);
+extern void	setmalloctag(void*, ulong);
+extern void	setrealloctag(void*, ulong);
+extern void	*mallocz(ulong, int);
+extern long	readn(int, void*, long);
+extern void	exits(char*);
+extern void	_exits(char*);
+extern ulong	getcallerpc(void*);
+
+/* string routines */
+extern char*	strecpy(char*, char*, char*);
+extern int		tokenize(char*, char**, int);
+extern int		cistrncmp(char*, char*, int);
+extern int		cistrcmp(char*, char*);
+extern char*	cistrstr(char*, char*);
+extern int		getfields(char*, char**, int, int, char*);
+extern int		gettokens(char *, char **, int, char *);
+
+/* formatting helpers */
+extern int		dec64(uchar*, int, char*, int);
+extern int		enc64(char*, int, uchar*, int);
+extern int		dec32(uchar*, int, char*, int);
+extern int		enc32(char*, int, uchar*, int);
+extern int		dec16(uchar*, int, char*, int);
+extern int		enc16(char*, int, uchar*, int);
+extern int		encodefmt(Fmt*);
+
+/* error string */
+enum
+{
+	ERRMAX = 128
+};
+extern void	rerrstr(char*, uint);
+extern void	werrstr(char*, ...);
+extern int		errstr(char*, uint);
+
+/* compiler directives on plan 9 */
+#define	USED(x)	if(x){}else{}
+#define	SET(x)	((x)=0)
+
+/* command line */
+extern char	*argv0;
+#define	ARGBEGIN	for((argv0||(argv0=*argv)),argv++,argc--;\
+			    argv[0] && argv[0][0]=='-' && argv[0][1];\
+			    argc--, argv++) {\
+				char *_args, *_argt;\
+				Rune _argc;\
+				_args = &argv[0][1];\
+				if(_args[0]=='-' && _args[1]==0){\
+					argc--; argv++; break;\
+				}\
+				_argc = 0;\
+				while(*_args && (_args += chartorune(&_argc, _args)))\
+				switch(_argc)
+#define	ARGEND		SET(_argt);USED(_argt);USED(_argc);USED(_args);}USED(argv);USED(argc);
+#define	ARGF()		(_argt=_args, _args="",\
+				(*_argt? _argt: argv[1]? (argc--, *++argv): 0))
+#define	EARGF(x)	(_argt=_args, _args="",\
+				(*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0)))
+
+#define	ARGC()		_argc
+
+#define OREAD O_RDONLY
+#define OWRITE O_WRONLY
+#define AEXIST 0
+#define AREAD 4
+#define AWRITE 2
+#define AEXEC 1
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif	/* _LIB9H_ */
diff --git a/src/lib9/lock.c b/src/lib9/lock.c
new file mode 100644
index 0000000..2da7362
--- /dev/null
+++ b/src/lib9/lock.c
@@ -0,0 +1,54 @@
+#include <unistd.h>
+#include <sched.h>
+#include <lib9.h>
+
+int _ntas;
+static int
+_xtas(void *v)
+{
+	int x;
+
+_ntas++;
+	x = _tas(v);
+	if(x == 0 || x == 0xCAFEBABE)
+		return x;
+	fprint(2, "%d: tas %p got %ux\n", getpid(), v, x);
+	abort();
+}
+
+int
+canlock(Lock *l)
+{
+	return !_xtas(&l->val);
+}
+
+void
+unlock(Lock *l)
+{
+	l->val = 0;
+}
+
+void
+lock(Lock *lk)
+{
+	int i;
+
+	/* once fast */
+	if(!_xtas(&lk->val))
+		return;
+	/* a thousand times pretty fast */
+	for(i=0; i<1000; i++){
+		if(!_xtas(&lk->val))
+			return;
+		sched_yield();
+	}
+	/* now nice and slow */
+	for(i=0; i<1000; i++){
+		if(!_xtas(&lk->val))
+			return;
+		usleep(100*1000);
+	}
+	/* take your time */
+	while(_xtas(&lk->val))
+		usleep(1000*1000);
+}
diff --git a/src/lib9/malloctag.c b/src/lib9/malloctag.c
new file mode 100644
index 0000000..e5682bc
--- /dev/null
+++ b/src/lib9/malloctag.c
@@ -0,0 +1,15 @@
+#include <lib9.h>
+
+void
+setmalloctag(void *v, ulong t)
+{
+	USED(v);
+	USED(t);
+}
+
+void
+setrealloctag(void *v, ulong t)
+{
+	USED(v);
+	USED(t);
+}
diff --git a/src/lib9/mallocz.c b/src/lib9/mallocz.c
new file mode 100644
index 0000000..c631300
--- /dev/null
+++ b/src/lib9/mallocz.c
@@ -0,0 +1,14 @@
+#include <unistd.h>
+#include <string.h>
+#include <lib9.h>
+
+void*
+mallocz(unsigned long n, int clr)
+{
+	void *v;
+
+	v = malloc(n);
+	if(clr && v)
+		memset(v, 0, n);
+	return v;
+}
diff --git a/src/lib9/mkfile b/src/lib9/mkfile
new file mode 100644
index 0000000..703f6b0
--- /dev/null
+++ b/src/lib9/mkfile
@@ -0,0 +1,2 @@
+<../libutf/mkfile
+
diff --git a/src/lib9/nrand.c b/src/lib9/nrand.c
new file mode 100644
index 0000000..cf9c17c
--- /dev/null
+++ b/src/lib9/nrand.c
@@ -0,0 +1,17 @@
+#include <lib9.h>
+
+#define	MASK	0x7fffffffL
+
+int
+nrand(int n)
+{
+	long slop, v;
+
+	if(n < 0)
+		return n;
+	slop = MASK % n;
+	do
+		v = lrand();
+	while(v <= slop);
+	return v % n;
+}
diff --git a/src/lib9/qlock.c b/src/lib9/qlock.c
new file mode 100644
index 0000000..55a1846
--- /dev/null
+++ b/src/lib9/qlock.c
@@ -0,0 +1,360 @@
+#include <lib9.h>
+
+static struct {
+	QLp	*p;
+	QLp	x[1024];
+} ql = {
+	ql.x
+};
+
+enum
+{
+	Queuing,
+	QueuingR,
+	QueuingW,
+	Sleeping,
+};
+
+static ulong	(*_rendezvousp)(ulong, ulong) = rendezvous;
+
+/* this gets called by the thread library ONLY to get us to use its rendezvous */
+void
+_qlockinit(ulong (*r)(ulong, ulong))
+{
+	_rendezvousp = r;
+}
+
+/* find a free shared memory location to queue ourselves in */
+static QLp*
+getqlp(void)
+{
+	QLp *p, *op;
+
+	op = ql.p;
+	for(p = op+1; ; p++){
+		if(p == &ql.x[nelem(ql.x)])
+			p = ql.x;
+		if(p == op)
+			abort();
+		if(_tas(&(p->inuse)) == 0){
+			ql.p = p;
+			p->next = nil;
+			break;
+		}
+	}
+	return p;
+}
+
+void
+qlock(QLock *q)
+{
+	QLp *p, *mp;
+
+	lock(&q->lock);
+	if(!q->locked){
+		q->locked = 1;
+		unlock(&q->lock);
+		return;
+	}
+
+
+	/* chain into waiting list */
+	mp = getqlp();
+	p = q->tail;
+	if(p == nil)
+		q->head = mp;
+	else
+		p->next = mp;
+	q->tail = mp;
+	mp->state = Queuing;
+	unlock(&q->lock);
+
+	/* wait */
+	while((*_rendezvousp)((ulong)mp, 1) == ~0)
+		;
+	mp->inuse = 0;
+}
+
+void
+qunlock(QLock *q)
+{
+	QLp *p;
+
+	lock(&q->lock);
+	p = q->head;
+	if(p != nil){
+		/* wakeup head waiting process */
+		q->head = p->next;
+		if(q->head == nil)
+			q->tail = nil;
+		unlock(&q->lock);
+		while((*_rendezvousp)((ulong)p, 0x12345) == ~0)
+			;
+		return;
+	}
+	q->locked = 0;
+	unlock(&q->lock);
+}
+
+int
+canqlock(QLock *q)
+{
+	if(!canlock(&q->lock))
+		return 0;
+	if(!q->locked){
+		q->locked = 1;
+		unlock(&q->lock);
+		return 1;
+	}
+	unlock(&q->lock);
+	return 0;
+}
+
+void
+rlock(RWLock *q)
+{
+	QLp *p, *mp;
+
+	lock(&q->lock);
+	if(q->writer == 0 && q->head == nil){
+		/* no writer, go for it */
+		q->readers++;
+		unlock(&q->lock);
+		return;
+	}
+
+	mp = getqlp();
+	p = q->tail;
+	if(p == 0)
+		q->head = mp;
+	else
+		p->next = mp;
+	q->tail = mp;
+	mp->next = nil;
+	mp->state = QueuingR;
+	unlock(&q->lock);
+
+	/* wait in kernel */
+	while((*_rendezvousp)((ulong)mp, 1) == ~0)
+		;
+	mp->inuse = 0;
+}
+
+int
+canrlock(RWLock *q)
+{
+	lock(&q->lock);
+	if (q->writer == 0 && q->head == nil) {
+		/* no writer; go for it */
+		q->readers++;
+		unlock(&q->lock);
+		return 1;
+	}
+	unlock(&q->lock);
+	return 0;
+}
+
+void
+runlock(RWLock *q)
+{
+	QLp *p;
+
+	lock(&q->lock);
+	if(q->readers <= 0)
+		abort();
+	p = q->head;
+	if(--(q->readers) > 0 || p == nil){
+		unlock(&q->lock);
+		return;
+	}
+
+	/* start waiting writer */
+	if(p->state != QueuingW)
+		abort();
+	q->head = p->next;
+	if(q->head == 0)
+		q->tail = 0;
+	q->writer = 1;
+	unlock(&q->lock);
+
+	/* wakeup waiter */
+	while((*_rendezvousp)((ulong)p, 0) == ~0)
+		;
+}
+
+void
+wlock(RWLock *q)
+{
+	QLp *p, *mp;
+
+	lock(&q->lock);
+	if(q->readers == 0 && q->writer == 0){
+		/* noone waiting, go for it */
+		q->writer = 1;
+		unlock(&q->lock);
+		return;
+	}
+
+	/* wait */
+	p = q->tail;
+	mp = getqlp();
+	if(p == nil)
+		q->head = mp;
+	else
+		p->next = mp;
+	q->tail = mp;
+	mp->next = nil;
+	mp->state = QueuingW;
+	unlock(&q->lock);
+
+	/* wait in kernel */
+	while((*_rendezvousp)((ulong)mp, 1) == ~0)
+		;
+	mp->inuse = 0;
+}
+
+int
+canwlock(RWLock *q)
+{
+	lock(&q->lock);
+	if (q->readers == 0 && q->writer == 0) {
+		/* no one waiting; go for it */
+		q->writer = 1;
+		unlock(&q->lock);
+		return 1;
+	}
+	unlock(&q->lock);
+	return 0;
+}
+
+void
+wunlock(RWLock *q)
+{
+	QLp *p;
+
+	lock(&q->lock);
+	if(q->writer == 0)
+		abort();
+	p = q->head;
+	if(p == nil){
+		q->writer = 0;
+		unlock(&q->lock);
+		return;
+	}
+	if(p->state == QueuingW){
+		/* start waiting writer */
+		q->head = p->next;
+		if(q->head == nil)
+			q->tail = nil;
+		unlock(&q->lock);
+		while((*_rendezvousp)((ulong)p, 0) == ~0)
+			;
+		return;
+	}
+
+	if(p->state != QueuingR)
+		abort();
+
+	/* wake waiting readers */
+	while(q->head != nil && q->head->state == QueuingR){
+		p = q->head;
+		q->head = p->next;
+		q->readers++;
+		while((*_rendezvousp)((ulong)p, 0) == ~0)
+			;
+	}
+	if(q->head == nil)
+		q->tail = nil;
+	q->writer = 0;
+	unlock(&q->lock);
+}
+
+void
+rsleep(Rendez *r)
+{
+	QLp *t, *me;
+
+	if(!r->l)
+		abort();
+	lock(&r->l->lock);
+	/* we should hold the qlock */
+	if(!r->l->locked)
+		abort();
+
+	/* add ourselves to the wait list */
+	me = getqlp();
+	me->state = Sleeping;
+	if(r->head == nil)
+		r->head = me;
+	else
+		r->tail->next = me;
+	me->next = nil;
+	r->tail = me;
+
+	/* pass the qlock to the next guy */
+	t = r->l->head;
+	if(t){
+		r->l->head = t->next;
+		if(r->l->head == nil)
+			r->l->tail = nil;
+		unlock(&r->l->lock);
+		while((*_rendezvousp)((ulong)t, 0x12345) == ~0)
+			;
+	}else{
+		r->l->locked = 0;
+		unlock(&r->l->lock);
+	}
+
+	/* wait for a wakeup */
+	while((*_rendezvousp)((ulong)me, 0x23456) == ~0)
+		;
+	me->inuse = 0;
+	if(!r->l->locked)
+		abort();
+}
+
+int
+rwakeup(Rendez *r)
+{
+	QLp *t;
+
+	/*
+	 * take off wait and put on front of queue
+	 * put on front so guys that have been waiting will not get starved
+	 */
+	
+	if(!r->l)
+		abort();
+	lock(&r->l->lock);
+	if(!r->l->locked)
+		abort();
+
+	t = r->head;
+	if(t == nil){
+		unlock(&r->l->lock);
+		return 0;
+	}
+
+	r->head = t->next;
+	if(r->head == nil)
+		r->tail = nil;
+
+	t->next = r->l->head;
+	r->l->head = t;
+	if(r->l->tail == nil)
+		r->l->tail = t;
+
+	t->state = Queuing;
+	unlock(&r->l->lock);
+	return 1;
+}
+
+int
+rwakeupall(Rendez *r)
+{
+	int i;
+
+	for(i=0; rwakeup(r); i++)
+		;
+	return i;
+}
diff --git a/src/lib9/rand.c b/src/lib9/rand.c
new file mode 100644
index 0000000..34f77ec
--- /dev/null
+++ b/src/lib9/rand.c
@@ -0,0 +1,89 @@
+#include	<lib9.h>
+
+/*
+ *	algorithm by
+ *	D. P. Mitchell & J. A. Reeds
+ */
+
+#define	LEN	607
+#define	TAP	273
+#define	MASK	0x7fffffffL
+#define	A	48271
+#define	M	2147483647
+#define	Q	44488
+#define	R	3399
+#define	NORM	(1.0/(1.0+MASK))
+
+static	ulong	rng_vec[LEN];
+static	ulong*	rng_tap = rng_vec;
+static	ulong*	rng_feed = 0;
+static	Lock	lk;
+
+static void
+isrand(long seed)
+{
+	long lo, hi, x;
+	int i;
+
+	rng_tap = rng_vec;
+	rng_feed = rng_vec+LEN-TAP;
+	seed = seed%M;
+	if(seed < 0)
+		seed += M;
+	if(seed == 0)
+		seed = 89482311;
+	x = seed;
+	/*
+	 *	Initialize by x[n+1] = 48271 * x[n] mod (2**31 - 1)
+	 */
+	for(i = -20; i < LEN; i++) {
+		hi = x / Q;
+		lo = x % Q;
+		x = A*lo - R*hi;
+		if(x < 0)
+			x += M;
+		if(i >= 0)
+			rng_vec[i] = x;
+	}
+}
+
+void
+srand(long seed)
+{
+	lock(&lk);
+	isrand(seed);
+	unlock(&lk);
+}
+
+long
+lrand(void)
+{
+	ulong x;
+
+	lock(&lk);
+
+	rng_tap--;
+	if(rng_tap < rng_vec) {
+		if(rng_feed == 0) {
+			isrand(1);
+			rng_tap--;
+		}
+		rng_tap += LEN;
+	}
+	rng_feed--;
+	if(rng_feed < rng_vec)
+		rng_feed += LEN;
+	x = (*rng_feed + *rng_tap) & MASK;
+	*rng_feed = x;
+
+	unlock(&lk);
+
+	return x;
+}
+
+int
+rand(void)
+{
+	return lrand() & 0x7fff;
+}
+
diff --git a/src/lib9/readn.c b/src/lib9/readn.c
new file mode 100644
index 0000000..e7b9d13
--- /dev/null
+++ b/src/lib9/readn.c
@@ -0,0 +1,21 @@
+#include <lib9.h>
+
+long
+readn(int f, void *av, long n)
+{
+	char *a;
+	long m, t;
+
+	a = av;
+	t = 0;
+	while(t < n){
+		m = read(f, a+t, n-t);
+		if(m <= 0){
+			if(t == 0)
+				return m;
+			break;
+		}
+		t += m;
+	}
+	return t;
+}
diff --git a/src/lib9/rendez.c b/src/lib9/rendez.c
new file mode 100644
index 0000000..320bd11
--- /dev/null
+++ b/src/lib9/rendez.c
@@ -0,0 +1,180 @@
+/*
+     NAME
+          rendezvous - user level process synchronization
+
+     SYNOPSIS
+          ulong rendezvous(ulong tag, ulong value)
+
+     DESCRIPTION
+          The rendezvous system call allows two processes to synchro-
+          nize and exchange a value.  In conjunction with the shared
+          memory system calls (see segattach(2) and fork(2)), it
+          enables parallel programs to control their scheduling.
+
+          Two processes wishing to synchronize call rendezvous with a
+          common tag, typically an address in memory they share.  One
+          process will arrive at the rendezvous first; it suspends
+          execution until a second arrives.  When a second process
+          meets the rendezvous the value arguments are exchanged
+          between the processes and returned as the result of the
+          respective rendezvous system calls.  Both processes are
+          awakened when the rendezvous succeeds.
+
+          The set of tag values which two processes may use to
+          rendezvous-their tag space-is inherited when a process
+          forks, unless RFREND is set in the argument to rfork; see
+          fork(2).
+
+          If a rendezvous is interrupted the return value is ~0, so
+          that value should not be used in normal communication.
+
+ * This simulates rendezvous with shared memory, pause, and SIGUSR1.
+ */
+
+#include <signal.h>
+#include <lib9.h>
+
+enum
+{
+	VOUSHASH = 257,
+};
+
+typedef struct Vous Vous;
+struct Vous
+{
+	Vous *link;
+	Lock lk;
+	int pid;
+	ulong val;
+	ulong tag;
+};
+
+static void
+ign(int x)
+{
+	USED(x);
+}
+
+void /*__attribute__((constructor))*/
+ignusr1(void)
+{
+	signal(SIGUSR1, ign);
+}
+
+static Vous vouspool[2048];
+static int nvousused;
+static Vous *vousfree;
+static Vous *voushash[VOUSHASH];
+static Lock vouslock;
+
+static Vous*
+getvous(void)
+{
+	Vous *v;
+
+	if(vousfree){
+		v = vousfree;
+		vousfree = v->link;
+	}else if(nvousused < nelem(vouspool))
+		v = &vouspool[nvousused++];
+	else
+		abort();
+	return v;
+}
+
+static void
+putvous(Vous *v)
+{
+	lock(&vouslock);
+	v->link = vousfree;
+	vousfree = v;
+	unlock(&vouslock);
+}
+
+static Vous*
+findvous(ulong tag, ulong val, int pid)
+{
+	int h;
+	Vous *v, **l;
+
+	lock(&vouslock);
+	h = tag%VOUSHASH;
+	for(l=&voushash[h], v=*l; v; l=&(*l)->link, v=*l){
+		if(v->tag == tag){
+			*l = v->link;
+			unlock(&vouslock);
+			return v;
+		}
+	}
+	v = getvous();
+	v->pid = pid;
+	v->link = voushash[h];
+	v->val = val;
+	v->tag = tag;
+	lock(&v->lk);
+	voushash[h] = v;
+	unlock(&vouslock);
+	return v;
+}
+
+#define DBG 0
+ulong
+rendezvous(ulong tag, ulong val)
+{
+	int me, vpid;
+	ulong rval;
+	Vous *v;
+	sigset_t mask;
+
+	me = getpid();
+	v = findvous(tag, val, me);
+	if(v->pid == me){
+		if(DBG)fprint(2, "pid is %d tag %lux, sleeping\n", me, tag);
+		/*
+		 * No rendezvous partner was found; the next guy
+		 * through will find v and wake us, so we must go
+		 * to sleep.
+		 *
+		 * To go to sleep:
+		 *	1. disable USR1 signals.
+		 *	2. unlock v->lk (tells waker okay to signal us).
+		 *	3. atomically suspend and enable USR1 signals.
+		 *
+		 * The call to ignusr1() could be done once at 
+		 * process creation instead of every time through rendezvous.
+		 */
+		v->val = val;
+		ignusr1();
+		sigprocmask(SIG_SETMASK, NULL, &mask);
+		sigaddset(&mask, SIGUSR1);
+		sigprocmask(SIG_SETMASK, &mask, NULL);
+		sigdelset(&mask, SIGUSR1);
+		unlock(&v->lk);
+		sigsuspend(&mask);
+		rval = v->val;
+		if(DBG)fprint(2, "pid is %d, awake\n", me);
+		putvous(v);
+	}else{
+		/*
+		 * Found someone to meet.  Wake him:
+		 *
+		 *	A. lock v->lk (waits for him to get to his step 2)
+		 *	B. send a USR1
+		 *
+		 * He won't get the USR1 until he suspends, which
+		 * means it must wake him up (it can't get delivered
+		 * before he sleeps).
+		 */
+		vpid = v->pid;
+		lock(&v->lk);
+		rval = v->val;
+		v->val = val;
+		unlock(&v->lk);
+		if(kill(vpid, SIGUSR1) < 0){
+			if(DBG)fprint(2, "pid is %d, kill %d failed: %r\n", me, vpid);
+			abort();
+		}
+	}
+	return rval;
+}
+
diff --git a/src/lib9/strecpy.c b/src/lib9/strecpy.c
new file mode 100644
index 0000000..7d2f227
--- /dev/null
+++ b/src/lib9/strecpy.c
@@ -0,0 +1,16 @@
+#include <lib9.h>
+
+char*
+strecpy(char *to, char *e, char *from)
+{
+	if(to >= e)
+		return to;
+	to = memccpy(to, from, '\0', e - to);
+	if(to == nil){
+		to = e - 1;
+		*to = '\0';
+	}else{
+		to--;
+	}
+	return to;
+}
diff --git a/src/lib9/sysfatal.c b/src/lib9/sysfatal.c
new file mode 100644
index 0000000..f9ab698
--- /dev/null
+++ b/src/lib9/sysfatal.c
@@ -0,0 +1,20 @@
+#include <lib9.h>
+
+void (*_sysfatal)(char*, ...);
+
+void
+sysfatal(char *fmt, ...)
+{
+	char buf[256];
+	va_list arg;
+
+	va_start(arg, fmt);
+	if(_sysfatal)
+		(*_sysfatal)(fmt, arg);
+	vseprint(buf, buf+sizeof buf, fmt, arg);
+	va_end(arg);
+
+	fprint(2, "%s; %s\n", argv0 ? argv0 : "<prog>", buf);
+	exits("fatal");
+}
+
diff --git a/src/lib9/tas-386.s b/src/lib9/tas-386.s
new file mode 100644
index 0000000..7a62d2d
--- /dev/null
+++ b/src/lib9/tas-386.s
@@ -0,0 +1,6 @@
+.globl _tas
+_tas:
+	movl $0xCAFEBABE, %eax
+	movl 4(%esp), %ecx
+	xchgl %eax, 0(%ecx)
+	ret
diff --git a/src/lib9/test.c b/src/lib9/test.c
new file mode 100644
index 0000000..3a358c6
--- /dev/null
+++ b/src/lib9/test.c
@@ -0,0 +1,8 @@
+#include <lib9.h>
+
+int
+main(int argc, char **argv)
+{
+	werrstr("hello world");
+	print("%r\n");
+}
diff --git a/src/lib9/testfork.c b/src/lib9/testfork.c
new file mode 100644
index 0000000..a5e6371
--- /dev/null
+++ b/src/lib9/testfork.c
@@ -0,0 +1,21 @@
+#include <lib9.h>
+
+void
+sayhi(void *v)
+{
+	USED(v);
+
+	print("hello from subproc\n");
+	print("rendez got %lu from main\n", rendezvous(0x1234, 1234));
+	exits(0);
+}
+
+int
+main(int argc, char **argv)
+{
+	print("hello from main\n");
+	ffork(RFMEM|RFPROC, sayhi, nil);
+
+	print("rendez got %lu from subproc\n", rendezvous(0x1234, 0));
+	exits(0);
+}
diff --git a/src/lib9/tokenize.c b/src/lib9/tokenize.c
new file mode 100644
index 0000000..6fa9fc7
--- /dev/null
+++ b/src/lib9/tokenize.c
@@ -0,0 +1,106 @@
+#include <lib9.h>
+
+static char qsep[] = " \t\r\n";
+
+static char*
+qtoken(char *s, char *sep)
+{
+	int quoting;
+	char *t;
+
+	quoting = 0;
+	t = s;	/* s is output string, t is input string */
+	while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
+		if(*t != '\''){
+			*s++ = *t++;
+			continue;
+		}
+		/* *t is a quote */
+		if(!quoting){
+			quoting = 1;
+			t++;
+			continue;
+		}
+		/* quoting and we're on a quote */
+		if(t[1] != '\''){
+			/* end of quoted section; absorb closing quote */
+			t++;
+			quoting = 0;
+			continue;
+		}
+		/* doubled quote; fold one quote into two */
+		t++;
+		*s++ = *t++;
+	}
+	if(*s != '\0'){
+		*s = '\0';
+		if(t == s)
+			t++;
+	}
+	return t;
+}
+
+static char*
+etoken(char *t, char *sep)
+{
+	int quoting;
+
+	/* move to end of next token */
+	quoting = 0;
+	while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
+		if(*t != '\''){
+			t++;
+			continue;
+		}
+		/* *t is a quote */
+		if(!quoting){
+			quoting = 1;
+			t++;
+			continue;
+		}
+		/* quoting and we're on a quote */
+		if(t[1] != '\''){
+			/* end of quoted section; absorb closing quote */
+			t++;
+			quoting = 0;
+			continue;
+		}
+		/* doubled quote; fold one quote into two */
+		t += 2;
+	}
+	return t;
+}
+
+int
+gettokens(char *s, char **args, int maxargs, char *sep)
+{
+	int nargs;
+
+	for(nargs=0; nargs<maxargs; nargs++){
+		while(*s!='\0' && utfrune(sep, *s)!=nil)
+			*s++ = '\0';
+		if(*s == '\0')
+			break;
+		args[nargs] = s;
+		s = etoken(s, sep);
+	}
+
+	return nargs;
+}
+
+int
+tokenize(char *s, char **args, int maxargs)
+{
+	int nargs;
+
+	for(nargs=0; nargs<maxargs; nargs++){
+		while(*s!='\0' && utfrune(qsep, *s)!=nil)
+			s++;
+		if(*s == '\0')
+			break;
+		args[nargs] = s;
+		s = qtoken(s, qsep);
+	}
+
+	return nargs;
+}
diff --git a/src/lib9/u16.c b/src/lib9/u16.c
new file mode 100644
index 0000000..d9f41e4
--- /dev/null
+++ b/src/lib9/u16.c
@@ -0,0 +1,52 @@
+#include <lib9.h>
+static char t16e[] = "0123456789ABCDEF";
+
+int
+dec16(uchar *out, int lim, char *in, int n)
+{
+	int c, w = 0, i = 0;
+	uchar *start = out;
+	uchar *eout = out + lim;
+
+	while(n-- > 0){
+		c = *in++;
+		if('0' <= c && c <= '9')
+			c = c - '0';
+		else if('a' <= c && c <= 'z')
+			c = c - 'a' + 10;
+		else if('A' <= c && c <= 'Z')
+			c = c - 'A' + 10;
+		else
+			continue;
+		w = (w<<4) + c;
+		i++;
+		if(i == 2){
+			if(out + 1 > eout)
+				goto exhausted;
+			*out++ = w;
+			w = 0;
+			i = 0;
+		}
+	}
+exhausted:
+	return out - start;
+}
+
+int
+enc16(char *out, int lim, uchar *in, int n)
+{
+	uint c;
+	char *eout = out + lim;
+	char *start = out;
+
+	while(n-- > 0){
+		c = *in++;
+		if(out + 2 >= eout)
+			goto exhausted;
+		*out++ = t16e[c>>4];
+		*out++ = t16e[c&0xf];
+	}
+exhausted:
+	*out = 0;
+	return out - start;
+}
diff --git a/src/lib9/u32.c b/src/lib9/u32.c
new file mode 100644
index 0000000..1eb0c6e
--- /dev/null
+++ b/src/lib9/u32.c
@@ -0,0 +1,109 @@
+#include <lib9.h>
+
+int
+dec32(uchar *dest, int ndest, char *src, int nsrc)
+{
+	char *s, *tab;
+	uchar *start;
+	int i, u[8];
+
+	if(ndest+1 < (5*nsrc+7)/8)
+		return -1;
+	start = dest;
+	tab = "23456789abcdefghijkmnpqrstuvwxyz";
+	while(nsrc>=8){
+		for(i=0; i<8; i++){
+			s = strchr(tab,(int)src[i]);
+			u[i] = s ? s-tab : 0;
+		}
+		*dest++ = (u[0]<<3) | (0x7 & (u[1]>>2));
+		*dest++ = ((0x3 & u[1])<<6) | (u[2]<<1) | (0x1 & (u[3]>>4));
+		*dest++ = ((0xf & u[3])<<4) | (0xf & (u[4]>>1));
+		*dest++ = ((0x1 & u[4])<<7) | (u[5]<<2) | (0x3 & (u[6]>>3));
+		*dest++ = ((0x7 & u[6])<<5) | u[7];
+		src  += 8;
+		nsrc -= 8;
+	}
+	if(nsrc > 0){
+		if(nsrc == 1 || nsrc == 3 || nsrc == 6)
+			return -1;
+		for(i=0; i<nsrc; i++){
+			s = strchr(tab,(int)src[i]);
+			u[i] = s ? s-tab : 0;
+		}
+		*dest++ = (u[0]<<3) | (0x7 & (u[1]>>2));
+		if(nsrc == 2)
+			goto out;
+		*dest++ = ((0x3 & u[1])<<6) | (u[2]<<1) | (0x1 & (u[3]>>4));
+		if(nsrc == 4)
+			goto out;
+		*dest++ = ((0xf & u[3])<<4) | (0xf & (u[4]>>1));
+		if(nsrc == 5)
+			goto out;
+		*dest++ = ((0x1 & u[4])<<7) | (u[5]<<2) | (0x3 & (u[6]>>3));
+	}
+out:
+	return dest-start;
+}
+
+int
+enc32(char *dest, int ndest, uchar *src, int nsrc)
+{
+	char *tab, *start;
+	int j;
+
+	if(ndest <= (8*nsrc+4)/5 )
+		return -1;
+	start = dest;
+	tab = "23456789abcdefghijkmnpqrstuvwxyz";
+	while(nsrc>=5){
+		j = (0x1f & (src[0]>>3));
+		*dest++ = tab[j];
+		j = (0x1c & (src[0]<<2)) | (0x03 & (src[1]>>6));
+		*dest++ = tab[j];
+		j = (0x1f & (src[1]>>1));
+		*dest++ = tab[j];
+		j = (0x10 & (src[1]<<4)) | (0x0f & (src[2]>>4));
+		*dest++ = tab[j];
+		j = (0x1e & (src[2]<<1)) | (0x01 & (src[3]>>7));
+		*dest++ = tab[j];
+		j = (0x1f & (src[3]>>2));
+		*dest++ = tab[j];
+		j = (0x18 & (src[3]<<3)) | (0x07 & (src[4]>>5));
+		*dest++ = tab[j];
+		j = (0x1f & (src[4]));
+		*dest++ = tab[j];
+		src  += 5;
+		nsrc -= 5;
+	}
+	if(nsrc){
+		j = (0x1f & (src[0]>>3));
+		*dest++ = tab[j];
+		j = (0x1c & (src[0]<<2));
+		if(nsrc == 1)
+			goto out;
+		j |= (0x03 & (src[1]>>6));
+		*dest++ = tab[j];
+		j = (0x1f & (src[1]>>1));
+		if(nsrc == 2)
+			goto out;
+		*dest++ = tab[j];
+		j = (0x10 & (src[1]<<4));
+		if(nsrc == 3)
+			goto out;
+		j |= (0x0f & (src[2]>>4));
+		*dest++ = tab[j];
+		j = (0x1e & (src[2]<<1));
+		if(nsrc == 4)
+			goto out;
+		j |= (0x01 & (src[3]>>7));
+		*dest++ = tab[j];
+		j = (0x1f & (src[3]>>2));
+		*dest++ = tab[j];
+		j = (0x18 & (src[3]<<3));
+out:
+		*dest++ = tab[j];
+	}
+	*dest = 0;
+	return dest-start;
+}
diff --git a/src/lib9/u64.c b/src/lib9/u64.c
new file mode 100644
index 0000000..a17bdf1
--- /dev/null
+++ b/src/lib9/u64.c
@@ -0,0 +1,126 @@
+#include <lib9.h>
+
+enum {
+	INVAL=	255
+};
+
+static uchar t64d[256] = {
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,   62,INVAL,INVAL,INVAL,   63,
+      52,   53,   54,   55,   56,   57,   58,   59,   60,   61,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,
+      15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,
+      41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,
+   INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL,INVAL
+};
+static char t64e[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+int
+dec64(uchar *out, int lim, char *in, int n)
+{
+	ulong b24;
+	uchar *start = out;
+	uchar *e = out + lim;
+	int i, c;
+
+	b24 = 0;
+	i = 0;
+	while(n-- > 0){
+ 
+		c = t64d[*(uchar*)in++];
+		if(c == INVAL)
+			continue;
+		switch(i){
+		case 0:
+			b24 = c<<18;
+			break;
+		case 1:
+			b24 |= c<<12;
+			break;
+		case 2:
+			b24 |= c<<6;
+			break;
+		case 3:
+			if(out + 3 > e)
+				goto exhausted;
+
+			b24 |= c;
+			*out++ = b24>>16;
+			*out++ = b24>>8;
+			*out++ = b24;
+			i = -1;
+			break;
+		}
+		i++;
+	}
+	switch(i){
+	case 2:
+		if(out + 1 > e)
+			goto exhausted;
+		*out++ = b24>>16;
+		break;
+	case 3:
+		if(out + 2 > e)
+			goto exhausted;
+		*out++ = b24>>16;
+		*out++ = b24>>8;
+		break;
+	}
+exhausted:
+	return out - start;
+}
+
+int
+enc64(char *out, int lim, uchar *in, int n)
+{
+	int i;
+	ulong b24;
+	char *start = out;
+	char *e = out + lim;
+
+	for(i = n/3; i > 0; i--){
+		b24 = (*in++)<<16;
+		b24 |= (*in++)<<8;
+		b24 |= *in++;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = t64e[(b24>>18)];
+		*out++ = t64e[(b24>>12)&0x3f];
+		*out++ = t64e[(b24>>6)&0x3f];
+		*out++ = t64e[(b24)&0x3f];
+	}
+
+	switch(n%3){
+	case 2:
+		b24 = (*in++)<<16;
+		b24 |= (*in)<<8;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = t64e[(b24>>18)];
+		*out++ = t64e[(b24>>12)&0x3f];
+		*out++ = t64e[(b24>>6)&0x3f];
+		*out++ = '=';
+		break;
+	case 1:
+		b24 = (*in)<<16;
+		if(out + 4 >= e)
+			goto exhausted;
+		*out++ = t64e[(b24>>18)];
+		*out++ = t64e[(b24>>12)&0x3f];
+		*out++ = '=';
+		*out++ = '=';
+		break;
+	}
+exhausted:
+	*out = 0;
+	return out - start;
+}
diff --git a/src/lib9/wait.c b/src/lib9/wait.c
new file mode 100644
index 0000000..14af715
--- /dev/null
+++ b/src/lib9/wait.c
@@ -0,0 +1,30 @@
+#include <lib9.h>
+
+Waitmsg*
+wait(void)
+{
+	int n, l;
+	char buf[512], *fld[5];
+	Waitmsg *w;
+
+	n = await(buf, sizeof buf-1);
+	if(n < 0)
+		return nil;
+	buf[n] = '\0';
+	if(tokenize(buf, fld, nelem(fld)) != nelem(fld)){
+		werrstr("couldn't parse wait message");
+		return nil;
+	}
+	l = strlen(fld[4])+1;
+	w = malloc(sizeof(Waitmsg)+l);
+	if(w == nil)
+		return nil;
+	w->pid = atoi(fld[0]);
+	w->time[0] = atoi(fld[1]);
+	w->time[1] = atoi(fld[2]);
+	w->time[2] = atoi(fld[3]);
+	w->msg = (char*)&w[1];
+	memmove(w->msg, fld[4], l);
+	return w;
+}
+
diff --git a/src/lib9/werrstr.c b/src/lib9/werrstr.c
new file mode 100644
index 0000000..7fa1f2e
--- /dev/null
+++ b/src/lib9/werrstr.c
@@ -0,0 +1,13 @@
+#include <lib9.h>
+
+void
+werrstr(char *fmt, ...)
+{
+	va_list arg;
+	char buf[ERRMAX];
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+ERRMAX, fmt, arg);
+	va_end(arg);
+	errstr(buf, ERRMAX);
+}