Initial revision
diff --git a/src/cmd/mk/LICENSE b/src/cmd/mk/LICENSE
new file mode 100644
index 0000000..a5d7d87
--- /dev/null
+++ b/src/cmd/mk/LICENSE
@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+   to redistribute (other than with the Plan 9 Operating System)
+   the screen imprinter fonts identified in subdirectory
+   /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+   Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+   Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+   These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+   are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+   covered by the Aladdin Free Public License, reproduced in the file
+   /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+  a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+     Program, and
+  b. in the case of each Contributor,
+
+     i. changes to the Program, and
+    ii. additions to the Program;
+
+    where such changes and/or additions to the Program were added to the
+    Program by such Contributor itself or anyone acting on such
+    Contributor's behalf, and the Contributor explicitly consents, in
+    accordance with Section 3C, to characterization of the changes and/or
+    additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free copyright
+    license to reproduce, prepare derivative works of, publicly display,
+    publicly perform, distribute and sublicense the Contribution of such
+    Contributor, if any, and such derivative works, in source code and
+    object code form.
+    
+ b. Subject to the terms of this Agreement, each Contributor hereby
+    grants Recipient a non-exclusive, worldwide, royalty-free patent
+    license under Licensed Patents to make, use, sell, offer to sell,
+    import and otherwise transfer the Contribution of such Contributor, if
+    any, in source code and object code form. The patent license granted
+    by a Contributor shall also apply to the combination of the
+    Contribution of that Contributor and the Program if, at the time the
+    Contribution is added by the Contributor, such addition of the
+    Contribution causes such combination to be covered by the Licensed
+    Patents. The patent license granted by a Contributor shall not apply
+    to (i) any other combinations which include the Contribution, nor to
+    (ii) Contributions of other Contributors. No hardware per se is
+    licensed hereunder.
+    
+ c. Recipient understands that although each Contributor grants the
+    licenses to its Contributions set forth herein, no assurances are
+    provided by any Contributor that the Program does not infringe the
+    patent or other intellectual property rights of any other entity. Each
+    Contributor disclaims any liability to Recipient for claims brought by
+    any other entity based on infringement of intellectual property rights
+    or otherwise. As a condition to exercising the rights and licenses
+    granted hereunder, each Recipient hereby assumes sole responsibility
+    to secure any other intellectual property rights needed, if any. For
+    example, if a third party patent license is required to allow
+    Recipient to distribute the Program, it is Recipient's responsibility
+    to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+    copyright rights in its Contribution, if any, to grant the copyright
+    license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+    form, a copy of this Agreement or Distributor's own license agreement
+    is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+    license agreement:
+
+      i. effectively disclaims on behalf of all Contributors all warranties
+         and conditions, express and implied, including warranties or
+         conditions of title and non-infringement, and implied warranties or
+         conditions of merchantability and fitness for a particular purpose;
+     ii. effectively excludes on behalf of all Contributors all liability
+         for damages, including direct, indirect, special, incidental and
+         consequential damages, such as lost profits; and
+    iii. states that any provisions which differ from this Agreement are
+         offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+   location in the Program:
+
+   Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+   Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/cmd/mk/Make.FreeBSD-386 b/src/cmd/mk/Make.FreeBSD-386
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/cmd/mk/Make.FreeBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.HP-UX-9000 b/src/cmd/mk/Make.HP-UX-9000
new file mode 100644
index 0000000..edbdc11
--- /dev/null
+++ b/src/cmd/mk/Make.HP-UX-9000
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.Linux-386 b/src/cmd/mk/Make.Linux-386
new file mode 100644
index 0000000..74b0252
--- /dev/null
+++ b/src/cmd/mk/Make.Linux-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.OSF1-alpha b/src/cmd/mk/Make.OSF1-alpha
new file mode 100644
index 0000000..3d45279
--- /dev/null
+++ b/src/cmd/mk/Make.OSF1-alpha
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u b/src/cmd/mk/Make.SunOS-sun4u
new file mode 100644
index 0000000..c5fe67b
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u
@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u-cc b/src/cmd/mk/Make.SunOS-sun4u-cc
new file mode 100644
index 0000000..829301d
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u-cc
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u-gcc b/src/cmd/mk/Make.SunOS-sun4u-gcc
new file mode 100644
index 0000000..5c41594
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u-gcc
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Makefile b/src/cmd/mk/Makefile
new file mode 100644
index 0000000..df21b60
--- /dev/null
+++ b/src/cmd/mk/Makefile
@@ -0,0 +1,117 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+TARG=mk
+VERSION=2.0
+PORTPLACE=devel/mk
+NAME=mk
+
+OFILES=\
+	arc.$O\
+	archive.$O\
+	bufblock.$O\
+	env.$O\
+	file.$O\
+	graph.$O\
+	job.$O\
+	lex.$O\
+	main.$O\
+	match.$O\
+	mk.$O\
+	parse.$O\
+	recipe.$O\
+	rule.$O\
+	run.$O\
+	sh.$O\
+	shprint.$O\
+	symtab.$O\
+	var.$O\
+	varsub.$O\
+	word.$O\
+	unix.$O\
+
+HFILES=\
+	mk.h\
+	fns.h\
+
+all: $(TARG)
+
+TGZFILES+=mk.pdf
+
+install: $(LIB)
+	test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
+	test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
+	install -m 0755 mk $(PREFIX)/bin/mk
+	cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
+	install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
+	install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
+
+
+$(TARG): $(OFILES)
+	$(CC) -o $(TARG) $(OFILES) -L$(PREFIX)/lib -lregexp9 -lbio -lfmt -lutf
+
+
+.c.$O:
+	$(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+	$(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+	rm -rf $(NAME)-$(VERSION)
+	mkdir $(NAME)-$(VERSION)
+	cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+	tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+	rm -rf $(NAME)-$(VERSION)
+
+clean:
+	rm -f $(OFILES) $(LIB)
+
+nuke:
+	rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+	make tgz
+	cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+	rpm -ba rpm.spec
+	cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+	cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+	scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+	make tgz
+	rm -rf $(PORTDIR)
+	mkdir $(PORTDIR)
+	cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+	cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+	(cd $(PORTDIR); make makesum)
+	(cd $(PORTDIR); make)
+	(cd $(PORTDIR); /usr/local/bin/portlint)
+	rm -rf $(PORTDIR)/work
+	shar `find $(PORTDIR)` > ports.shar
+	(cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+	scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/cmd/mk/Makefile.MID b/src/cmd/mk/Makefile.MID
new file mode 100644
index 0000000..f840b18
--- /dev/null
+++ b/src/cmd/mk/Makefile.MID
@@ -0,0 +1,45 @@
+TARG=mk
+VERSION=2.0
+PORTPLACE=devel/mk
+NAME=mk
+
+OFILES=\
+	arc.$O\
+	archive.$O\
+	bufblock.$O\
+	env.$O\
+	file.$O\
+	graph.$O\
+	job.$O\
+	lex.$O\
+	main.$O\
+	match.$O\
+	mk.$O\
+	parse.$O\
+	recipe.$O\
+	rule.$O\
+	run.$O\
+	sh.$O\
+	shprint.$O\
+	symtab.$O\
+	var.$O\
+	varsub.$O\
+	word.$O\
+	unix.$O\
+
+HFILES=\
+	mk.h\
+	fns.h\
+
+all: $(TARG)
+
+TGZFILES+=mk.pdf
+
+install: $(LIB)
+	test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
+	test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
+	install -m 0755 mk $(PREFIX)/bin/mk
+	cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
+	install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
+	install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
+
diff --git a/src/cmd/mk/arc.c b/src/cmd/mk/arc.c
new file mode 100644
index 0000000..521ef7a
--- /dev/null
+++ b/src/cmd/mk/arc.c
@@ -0,0 +1,52 @@
+#include	"mk.h"
+
+Arc *
+newarc(Node *n, Rule *r, char *stem, Resub *match)
+{
+	Arc *a;
+
+	a = (Arc *)Malloc(sizeof(Arc));
+	a->n = n;
+	a->r = r;
+	a->stem = strdup(stem);
+	rcopy(a->match, match, NREGEXP);
+	a->next = 0;
+	a->flag = 0;
+	a->prog = r->prog;
+	return(a);
+}
+
+void
+dumpa(char *s, Arc *a)
+{
+	char buf[1024];
+
+	Bprint(&bout, "%sArc@%p: n=%p r=%p flag=0x%x stem='%s'",
+		s, a, a->n, a->r, a->flag, a->stem);
+	if(a->prog)
+		Bprint(&bout, " prog='%s'", a->prog);
+	Bprint(&bout, "\n");
+
+	if(a->n){
+		snprint(buf, sizeof(buf), "%s    ", (*s == ' ')? s:"");
+		dumpn(buf, a->n);
+	}
+}
+
+void
+nrep(void)
+{
+	Symtab *sym;
+	Word *w;
+
+	sym = symlook("NREP", S_VAR, 0);
+	if(sym){
+		w = (Word *) sym->value;
+		if (w && w->s && *w->s)
+			nreps = atoi(w->s);
+	}
+	if(nreps < 1)
+		nreps = 1;
+	if(DEBUG(D_GRAPH))
+		Bprint(&bout, "nreps = %d\n", nreps);
+}
diff --git a/src/cmd/mk/archive.c b/src/cmd/mk/archive.c
new file mode 100644
index 0000000..9ba7a12
--- /dev/null
+++ b/src/cmd/mk/archive.c
@@ -0,0 +1,180 @@
+#include	"mk.h"
+#define	ARMAG	"!<arch>\n"
+#define	SARMAG	8
+
+#define	ARFMAG	"`\n"
+#define SARNAME	16
+
+struct	ar_hdr
+{
+	char	name[SARNAME];
+	char	date[12];
+	char	uid[6];
+	char	gid[6];
+	char	mode[8];
+	char	size[10];
+	char	fmag[2];
+};
+#define	SAR_HDR	(SARNAME+44)
+
+static int dolong;
+
+static void atimes(char *);
+static char *split(char*, char**);
+
+long
+atimeof(int force, char *name)
+{
+	Symtab *sym;
+	long t;
+	char *archive, *member, buf[512];
+
+	archive = split(name, &member);
+	if(archive == 0)
+		Exit();
+
+	t = mtime(archive);
+	sym = symlook(archive, S_AGG, 0);
+	if(sym){
+		if(force || (t > (long)sym->value)){
+			atimes(archive);
+			sym->value = (void *)t;
+		}
+	}
+	else{
+		atimes(archive);
+		/* mark the aggegate as having been done */
+		symlook(strdup(archive), S_AGG, "")->value = (void *)t;
+	}
+		/* truncate long member name to sizeof of name field in archive header */
+	if(dolong)
+		snprint(buf, sizeof(buf), "%s(%s)", archive, member);
+	else
+		snprint(buf, sizeof(buf), "%s(%.*s)", archive, SARNAME, member);
+	sym = symlook(buf, S_TIME, 0);
+	if (sym)
+		return (long)sym->value;	/* uggh */
+	return 0;
+}
+
+void
+atouch(char *name)
+{
+	char *archive, *member;
+	int fd, i;
+	struct ar_hdr h;
+	long t;
+
+	archive = split(name, &member);
+	if(archive == 0)
+		Exit();
+
+	fd = open(archive, ORDWR);
+	if(fd < 0){
+		fd = create(archive, OWRITE, 0666);
+		if(fd < 0){
+			fprint(2, "create %s: %r\n", archive);
+			Exit();
+		}
+		write(fd, ARMAG, SARMAG);
+	}
+	if(symlook(name, S_TIME, 0)){
+		/* hoon off and change it in situ */
+		LSEEK(fd, SARMAG, 0);
+		while(read(fd, (char *)&h, sizeof(h)) == sizeof(h)){
+			for(i = SARNAME-1; i > 0 && h.name[i] == ' '; i--)
+					;
+			h.name[i+1]=0;
+			if(strcmp(member, h.name) == 0){
+				t = SARNAME-sizeof(h);	/* ughgghh */
+				LSEEK(fd, t, 1);
+				fprint(fd, "%-12ld", time(0));
+				break;
+			}
+			t = atol(h.size);
+			if(t&01) t++;
+			LSEEK(fd, t, 1);
+		}
+	}
+	close(fd);
+}
+
+static void
+atimes(char *ar)
+{
+	struct ar_hdr h;
+	long t;
+	int fd, i;
+	char buf[BIGBLOCK];
+	char name[sizeof(h.name)+1];
+
+	fd = open(ar, OREAD);
+	if(fd < 0)
+		return;
+
+	if(read(fd, buf, SARMAG) != SARMAG){
+		close(fd);
+		return;
+	}
+	while(read(fd, (char *)&h, sizeof(h)) == sizeof(h)){
+		t = atol(h.date);
+		if(t == 0)	/* as it sometimes happens; thanks ken */
+			t = 1;
+		strncpy(name, h.name, sizeof(h.name));
+		for(i = sizeof(h.name)-1; i > 0 && name[i] == ' '; i--)
+				;
+		if(name[i] == '/')		/* system V bug */
+			i--;
+		name[i+1]=0;
+		sprint(buf, "%s(%s)", ar, h.size);
+		symlook(strdup(buf), S_TIME, (void *)t)->value = (void *)t;
+		t = atol(h.size);
+		if(t&01) t++;
+		LSEEK(fd, t, 1);
+	}
+	close(fd);
+}
+
+static int
+type(char *file)
+{
+	int fd;
+	char buf[SARMAG];
+
+	fd = open(file, OREAD);
+	if(fd < 0){
+		if(symlook(file, S_BITCH, 0) == 0){
+			Bprint(&bout, "%s doesn't exist: assuming it will be an archive\n", file);
+			symlook(file, S_BITCH, (void *)file);
+		}
+		return 1;
+	}
+	if(read(fd, buf, SARMAG) != SARMAG){
+		close(fd);
+		return 0;
+	}
+	close(fd);
+	return !strncmp(ARMAG, buf, SARMAG);
+}
+
+static char*
+split(char *name, char **member)
+{
+	char *p, *q;
+
+	p = strdup(name);
+	q = utfrune(p, '(');
+	if(q){
+		*q++ = 0;
+		if(member)
+			*member = q;
+		q = utfrune(q, ')');
+		if (q)
+			*q = 0;
+		if(type(p))
+			return p;
+		free(p);
+		fprint(2, "mk: '%s' is not an archive\n", name);
+	}
+	return 0;
+}
diff --git a/src/cmd/mk/bundle.ports b/src/cmd/mk/bundle.ports
new file mode 100644
index 0000000..4649831
--- /dev/null
+++ b/src/cmd/mk/bundle.ports
@@ -0,0 +1,46 @@
+--- Makefile ---
+# New ports collection makefile for: mk
+# Date Created:		11 Feb 2003
+# Whom:			rsc
+#
+# THIS LINE NEEDS REPLACING.  IT'S HERE TO GET BY PORTLINT
+# $FreeBSD: ports/devel/mk/Makefile,v 1.1 2003/02/12 00:51:22 rsc Exp $
+
+PORTNAME=	mk
+PORTVERSION=	2.0
+CATEGORIES=	devel
+MASTER_SITES=	http://pdos.lcs.mit.edu/~rsc/software/
+EXTRACT_SUFX=	.tgz
+
+MAINTAINER=	rsc@post.harvard.edu
+
+DEPENDS=	${PORTSDIR}/devel/libutf \
+	${PORTSDIR}/devel/libfmt \
+	${PORTSDIR}/devel/libbio \
+	${PORTSDIR}/devel/libregexp9
+
+MAN1=		mk.1
+USE_REINPLACE=	yes
+
+.include <bsd.port.pre.mk>
+
+post-patch:
+	${REINPLACE_CMD} -e 's,$$(PREFIX),${PREFIX},g' ${WRKSRC}/Makefile
+
+.include <bsd.port.post.mk>
+--- pkg-comment ---
+Streamlined replacement for make
+--- pkg-descr ---
+Mk is a streamlined replacement for make, written for 
+Tenth Edition Research Unix by Andrew Hume.
+
+WWW: http://pdos.lcs.mit.edu/~rsc/software/#mk
+
+Russ Cox
+rsc@post.harvard.edu
+--- pkg-plist ---
+bin/mk
+doc/mk.pdf
+--- /dev/null ---
+This is just a way to make sure blank lines don't
+creep into pkg-plist.
diff --git a/src/cmd/mk/env.c b/src/cmd/mk/env.c
new file mode 100644
index 0000000..c040db5
--- /dev/null
+++ b/src/cmd/mk/env.c
@@ -0,0 +1,149 @@
+#include	"mk.h"
+
+enum {
+	ENVQUANTA=10
+};
+
+Envy	*envy;
+static int nextv;
+
+static char	*myenv[] =
+{
+	"target",
+	"stem",
+	"prereq",
+	"pid",
+	"nproc",
+	"newprereq",
+	"alltarget",
+	"newmember",
+	"stem0",		/* must be in order from here */
+	"stem1",
+	"stem2",
+	"stem3",
+	"stem4",
+	"stem5",
+	"stem6",
+	"stem7",
+	"stem8",
+	"stem9",
+	0,
+};
+
+void
+initenv(void)
+{
+	char **p;
+
+	for(p = myenv; *p; p++)
+		symlook(*p, S_INTERNAL, (void *)"");
+	readenv();				/* o.s. dependent */
+}
+
+static void
+envinsert(char *name, Word *value)
+{
+	static int envsize;
+
+	if (nextv >= envsize) {
+		envsize += ENVQUANTA;
+		envy = (Envy *) Realloc((char *) envy, envsize*sizeof(Envy));
+	}
+	envy[nextv].name = name;
+	envy[nextv++].values = value;
+}
+
+static void
+envupd(char *name, Word *value)
+{
+	Envy *e;
+
+	for(e = envy; e->name; e++)
+		if(strcmp(name, e->name) == 0){
+			delword(e->values);
+			e->values = value;
+			return;
+		}
+	e->name = name;
+	e->values = value;
+	envinsert(0,0);
+}
+
+static void
+ecopy(Symtab *s)
+{
+	char **p;
+
+	if(symlook(s->name, S_NOEXPORT, 0))
+		return;
+	for(p = myenv; *p; p++)
+		if(strcmp(*p, s->name) == 0)
+			return;
+	envinsert(s->name, (Word *) s->value);
+}
+
+void
+execinit(void)
+{
+	char **p;
+
+	nextv = 0;
+	for(p = myenv; *p; p++)
+		envinsert(*p, stow(""));
+
+	symtraverse(S_VAR, ecopy);
+	envinsert(0, 0);
+}
+
+Envy*
+buildenv(Job *j, int slot)
+{
+	char **p, *cp, *qp;
+	Word *w, *v, **l;
+	int i;
+	char buf[256];
+
+	envupd("target", wdup(j->t));
+	if(j->r->attr&REGEXP)
+		envupd("stem",newword(""));
+	else
+		envupd("stem", newword(j->stem));
+	envupd("prereq", wdup(j->p));
+	sprint(buf, "%d", getpid());
+	envupd("pid", newword(buf));
+	sprint(buf, "%d", slot);
+	envupd("nproc", newword(buf));
+	envupd("newprereq", wdup(j->np));
+	envupd("alltarget", wdup(j->at));
+	l = &v;
+	v = w = wdup(j->np);
+	while(w){
+		cp = strchr(w->s, '(');
+		if(cp){
+			qp = strchr(cp+1, ')');
+			if(qp){
+				*qp = 0;
+				strcpy(w->s, cp+1);
+				l = &w->next;
+				w = w->next;
+				continue;
+			}
+		}
+		*l = w->next;
+		free(w->s);
+		free(w);
+		w = *l;
+	}
+	envupd("newmember", v);
+		/* update stem0 -> stem9 */
+	for(p = myenv; *p; p++)
+		if(strcmp(*p, "stem0") == 0)
+			break;
+	for(i = 0; *p; i++, p++){
+		if((j->r->attr&REGEXP) && j->match[i])
+			envupd(*p, newword(j->match[i]));
+		else 
+			envupd(*p, newword(""));
+	}
+	return envy;
+}
diff --git a/src/cmd/mk/file.c b/src/cmd/mk/file.c
new file mode 100644
index 0000000..2533343
--- /dev/null
+++ b/src/cmd/mk/file.c
@@ -0,0 +1,90 @@
+#include	"mk.h"
+
+/* table-driven version in bootes dump of 12/31/96 */
+
+long
+mtime(char *name)
+{
+	return mkmtime(name);
+}
+
+long
+timeof(char *name, int force)
+{
+	Symtab *sym;
+	long t;
+
+	if(utfrune(name, '('))
+		return atimeof(force, name);	/* archive */
+
+	if(force)
+		return mtime(name);
+
+
+	sym = symlook(name, S_TIME, 0);
+	if (sym)
+		return (long) sym->value;		/* uggh */
+
+	t = mtime(name);
+	if(t == 0)
+		return 0;
+
+	symlook(name, S_TIME, (void*)t);		/* install time in cache */
+	return t;
+}
+
+void
+touch(char *name)
+{
+	Bprint(&bout, "touch(%s)\n", name);
+	if(nflag)
+		return;
+
+	if(utfrune(name, '('))
+		atouch(name);		/* archive */
+	else if(chgtime(name) < 0) {
+		fprint(2, "%s: %r\n", name);
+		Exit();
+	}
+}
+
+void
+delete(char *name)
+{
+	if(utfrune(name, '(') == 0) {		/* file */
+		if(remove(name) < 0)
+			fprint(2, "remove %s: %r\n", name);
+	} else
+		fprint(2, "hoon off; mk can'tdelete archive members\n");
+}
+
+void
+timeinit(char *s)
+{
+	long t;
+	char *cp;
+	Rune r;
+	int c, n;
+
+	t = time(0);
+	while (*s) {
+		cp = s;
+		do{
+			n = chartorune(&r, s);
+			if (r == ' ' || r == ',' || r == '\n')
+				break;
+			s += n;
+		} while(*s);
+		c = *s;
+		*s = 0;
+		symlook(strdup(cp), S_TIME, (void *)t)->value = (void *)t;
+		if (c)
+			*s++ = c;
+		while(*s){
+			n = chartorune(&r, s);
+			if(r != ' ' && r != ',' && r != '\n')
+				break;
+			s += n;
+		}
+	}
+}
diff --git a/src/cmd/mk/fns.h b/src/cmd/mk/fns.h
new file mode 100644
index 0000000..2c9f46a
--- /dev/null
+++ b/src/cmd/mk/fns.h
@@ -0,0 +1,84 @@
+void	addrule(char*, Word*, char*, Word*, int, int, char*);
+void	addrules(Word*, Word*, char*, int, int, char*);
+void	addw(Word*, char*);
+void	assert(char*, int);
+int	assline(Biobuf *, Bufblock *);
+long	atimeof(int,char*);
+void	atouch(char*);
+void	bufcpy(Bufblock *, char *, int);
+Envy	*buildenv(Job*, int);
+void	catchnotes(void);
+char 	*charin(char *, char *);
+int	chgtime(char*);
+void	clrmade(Node*);
+char	*copyq(char*, Rune, Bufblock*);
+void	delete(char*);
+void	delword(Word*);
+int	dorecipe(Node*);
+void	dumpa(char*, Arc*);
+void	dumpj(char*, Job*, int);
+void	dumpn(char*, Node*);
+void	dumpr(char*, Rule*);
+void	dumpv(char*);
+void	dumpw(char*, Word*);
+int	escapetoken(Biobuf*, Bufblock*, int, int);
+void	execinit(void);
+int	execsh(char*, char*, Bufblock*, Envy*);
+void	Exit(void);
+char	*expandquote(char*, Rune, Bufblock*);
+void	expunge(int, char*);
+void	freebuf(Bufblock*);
+void	front(char*);
+Node	*graph(char*);
+void	growbuf(Bufblock *);
+void	initenv(void);
+void	insert(Bufblock *, int);
+void	ipop(void);
+void	ipush(void);
+void	killchildren(char*);
+void	*Malloc(int);
+char	*maketmp(int*);
+int	match(char*, char*, char*);
+char *membername(char*, int, char*);
+void	mk(char*);
+ulong	mkmtime(char*);
+long	mtime(char*);
+Arc	*newarc(Node*, Rule*, char*, Resub*);
+Bufblock *newbuf(void);
+Job	*newjob(Rule*, Node*, char*, char**, Word*, Word*, Word*, Word*);
+Word	*newword(char*);
+int	nextrune(Biobuf*, int);
+int	nextslot(void);
+void	nproc(void);
+void	nrep(void);
+int	outofdate(Node*, Arc*, int);
+void	parse(char*, int, int);
+int	pipecmd(char*, Envy*, int*);
+void	prusage(void);
+void	rcopy(char**, Resub*, int);
+void	readenv(void);
+void	*Realloc(void*, int);
+void	rinsert(Bufblock *, Rune);
+char	*rulecnt(void);
+void	run(Job*);
+void	setvar(char*, void*);
+char	*shname(char*);
+void	shprint(char*, Envy*, Bufblock*);
+Word	*stow(char*);
+void	subst(char*, char*, char*);
+void	symdel(char*, int);
+void	syminit(void);
+Symtab	*symlook(char*, int, void*);
+void	symstat(void);
+void	symtraverse(int, void(*)(Symtab*));
+void	timeinit(char*);
+long	timeof(char*, int);
+void	touch(char*);
+void	update(int, Node*);
+void	usage(void);
+Word	*varsub(char**);
+int	waitfor(char*);
+int	waitup(int, int*);
+Word	*wdup(Word*);
+int	work(Node*, Node*, Arc*);
+char	*wtos(Word*, int);
diff --git a/src/cmd/mk/graph.c b/src/cmd/mk/graph.c
new file mode 100644
index 0000000..5852925
--- /dev/null
+++ b/src/cmd/mk/graph.c
@@ -0,0 +1,279 @@
+#include	"mk.h"
+
+static Node *applyrules(char *, char *);
+static void togo(Node *);
+static int vacuous(Node *);
+static Node *newnode(char *);
+static void trace(char *, Arc *);
+static void cyclechk(Node *);
+static void ambiguous(Node *);
+static void attribute(Node *);
+
+Node *
+graph(char *target)
+{
+	Node *node;
+	char *cnt;
+
+	cnt = rulecnt();
+	node = applyrules(target, cnt);
+	free(cnt);
+	cyclechk(node);
+	node->flags |= PROBABLE;	/* make sure it doesn't get deleted */
+	vacuous(node);
+	ambiguous(node);
+	attribute(node);
+	return(node);
+}
+
+static Node *
+applyrules(char *target, char *cnt)
+{
+	Symtab *sym;
+	Node *node;
+	Rule *r;
+	Arc head, *a = &head;
+	Word *w;
+	char stem[NAMEBLOCK], buf[NAMEBLOCK];
+	Resub rmatch[NREGEXP];
+
+/*	print("applyrules(%lux='%s')\n", target, target);*//**/
+	sym = symlook(target, S_NODE, 0);
+	if(sym)
+		return (Node *)(sym->value);
+	target = strdup(target);
+	node = newnode(target);
+	head.n = 0;
+	head.next = 0;
+	sym = symlook(target, S_TARGET, 0);
+	memset((char*)rmatch, 0, sizeof(rmatch));
+	for(r = sym? (Rule *)(sym->value):0; r; r = r->chain){
+		if(r->attr&META) continue;
+		if(strcmp(target, r->target)) continue;
+		if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue;	/* no effect; ignore */
+		if(cnt[r->rule] >= nreps) continue;
+		cnt[r->rule]++;
+		node->flags |= PROBABLE;
+
+/*		if(r->attr&VIR)
+ *			node->flags |= VIRTUAL;
+ *		if(r->attr&NOREC)
+ *			node->flags |= NORECIPE;
+ *		if(r->attr&DEL)
+ *			node->flags |= DELETE;
+ */
+		if(!r->tail || !r->tail->s || !*r->tail->s) {
+			a->next = newarc((Node *)0, r, "", rmatch);
+			a = a->next;
+		} else
+			for(w = r->tail; w; w = w->next){
+				a->next = newarc(applyrules(w->s, cnt), r, "", rmatch);
+				a = a->next;
+		}
+		cnt[r->rule]--;
+		head.n = node;
+	}
+	for(r = metarules; r; r = r->next){
+		if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue;	/* no effect; ignore */
+		if ((r->attr&NOVIRT) && a != &head && (a->r->attr&VIR))
+			continue;
+		if(r->attr&REGEXP){
+			stem[0] = 0;
+			patrule = r;
+			memset((char*)rmatch, 0, sizeof(rmatch));
+			if(regexec(r->pat, node->name, rmatch, NREGEXP) == 0)
+				continue;
+		} else {
+			if(!match(node->name, r->target, stem)) continue;
+		}
+		if(cnt[r->rule] >= nreps) continue;
+		cnt[r->rule]++;
+
+/*		if(r->attr&VIR)
+ *			node->flags |= VIRTUAL;
+ *		if(r->attr&NOREC)
+ *			node->flags |= NORECIPE;
+ *		if(r->attr&DEL)
+ *			node->flags |= DELETE;
+ */
+
+		if(!r->tail || !r->tail->s || !*r->tail->s) {
+			a->next = newarc((Node *)0, r, stem, rmatch);
+			a = a->next;
+		} else
+			for(w = r->tail; w; w = w->next){
+				if(r->attr&REGEXP)
+					regsub(w->s, buf, sizeof buf, rmatch, NREGEXP);
+				else
+					subst(stem, w->s, buf);
+				a->next = newarc(applyrules(buf, cnt), r, stem, rmatch);
+				a = a->next;
+			}
+		cnt[r->rule]--;
+	}
+	a->next = node->prereqs;
+	node->prereqs = head.next;
+	return(node);
+}
+
+static void
+togo(Node *node)
+{
+	Arc *la, *a;
+
+	/* delete them now */
+	la = 0;
+	for(a = node->prereqs; a; la = a, a = a->next)
+		if(a->flag&TOGO){
+			if(a == node->prereqs)
+				node->prereqs = a->next;
+			else
+				la->next = a->next, a = la;
+		}
+}
+
+static int
+vacuous(Node *node)
+{
+	Arc *la, *a;
+	int vac = !(node->flags&PROBABLE);
+
+	if(node->flags&READY)
+		return(node->flags&VACUOUS);
+	node->flags |= READY;
+	for(a = node->prereqs; a; a = a->next)
+		if(a->n && vacuous(a->n) && (a->r->attr&META))
+			a->flag |= TOGO;
+		else
+			vac = 0;
+	/* if a rule generated arcs that DON'T go; no others from that rule go */
+	for(a = node->prereqs; a; a = a->next)
+		if((a->flag&TOGO) == 0)
+			for(la = node->prereqs; la; la = la->next)
+				if((la->flag&TOGO) && (la->r == a->r)){
+					la->flag &= ~TOGO;
+				}
+	togo(node);
+	if(vac)
+		node->flags |= VACUOUS;
+	return(vac);
+}
+
+static Node *
+newnode(char *name)
+{
+	register Node *node;
+
+	node = (Node *)Malloc(sizeof(Node));
+	symlook(name, S_NODE, (void *)node);
+	node->name = name;
+	node->time = timeof(name, 0);
+	node->prereqs = 0;
+	node->flags = node->time? PROBABLE : 0;
+	node->next = 0;
+	return(node);
+}
+
+void
+dumpn(char *s, Node *n)
+{
+	char buf[1024];
+	Arc *a;
+
+	sprint(buf, "%s   ", (*s == ' ')? s:"");
+	Bprint(&bout, "%s%s@%ld: time=%ld flags=0x%x next=%ld\n",
+		s, n->name, n, n->time, n->flags, n->next);
+	for(a = n->prereqs; a; a = a->next)
+		dumpa(buf, a);
+}
+
+static void
+trace(char *s, Arc *a)
+{
+	fprint(2, "\t%s", s);
+	while(a){
+		fprint(2, " <-(%s:%d)- %s", a->r->file, a->r->line,
+			a->n? a->n->name:"");
+		if(a->n){
+			for(a = a->n->prereqs; a; a = a->next)
+				if(*a->r->recipe) break;
+		} else
+			a = 0;
+	}
+	fprint(2, "\n");
+}
+
+static void
+cyclechk(Node *n)
+{
+	Arc *a;
+
+	if((n->flags&CYCLE) && n->prereqs){
+		fprint(2, "mk: cycle in graph detected at target %s\n", n->name);
+		Exit();
+	}
+	n->flags |= CYCLE;
+	for(a = n->prereqs; a; a = a->next)
+		if(a->n)
+			cyclechk(a->n);
+	n->flags &= ~CYCLE;
+}
+
+static void
+ambiguous(Node *n)
+{
+	Arc *a;
+	Rule *r = 0;
+	Arc *la;
+	int bad = 0;
+
+	la = 0;
+	for(a = n->prereqs; a; a = a->next){
+		if(a->n)
+			ambiguous(a->n);
+		if(*a->r->recipe == 0) continue;
+		if(r == 0)
+			r = a->r, la = a;
+		else{
+			if(r->recipe != a->r->recipe){
+				if((r->attr&META) && !(a->r->attr&META)){
+					la->flag |= TOGO;
+					r = a->r, la = a;
+				} else if(!(r->attr&META) && (a->r->attr&META)){
+					a->flag |= TOGO;
+					continue;
+				}
+			}
+			if(r->recipe != a->r->recipe){
+				if(bad == 0){
+					fprint(2, "mk: ambiguous recipes for %s:\n", n->name);
+					bad = 1;
+					trace(n->name, la);
+				}
+				trace(n->name, a);
+			}
+		}
+	}
+	if(bad)
+		Exit();
+	togo(n);
+}
+
+static void
+attribute(Node *n)
+{
+	register Arc *a;
+
+	for(a = n->prereqs; a; a = a->next){
+		if(a->r->attr&VIR)
+			n->flags |= VIRTUAL;
+		if(a->r->attr&NOREC)
+			n->flags |= NORECIPE;
+		if(a->r->attr&DEL)
+			n->flags |= DELETE;
+		if(a->n)
+			attribute(a->n);
+	}
+	if(n->flags&VIRTUAL)
+		n->time = 0;
+}
diff --git a/src/cmd/mk/lex.c b/src/cmd/mk/lex.c
new file mode 100644
index 0000000..3ee244f
--- /dev/null
+++ b/src/cmd/mk/lex.c
@@ -0,0 +1,147 @@
+#include	"mk.h"
+
+static	int	bquote(Biobuf*, Bufblock*);
+
+/*
+ *	Assemble a line skipping blank lines, comments, and eliding
+ *	escaped newlines
+ */
+int
+assline(Biobuf *bp, Bufblock *buf)
+{
+	int c;
+	int lastc;
+
+	buf->current=buf->start;
+	while ((c = nextrune(bp, 1)) >= 0){
+		switch(c)
+		{
+		case '\r':		/* consumes CRs for Win95 */
+			continue;
+		case '\n':
+			if (buf->current != buf->start) {
+				insert(buf, 0);
+				return 1;
+			}
+			break;		/* skip empty lines */
+		case '\\':
+		case '\'':
+		case '"':
+			rinsert(buf, c);
+			if (escapetoken(bp, buf, 1, c) == 0)
+				Exit();
+			break;
+		case '`':
+			if (bquote(bp, buf) == 0)
+				Exit();
+			break;
+		case '#':
+			lastc = '#';
+			while ((c = Bgetc(bp)) != '\n') {
+				if (c < 0)
+					goto eof;
+				if(c != '\r')
+					lastc = c;
+			}
+			mkinline++;
+			if (lastc == '\\')
+				break;		/* propagate escaped newlines??*/
+			if (buf->current != buf->start) {
+				insert(buf, 0);
+				return 1;
+			}
+			break;
+		default:
+			rinsert(buf, c);
+			break;
+		}
+	}
+eof:
+	insert(buf, 0);
+	return *buf->start != 0;
+}
+
+/*
+ *	assemble a back-quoted shell command into a buffer
+ */
+static int
+bquote(Biobuf *bp, Bufblock *buf)
+{
+	int c, line, term;
+	int start;
+
+	line = mkinline;
+	while((c = Bgetrune(bp)) == ' ' || c == '\t')
+			;
+	if(c == '{'){
+		term = '}';		/* rc style */
+		while((c = Bgetrune(bp)) == ' ' || c == '\t')
+			;
+	} else
+		term = '`';		/* sh style */
+
+	start = buf->current-buf->start;
+	for(;c > 0; c = nextrune(bp, 0)){
+		if(c == term){
+			insert(buf, '\n');
+			insert(buf,0);
+			buf->current = buf->start+start;
+			execinit();
+			execsh(0, buf->current, buf, envy);
+			return 1;
+		}
+		if(c == '\n')
+			break;
+		if(c == '\'' || c == '"' || c == '\\'){
+			insert(buf, c);
+			if(!escapetoken(bp, buf, 1, c))
+				return 0;
+			continue;
+		}
+		rinsert(buf, c);
+	}
+	SYNERR(line);
+	fprint(2, "missing closing %c after `\n", term);
+	return 0;
+}
+
+/*
+ *	get next character stripping escaped newlines
+ *	the flag specifies whether escaped newlines are to be elided or
+ *	replaced with a blank.
+ */
+int
+nextrune(Biobuf *bp, int elide)
+{
+	int c, c2;
+	static int savec;
+
+	if(savec){
+		c = savec;
+		savec = 0;
+		return c;
+	}
+
+	for (;;) {
+		c = Bgetrune(bp);
+		if (c == '\\') {
+			c2 = Bgetrune(bp);
+			if(c2 == '\r'){
+				savec = c2;
+				c2 = Bgetrune(bp);
+			}
+			if (c2 == '\n') {
+				savec = 0;
+				mkinline++;
+				if (elide)
+					continue;
+				return ' ';
+			}
+			Bungetrune(bp);
+		}
+		if (c == '\n')
+			mkinline++;
+		return c;
+	}
+	return 0;
+}
diff --git a/src/cmd/mk/main.c b/src/cmd/mk/main.c
new file mode 100644
index 0000000..267ed73
--- /dev/null
+++ b/src/cmd/mk/main.c
@@ -0,0 +1,285 @@
+#include	"mk.h"
+
+#define		MKFILE		"mkfile"
+
+int debug;
+Rule *rules, *metarules;
+int nflag = 0;
+int tflag = 0;
+int iflag = 0;
+int kflag = 0;
+int aflag = 0;
+int uflag = 0;
+char *explain = 0;
+Word *target1;
+int nreps = 1;
+Job *jobs;
+Biobuf bout;
+Rule *patrule;
+void badusage(void);
+#ifdef	PROF
+short buf[10000];
+#endif
+
+int
+main(int argc, char **argv)
+{
+	Word *w;
+	char *s, *temp;
+	char *files[256], **f = files, **ff;
+	int sflag = 0;
+	int i;
+	int tfd = -1;
+	Biobuf tb;
+	Bufblock *buf;
+	Bufblock *whatif;
+
+	/*
+	 *  start with a copy of the current environment variables
+	 *  instead of sharing them
+	 */
+
+	Binit(&bout, 1, OWRITE);
+	buf = newbuf();
+	whatif = 0;
+	USED(argc);
+	for(argv++; *argv && (**argv == '-'); argv++)
+	{
+		bufcpy(buf, argv[0], strlen(argv[0]));
+		insert(buf, ' ');
+		switch(argv[0][1])
+		{
+		case 'a':
+			aflag = 1;
+			break;
+		case 'd':
+			if(*(s = &argv[0][2]))
+				while(*s) switch(*s++)
+				{
+				case 'p':	debug |= D_PARSE; break;
+				case 'g':	debug |= D_GRAPH; break;
+				case 'e':	debug |= D_EXEC; break;
+				}
+			else
+				debug = 0xFFFF;
+			break;
+		case 'e':
+			explain = &argv[0][2];
+			break;
+		case 'f':
+			if(*++argv == 0)
+				badusage();
+			*f++ = *argv;
+			bufcpy(buf, argv[0], strlen(argv[0]));
+			insert(buf, ' ');
+			break;
+		case 'i':
+			iflag = 1;
+			break;
+		case 'k':
+			kflag = 1;
+			break;
+		case 'n':
+			nflag = 1;
+			break;
+		case 's':
+			sflag = 1;
+			break;
+		case 't':
+			tflag = 1;
+			break;
+		case 'u':
+			uflag = 1;
+			break;
+		case 'w':
+			if(whatif == 0)
+				whatif = newbuf();
+			else
+				insert(whatif, ' ');
+			if(argv[0][2])
+				bufcpy(whatif, &argv[0][2], strlen(&argv[0][2]));
+			else {
+				if(*++argv == 0)
+					badusage();
+				bufcpy(whatif, &argv[0][0], strlen(&argv[0][0]));
+			}
+			break;
+		default:
+			badusage();
+		}
+	}
+#ifdef	PROF
+	{
+		extern etext();
+		monitor(main, etext, buf, sizeof buf, 300);
+	}
+#endif
+
+	if(aflag)
+		iflag = 1;
+	usage();
+	syminit();
+	initenv();
+	usage();
+
+	/*
+		assignment args become null strings
+	*/
+	temp = 0;
+	for(i = 0; argv[i]; i++) if(utfrune(argv[i], '=')){
+		bufcpy(buf, argv[i], strlen(argv[i]));
+		insert(buf, ' ');
+		if(tfd < 0){
+			temp = maketmp(&tfd);
+			if(temp == 0) {
+				fprint(2, "temp file: %r\n");
+				Exit();
+			}
+			Binit(&tb, tfd, OWRITE);
+		}
+		Bprint(&tb, "%s\n", argv[i]);
+		*argv[i] = 0;
+	}
+	if(tfd >= 0){
+		Bflush(&tb);
+		LSEEK(tfd, 0L, 0);
+		parse("command line args", tfd, 1);
+		remove(temp);
+	}
+
+	if (buf->current != buf->start) {
+		buf->current--;
+		insert(buf, 0);
+	}
+	symlook("MKFLAGS", S_VAR, (void *) stow(buf->start));
+	buf->current = buf->start;
+	for(i = 0; argv[i]; i++){
+		if(*argv[i] == 0) continue;
+		if(i)
+			insert(buf, ' ');
+		bufcpy(buf, argv[i], strlen(argv[i]));
+	}
+	insert(buf, 0);
+	symlook("MKARGS", S_VAR, (void *) stow(buf->start));
+	freebuf(buf);
+
+	if(f == files){
+		if(access(MKFILE, 4) == 0)
+			parse(MKFILE, open(MKFILE, 0), 0);
+	} else
+		for(ff = files; ff < f; ff++)
+			parse(*ff, open(*ff, 0), 0);
+	if(DEBUG(D_PARSE)){
+		dumpw("default targets", target1);
+		dumpr("rules", rules);
+		dumpr("metarules", metarules);
+		dumpv("variables");
+	}
+	if(whatif){
+		insert(whatif, 0);
+		timeinit(whatif->start);
+		freebuf(whatif);
+	}
+	execinit();
+	/* skip assignment args */
+	while(*argv && (**argv == 0))
+		argv++;
+
+	catchnotes();
+	if(*argv == 0){
+		if(target1)
+			for(w = target1; w; w = w->next)
+				mk(w->s);
+		else {
+			fprint(2, "mk: nothing to mk\n");
+			Exit();
+		}
+	} else {
+		if(sflag){
+			for(; *argv; argv++)
+				if(**argv)
+					mk(*argv);
+		} else {
+			Word *head, *tail, *t;
+
+			/* fake a new rule with all the args as prereqs */
+			tail = 0;
+			t = 0;
+			for(; *argv; argv++)
+				if(**argv){
+					if(tail == 0)
+						tail = t = newword(*argv);
+					else {
+						t->next = newword(*argv);
+						t = t->next;
+					}
+				}
+			if(tail->next == 0)
+				mk(tail->s);
+			else {
+				head = newword("command line arguments");
+				addrules(head, tail, strdup(""), VIR, mkinline, 0);
+				mk(head->s);
+			}
+		}
+	}
+	if(uflag)
+		prusage();
+	exits(0);
+}
+
+void
+badusage(void)
+{
+
+	fprint(2, "Usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n");
+	Exit();
+}
+
+void *
+Malloc(int n)
+{
+	register void *s;
+
+	s = malloc(n);
+	if(!s) {
+		fprint(2, "mk: cannot alloc %d bytes\n", n);
+		Exit();
+	}
+	return(s);
+}
+
+void *
+Realloc(void *s, int n)
+{
+	if(s)
+		s = realloc(s, n);
+	else
+		s = malloc(n);
+	if(!s) {
+		fprint(2, "mk: cannot alloc %d bytes\n", n);
+		Exit();
+	}
+	return(s);
+}
+
+void
+assert(char *s, int n)
+{
+	if(!n){
+		fprint(2, "mk: Assertion ``%s'' failed.\n", s);
+		Exit();
+	}
+}
+
+void
+regerror(char *s)
+{
+	if(patrule)
+		fprint(2, "mk: %s:%d: regular expression error; %s\n",
+			patrule->file, patrule->line, s);
+	else
+		fprint(2, "mk: %s:%d: regular expression error; %s\n",
+			infile, mkinline, s);
+	Exit();
+}
diff --git a/src/cmd/mk/match.c b/src/cmd/mk/match.c
new file mode 100644
index 0000000..2a96394
--- /dev/null
+++ b/src/cmd/mk/match.c
@@ -0,0 +1,49 @@
+#include	"mk.h"
+
+int
+match(char *name, char *template, char *stem)
+{
+	Rune r;
+	int n;
+
+	while(*name && *template){
+		n = chartorune(&r, template);
+		if (PERCENT(r))
+			break;
+		while (n--)
+			if(*name++ != *template++)
+				return 0;
+	}
+	if(!PERCENT(*template))
+		return 0;
+	n = strlen(name)-strlen(template+1);
+	if (n < 0)
+		return 0;
+	if (strcmp(template+1, name+n))
+		return 0;
+	strncpy(stem, name, n);
+	stem[n] = 0;
+	if(*template == '&')
+		return !charin(stem, "./");
+	return 1;
+}
+
+void
+subst(char *stem, char *template, char *dest)
+{
+	Rune r;
+	char *s;
+	int n;
+
+	while(*template){
+		n = chartorune(&r, template);
+		if (PERCENT(r)) {
+			template += n;
+			for (s = stem; *s; s++)
+				*dest++ = *s;
+		} else
+			while (n--)
+				*dest++ = *template++;
+	}
+	*dest = 0;
+}
diff --git a/src/cmd/mk/mk.1 b/src/cmd/mk/mk.1
new file mode 100644
index 0000000..c58a6df
--- /dev/null
+++ b/src/cmd/mk/mk.1
@@ -0,0 +1,665 @@
+.TH MK 1
+.de EX
+.nf
+.ft B
+..
+.de EE
+.fi
+.ft R
+..
+.de LR
+.if t .BR \\$1 \\$2
+.if n .RB ` \\$1 '\\$2
+..
+.de L
+.nh
+.if t .B \\$1
+.if n .RB ` \\$1 '
+..
+.SH NAME
+mk \- maintain (make) related files
+.SH SYNOPSIS
+.B mk
+[
+.B -f
+.I mkfile
+] ...
+[
+.I option ...
+]
+[
+.I target ...
+]
+.SH DESCRIPTION
+.I Mk
+uses the dependency rules specified in
+.I mkfile
+to control the update (usually by compilation) of
+.I targets
+(usually files)
+from the source files upon which they depend.
+The
+.I mkfile
+(default
+.LR mkfile )
+contains a
+.I rule
+for each target that identifies the files and other
+targets upon which it depends and an
+.IR sh (1)
+script, a
+.IR recipe ,
+to update the target.
+The script is run if the target does not exist
+or if it is older than any of the files it depends on.
+.I Mkfile
+may also contain
+.I meta-rules
+that define actions for updating implicit targets.
+If no
+.I target
+is specified, the target of the first rule (not meta-rule) in
+.I mkfile
+is updated.
+.PP
+The environment variable
+.B $NPROC
+determines how many targets may be updated simultaneously;
+Some operating systems, e.g., Plan 9, set
+.B $NPROC
+automatically to the number of CPUs on the current machine.
+.PP
+Options are:
+.TP \w'\fL-d[egp]\ 'u
+.B -a
+Assume all targets to be out of date.
+Thus, everything is updated.
+.PD 0
+.TP
+.BR -d [ egp ]
+Produce debugging output
+.RB ( p
+is for parsing,
+.B g
+for graph building,
+.B e
+for execution).
+.TP
+.B -e
+Explain why each target is made.
+.TP
+.B -i
+Force any missing intermediate targets to be made.
+.TP
+.B -k
+Do as much work as possible in the face of errors.
+.TP
+.B -n
+Print, but do not execute, the commands
+needed to update the targets.
+.TP
+.B -s
+Make the command line arguments sequentially rather than in parallel.
+.TP
+.B -t
+Touch (update the modified date of) file targets, without
+executing any recipes.
+.TP
+.BI -w target1 , target2,...
+Pretend the modify time for each
+.I target
+is the current time; useful in conjunction with
+.B -n
+to learn what updates would be triggered by
+modifying the
+.IR targets .
+.PD
+.SS The \fLmkfile\fP
+A
+.I mkfile
+consists of
+.I assignments
+(described under `Environment') and
+.IR rules .
+A rule contains
+.I targets
+and a
+.IR tail .
+A target is a literal string
+and is normally a file name.
+The tail contains zero or more 
+.I prerequisites
+and an optional
+.IR recipe ,
+which is an
+.B shell
+script.
+Each line of the recipe must begin with white space.
+A rule takes the form
+.IP
+.EX
+target: prereq1 prereq2
+        \f2recipe using\fP prereq1, prereq2 \f2to build\fP target
+.EE
+.PP
+When the recipe is executed,
+the first character on every line is elided.
+.PP
+After the colon on the target line, a rule may specify
+.IR attributes ,
+described below.
+.PP
+A
+.I meta-rule 
+has a target of the form
+.IB A % B
+where
+.I A
+and
+.I B
+are (possibly empty) strings.
+A meta-rule acts as a rule for any potential target whose
+name matches
+.IB A % B
+with
+.B %
+replaced by an arbitrary string, called the
+.IR stem .
+In interpreting a meta-rule,
+the stem is substituted for all occurrences of
+.B %
+in the prerequisite names.
+In the recipe of a meta-rule, the environment variable
+.B $stem
+contains the string matched by the
+.BR % .
+For example, a meta-rule to compile a C program using
+.IR cc (1)
+might be:
+.IP
+.EX
+%:    %.c
+        cc -c $stem.c
+        cc -o $stem $stem.o
+.EE
+.PP
+Meta-rules may contain an ampersand
+.B &
+rather than a percent sign
+.BR % .
+A
+.B %
+matches a maximal length string of any characters;
+an
+.B &
+matches a maximal length string of any characters except period
+or slash.
+.PP
+The text of the
+.I mkfile
+is processed as follows.
+Lines beginning with
+.B <
+followed by a file name are replaced by the contents of the named
+file.
+Lines beginning with
+.B "<|"
+followed by a file name are replaced by the output
+of the execution of the named
+file.
+Blank lines and comments, which run from unquoted
+.B #
+characters to the following newline, are deleted.
+The character sequence backslash-newline is deleted,
+so long lines in
+.I mkfile
+may be folded.
+Non-recipe lines are processed by substituting for
+.BI `{ command }
+the output of the
+.I command
+when run by
+.IR sh .
+References to variables are replaced by the variables' values.
+Special characters may be quoted using single quotes
+.BR \&''
+as in
+.IR sh (1).
+.PP
+Assignments and rules are distinguished by
+the first unquoted occurrence of
+.B :
+(rule)
+or
+.B =
+(assignment).
+.PP
+A later rule may modify or override an existing rule under the
+following conditions:
+.TP
+\-
+If the targets of the rules exactly match and one rule
+contains only a prerequisite clause and no recipe, the
+clause is added to the prerequisites of the other rule.
+If either or both targets are virtual, the recipe is
+always executed.
+.TP
+\-
+If the targets of the rules match exactly and the
+prerequisites do not match and both rules
+contain recipes,
+.I mk
+reports an ``ambiguous recipe'' error.
+.TP
+\-
+If the target and prerequisites of both rules match exactly,
+the second rule overrides the first.
+.SS Environment
+Rules may make use of
+shell
+environment variables.
+A legal reference of the form
+.B $OBJ
+or
+.B ${name}
+is expanded as in
+.IR sh (1).
+A reference of the form
+.BI ${name: A % B = C\fL%\fID\fL}\fR,
+where
+.I A, B, C, D
+are (possibly empty) strings,
+has the value formed by expanding
+.B $name
+and substituting
+.I C
+for
+.I A
+and
+.I D
+for
+.I B
+in each word in
+.B $name
+that matches pattern
+.IB A % B\f1.
+.PP
+Variables can be set by
+assignments of the form
+.I
+        var\fL=\fR[\fIattr\fL=\fR]\fIvalue\fR
+.br
+Blanks in the
+.I value
+break it into words.
+Such variables are exported
+to the environment of
+recipes as they are executed, unless
+.BR U ,
+the only legal attribute
+.IR attr ,
+is present.
+The initial value of a variable is
+taken from (in increasing order of precedence)
+the default values below,
+.I mk's
+environment, the
+.IR mkfiles ,
+and any command line assignment as an argument to
+.IR mk .
+A variable assignment argument overrides the first (but not any subsequent)
+assignment to that variable.
+The variable
+.B MKFLAGS
+contains all the option arguments (arguments starting with
+.L -
+or containing
+.LR = )
+and
+.B MKARGS
+contains all the targets in the call to
+.IR mk .
+.PP
+Dynamic information may be included in the mkfile by using a line of the form
+.IP
+\fR<|\fIcommand\fR \fIargs\fR
+.LP
+This runs the command 
+.I command
+with the given arguments
+.I args
+and pipes its standard output to
+.I mk
+to be included as part of the mkfile. For instance, the Inferno kernels
+use this technique
+to run a shell command with an awk script and a configuration
+file as arguments in order for
+the
+.I awk
+script to process the file and output a set of variables and their values.
+.SS Execution
+.PP
+During execution,
+.I mk
+determines which targets must be updated, and in what order,
+to build the
+.I names
+specified on the command line.
+It then runs the associated recipes.
+.PP
+A target is considered up to date if it has no prerequisites or
+if all its prerequisites are up to date and it is newer
+than all its prerequisites.
+Once the recipe for a target has executed, the target is
+considered up to date.
+.PP
+The date stamp
+used to determine if a target is up to date is computed
+differently for different types of targets.
+If a target is
+.I virtual
+(the target of a rule with the
+.B V
+attribute),
+its date stamp is initially zero; when the target is
+updated the date stamp is set to
+the most recent date stamp of its prerequisites.
+Otherwise, if a target does not exist as a file,
+its date stamp is set to the most recent date stamp of its prerequisites,
+or zero if it has no prerequisites.
+Otherwise, the target is the name of a file and
+the target's date stamp is always that file's modification date.
+The date stamp is computed when the target is needed in
+the execution of a rule; it is not a static value.
+.PP
+Nonexistent targets that have prerequisites
+and are themselves prerequisites are treated specially.
+Such a target
+.I t
+is given the date stamp of its most recent prerequisite
+and if this causes all the targets which have
+.I t
+as a prerequisite to be up to date,
+.I t
+is considered up to date.
+Otherwise,
+.I t
+is made in the normal fashion.
+The
+.B -i
+flag overrides this special treatment.
+.PP
+Files may be made in any order that respects
+the preceding restrictions.
+.PP
+A recipe is executed by supplying the recipe as standard input to
+the command
+.BR /bin/sh .
+(Note that unlike
+.IR make ,
+.I mk
+feeds the entire recipe to the shell rather than running each line
+of the recipe separately.)
+The environment is augmented by the following variables:
+.TP 14
+.B $alltarget
+all the targets of this rule.
+.TP
+.B $newprereq
+the prerequisites that caused this rule to execute.
+.TP
+.B $newmember
+the prerequisites that are members of an aggregate
+that caused this rule to execute.
+When the prerequisites of a rule are members of an
+aggregate,
+.B $newprereq
+contains the name of the aggregate and out of date
+members, while
+.B $newmember
+contains only the name of the members.
+.TP
+.B $nproc
+the process slot for this recipe.
+It satisfies
+.RB 0≤ $nproc < $NPROC .
+.TP
+.B $pid
+the process id for the
+.I mk
+executing the recipe.
+.TP
+.B $prereq
+all the prerequisites for this rule.
+.TP
+.B $stem
+if this is a meta-rule,
+.B $stem
+is the string that matched
+.B %
+or
+.BR & .
+Otherwise, it is empty.
+For regular expression meta-rules (see below), the variables
+.LR stem0 ", ...,"
+.L stem9
+are set to the corresponding subexpressions.
+.TP
+.B $target
+the targets for this rule that need to be remade.
+.PP
+These variables are available only during the execution of a recipe,
+not while evaluating the
+.IR mkfile .
+.PP
+Unless the rule has the
+.B Q
+attribute,
+the recipe is printed prior to execution
+with recognizable environment variables expanded.
+Commands returning error status
+cause
+.I mk
+to terminate.
+.PP
+Recipes and backquoted
+.B rc
+commands in places such as assignments
+execute in a copy of
+.I mk's
+environment; changes they make to
+environment variables are not visible from
+.IR mk .
+.PP
+Variable substitution in a rule is done when
+the rule is read; variable substitution in the recipe is done
+when the recipe is executed.  For example:
+.IP
+.EX
+bar=a.c
+foo:	$bar
+        $CC -o foo $bar
+bar=b.c
+.EE
+.PP
+will compile
+.B b.c
+into
+.BR foo ,
+if
+.B a.c
+is newer than
+.BR foo .
+.SS Aggregates
+Names of the form
+.IR a ( b )
+refer to member
+.I b
+of the aggregate
+.IR a .
+Currently, the only aggregates supported are
+.IR ar (1)
+archives.
+.SS Attributes
+The colon separating the target from the prerequisites
+may be
+immediately followed by
+.I attributes
+and another colon.
+The attributes are:
+.TP
+.B D
+If the recipe exits with a non-null status, the target is deleted.
+.TP
+.B E
+Continue execution if the recipe draws errors.
+.TP
+.B N
+If there is no recipe, the target has its time updated.
+.TP
+.B n
+The rule is a meta-rule that cannot be a target of a virtual rule.
+Only files match the pattern in the target.
+.TP
+.B P
+The characters after the
+.B P
+until the terminating
+.B :
+are taken as a program name.
+It will be invoked as
+.B "sh -c prog 'arg1' 'arg2'"
+and should return a zero exit status
+if and only if arg1 is up to date with respect to arg2.
+Date stamps are still propagated in the normal way.
+.TP
+.B Q
+The recipe is not printed prior to execution.
+.TP
+.B R
+The rule is a meta-rule using regular expressions.
+In the rule,
+.B %
+has no special meaning.
+The target is interpreted as a regular expression as defined in
+.IR regexp (6).
+The prerequisites may contain references
+to subexpressions in form
+.BI \e n\f1,
+as in the substitute command of
+.IR sed (1).
+.TP
+.B U
+The targets are considered to have been updated
+even if the recipe did not do so.
+.TP
+.B V
+The targets of this rule are marked as virtual.
+They are distinct from files of the same name.
+.PD
+.SH EXAMPLES
+A simple mkfile to compile a program:
+.IP
+.EX
+.ta 8n +8n +8n +8n +8n +8n +8n
+</$objtype/mkfile
+
+prog:	a.$O b.$O c.$O
+	$LD $LDFLAGS -o $target $prereq
+
+%.$O:	%.c
+	$CC $CFLAGS $stem.c
+.EE
+.PP
+Override flag settings in the mkfile:
+.IP
+.EX
+% mk target 'CFLAGS=-S -w'
+.EE
+.PP
+Maintain a library:
+.IP
+.EX
+libc.a(%.$O):N:	%.$O
+libc.a:	libc.a(abs.$O) libc.a(access.$O) libc.a(alarm.$O) ...
+	ar r libc.a $newmember
+.EE
+.PP
+String expression variables to derive names from a master list:
+.IP
+.EX
+NAMES=alloc arc bquote builtins expand main match mk var word
+OBJ=${NAMES:%=%.$O}
+.EE
+.PP
+Regular expression meta-rules:
+.IP
+.EX
+([^/]*)/(.*)\e.$O:R:  \e1/\e2.c
+	cd $stem1; $CC $CFLAGS $stem2.c
+.EE
+.PP
+A correct way to deal with
+.IR yacc (1)
+grammars.
+The file
+.B lex.c
+includes the file
+.B x.tab.h
+rather than
+.B y.tab.h
+in order to reflect changes in content, not just modification time.
+.IP
+.EX
+lex.$O:	x.tab.h
+x.tab.h:	y.tab.h
+	cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
+y.tab.c y.tab.h:	gram.y
+	$YACC -d gram.y
+.EE
+.PP
+The above example could also use the
+.B P
+attribute for the
+.B x.tab.h
+rule:
+.IP
+.EX
+x.tab.h:Pcmp -s:	y.tab.h
+	cp y.tab.h x.tab.h
+.EE
+.SH SEE ALSO
+.IR sh (1),
+.IR regexp9 (7)
+.PP
+A. Hume,
+``Mk: a Successor to Make''
+(Tenth Edition Research Unix Manuals).
+.PP
+Andrew G. Hume and Bob Flandrena,
+``Maintaining Files on Plan 9 with Mk''.
+DOCPREFIX/doc/mk.pdf
+.SH HISTORY
+Andrew Hume wrote
+.I mk
+for Tenth Edition Research Unix.
+It was later ported to Plan 9.
+This software is a port of the Plan 9 version back to Unix.
+.SH BUGS
+Identical recipes for regular expression meta-rules only have one target.
+.br
+Seemingly appropriate input like
+.B CFLAGS=-DHZ=60
+is parsed as an erroneous attribute; correct it by inserting
+a space after the first 
+.LR = .
+.br
+The recipes printed by
+.I mk
+before being passed to
+.I sh
+for execution are sometimes erroneously expanded
+for printing.  Don't trust what's printed; rely
+on what
+.I sh
+does.
diff --git a/src/cmd/mk/mk.c b/src/cmd/mk/mk.c
new file mode 100644
index 0000000..26b8a97
--- /dev/null
+++ b/src/cmd/mk/mk.c
@@ -0,0 +1,226 @@
+#include	"mk.h"
+
+int runerrs;
+
+void
+mk(char *target)
+{
+	Node *node;
+	int did = 0;
+
+	nproc();		/* it can be updated dynamically */
+	nrep();			/* it can be updated dynamically */
+	runerrs = 0;
+	node = graph(target);
+	if(DEBUG(D_GRAPH)){
+		dumpn("new target\n", node);
+		Bflush(&bout);
+	}
+	clrmade(node);
+	while(node->flags&NOTMADE){
+		if(work(node, (Node *)0, (Arc *)0))
+			did = 1;	/* found something to do */
+		else {
+			if(waitup(1, (int *)0) > 0){
+				if(node->flags&(NOTMADE|BEINGMADE)){
+					assert("must be run errors", runerrs);
+					break;	/* nothing more waiting */
+				}
+			}
+		}
+	}
+	if(node->flags&BEINGMADE)
+		waitup(-1, (int *)0);
+	while(jobs)
+		waitup(-2, (int *)0);
+	assert("target didn't get done", runerrs || (node->flags&MADE));
+	if(did == 0)
+		Bprint(&bout, "mk: '%s' is up to date\n", node->name);
+}
+
+void
+clrmade(Node *n)
+{
+	Arc *a;
+
+	n->flags &= ~(CANPRETEND|PRETENDING);
+	if(strchr(n->name, '(') ==0 || n->time)
+		n->flags |= CANPRETEND;
+	MADESET(n, NOTMADE);
+	for(a = n->prereqs; a; a = a->next)
+		if(a->n)
+			clrmade(a->n);
+}
+
+static void
+unpretend(Node *n)
+{
+	MADESET(n, NOTMADE);
+	n->flags &= ~(CANPRETEND|PRETENDING);
+	n->time = 0;
+}
+
+int
+work(Node *node, Node *p, Arc *parc)
+{
+	Arc *a, *ra;
+	int weoutofdate;
+	int ready;
+	int did = 0;
+
+	/*print("work(%s) flags=0x%x time=%ld\n", node->name, node->flags, node->time);*//**/
+	if(node->flags&BEINGMADE)
+		return(did);
+	if((node->flags&MADE) && (node->flags&PRETENDING) && p && outofdate(p, parc, 0)){
+		if(explain)
+			fprint(1, "unpretending %s(%ld) because %s is out of date(%ld)\n",
+				node->name, node->time, p->name, p->time);
+		unpretend(node);
+	}
+	/*
+		have a look if we are pretending in case
+		someone has been unpretended out from underneath us
+	*/
+	if(node->flags&MADE){
+		if(node->flags&PRETENDING){
+			node->time = 0;
+		}else
+			return(did);
+	}
+	/* consider no prerequsite case */
+	if(node->prereqs == 0){
+		if(node->time == 0){
+			fprint(2, "mk: don't know how to make '%s'\n", node->name);
+			if(kflag){
+				node->flags |= BEINGMADE;
+				runerrs++;
+			} else
+				Exit();
+		} else
+			MADESET(node, MADE);
+		return(did);
+	}
+	/*
+		now see if we are out of date or what
+	*/
+	ready = 1;
+	weoutofdate = aflag;
+	ra = 0;
+	for(a = node->prereqs; a; a = a->next)
+		if(a->n){
+			did = work(a->n, node, a) || did;
+			if(a->n->flags&(NOTMADE|BEINGMADE))
+				ready = 0;
+			if(outofdate(node, a, 0)){
+				weoutofdate = 1;
+				if((ra == 0) || (ra->n == 0)
+						|| (ra->n->time < a->n->time))
+					ra = a;
+			}
+		} else {
+			if(node->time == 0){
+				if(ra == 0)
+					ra = a;
+				weoutofdate = 1;
+			}
+		}
+	if(ready == 0)	/* can't do anything now */
+		return(did);
+	if(weoutofdate == 0){
+		MADESET(node, MADE);
+		return(did);
+	}
+	/*
+		can we pretend to be made?
+	*/
+	if((iflag == 0) && (node->time == 0) && (node->flags&(PRETENDING|CANPRETEND))
+			&& p && ra->n && !outofdate(p, ra, 0)){
+		node->flags &= ~CANPRETEND;
+		MADESET(node, MADE);
+		if(explain && ((node->flags&PRETENDING) == 0))
+			fprint(1, "pretending %s has time %ld\n", node->name, node->time);
+		node->flags |= PRETENDING;
+		return(did);
+	}
+	/*
+		node is out of date and we REALLY do have to do something.
+		quickly rescan for pretenders
+	*/
+	for(a = node->prereqs; a; a = a->next)
+		if(a->n && (a->n->flags&PRETENDING)){
+			if(explain)
+				Bprint(&bout, "unpretending %s because of %s because of %s\n",
+				a->n->name, node->name, ra->n? ra->n->name : "rule with no prerequisites");
+
+			unpretend(a->n);
+			did = work(a->n, node, a) || did;
+			ready = 0;
+		}
+	if(ready == 0)	/* try later unless nothing has happened for -k's sake */
+		return(did || work(node, p, parc));
+	did = dorecipe(node) || did;
+	return(did);
+}
+
+void
+update(int fake, Node *node)
+{
+	Arc *a;
+
+	MADESET(node, fake? BEINGMADE : MADE);
+	if(((node->flags&VIRTUAL) == 0) && (access(node->name, 0) == 0)){
+		node->time = timeof(node->name, 1);
+		node->flags &= ~(CANPRETEND|PRETENDING);
+		for(a = node->prereqs; a; a = a->next)
+			if(a->prog)
+				outofdate(node, a, 1);
+	} else {
+		node->time = 1;
+		for(a = node->prereqs; a; a = a->next)
+			if(a->n && outofdate(node, a, 1))
+				node->time = a->n->time;
+	}
+/*	print("----node %s time=%ld flags=0x%x\n", node->name, node->time, node->flags);*//**/
+}
+
+static int
+pcmp(char *prog, char *p, char *q)
+{
+	char buf[3*NAMEBLOCK];
+	int pid;
+
+	Bflush(&bout);
+	sprint(buf, "%s '%s' '%s'\n", prog, p, q);
+	pid = pipecmd(buf, 0, 0);
+	while(waitup(-3, &pid) >= 0)
+		;
+	return(pid? 2:1);
+}
+
+int
+outofdate(Node *node, Arc *arc, int eval)
+{
+	char buf[3*NAMEBLOCK], *str;
+	Symtab *sym;
+	int ret;
+
+	str = 0;
+	if(arc->prog){
+		sprint(buf, "%s%c%s", node->name, 0377, arc->n->name);
+		sym = symlook(buf, S_OUTOFDATE, 0);
+		if(sym == 0 || eval){
+			if(sym == 0)
+				str = strdup(buf);
+			ret = pcmp(arc->prog, node->name, arc->n->name);
+			if(sym)
+				sym->value = (void *)ret;
+			else
+				symlook(str, S_OUTOFDATE, (void *)ret);
+		} else
+			ret = (int)sym->value;
+		return(ret-1);
+	} else if(strchr(arc->n->name, '(') && arc->n->time == 0)  /* missing archive member */
+		return 1;
+	else
+		return node->time < arc->n->time;
+}
diff --git a/src/cmd/mk/mk.h b/src/cmd/mk/mk.h
new file mode 100644
index 0000000..9b3f627
--- /dev/null
+++ b/src/cmd/mk/mk.h
@@ -0,0 +1,203 @@
+#include <utf.h>
+#include <fmt.h>
+#include <setjmp.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include	<bio.h>
+#include	<regexp9.h>
+#include <time.h>
+
+#define uchar _mkuchar
+#define ushort _mkushort
+#define uint _mkuint
+#define ulong _mkulong
+#define vlong _mkvlong
+#define uvlong _mkuvlong
+
+#define nil ((void*)0)
+
+typedef unsigned char		uchar;
+typedef unsigned short		ushort;
+typedef unsigned int		uint;
+typedef unsigned long		ulong;
+
+#define nelem(x) (sizeof(x)/sizeof((x)[0]))
+
+#define OREAD O_RDONLY
+#define OWRITE O_WRONLY
+#define ORDWR O_RDWR
+#define USED(x) if(x);else
+#define remove unlink
+#define seek lseek
+#define exits(s) exit((s) && ((char*)s)[0] ? 1 : 0)
+#define create(name, mode, perm) creat(name, perm)
+#define ERRMAX 256
+
+#undef assert
+#define	assert	mkassert
+extern Biobuf bout;
+
+typedef struct Bufblock
+{
+	struct Bufblock *next;
+	char 		*start;
+	char 		*end;
+	char 		*current;
+} Bufblock;
+
+typedef struct Word
+{
+	char 		*s;
+	struct Word 	*next;
+} Word;
+
+typedef struct Envy
+{
+	char 		*name;
+	Word 		*values;
+} Envy;
+
+extern Envy *envy;
+
+typedef struct Rule
+{
+	char 		*target;	/* one target */
+	Word 		*tail;		/* constituents of targets */
+	char 		*recipe;	/* do it ! */
+	short 		attr;		/* attributes */
+	short 		line;		/* source line */
+	char 		*file;		/* source file */
+	Word 		*alltargets;	/* all the targets */
+	int 		rule;		/* rule number */
+	Reprog		*pat;		/* reg exp goo */
+	char		*prog;		/* to use in out of date */
+	struct Rule	*chain;		/* hashed per target */
+	struct Rule	*next;
+} Rule;
+
+extern Rule *rules, *metarules, *patrule;
+
+/*	Rule.attr	*/
+#define		META		0x0001
+#define		UNUSED		0x0002
+#define		UPD		0x0004
+#define		QUIET		0x0008
+#define		VIR		0x0010
+#define		REGEXP		0x0020
+#define		NOREC		0x0040
+#define		DEL		0x0080
+#define		NOVIRT		0x0100
+
+#define		NREGEXP		10
+
+typedef struct Arc
+{
+	short		flag;
+	struct Node	*n;
+	Rule		*r;
+	char		*stem;
+	char		*prog;
+	char		*match[NREGEXP];
+	struct Arc	*next;
+} Arc;
+
+	/* Arc.flag */
+#define		TOGO		1
+
+typedef struct Node
+{
+	char		*name;
+	long		time;
+	unsigned short	flags;
+	Arc		*prereqs;
+	struct Node	*next;		/* list for a rule */
+} Node;
+
+	/* Node.flags */
+#define		VIRTUAL		0x0001
+#define		CYCLE		0x0002
+#define		READY		0x0004
+#define		CANPRETEND	0x0008
+#define		PRETENDING	0x0010
+#define		NOTMADE		0x0020
+#define		BEINGMADE	0x0040
+#define		MADE		0x0080
+#define		MADESET(n,m)	n->flags = (n->flags&~(NOTMADE|BEINGMADE|MADE))|(m)
+#define		PROBABLE	0x0100
+#define		VACUOUS		0x0200
+#define		NORECIPE	0x0400
+#define		DELETE		0x0800
+#define		NOMINUSE	0x1000
+
+typedef struct Job
+{
+	Rule		*r;	/* master rule for job */
+	Node		*n;	/* list of node targets */
+	char		*stem;
+	char		**match;
+	Word		*p;	/* prerequistes */
+	Word		*np;	/* new prerequistes */
+	Word		*t;	/* targets */
+	Word		*at;	/* all targets */
+	int		nproc;	/* slot number */
+	struct Job	*next;
+} Job;
+extern Job *jobs;
+
+typedef struct Symtab
+{
+	short		space;
+	char		*name;
+	void		*value;
+	struct Symtab	*next;
+} Symtab;
+
+enum {
+	S_VAR,		/* variable -> value */
+	S_TARGET,	/* target -> rule */
+	S_TIME,		/* file -> time */
+	S_PID,		/* pid -> products */
+	S_NODE,		/* target name -> node */
+	S_AGG,		/* aggregate -> time */
+	S_BITCH,	/* bitched about aggregate not there */
+	S_NOEXPORT,	/* var -> noexport */
+	S_OVERRIDE,	/* can't override */
+	S_OUTOFDATE,	/* n1\377n2 -> 2(outofdate) or 1(not outofdate) */
+	S_MAKEFILE,	/* target -> node */
+	S_MAKEVAR,	/* dumpable mk variable */
+	S_EXPORTED,	/* var -> current exported value */
+	S_WESET,	/* variable; we set in the mkfile */
+	S_INTERNAL	/* an internal mk variable (e.g., stem, target) */
+};
+
+extern	int	debug;
+extern	int	nflag, tflag, iflag, kflag, aflag, mflag;
+extern	int	mkinline;
+extern	char	*infile;
+extern	int	nreps;
+extern	char	*explain;
+extern	char	*termchars;
+extern	int	IWS;
+extern	char 	*shell;
+extern	char 	*shellname;
+extern	char 	*shflags;
+
+#define	SYNERR(l)	(fprint(2, "mk: %s:%d: syntax error; ", infile, ((l)>=0)?(l):mkinline))
+#define	RERR(r)		(fprint(2, "mk: %s:%d: rule error; ", (r)->file, (r)->line))
+#define	NAMEBLOCK	1000
+#define	BIGBLOCK	20000
+
+#define	SEP(c)		(((c)==' ')||((c)=='\t')||((c)=='\n'))
+#define WORDCHR(r)	((r) > ' ' && !utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", (r)))
+
+#define	DEBUG(x)	(debug&(x))
+#define		D_PARSE		0x01
+#define		D_GRAPH		0x02
+#define		D_EXEC		0x04
+
+#define	LSEEK(f,o,p)	seek(f,o,p)
+
+#define	PERCENT(ch)	(((ch) == '%') || ((ch) == '&'))
+
+#include	"fns.h"
diff --git a/src/cmd/mk/mkfile b/src/cmd/mk/mkfile
new file mode 100644
index 0000000..1015135
--- /dev/null
+++ b/src/cmd/mk/mkfile
@@ -0,0 +1,9 @@
+all:V: Makefile Make.FreeBSD-386 Make.Linux-386 Make.HP-UX-9000 Make.OSF1-alpha \
+	Make.SunOS-sun4u Make.SunOS-sun4u-cc Make.SunOS-sun4u-gcc \
+	Make.NetBSD-386 Make.Darwin-PowerMacintosh
+
+Makefile:D: ../libutf/Makefile.TOP Makefile.MID ../libutf/Makefile.CMD ../libutf/Makefile.BOT
+	cat $prereq >$target
+
+Make.%: ../libutf/Make.%
+	cp $prereq $target
diff --git a/src/cmd/mk/mkfile.test b/src/cmd/mk/mkfile.test
new file mode 100644
index 0000000..5c1c0ed
--- /dev/null
+++ b/src/cmd/mk/mkfile.test
@@ -0,0 +1,5 @@
+a: b
+	cp b a
+
+c:V:
+	echo hello world
diff --git a/src/cmd/mk/parse.c b/src/cmd/mk/parse.c
new file mode 100644
index 0000000..b7737ef
--- /dev/null
+++ b/src/cmd/mk/parse.c
@@ -0,0 +1,307 @@
+#include	"mk.h"
+
+char *infile;
+int mkinline;
+static int rhead(char *, Word **, Word **, int *, char **);
+static char *rbody(Biobuf*);
+extern Word *target1;
+
+void
+parse(char *f, int fd, int varoverride)
+{
+	int hline;
+	char *body;
+	Word *head, *tail;
+	int attr, set, pid;
+	char *prog, *p;
+	int newfd;
+	Biobuf in;
+	Bufblock *buf;
+
+	if(fd < 0){
+		fprint(2, "open %s: %r\n", f);
+		Exit();
+	}
+	ipush();
+	infile = strdup(f);
+	mkinline = 1;
+	Binit(&in, fd, OREAD);
+	buf = newbuf();
+	while(assline(&in, buf)){
+		hline = mkinline;
+		switch(rhead(buf->start, &head, &tail, &attr, &prog))
+		{
+		case '<':
+			p = wtos(tail, ' ');
+			if(*p == 0){
+				SYNERR(-1);
+				fprint(2, "missing include file name\n");
+				Exit();
+			}
+			newfd = open(p, OREAD);
+			if(newfd < 0){
+				fprint(2, "warning: skipping missing include file %s: %r\n", p);
+			} else
+				parse(p, newfd, 0);
+			break;
+		case '|':
+			p = wtos(tail, ' ');
+			if(*p == 0){
+				SYNERR(-1);
+				fprint(2, "missing include program name\n");
+				Exit();
+			}
+			execinit();
+			pid=pipecmd(p, envy, &newfd);
+			if(newfd < 0){
+				fprint(2, "warning: skipping missing program file %s: %r\n", p);
+			} else
+				parse(p, newfd, 0);
+			while(waitup(-3, &pid) >= 0)
+				;
+			if(pid != 0){
+				fprint(2, "bad include program status\n");
+				Exit();
+			}
+			break;
+		case ':':
+			body = rbody(&in);
+			addrules(head, tail, body, attr, hline, prog);
+			break;
+		case '=':
+			if(head->next){
+				SYNERR(-1);
+				fprint(2, "multiple vars on left side of assignment\n");
+				Exit();
+			}
+			if(symlook(head->s, S_OVERRIDE, 0)){
+				set = varoverride;
+			} else {
+				set = 1;
+				if(varoverride)
+					symlook(head->s, S_OVERRIDE, (void *)"");
+			}
+			if(set){
+/*
+char *cp;
+dumpw("tail", tail);
+cp = wtos(tail, ' '); print("assign %s to %s\n", head->s, cp); free(cp);
+*/
+				setvar(head->s, (void *) tail);
+				symlook(head->s, S_WESET, (void *)"");
+			}
+			if(attr)
+				symlook(head->s, S_NOEXPORT, (void *)"");
+			break;
+		default:
+			SYNERR(hline);
+			fprint(2, "expected one of :<=\n");
+			Exit();
+			break;
+		}
+	}
+	close(fd);
+	freebuf(buf);
+	ipop();
+}
+
+void
+addrules(Word *head, Word *tail, char *body, int attr, int hline, char *prog)
+{
+	Word *w;
+
+	assert("addrules args", head && body);
+		/* tuck away first non-meta rule as default target*/
+	if(target1 == 0 && !(attr&REGEXP)){
+		for(w = head; w; w = w->next)
+			if(charin(w->s, "%&"))
+				break;
+		if(w == 0)
+			target1 = wdup(head);
+	}
+	for(w = head; w; w = w->next)
+		addrule(w->s, tail, body, head, attr, hline, prog);
+}
+
+static int
+rhead(char *line, Word **h, Word **t, int *attr, char **prog)
+{
+	char *p;
+	char *pp;
+	int sep;
+	Rune r;
+	int n;
+	Word *w;
+
+	p = charin(line,":=<");
+	if(p == 0)
+		return('?');
+	sep = *p;
+	*p++ = 0;
+	if(sep == '<' && *p == '|'){
+		sep = '|';
+		p++;
+	}
+	*attr = 0;
+	*prog = 0;
+	if(sep == '='){
+		pp = charin(p, termchars);	/* termchars is shell-dependent */
+		if (pp && *pp == '=') {
+			while (p != pp) {
+				n = chartorune(&r, p);
+				switch(r)
+				{
+				default:
+					SYNERR(-1);
+					fprint(2, "unknown attribute '%c'\n",*p);
+					Exit();
+				case 'U':
+					*attr = 1;
+					break;
+				}
+				p += n;
+			}
+			p++;		/* skip trailing '=' */
+		}
+	}
+	if((sep == ':') && *p && (*p != ' ') && (*p != '\t')){
+		while (*p) {
+			n = chartorune(&r, p);
+			if (r == ':')
+				break;
+			p += n;
+			switch(r)
+			{
+			default:
+				SYNERR(-1);
+				fprint(2, "unknown attribute '%c'\n", p[-1]);
+				Exit();
+			case 'D':
+				*attr |= DEL;
+				break;
+			case 'E':
+				*attr |= NOMINUSE;
+				break;
+			case 'n':
+				*attr |= NOVIRT;
+				break;
+			case 'N':
+				*attr |= NOREC;
+				break;
+			case 'P':
+				pp = utfrune(p, ':');
+				if (pp == 0 || *pp == 0)
+					goto eos;
+				*pp = 0;
+				*prog = strdup(p);
+				*pp = ':';
+				p = pp;
+				break;
+			case 'Q':
+				*attr |= QUIET;
+				break;
+			case 'R':
+				*attr |= REGEXP;
+				break;
+			case 'U':
+				*attr |= UPD;
+				break;
+			case 'V':
+				*attr |= VIR;
+				break;
+			}
+		}
+		if (*p++ != ':') {
+	eos:
+			SYNERR(-1);
+			fprint(2, "missing trailing :\n");
+			Exit();
+		}
+	}
+	*h = w = stow(line);
+	if(*w->s == 0 && sep != '<' && sep != '|') {
+		SYNERR(mkinline-1);
+		fprint(2, "no var on left side of assignment/rule\n");
+		Exit();
+	}
+	*t = stow(p);
+	return(sep);
+}
+
+static char *
+rbody(Biobuf *in)
+{
+	Bufblock *buf;
+	int r, lastr;
+	char *p;
+
+	lastr = '\n';
+	buf = newbuf();
+	for(;;){
+		r = Bgetrune(in);
+		if (r < 0)
+			break;
+		if (lastr == '\n') {
+			if (r == '#')
+				rinsert(buf, r);
+			else if (r != ' ' && r != '\t') {
+				Bungetrune(in);
+				break;
+			}
+		} else
+			rinsert(buf, r);
+		lastr = r;
+		if (r == '\n')
+			mkinline++;
+	}
+	insert(buf, 0);
+	p = strdup(buf->start);
+	freebuf(buf);
+	return p;
+}
+
+struct input
+{
+	char *file;
+	int line;
+	struct input *next;
+};
+static struct input *inputs = 0;
+
+void
+ipush(void)
+{
+	struct input *in, *me;
+
+	me = (struct input *)Malloc(sizeof(*me));
+	me->file = infile;
+	me->line = mkinline;
+	me->next = 0;
+	if(inputs == 0)
+		inputs = me;
+	else {
+		for(in = inputs; in->next; )
+			in = in->next;
+		in->next = me;
+	}
+}
+
+void
+ipop(void)
+{
+	struct input *in, *me;
+
+	assert("pop input list", inputs != 0);
+	if(inputs->next == 0){
+		me = inputs;
+		inputs = 0;
+	} else {
+		for(in = inputs; in->next->next; )
+			in = in->next;
+		me = in->next;
+		in->next = 0;
+	}
+	infile = me->file;
+	mkinline = me->line;
+	free((char *)me);
+}
diff --git a/src/cmd/mk/rc.c b/src/cmd/mk/rc.c
new file mode 100644
index 0000000..657ddf2
--- /dev/null
+++ b/src/cmd/mk/rc.c
@@ -0,0 +1,175 @@
+#include	"mk.h"
+
+char	*termchars = "'= \t";	/*used in parse.c to isolate assignment attribute*/
+char	*shflags = "-I";	/* rc flag to force non-interactive mode */
+int	IWS = '\1';		/* inter-word separator in env - not used in plan 9 */
+
+/*
+ *	This file contains functions that depend on rc's syntax.  Most
+ *	of the routines extract strings observing rc's escape conventions
+ */
+
+
+/*
+ *	skip a token in single quotes.
+ */
+static char *
+squote(char *cp)
+{
+	Rune r;
+	int n;
+
+	while(*cp){
+		n = chartorune(&r, cp);
+		if(r == '\'') {
+			n += chartorune(&r, cp+n);
+			if(r != '\'')
+				return(cp);
+		}
+		cp += n;
+	}
+	SYNERR(-1);		/* should never occur */
+	fprint(2, "missing closing '\n");
+	return 0;
+}
+
+/*
+ *	search a string for characters in a pattern set
+ *	characters in quotes and variable generators are escaped
+ */
+char *
+charin(char *cp, char *pat)
+{
+	Rune r;
+	int n, vargen;
+
+	vargen = 0;
+	while(*cp){
+		n = chartorune(&r, cp);
+		switch(r){
+		case '\'':			/* skip quoted string */
+			cp = squote(cp+1);	/* n must = 1 */
+			if(!cp)
+				return 0;
+			break;
+		case '$':
+			if(*(cp+1) == '{')
+				vargen = 1;
+			break;
+		case '}':
+			if(vargen)
+				vargen = 0;
+			else if(utfrune(pat, r))
+				return cp;
+			break;
+		default:
+			if(vargen == 0 && utfrune(pat, r))
+				return cp;
+			break;
+		}
+		cp += n;
+	}
+	if(vargen){
+		SYNERR(-1);
+		fprint(2, "missing closing } in pattern generator\n");
+	}
+	return 0;
+}
+
+/*
+ *	extract an escaped token.  Possible escape chars are single-quote,
+ *	double-quote,and backslash.  Only the first is valid for rc. the
+ *	others are just inserted into the receiving buffer.
+ */
+char*
+expandquote(char *s, Rune r, Bufblock *b)
+{
+	if (r != '\'') {
+		rinsert(b, r);
+		return s;
+	}
+
+	while(*s){
+		s += chartorune(&r, s);
+		if(r == '\'') {
+			if(*s == '\'')
+				s++;
+			else
+				return s;
+		}
+		rinsert(b, r);
+	}
+	return 0;
+}
+
+/*
+ *	Input an escaped token.  Possible escape chars are single-quote,
+ *	double-quote and backslash.  Only the first is a valid escape for
+ *	rc; the others are just inserted into the receiving buffer.
+ */
+int
+escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
+{
+	int c, line;
+
+	if(esc != '\'')
+		return 1;
+
+	line = mkinline;
+	while((c = nextrune(bp, 0)) > 0){
+		if(c == '\''){
+			if(preserve)
+				rinsert(buf, c);
+			c = Bgetrune(bp);
+			if (c < 0)
+				break;
+			if(c != '\''){
+				Bungetrune(bp);
+				return 1;
+			}
+		}
+		rinsert(buf, c);
+	}
+	SYNERR(line); fprint(2, "missing closing %c\n", esc);
+	return 0;
+}
+
+/*
+ *	copy a single-quoted string; s points to char after opening quote
+ */
+static char *
+copysingle(char *s, Bufblock *buf)
+{
+	Rune r;
+
+	while(*s){
+		s += chartorune(&r, s);
+		rinsert(buf, r);
+		if(r == '\'')
+			break;
+	}
+	return s;
+}
+/*
+ *	check for quoted strings.  backquotes are handled here; single quotes above.
+ *	s points to char after opening quote, q.
+ */
+char *
+copyq(char *s, Rune q, Bufblock *buf)
+{
+	if(q == '\'')				/* copy quoted string */
+		return copysingle(s, buf);
+
+	if(q != '`')				/* not quoted */
+		return s;
+
+	while(*s){				/* copy backquoted string */
+		s += chartorune(&q, s);
+		rinsert(buf, q);
+		if(q == '}')
+			break;
+		if(q == '\'')
+			s = copysingle(s, buf);	/* copy quoted string */
+	}
+	return s;
+}
diff --git a/src/cmd/mk/recipe.c b/src/cmd/mk/recipe.c
new file mode 100644
index 0000000..144a490
--- /dev/null
+++ b/src/cmd/mk/recipe.c
@@ -0,0 +1,117 @@
+#include	"mk.h"
+
+int
+dorecipe(Node *node)
+{
+	char buf[BIGBLOCK];
+	register Node *n;
+	Rule *r = 0;
+	Arc *a, *aa;
+	Word head, ahead, lp, ln, *w, *ww, *aw;
+	Symtab *s;
+	int did = 0;
+
+	aa = 0;
+	/*
+		pick up the rule
+	*/
+	for(a = node->prereqs; a; a = a->next)
+		if(*a->r->recipe)
+			r = (aa = a)->r;
+	/*
+		no recipe? go to buggery!
+	*/
+	if(r == 0){
+		if(!(node->flags&VIRTUAL) && !(node->flags&NORECIPE)){
+			fprint(2, "mk: no recipe to make '%s'\n", node->name);
+			Exit();
+		}
+		if(strchr(node->name, '(') && node->time == 0)
+			MADESET(node, MADE);
+		else
+			update(0, node);
+		if(tflag){
+			if(!(node->flags&VIRTUAL))
+				touch(node->name);
+			else if(explain)
+				Bprint(&bout, "no touch of virtual '%s'\n", node->name);
+		}
+		return(did);
+	}
+	/*
+		build the node list
+	*/
+	node->next = 0;
+	head.next = 0;
+	ww = &head;
+	ahead.next = 0;
+	aw = &ahead;
+	if(r->attr&REGEXP){
+		ww->next = newword(node->name);
+		aw->next = newword(node->name);
+	} else {
+		for(w = r->alltargets; w; w = w->next){
+			if(r->attr&META)
+				subst(aa->stem, w->s, buf);
+			else
+				strcpy(buf, w->s);
+			aw->next = newword(buf);
+			aw = aw->next;
+			if((s = symlook(buf, S_NODE, 0)) == 0)
+				continue;	/* not a node we are interested in */
+			n = (Node *)s->value;
+			if(aflag == 0 && n->time) {
+				for(a = n->prereqs; a; a = a->next)
+					if(a->n && outofdate(n, a, 0))
+						break;
+				if(a == 0)
+					continue;
+			}
+			ww->next = newword(buf);
+			ww = ww->next;
+			if(n == node) continue;
+			n->next = node->next;
+			node->next = n;
+		}
+	}
+	for(n = node; n; n = n->next)
+		if((n->flags&READY) == 0)
+			return(did);
+	/*
+		gather the params for the job
+	*/
+	lp.next = ln.next = 0;
+	for(n = node; n; n = n->next){
+		for(a = n->prereqs; a; a = a->next){
+			if(a->n){
+				addw(&lp, a->n->name);
+				if(outofdate(n, a, 0)){
+					addw(&ln, a->n->name);
+					if(explain)
+						fprint(1, "%s(%ld) < %s(%ld)\n",
+							n->name, n->time, a->n->name, a->n->time);
+				}
+			} else {
+				if(explain)
+					fprint(1, "%s has no prerequisites\n",
+							n->name);
+			}
+		}
+		MADESET(n, BEINGMADE);
+	}
+	/*print("lt=%s ln=%s lp=%s\n",wtos(head.next, ' '),wtos(ln.next, ' '),wtos(lp.next, ' '));*//**/
+	run(newjob(r, node, aa->stem, aa->match, lp.next, ln.next, head.next, ahead.next));
+	return(1);
+}
+
+void
+addw(Word *w, char *s)
+{
+	Word *lw;
+
+	for(lw = w; w = w->next; lw = w){
+		if(strcmp(s, w->s) == 0)
+			return;
+	}
+	lw->next = newword(s);
+}
diff --git a/src/cmd/mk/rpm.spec b/src/cmd/mk/rpm.spec
new file mode 100644
index 0000000..be75e6b
--- /dev/null
+++ b/src/cmd/mk/rpm.spec
@@ -0,0 +1,29 @@
+Summary: Streamlined replacement for make
+Name: mk
+Version: 2.0
+Release: 1
+Group: Development/Utils
+Copyright: Public Domain
+Packager: Russ Cox <rsc@post.harvard.edu>
+Source: http://pdos.lcs.mit.edu/~rsc/software/mk-2.0.tgz
+URL: http://pdos.lcs.mit.edu/~rsc/software/#mk
+Requires: libfmt libbio libregexp9 libutf
+
+%description
+Mk is a streamlined replacement for make, written for 
+Tenth Edition Research Unix by Andrew Hume.
+
+http://plan9.bell-labs.com/sys/doc/mk.pdf
+%prep
+%setup
+
+%build
+make
+
+%install
+make install
+
+%files
+/usr/local/doc/mk.pdf
+/usr/local/man/man1/mk.1
+/usr/local/bin/mk
diff --git a/src/cmd/mk/rule.c b/src/cmd/mk/rule.c
new file mode 100644
index 0000000..662f067
--- /dev/null
+++ b/src/cmd/mk/rule.c
@@ -0,0 +1,107 @@
+#include	"mk.h"
+
+static Rule *lr, *lmr;
+static int rcmp(Rule *r, char *target, Word *tail);
+static int nrules = 0;
+
+void
+addrule(char *head, Word *tail, char *body, Word *ahead, int attr, int hline, char *prog)
+{
+	Rule *r;
+	Rule *rr;
+	Symtab *sym;
+	int reuse;
+
+	r = 0;
+	reuse = 0;
+	if(sym = symlook(head, S_TARGET, 0)){
+		for(r = (Rule *)sym->value; r; r = r->chain)
+			if(rcmp(r, head, tail) == 0){
+				reuse = 1;
+				break;
+			}
+	}
+	if(r == 0)
+		r = (Rule *)Malloc(sizeof(Rule));
+	r->target = head;
+	r->tail = tail;
+	r->recipe = body;
+	r->line = hline;
+	r->file = infile;
+	r->attr = attr;
+	r->alltargets = ahead;
+	r->prog = prog;
+	r->rule = nrules++;
+	if(!reuse){
+		rr = (Rule *)symlook(head, S_TARGET, (void *)r)->value;
+		if(rr != r){
+			r->chain = rr->chain;
+			rr->chain = r;
+		} else
+			r->chain = 0;
+	}
+	if(!reuse)
+		r->next = 0;
+	if((attr&REGEXP) || charin(head, "%&")){
+		r->attr |= META;
+		if(reuse)
+			return;
+		if(attr&REGEXP){
+			patrule = r;
+			r->pat = regcomp(head);
+		}
+		if(metarules == 0)
+			metarules = lmr = r;
+		else {
+			lmr->next = r;
+			lmr = r;
+		}
+	} else {
+		if(reuse)
+			return;
+		r->pat = 0;
+		if(rules == 0)
+			rules = lr = r;
+		else {
+			lr->next = r;
+			lr = r;
+		}
+	}
+}
+
+void
+dumpr(char *s, Rule *r)
+{
+	Bprint(&bout, "%s: start=%ld\n", s, r);
+	for(; r; r = r->next){
+		Bprint(&bout, "\tRule %ld: %s[%d] attr=%x next=%ld chain=%ld alltarget='%s'",
+			r, r->file, r->line, r->attr, r->next, r->chain, wtos(r->alltargets, ' '));
+		if(r->prog)
+			Bprint(&bout, " prog='%s'", r->prog);
+		Bprint(&bout, "\n\ttarget=%s: %s\n", r->target, wtos(r->tail, ' '));
+		Bprint(&bout, "\trecipe@%ld='%s'\n", r->recipe, r->recipe);
+	}
+}
+
+static int
+rcmp(Rule *r, char *target, Word *tail)
+{
+	Word *w;
+
+	if(strcmp(r->target, target))
+		return 1;
+	for(w = r->tail; w && tail; w = w->next, tail = tail->next)
+		if(strcmp(w->s, tail->s))
+			return 1;
+	return(w || tail);
+}
+
+char *
+rulecnt(void)
+{
+	char *s;
+
+	s = Malloc(nrules);
+	memset(s, 0, nrules);
+	return(s);
+}
diff --git a/src/cmd/mk/run.c b/src/cmd/mk/run.c
new file mode 100644
index 0000000..3c0c7f3
--- /dev/null
+++ b/src/cmd/mk/run.c
@@ -0,0 +1,296 @@
+#include	"mk.h"
+
+typedef struct Event
+{
+	int pid;
+	Job *job;
+} Event;
+static Event *events;
+static int nevents, nrunning, nproclimit;
+
+typedef struct Process
+{
+	int pid;
+	int status;
+	struct Process *b, *f;
+} Process;
+static Process *phead, *pfree;
+static void sched(void);
+static void pnew(int, int), pdelete(Process *);
+
+int pidslot(int);
+
+void
+run(Job *j)
+{
+	Job *jj;
+
+	if(jobs){
+		for(jj = jobs; jj->next; jj = jj->next)
+			;
+		jj->next = j;
+	} else 
+		jobs = j;
+	j->next = 0;
+	/* this code also in waitup after parse redirect */
+	if(nrunning < nproclimit)
+		sched();
+}
+
+static void
+sched(void)
+{
+	char *flags;
+	Job *j;
+	Bufblock *buf;
+	int slot;
+	Node *n;
+	Envy *e;
+
+	if(jobs == 0){
+		usage();
+		return;
+	}
+	j = jobs;
+	jobs = j->next;
+	if(DEBUG(D_EXEC))
+		fprint(1, "firing up job for target %s\n", wtos(j->t, ' '));
+	slot = nextslot();
+	events[slot].job = j;
+	buf = newbuf();
+	e = buildenv(j, slot);
+	shprint(j->r->recipe, e, buf);
+	if(!tflag && (nflag || !(j->r->attr&QUIET)))
+		Bwrite(&bout, buf->start, (long)strlen(buf->start));
+	freebuf(buf);
+	if(nflag||tflag){
+		for(n = j->n; n; n = n->next){
+			if(tflag){
+				if(!(n->flags&VIRTUAL))
+					touch(n->name);
+				else if(explain)
+					Bprint(&bout, "no touch of virtual '%s'\n", n->name);
+			}
+			n->time = time((long *)0);
+			MADESET(n, MADE);
+		}
+	} else {
+		if(DEBUG(D_EXEC))
+			fprint(1, "recipe='%s'", j->r->recipe);/**/
+		Bflush(&bout);
+		if(j->r->attr&NOMINUSE)
+			flags = 0;
+		else
+			flags = "-e";
+		events[slot].pid = execsh(flags, j->r->recipe, 0, e);
+		usage();
+		nrunning++;
+		if(DEBUG(D_EXEC))
+			fprint(1, "pid for target %s = %d\n", wtos(j->t, ' '), events[slot].pid);
+	}
+}
+
+int
+waitup(int echildok, int *retstatus)
+{
+	Envy *e;
+	int pid;
+	int slot;
+	Symtab *s;
+	Word *w;
+	Job *j;
+	char buf[ERRMAX];
+	Bufblock *bp;
+	int uarg = 0;
+	int done;
+	Node *n;
+	Process *p;
+	extern int runerrs;
+
+	/* first check against the proces slist */
+	if(retstatus)
+		for(p = phead; p; p = p->f)
+			if(p->pid == *retstatus){
+				*retstatus = p->status;
+				pdelete(p);
+				return(-1);
+			}
+again:		/* rogue processes */
+	pid = waitfor(buf);
+	if(pid == -1){
+		if(echildok > 0)
+			return(1);
+		else {
+			fprint(2, "mk: (waitup %d): %r\n", echildok);
+			Exit();
+		}
+	}
+	if(DEBUG(D_EXEC))
+		fprint(1, "waitup got pid=%d, status='%s'\n", pid, buf);
+	if(retstatus && pid == *retstatus){
+		*retstatus = buf[0]? 1:0;
+		return(-1);
+	}
+	slot = pidslot(pid);
+	if(slot < 0){
+		if(DEBUG(D_EXEC))
+			fprint(2, "mk: wait returned unexpected process %d\n", pid);
+		pnew(pid, buf[0]? 1:0);
+		goto again;
+	}
+	j = events[slot].job;
+	usage();
+	nrunning--;
+	events[slot].pid = -1;
+	if(buf[0]){
+		e = buildenv(j, slot);
+		bp = newbuf();
+		shprint(j->r->recipe, e, bp);
+		front(bp->start);
+		fprint(2, "mk: %s: exit status=%s", bp->start, buf);
+		freebuf(bp);
+		for(n = j->n, done = 0; n; n = n->next)
+			if(n->flags&DELETE){
+				if(done++ == 0)
+					fprint(2, ", deleting");
+				fprint(2, " '%s'", n->name);
+				delete(n->name);
+			}
+		fprint(2, "\n");
+		if(kflag){
+			runerrs++;
+			uarg = 1;
+		} else {
+			jobs = 0;
+			Exit();
+		}
+	}
+	for(w = j->t; w; w = w->next){
+		if((s = symlook(w->s, S_NODE, 0)) == 0)
+			continue;	/* not interested in this node */
+		update(uarg, (Node *)s->value);
+	}
+	if(nrunning < nproclimit)
+		sched();
+	return(0);
+}
+
+void
+nproc(void)
+{
+	Symtab *sym;
+	Word *w;
+
+	if(sym = symlook("NPROC", S_VAR, 0)) {
+		w = (Word *) sym->value;
+		if (w && w->s && w->s[0])
+			nproclimit = atoi(w->s);
+	}
+	if(nproclimit < 1)
+		nproclimit = 1;
+	if(DEBUG(D_EXEC))
+		fprint(1, "nprocs = %d\n", nproclimit);
+	if(nproclimit > nevents){
+		if(nevents)
+			events = (Event *)Realloc((char *)events, nproclimit*sizeof(Event));
+		else
+			events = (Event *)Malloc(nproclimit*sizeof(Event));
+		while(nevents < nproclimit)
+			events[nevents++].pid = 0;
+	}
+}
+
+int
+nextslot(void)
+{
+	int i;
+
+	for(i = 0; i < nproclimit; i++)
+		if(events[i].pid <= 0) return i;
+	assert("out of slots!!", 0);
+	return 0;	/* cyntax */
+}
+
+int
+pidslot(int pid)
+{
+	int i;
+
+	for(i = 0; i < nevents; i++)
+		if(events[i].pid == pid) return(i);
+	if(DEBUG(D_EXEC))
+		fprint(2, "mk: wait returned unexpected process %d\n", pid);
+	return(-1);
+}
+
+
+static void
+pnew(int pid, int status)
+{
+	Process *p;
+
+	if(pfree){
+		p = pfree;
+		pfree = p->f;
+	} else
+		p = (Process *)Malloc(sizeof(Process));
+	p->pid = pid;
+	p->status = status;
+	p->f = phead;
+	phead = p;
+	if(p->f)
+		p->f->b = p;
+	p->b = 0;
+}
+
+static void
+pdelete(Process *p)
+{
+	if(p->f)
+		p->f->b = p->b;
+	if(p->b)
+		p->b->f = p->f;
+	else
+		phead = p->f;
+	p->f = pfree;
+	pfree = p;
+}
+
+void
+killchildren(char *msg)
+{
+	Process *p;
+
+	kflag = 1;	/* to make sure waitup doesn't exit */
+	jobs = 0;	/* make sure no more get scheduled */
+	for(p = phead; p; p = p->f)
+		expunge(p->pid, msg);
+	while(waitup(1, (int *)0) == 0)
+		;
+	Bprint(&bout, "mk: %s\n", msg);
+	Exit();
+}
+
+static long tslot[1000];
+static long tick;
+
+void
+usage(void)
+{
+	long t;
+
+	time(&t);
+	if(tick)
+		tslot[nrunning] += (t-tick);
+	tick = t;
+}
+
+void
+prusage(void)
+{
+	int i;
+
+	usage();
+	for(i = 0; i <= nevents; i++)
+		fprint(1, "%d: %ld\n", i, tslot[i]);
+}
diff --git a/src/cmd/mk/sh.c b/src/cmd/mk/sh.c
new file mode 100644
index 0000000..524167a
--- /dev/null
+++ b/src/cmd/mk/sh.c
@@ -0,0 +1,189 @@
+#include	"mk.h"
+
+char	*termchars = "\"'= \t";	/*used in parse.c to isolate assignment attribute*/
+char	*shflags = 0;
+int	IWS = ' ';		/* inter-word separator in env */
+
+/*
+ *	This file contains functions that depend on the shell's syntax.  Most
+ *	of the routines extract strings observing the shell's escape conventions.
+ */
+
+
+/*
+ *	skip a token in quotes.
+ */
+static char *
+squote(char *cp, int c)
+{
+	Rune r;
+	int n;
+
+	while(*cp){
+		n = chartorune(&r, cp);
+		if(r == c)
+			return cp;
+		if(r == '\\')
+			n += chartorune(&r, cp+n);
+		cp += n;
+	}
+	SYNERR(-1);		/* should never occur */
+	fprint(2, "missing closing '\n");
+	return 0;
+}
+/*
+ *	search a string for unescaped characters in a pattern set
+ */
+char *
+charin(char *cp, char *pat)
+{
+	Rune r;
+	int n, vargen;
+
+	vargen = 0;
+	while(*cp){
+		n = chartorune(&r, cp);
+		switch(r){
+		case '\\':			/* skip escaped char */
+			cp += n;
+			n = chartorune(&r, cp);
+			break;
+		case '\'':			/* skip quoted string */
+		case '"':
+			cp = squote(cp+1, r);	/* n must = 1 */
+			if(!cp)
+				return 0;
+			break;
+		case '$':
+			if(*(cp+1) == '{')
+				vargen = 1;
+			break;
+		case '}':
+			if(vargen)
+				vargen = 0;
+			else if(utfrune(pat, r))
+				return cp;
+			break;
+		default:
+			if(vargen == 0 && utfrune(pat, r))
+				return cp;
+			break;
+		}
+		cp += n;
+	}
+	if(vargen){
+		SYNERR(-1);
+		fprint(2, "missing closing } in pattern generator\n");
+	}
+	return 0;
+}
+
+/*
+ *	extract an escaped token.  Possible escape chars are single-quote,
+ *	double-quote,and backslash.
+ */
+char*
+expandquote(char *s, Rune esc, Bufblock *b)
+{
+	Rune r;
+
+	if (esc == '\\') {
+		s += chartorune(&r, s);
+		rinsert(b, r);
+		return s;
+	}
+
+	while(*s){
+		s += chartorune(&r, s);
+		if(r == esc)
+			return s;
+		if (r == '\\') {
+			rinsert(b, r);
+			s += chartorune(&r, s);
+		}
+		rinsert(b, r);
+	}
+	return 0;
+}
+
+/*
+ *	Input an escaped token.  Possible escape chars are single-quote,
+ *	double-quote and backslash.
+ */
+int
+escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
+{
+	int c, line;
+
+	if(esc == '\\') {
+		c = Bgetrune(bp);
+		if(c == '\r')
+			c = Bgetrune(bp);
+		if (c == '\n')
+			mkinline++;
+		rinsert(buf, c);
+		return 1;
+	}
+
+	line = mkinline;
+	while((c = nextrune(bp, 0)) >= 0){
+		if(c == esc){
+			if(preserve)
+				rinsert(buf, c);
+			return 1;
+		}
+		if(c == '\\') {
+			rinsert(buf, c);
+			c = Bgetrune(bp);
+			if(c == '\r')
+				c = Bgetrune(bp);
+			if (c < 0)
+				break;
+			if (c == '\n')
+				mkinline++;
+		}
+		rinsert(buf, c);
+	}
+	SYNERR(line); fprint(2, "missing closing %c\n", esc);
+	return 0;
+}
+
+/*
+ *	copy a quoted string; s points to char after opening quote
+ */
+static char *
+copysingle(char *s, Rune q, Bufblock *buf)
+{
+	Rune r;
+
+	while(*s){
+		s += chartorune(&r, s);
+		rinsert(buf, r);
+		if(r == q)
+			break;
+	}
+	return s;
+}
+/*
+ *	check for quoted strings.  backquotes are handled here; single quotes above.
+ *	s points to char after opening quote, q.
+ */
+char *
+copyq(char *s, Rune q, Bufblock *buf)
+{
+	if(q == '\'' || q == '"')		/* copy quoted string */
+		return copysingle(s, q, buf);
+
+	if(q != '`')				/* not quoted */
+		return s;
+
+	while(*s){				/* copy backquoted string */
+		s += chartorune(&q, s);
+		rinsert(buf, q);
+		if(q == '`')
+			break;
+		if(q == '\'' || q == '"')
+			s = copysingle(s, q, buf);	/* copy quoted string */
+	}
+	return s;
+}
diff --git a/src/cmd/mk/shprint.c b/src/cmd/mk/shprint.c
new file mode 100644
index 0000000..9e15bce
--- /dev/null
+++ b/src/cmd/mk/shprint.c
@@ -0,0 +1,123 @@
+#include	"mk.h"
+
+static char *vexpand(char*, Envy*, Bufblock*);
+
+static int
+getfields(char *str, char **args, int max, int mflag, char *set)
+{
+	Rune r;
+	int nr, intok, narg;
+
+	if(max <= 0)
+		return 0;
+
+	narg = 0;
+	args[narg] = str;
+	if(!mflag)
+		narg++;
+	intok = 0;
+	for(;; str += nr) {
+		nr = chartorune(&r, str);
+		if(r == 0)
+			break;
+		if(utfrune(set, r)) {
+			if(narg >= max)
+				break;
+			*str = 0;
+			intok = 0;
+			args[narg] = str + nr;
+			if(!mflag)
+				narg++;
+		} else {
+			if(!intok && mflag)
+				narg++;
+			intok = 1;
+		}
+	}
+	return narg;
+}
+
+void
+shprint(char *s, Envy *env, Bufblock *buf)
+{
+	int n;
+	Rune r;
+
+	while(*s) {
+		n = chartorune(&r, s);
+		if (r == '$')
+			s = vexpand(s, env, buf);
+		else {
+			rinsert(buf, r);
+			s += n;
+			s = copyq(s, r, buf);	/*handle quoted strings*/
+		}
+	}
+	insert(buf, 0);
+}
+
+static char *
+mygetenv(char *name, Envy *env)
+{
+	if (!env)
+		return 0;
+	if (symlook(name, S_WESET, 0) == 0 && symlook(name, S_INTERNAL, 0) == 0)
+		return 0;
+		/* only resolve internal variables and variables we've set */
+	for(; env->name; env++){
+		if (strcmp(env->name, name) == 0)
+			return wtos(env->values, ' ');
+	}
+	return 0;
+}
+
+static char *
+vexpand(char *w, Envy *env, Bufblock *buf)
+{
+	char *s, carry, *p, *q;
+
+	assert("vexpand no $", *w == '$');
+	p = w+1;	/* skip dollar sign */
+	if(*p == '{') {
+		p++;
+		q = utfrune(p, '}');
+		if (!q)
+			q = strchr(p, 0);
+	} else
+		q = shname(p);
+	carry = *q;
+	*q = 0;
+	s = mygetenv(p, env);
+	*q = carry;
+	if (carry == '}')
+		q++;
+	if (s) {
+		bufcpy(buf, s, strlen(s));
+		free(s);
+	} else 		/* copy name intact*/
+		bufcpy(buf, w, q-w);
+	return(q);
+}
+
+void
+front(char *s)
+{
+	char *t, *q;
+	int i, j;
+	char *flds[512];
+
+	q = strdup(s);
+	i = getfields(q, flds, 512, 0, " \t\n");
+	if(i > 5){
+		flds[4] = flds[i-1];
+		flds[3] = "...";
+		i = 5;
+	}
+	t = s;
+	for(j = 0; j < i; j++){
+		for(s = flds[j]; *s; *t++ = *s++);
+		*t++ = ' ';
+	}
+	*t = 0;
+	free(q);
+}
diff --git a/src/cmd/mk/symtab.c b/src/cmd/mk/symtab.c
new file mode 100644
index 0000000..6f7b888
--- /dev/null
+++ b/src/cmd/mk/symtab.c
@@ -0,0 +1,95 @@
+#include	"mk.h"
+
+#define	NHASH	4099
+#define	HASHMUL	79L	/* this is a good value */
+static Symtab *hash[NHASH];
+
+void
+syminit(void)
+{
+	Symtab **s, *ss;
+
+	for(s = hash; s < &hash[NHASH]; s++){
+		for(ss = *s; ss; ss = ss->next)
+			free((char *)ss);
+		*s = 0;
+	}
+}
+
+Symtab *
+symlook(char *sym, int space, void *install)
+{
+	long h;
+	char *p;
+	Symtab *s;
+
+	for(p = sym, h = space; *p; h += *p++)
+		h *= HASHMUL;
+	if(h < 0)
+		h = ~h;
+	h %= NHASH;
+	for(s = hash[h]; s; s = s->next)
+		if((s->space == space) && (strcmp(s->name, sym) == 0))
+			return(s);
+	if(install == 0)
+		return(0);
+	s = (Symtab *)Malloc(sizeof(Symtab));
+	s->space = space;
+	s->name = sym;
+	s->value = install;
+	s->next = hash[h];
+	hash[h] = s;
+	return(s);
+}
+
+void
+symdel(char *sym, int space)
+{
+	long h;
+	char *p;
+	Symtab *s, *ls;
+
+	/* multiple memory leaks */
+
+	for(p = sym, h = space; *p; h += *p++)
+		h *= HASHMUL;
+	if(h < 0)
+		h = ~h;
+	h %= NHASH;
+	for(s = hash[h], ls = 0; s; ls = s, s = s->next)
+		if((s->space == space) && (strcmp(s->name, sym) == 0)){
+			if(ls)
+				ls->next = s->next;
+			else
+				hash[h] = s->next;
+			free((char *)s);
+		}
+}
+
+void
+symtraverse(int space, void (*fn)(Symtab*))
+{
+	Symtab **s, *ss;
+
+	for(s = hash; s < &hash[NHASH]; s++)
+		for(ss = *s; ss; ss = ss->next)
+			if(ss->space == space)
+				(*fn)(ss);
+}
+
+void
+symstat(void)
+{
+	Symtab **s, *ss;
+	int n;
+	int l[1000];
+
+	memset((char *)l, 0, sizeof(l));
+	for(s = hash; s < &hash[NHASH]; s++){
+		for(ss = *s, n = 0; ss; ss = ss->next)
+			n++;
+		l[n]++;
+	}
+	for(n = 0; n < 1000; n++)
+		if(l[n]) Bprint(&bout, "%ld of length %d\n", l[n], n);
+}
diff --git a/src/cmd/mk/unix.c b/src/cmd/mk/unix.c
new file mode 100644
index 0000000..6022278
--- /dev/null
+++ b/src/cmd/mk/unix.c
@@ -0,0 +1,306 @@
+#include	"mk.h"
+#include	<sys/wait.h>
+#include	<signal.h>
+#include	<sys/stat.h>
+#include	<sys/time.h>
+
+char	*shell = "/bin/sh";
+char	*shellname = "sh";
+
+extern char **environ;
+
+static void
+mkperror(char *s)
+{
+	fprint(2, "%s: %r\n", s);
+}
+
+void
+readenv(void)
+{
+	char **p, *s;
+	Word *w;
+
+	for(p = environ; *p; p++){
+		s = shname(*p);
+		if(*s == '=') {
+			*s = 0;
+			w = newword(s+1);
+		} else
+			w = newword("");
+		if (symlook(*p, S_INTERNAL, 0))
+			continue;
+		s = strdup(*p);
+		setvar(s, (void *)w);
+		symlook(s, S_EXPORTED, (void*)"")->value = (void*)"";
+	}
+}
+
+/*
+ *	done on child side of fork, so parent's env is not affected
+ *	and we don't care about freeing memory because we're going
+ *	to exec immediately after this.
+ */
+void
+exportenv(Envy *e)
+{
+	int i;
+	char **p;
+	char buf[4096];
+
+	p = 0;
+	for(i = 0; e->name; e++, i++) {
+		p = (char**) Realloc(p, (i+2)*sizeof(char*));
+		if(e->values)
+			sprint(buf, "%s=%s", e->name,  wtos(e->values, IWS));
+		else
+			sprint(buf, "%s=", e->name);
+		p[i] = strdup(buf);
+	}
+	p[i] = 0;
+	environ = p;
+}
+
+int
+waitfor(char *msg)
+{
+	int status;
+	int pid;
+
+	*msg = 0;
+	pid = wait(&status);
+	if(pid > 0) {
+		if(status&0x7f) {
+			if(status&0x80)
+				snprint(msg, ERRMAX, "signal %d, core dumped", status&0x7f);
+			else
+				snprint(msg, ERRMAX, "signal %d", status&0x7f);
+		} else if(status&0xff00)
+			snprint(msg, ERRMAX, "exit(%d)", (status>>8)&0xff);
+	}
+	return pid;
+}
+
+void
+expunge(int pid, char *msg)
+{
+	if(strcmp(msg, "interrupt"))
+		kill(pid, SIGINT);
+	else
+		kill(pid, SIGHUP);
+}
+
+int
+execsh(char *args, char *cmd, Bufblock *buf, Envy *e)
+{
+	char *p;
+	int tot, n, pid, in[2], out[2];
+
+	if(buf && pipe(out) < 0){
+		mkperror("pipe");
+		Exit();
+	}
+	pid = fork();
+	if(pid < 0){
+		mkperror("mk fork");
+		Exit();
+	}
+	if(pid == 0){
+		if(buf)
+			close(out[0]);
+		if(pipe(in) < 0){
+			mkperror("pipe");
+			Exit();
+		}
+		pid = fork();
+		if(pid < 0){
+			mkperror("mk fork");
+			Exit();
+		}
+		if(pid != 0){
+			dup2(in[0], 0);
+			if(buf){
+				dup2(out[1], 1);
+				close(out[1]);
+			}
+			close(in[0]);
+			close(in[1]);
+			if (e)
+				exportenv(e);
+			if(shflags)
+				execl(shell, shellname, shflags, args, 0);
+			else
+				execl(shell, shellname, args, 0);
+			mkperror(shell);
+			_exit(1);
+		}
+		close(out[1]);
+		close(in[0]);
+		if(DEBUG(D_EXEC))
+			fprint(1, "starting: %s\n", cmd);
+		p = cmd+strlen(cmd);
+		while(cmd < p){
+			n = write(in[1], cmd, p-cmd);
+			if(n < 0)
+				break;
+			cmd += n;
+		}
+		close(in[1]);
+		_exit(0);
+	}
+	if(buf){
+		close(out[1]);
+		tot = 0;
+		for(;;){
+			if (buf->current >= buf->end)
+				growbuf(buf);
+			n = read(out[0], buf->current, buf->end-buf->current);
+			if(n <= 0)
+				break;
+			buf->current += n;
+			tot += n;
+		}
+		if (tot && buf->current[-1] == '\n')
+			buf->current--;
+		close(out[0]);
+	}
+	return pid;
+}
+
+int
+pipecmd(char *cmd, Envy *e, int *fd)
+{
+	int pid, pfd[2];
+
+	if(DEBUG(D_EXEC))
+		fprint(1, "pipecmd='%s'\n", cmd);/**/
+
+	if(fd && pipe(pfd) < 0){
+		mkperror("pipe");
+		Exit();
+	}
+	pid = fork();
+	if(pid < 0){
+		mkperror("mk fork");
+		Exit();
+	}
+	if(pid == 0){
+		if(fd){
+			close(pfd[0]);
+			dup2(pfd[1], 1);
+			close(pfd[1]);
+		}
+		if(e)
+			exportenv(e);
+		if(shflags)
+			execl(shell, shellname, shflags, "-c", cmd, 0);
+		else
+			execl(shell, shellname, "-c", cmd, 0);
+		mkperror(shell);
+		_exit(1);
+	}
+	if(fd){
+		close(pfd[1]);
+		*fd = pfd[0];
+	}
+	return pid;
+}
+
+void
+Exit(void)
+{
+	while(wait(0) >= 0)
+		;
+	exits("error");
+}
+
+static	struct
+{
+	int	sig;
+	char	*msg;
+}	sigmsgs[] =
+{
+	SIGALRM,	"alarm",
+	SIGFPE,		"sys: fp: fptrap",
+	SIGPIPE,	"sys: write on closed pipe",
+	SIGILL,		"sys: trap: illegal instruction",
+	SIGSEGV,	"sys: segmentation violation",
+	0,		0
+};
+
+static void
+notifyf(int sig)
+{
+	int i;
+
+	for(i = 0; sigmsgs[i].msg; i++)
+		if(sigmsgs[i].sig == sig)
+			killchildren(sigmsgs[i].msg);
+
+	/* should never happen */
+	signal(sig, SIG_DFL);
+	kill(getpid(), sig);
+}
+
+void
+catchnotes()
+{
+	int i;
+
+	for(i = 0; sigmsgs[i].msg; i++)
+		signal(sigmsgs[i].sig, notifyf);
+}
+
+char*
+maketmp(int *pfd)
+{
+	static char temp[] = "/tmp/mkargXXXXXX";
+	static char buf[100];
+	int fd;
+
+	strcpy(buf, temp);
+	fd = mkstemp(buf);
+	if(fd < 0)
+		return 0;
+	*pfd = fd;
+	return buf;
+}
+
+int
+chgtime(char *name)
+{
+	if(access(name, 0) >= 0)
+		return utimes(name, 0);
+	return close(creat(name, 0666));
+}
+
+void
+rcopy(char **to, Resub *match, int n)
+{
+	int c;
+	char *p;
+
+	*to = match->s.sp;		/* stem0 matches complete target */
+	for(to++, match++; --n > 0; to++, match++){
+		if(match->s.sp && match->e.ep){
+			p = match->e.ep;
+			c = *p;
+			*p = 0;
+			*to = strdup(match->s.sp);
+			*p = c;
+		}
+		else
+			*to = 0;
+	}
+}
+
+ulong
+mkmtime(char *name)
+{
+	struct stat st;
+
+	if(stat(name, &st) < 0)
+		return 0;
+
+	return st.st_mtime;
+}
diff --git a/src/cmd/mk/var.c b/src/cmd/mk/var.c
new file mode 100644
index 0000000..8429918
--- /dev/null
+++ b/src/cmd/mk/var.c
@@ -0,0 +1,41 @@
+#include	"mk.h"
+
+void
+setvar(char *name, void *value)
+{
+	symlook(name, S_VAR, value)->value = value;
+	symlook(name, S_MAKEVAR, (void*)"");
+}
+
+static void
+print1(Symtab *s)
+{
+	Word *w;
+
+	Bprint(&bout, "\t%s=", s->name);
+	for (w = (Word *) s->value; w; w = w->next)
+		Bprint(&bout, "'%s'", w->s);
+	Bprint(&bout, "\n");
+}
+
+void
+dumpv(char *s)
+{
+	Bprint(&bout, "%s:\n", s);
+	symtraverse(S_VAR, print1);
+}
+
+char *
+shname(char *a)
+{
+	Rune r;
+	int n;
+
+	while (*a) {
+		n = chartorune(&r, a);
+		if (!WORDCHR(r))
+			break;
+		a += n;
+	}
+	return a;
+}
diff --git a/src/cmd/mk/varsub.c b/src/cmd/mk/varsub.c
new file mode 100644
index 0000000..2a9ad98
--- /dev/null
+++ b/src/cmd/mk/varsub.c
@@ -0,0 +1,256 @@
+#include	"mk.h"
+
+static	Word		*subsub(Word*, char*, char*);
+static	Word		*expandvar(char**);
+static	Bufblock	*varname(char**);
+static	Word		*extractpat(char*, char**, char*, char*);
+static	int		submatch(char*, Word*, Word*, int*, char**);
+static	Word		*varmatch(char *, char**);
+
+Word *
+varsub(char **s)
+{
+	Bufblock *b;
+	Word *w;
+
+	if(**s == '{')		/* either ${name} or ${name: A%B==C%D}*/
+		return expandvar(s);
+
+	b = varname(s);
+	if(b == 0)
+		return 0;
+
+	w = varmatch(b->start, s);
+	freebuf(b);
+	return w;
+}
+
+/*
+ *	extract a variable name
+ */
+static Bufblock*
+varname(char **s)
+{
+	Bufblock *b;
+	char *cp;
+	Rune r;
+	int n;
+
+	b = newbuf();
+	cp = *s;
+	for(;;){
+		n = chartorune(&r, cp);
+		if (!WORDCHR(r))
+			break;
+		rinsert(b, r);
+		cp += n;
+	}
+	if (b->current == b->start){
+		SYNERR(-1);
+		fprint(2, "missing variable name <%s>\n", *s);
+		freebuf(b);
+		return 0;
+	}
+	*s = cp;
+	insert(b, 0);
+	return b;
+}
+
+static Word*
+varmatch(char *name, char **s)
+{
+	Word *w;
+	Symtab *sym;
+	char *cp;
+	
+	sym = symlook(name, S_VAR, 0);
+	if(sym){
+			/* check for at least one non-NULL value */
+		for (w = (Word*)sym->value; w; w = w->next)
+			if(w->s && *w->s)
+				return wdup(w);
+	}
+	for(cp = *s; *cp == ' ' || *cp == '\t'; cp++)	/* skip trailing whitespace */
+			;
+	*s = cp;
+	return 0;
+}
+
+static Word*
+expandvar(char **s)
+{
+	Word *w;
+	Bufblock *buf;
+	Symtab *sym;
+	char *cp, *begin, *end;
+
+	begin = *s;
+	(*s)++;						/* skip the '{' */
+	buf = varname(s);
+	if (buf == 0)
+		return 0;
+	cp = *s;
+	if (*cp == '}') {				/* ${name} variant*/
+		(*s)++;					/* skip the '}' */
+		w = varmatch(buf->start, s);
+		freebuf(buf);
+		return w;
+	}
+	if (*cp != ':') {
+		SYNERR(-1);
+		fprint(2, "bad variable name <%s>\n", buf->start);
+		freebuf(buf);
+		return 0;
+	}
+	cp++;
+	end = charin(cp , "}");
+	if(end == 0){
+		SYNERR(-1);
+		fprint(2, "missing '}': %s\n", begin);
+		Exit();
+	}
+	*end = 0;
+	*s = end+1;
+	
+	sym = symlook(buf->start, S_VAR, 0);
+	if(sym == 0 || sym->value == 0)
+		w = newword(buf->start);
+	else
+		w = subsub((Word*) sym->value, cp, end);
+	freebuf(buf);
+	return w;
+}
+
+static Word*
+extractpat(char *s, char **r, char *term, char *end)
+{
+	int save;
+	char *cp;
+	Word *w;
+
+	cp = charin(s, term);
+	if(cp){
+		*r = cp;
+		if(cp == s)
+			return 0;
+		save = *cp;
+		*cp = 0;
+		w = stow(s);
+		*cp = save;
+	} else {
+		*r = end;
+		w = stow(s);
+	}
+	return w;
+}
+
+static Word*
+subsub(Word *v, char *s, char *end)
+{
+	int nmid;
+	Word *head, *tail, *w, *h;
+	Word *a, *b, *c, *d;
+	Bufblock *buf;
+	char *cp, *enda;
+
+	a = extractpat(s, &cp, "=%&", end);
+	b = c = d = 0;
+	if(PERCENT(*cp))
+		b = extractpat(cp+1, &cp, "=", end);
+	if(*cp == '=')
+		c = extractpat(cp+1, &cp, "&%", end);
+	if(PERCENT(*cp))
+		d = stow(cp+1);
+	else if(*cp)
+		d = stow(cp);
+
+	head = tail = 0;
+	buf = newbuf();
+	for(; v; v = v->next){
+		h = w = 0;
+		if(submatch(v->s, a, b, &nmid, &enda)){
+			/* enda points to end of A match in source;
+			 * nmid = number of chars between end of A and start of B
+			 */
+			if(c){
+				h = w = wdup(c);
+				while(w->next)
+					w = w->next;
+			}
+			if(PERCENT(*cp) && nmid > 0){	
+				if(w){
+					bufcpy(buf, w->s, strlen(w->s));
+					bufcpy(buf, enda, nmid);
+					insert(buf, 0);
+					free(w->s);
+					w->s = strdup(buf->start);
+				} else {
+					bufcpy(buf, enda, nmid);
+					insert(buf, 0);
+					h = w = newword(buf->start);
+				}
+				buf->current = buf->start;
+			}
+			if(d && *d->s){
+				if(w){
+
+					bufcpy(buf, w->s, strlen(w->s));
+					bufcpy(buf, d->s, strlen(d->s));
+					insert(buf, 0);
+					free(w->s);
+					w->s = strdup(buf->start);
+					w->next = wdup(d->next);
+					while(w->next)
+						w = w->next;
+					buf->current = buf->start;
+				} else
+					h = w = wdup(d);
+			}
+		}
+		if(w == 0)
+			h = w = newword(v->s);
+	
+		if(head == 0)
+			head = h;
+		else
+			tail->next = h;
+		tail = w;
+	}
+	freebuf(buf);
+	delword(a);
+	delword(b);
+	delword(c);
+	delword(d);
+	return head;
+}
+
+static int
+submatch(char *s, Word *a, Word *b, int *nmid, char **enda)
+{
+	Word *w;
+	int n;
+	char *end;
+
+	n = 0;
+	for(w = a; w; w = w->next){
+		n = strlen(w->s);
+		if(strncmp(s, w->s, n) == 0)
+			break;
+	}
+	if(a && w == 0)		/*  a == NULL matches everything*/
+		return 0;
+
+	*enda = s+n;		/* pointer to end a A part match */
+	*nmid = strlen(s)-n;	/* size of remainder of source */
+	end = *enda+*nmid;
+	for(w = b; w; w = w->next){
+		n = strlen(w->s);
+		if(strcmp(w->s, end-n) == 0){
+			*nmid -= n;
+			break;
+		}
+	}
+	if(b && w == 0)		/* b == NULL matches everything */
+		return 0;
+	return 1;
+}
diff --git a/src/cmd/mk/word.c b/src/cmd/mk/word.c
new file mode 100644
index 0000000..ac34c47
--- /dev/null
+++ b/src/cmd/mk/word.c
@@ -0,0 +1,180 @@
+#include	"mk.h"
+
+static	Word	*nextword(char**);
+
+Word*
+newword(char *s)
+{
+	Word *w;
+
+	w = (Word *)Malloc(sizeof(Word));
+	w->s = strdup(s);
+	w->next = 0;
+	return(w);
+}
+
+Word *
+stow(char *s)
+{
+	Word *head, *w, *new;
+
+	w = head = 0;
+	while(*s){
+		new = nextword(&s);
+		if(new == 0)
+			break;
+		if (w)
+			w->next = new;
+		else
+			head = w = new;
+		while(w->next)
+			w = w->next;
+		
+	}
+	if (!head)
+		head = newword("");
+	return(head);
+}
+
+char *
+wtos(Word *w, int sep)
+{
+	Bufblock *buf;
+	char *cp;
+
+	buf = newbuf();
+	for(; w; w = w->next){
+		for(cp = w->s; *cp; cp++)
+			insert(buf, *cp);
+		if(w->next)
+			insert(buf, sep);
+	}
+	insert(buf, 0);
+	cp = strdup(buf->start);
+	freebuf(buf);
+	return(cp);
+}
+
+Word*
+wdup(Word *w)
+{
+	Word *v, *new, *base;
+
+	v = base = 0;
+	while(w){
+		new = newword(w->s);
+		if(v)
+			v->next = new;
+		else
+			base = new;
+		v = new;
+		w = w->next;
+	}
+	return base;
+}
+
+void
+delword(Word *w)
+{
+	Word *v;
+
+	while(v = w){
+		w = w->next;
+		if(v->s)
+			free(v->s);
+		free(v);
+	}
+}
+
+/*
+ *	break out a word from a string handling quotes, executions,
+ *	and variable expansions.
+ */
+static Word*
+nextword(char **s)
+{
+	Bufblock *b;
+	Word *head, *tail, *w;
+	Rune r;
+	char *cp;
+
+	cp = *s;
+	b = newbuf();
+	head = tail = 0;
+	while(*cp == ' ' || *cp == '\t')		/* leading white space */
+		cp++;
+	while(*cp){
+		cp += chartorune(&r, cp);
+		switch(r)
+		{
+		case ' ':
+		case '\t':
+		case '\n':
+			goto out;
+		case '\\':
+		case '\'':
+		case '"':
+			cp = expandquote(cp, r, b);
+			if(cp == 0){
+				fprint(2, "missing closing quote: %s\n", *s);
+				Exit();
+			}
+			break;
+		case '$':
+			w = varsub(&cp);
+			if(w == 0)
+				break;
+			if(b->current != b->start){
+				bufcpy(b, w->s, strlen(w->s));
+				insert(b, 0);
+				free(w->s);
+				w->s = strdup(b->start);
+				b->current = b->start;
+			}
+			if(head){
+				bufcpy(b, tail->s, strlen(tail->s));
+				bufcpy(b, w->s, strlen(w->s));
+				insert(b, 0);
+				free(tail->s);
+				tail->s = strdup(b->start);
+				tail->next = w->next;
+				free(w->s);
+				free(w);
+				b->current = b->start;
+			} else
+				tail = head = w;
+			while(tail->next)
+				tail = tail->next;
+			break;
+		default:
+			rinsert(b, r);
+			break;
+		}
+	}
+out:
+	*s = cp;
+	if(b->current != b->start){
+		if(head){
+			cp = b->current;
+			bufcpy(b, tail->s, strlen(tail->s));
+			bufcpy(b, b->start, cp-b->start);
+			insert(b, 0);
+			free(tail->s);
+			tail->s = strdup(cp);
+		} else {
+			insert(b, 0);
+			head = newword(b->start);
+		}
+	}
+	freebuf(b);
+	return head;
+}
+
+void
+dumpw(char *s, Word *w)
+{
+	Bprint(&bout, "%s", s);
+	for(; w; w = w->next)
+		Bprint(&bout, " '%s'", w->s);
+	Bputc(&bout, '\n');
+}