code: plan9front

Download patch

ref: 94b1d3f0ad587b6e507ff872eb8562466e3af21a
parent: 6b0150764cc579c3764e6ed2a6db1237f11577de
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Thu Dec 28 13:10:41 EST 2023

mk: Various improvements

Variables are lists, just as in rc,
so preserve emptry strings ('')
in variables properly: FOO='' bar baz
should result in FOO=('' bar baz) in
the environment, while FOO=
becomes a emptry list FOO=().

Get rid of the Evy struct, the environment
can be represented as just an array
of Symtab pointers, also meaning the
value can never be out of sync.

Embedd the name string in Symtab struct.
It is immutable and dont require
the caller to strdup() the name.

Make Word's immutable, embedding the
string value in the Word struct.
This avoids alot of extra allocations.

Provide a Strdup() function that handles
allocation error.

Handle variable overrides from the
command line internally, avoiding the
maketmp() file.

When executing a recipe, pass the body
using the -c flag to rc, avoiding
a pipe() and fork(). This also has
the advntage that rc can optimize the
script as it sees it in its entirety
and avoid fork() down the line.

Make sure not to leak file-descriptors
into sub-processes.

Do an attempt at freeing memory
(mostly for Arc's and environment values).

--- a/sys/src/cmd/mk/arc.c
+++ b/sys/src/cmd/mk/arc.c
@@ -8,7 +8,7 @@
 	a = (Arc *)Malloc(sizeof(Arc));
 	a->n = n;
 	a->r = r;
-	a->stem = strdup(stem);
+	a->stem = Strdup(stem);
 	rcopy(a->match, match, NREGEXP);
 	a->next = 0;
 	a->flag = 0;
@@ -17,6 +17,13 @@
 }
 
 void
+freearc(Arc *a)
+{
+	free(a->stem);
+	free(a);
+}
+
+void
 dumpa(char *s, Arc *a)
 {
 	char buf[1024];
@@ -36,15 +43,10 @@
 void
 nrep(void)
 {
-	Symtab *sym;
 	Word *w;
 
-	sym = symlook("NREP", S_VAR, 0);
-	if(sym){
-		w = sym->u.ptr;
-		if (w && w->s && *w->s)
-			nreps = atoi(w->s);
-	}
+	if(!empty(w = getvar("NREP")))
+		nreps = atoi(w->s);
 	if(nreps < 1)
 		nreps = 1;
 	if(DEBUG(D_GRAPH))
--- a/sys/src/cmd/mk/archive.c
+++ b/sys/src/cmd/mk/archive.c
@@ -7,9 +7,9 @@
 long
 atimeof(int force, char *name)
 {
+	char buf[512], *archive, *member;
 	Symtab *sym;
 	long t;
-	char *archive, *member, buf[512];
 
 	archive = split(name, &member);
 	if(archive == 0)
@@ -26,7 +26,7 @@
 	else{
 		atimes(archive);
 		/* mark the aggegate as having been done */
-		symlook(strdup(archive), S_AGG, "")->u.value = t;
+		symlook(archive, S_AGG, 1)->u.value = t;
 	}
 		/* truncate long member name to sizeof of name field in archive header */
 	snprint(buf, sizeof(buf), "%s(%.*s)", archive, utfnlen(member, SARNAME), member);
@@ -113,7 +113,7 @@
 			i--;
 		h.name[i+1]=0;		/* can stomp on date field */
 		snprint(buf, sizeof buf, "%s(%s)", ar, h.name);
-		symlook(strdup(buf), S_TIME, (void*)t)->u.value = t;
+		symlook(buf, S_TIME, 1)->u.value = t;
 		t = atol(h.size);
 		if(t&01) t++;
 		LSEEK(fd, t, 1);
@@ -131,7 +131,7 @@
 	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);
+			symlook(file, S_BITCH, 1);
 		}
 		return 1;
 	}
@@ -148,7 +148,7 @@
 {
 	char *p, *q;
 
-	p = strdup(name);
+	p = Strdup(name);
 	q = utfrune(p, '(');
 	if(q){
 		*q++ = 0;
@@ -159,8 +159,8 @@
 			*q = 0;
 		if(type(p))
 			return p;
-		free(p);
 		fprint(2, "mk: '%s' is not an archive\n", name);
 	}
+	free(p);
 	return 0;
 }
--- a/sys/src/cmd/mk/bufblock.c
+++ b/sys/src/cmd/mk/bufblock.c
@@ -25,6 +25,8 @@
 void
 freebuf(Bufblock *p)
 {
+	assert(p->current >= p->start);
+	p->current = 0;
 	p->next = freelist;
 	freelist = p;
 }
@@ -39,6 +41,7 @@
 	n = p->end-p->start+QUANTA;
 		/* search the free list for a big buffer */
 	for (f = freelist; f; f = f->next) {
+		assert(f->current == 0);
 		if (f->end-f->start >= n) {
 			memcpy(f->start, p->start, p->end-p->start);
 			cp = f->start;
@@ -47,7 +50,6 @@
 			cp = f->end;
 			f->end = p->end;
 			p->end = cp;
-			f->current = f->start;
 			break;
 		}
 	}
@@ -59,9 +61,17 @@
 }
 
 void
-bufcpy(Bufblock *buf, char *cp, int n)
+bufcpy(Bufblock *buf, char *cp)
 {
+	int c;
 
+	while (c = *cp++)
+		insert(buf, c);
+}
+
+void
+bufncpy(Bufblock *buf, char *cp, int n)
+{
 	while (n--)
 		insert(buf, *cp++);
 }
@@ -69,7 +79,6 @@
 void
 insert(Bufblock *buf, int c)
 {
-
 	if (buf->current >= buf->end)
 		growbuf(buf);
 	*buf->current++ = c;
--- a/sys/src/cmd/mk/env.c
+++ b/sys/src/cmd/mk/env.c
@@ -1,10 +1,10 @@
 #include	"mk.h"
 
 enum {
-	ENVQUANTA=10
+	ENVQUANTA=64
 };
 
-Envy	*envy;
+static Symtab **envy;
 static int nextv;
 
 static char	*myenv[] =
@@ -36,72 +36,66 @@
 	char **p;
 
 	for(p = myenv; *p; p++)
-		symlook(*p, S_INTERNAL, (void *)"");
+		symlook(*p, S_INTERNAL, 1)->u.ptr = 0;
 	readenv();				/* o.s. dependent */
 }
 
 static void
-envinsert(char *name, Word *value)
+envupd(char *name, Word *value)
 {
+	Symtab *sym = symlook(name, S_INTERNAL, 0);
+	assert(sym != 0);
+	delword(sym->u.ptr);
+	sym->u.ptr = value;
+}
+
+static void
+envinsert(Symtab *sym)
+{
 	static int envsize;
 
 	if (nextv >= envsize) {
 		envsize += ENVQUANTA;
-		envy = (Envy *) Realloc((char *) envy, envsize*sizeof(Envy));
+		envy = (Symtab **) Realloc(envy, envsize*sizeof(Symtab*));
 	}
-	envy[nextv].name = name;
-	envy[nextv++].values = value;
+	envy[nextv++] = sym;
 }
 
 static void
-envupd(char *name, Word *value)
+ereset(Symtab *s)
 {
-	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);
+	delword(s->u.ptr);
+	s->u.ptr = 0;
+	envinsert(s);
 }
 
 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, s->u.ptr);
+	if(symlook(s->name, S_INTERNAL, 0))
+		return;
+	envinsert(s);
 }
 
-void
+Symtab**
 execinit(void)
 {
-	char **p;
-
 	nextv = 0;
-	for(p = myenv; *p; p++)
-		envinsert(*p, stow(""));
-
+	symtraverse(S_INTERNAL, ereset);
 	symtraverse(S_VAR, ecopy);
-	envinsert(0, 0);
+	envinsert(0);
+	return envy;
 }
 
-Envy*
+Symtab**
 buildenv(Job *j, int slot)
 {
 	char **p, *cp, *qp;
 	Word *w, *v, **l;
+	char num[16];
 	int i;
-	char buf[256];
 
 	envupd("target", wdup(j->t));
 	if(j->r->attr&REGEXP)
@@ -109,10 +103,10 @@
 	else
 		envupd("stem", newword(j->stem));
 	envupd("prereq", wdup(j->p));
-	snprint(buf, sizeof buf, "%d", getpid());
-	envupd("pid", newword(buf));
-	snprint(buf, sizeof buf, "%d", slot);
-	envupd("nproc", newword(buf));
+	snprint(num, sizeof num, "%d", getpid());
+	envupd("pid", newword(num));
+	snprint(num, sizeof num, "%d", slot);
+	envupd("nproc", newword(num));
 	envupd("newprereq", wdup(j->np));
 	envupd("alltarget", wdup(j->at));
 	l = &v;
@@ -129,10 +123,7 @@
 				continue;
 			}
 		}
-		*l = w->next;
-		free(w->s);
-		free(w);
-		w = *l;
+		*l = w = popword(w);
 	}
 	envupd("newmember", v);
 		/* update stem0 -> stem9 */
--- a/sys/src/cmd/mk/file.c
+++ b/sys/src/cmd/mk/file.c
@@ -21,7 +21,7 @@
 		return mtime(name);
 
 	sym = symlook(name, S_TIME, 0);
-	if (sym)
+	if(sym)
 		return sym->u.value;		/* uggh */
 
 	t = mkmtime(name, 0);
@@ -28,7 +28,7 @@
 	if(t == 0)
 		return 0;
 
-	symlook(name, S_TIME, (void*)t);		/* install time in cache */
+	symlook(name, S_TIME, 1)->u.value = t;		/* install time in cache */
 	return t;
 }
 
@@ -76,7 +76,7 @@
 		} while(*s);
 		c = *s;
 		*s = 0;
-		symlook(strdup(cp), S_TIME, (void *)t)->u.value = t;
+		symlook(cp, S_TIME, 1)->u.value = t;
 		if (c)
 			*s++ = c;
 		while(*s){
--- a/sys/src/cmd/mk/fns.h
+++ b/sys/src/cmd/mk/fns.h
@@ -1,11 +1,13 @@
 void	addrule(char*, Word*, char*, Word*, int, int, char*);
 void	addrules(Word*, Word*, char*, int, int, char*);
-void	addw(Word*, char*);
 int	assline(Biobuf *, Bufblock *);
 long	atimeof(int,char*);
 void	atouch(char*);
-void	bufcpy(Bufblock *, char *, int);
-Envy	*buildenv(Job*, int);
+void	bufcpy(Bufblock *, char *);
+void	bufcpyq(Bufblock *, char *);
+void	bufcpyw(Bufblock *, Word *);
+void	bufncpy(Bufblock *, char *, int);
+Symtab	**buildenv(Job*, int);
 void	catchnotes(void);
 char 	*charin(char *, char *);
 int	chgtime(char*);
@@ -20,23 +22,24 @@
 void	dumpr(char*, Rule*);
 void	dumpv(char*);
 void	dumpw(char*, Word*);
+int	empty(Word*);
 int	escapetoken(Biobuf*, Bufblock*, int, int);
-void	execinit(void);
-int	execsh(char*, char*, Bufblock*, Envy*);
+Symtab	**execinit(void);
+int	execsh(char*, char*, Symtab**, Bufblock*);
 void	Exit(void);
 char	*expandquote(char*, Rune, Bufblock*);
 void	expunge(int, char*);
+void	freearc(Arc*);
 void	freebuf(Bufblock*);
+void	freejob(Job*);
 void	front(char*);
+Word	*getvar(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(void);
 int	match(char*, char*, char*);
 void	mk(char*);
 ulong	mkmtime(char*, int);
@@ -50,8 +53,9 @@
 void	nproc(void);
 void	nrep(void);
 int	outofdate(Node*, Arc*, int);
-void	parse(char*, int, int);
-int	pipecmd(char*, Envy*, int*);
+void	parse(char*, int);
+int	pipecmd(char*, char*, Symtab**, int*);
+Word	*popword(Word*);
 void	prusage(void);
 void	rcopy(char**, Resub*, int);
 void	readenv(void);
@@ -59,12 +63,13 @@
 void	rinsert(Bufblock *, Rune);
 char	*rulecnt(void);
 void	run(Job*);
-void	setvar(char*, void*);
+void	setvar(char*, Word*);
 char	*shname(char*);
-void	shprint(char*, Envy*, Bufblock*);
+void	shprint(char*, Bufblock*);
 Word	*stow(char*);
+char	*Strdup(char*);
 void	subst(char*, char*, char*, int);
-Symtab	*symlook(char*, int, void*);
+Symtab	*symlook(char*, int, int);
 void	symtraverse(int, void(*)(Symtab*));
 void	timeinit(char*);
 long	timeof(char*, int);
@@ -71,9 +76,12 @@
 void	touch(char*);
 void	update(int, Node*);
 void	usage(void);
+void	varoverride(char*);
 Word	*varsub(char**);
+int	wadd(Word**, char*);
 int	waitfor(char*);
 int	waitup(int, int*);
+int	wcmp(Word*, Word*);
 Word	*wdup(Word*);
 int	work(Node*, Node*, Arc*);
-char	*wtos(Word*, int);
+char	*wtos(Word*);
--- a/sys/src/cmd/mk/graph.c
+++ b/sys/src/cmd/mk/graph.c
@@ -41,7 +41,6 @@
 	sym = symlook(target, S_NODE, 0);
 	if(sym)
 		return sym->u.ptr;
-	target = strdup(target);
 	node = newnode(target);
 	head.n = 0;
 	head.next = 0;
@@ -49,8 +48,7 @@
 	memset((char*)rmatch, 0, sizeof(rmatch));
 	for(r = sym? sym->u.ptr: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((!r->recipe || !*r->recipe) && empty(r->tail)) continue;	/* no effect; ignore */
 		if(cnt[r->rule] >= nreps) continue;
 		cnt[r->rule]++;
 		node->flags |= PROBABLE;
@@ -62,19 +60,20 @@
  *		if(r->attr&DEL)
  *			node->flags |= DELETE;
  */
-		if(!r->tail || !r->tail->s || !*r->tail->s) {
+		if(empty(r->tail)) {
 			a->next = newarc((Node *)0, r, "", rmatch);
 			a = a->next;
-		} else
+		} 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->recipe || !*r->recipe) && empty(r->tail)) continue;	/* no effect; ignore */
 		if ((r->attr&NOVIRT) && a != &head && (a->r->attr&VIR))
 			continue;
 		if(r->attr&REGEXP){
@@ -84,7 +83,7 @@
 			if(regexec(r->pat, node->name, rmatch, NREGEXP) == 0)
 				continue;
 		} else {
-			if(!match(node->name, r->target, stem)) continue;
+			if(!match(node->name, r->target->name, stem)) continue;
 		}
 		if(cnt[r->rule] >= nreps) continue;
 		cnt[r->rule]++;
@@ -96,7 +95,7 @@
  *		if(r->attr&DEL)
  *			node->flags |= DELETE;
  */
-		if(!r->tail || !r->tail->s || !*r->tail->s) {
+		if(empty(r->tail)){
 			a->next = newarc((Node *)0, r, stem, rmatch);
 			a = a->next;
 		} else
@@ -118,20 +117,20 @@
 static void
 togo(Node *node)
 {
-	Arc *la, *a;
+	Arc **l, *a;
 
 	/* delete them now */
-	la = 0;
-	for(a = node->prereqs; a; la = a, a = a->next)
+	l = &node->prereqs;
+	while(a = *l){
 		if(a->flag&TOGO){
-			if(a == node->prereqs)
-				node->prereqs = a->next;
-			else
-				la->next = a->next, a = la;
-		}
+			*l = a->next;
+			freearc(a);
+		} else
+			l = &a->next;
+	}
 }
 
-static
+static int
 vacuous(Node *node)
 {
 	Arc *la, *a;
@@ -149,9 +148,8 @@
 	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)){
+				if((la->flag&TOGO) && (la->r == a->r))
 					la->flag &= ~TOGO;
-				}
 	togo(node);
 	if(vac)
 		node->flags |= VACUOUS;
@@ -161,15 +159,17 @@
 static Node *
 newnode(char *name)
 {
-	register Node *node;
+	Symtab *sym;
+	Node *node;
 
+	sym = symlook(name, S_NODE, 1);
 	node = (Node *)Malloc(sizeof(Node));
-	symlook(name, S_NODE, (void *)node);
-	node->name = name;
+	node->name = sym->name;
 	node->time = timeof(name, 0);
 	node->prereqs = 0;
 	node->flags = node->time? PROBABLE : 0;
 	node->next = 0;
+	sym->u.ptr = node;
 	return(node);
 }
 
--- a/sys/src/cmd/mk/job.c
+++ b/sys/src/cmd/mk/job.c
@@ -3,7 +3,7 @@
 Job *
 newjob(Rule *r, Node *nlist, char *stem, char **match, Word *pre, Word *npre, Word *tar, Word *atar)
 {
-	register Job *j;
+	Job *j;
 
 	j = (Job *)Malloc(sizeof(Job));
 	j->r = r;
@@ -20,14 +20,28 @@
 }
 
 void
+freejob(Job *j)
+{
+	delword(j->p);
+	delword(j->np);
+	delword(j->t);
+	delword(j->at);
+	free(j);
+}
+
+void
 dumpj(char *s, Job *j, int all)
 {
+	char *t;
+
 	Bprint(&bout, "%s\n", s);
 	while(j){
 		Bprint(&bout, "job@%p: r=%p n=%p stem='%s' nproc=%d\n",
 			j, j->r, j->n, j->stem, j->nproc);
-		Bprint(&bout, "\ttarget='%s' alltarget='%s' prereq='%s' nprereq='%s'\n",
-			wtos(j->t, ' '), wtos(j->at, ' '), wtos(j->p, ' '), wtos(j->np, ' '));
+		Bprint(&bout, "\ttarget=%s", t = wtos(j->t)), free(t);
+		Bprint(&bout, " alltarget=%s", t = wtos(j->at)), free(t);
+		Bprint(&bout, " prereq=%s", t = wtos(j->p)), free(t);
+		Bprint(&bout, " nprereq=%s\n", t = wtos(j->np)), free(t);
 		j = all? j->next : 0;
 	}
 }
--- a/sys/src/cmd/mk/lex.c
+++ b/sys/src/cmd/mk/lex.c
@@ -89,8 +89,7 @@
 			insert(buf, '\n');
 			insert(buf,0);
 			buf->current = buf->start+start;
-			execinit();
-			execsh(0, buf->current, buf, envy);
+			execsh(buf->current, 0, execinit(), buf);
 			return 1;
 		}
 		if(c == '\n')
--- a/sys/src/cmd/mk/main.c
+++ b/sys/src/cmd/mk/main.c
@@ -3,36 +3,29 @@
 #define		MKFILE		"mkfile"
 
 int debug;
-Rule *rules, *metarules;
 int nflag = 0;
+int sflag = 0;
 int tflag = 0;
 int iflag = 0;
 int kflag = 0;
 int aflag = 0;
 int uflag = 0;
+int nreps = 1;
 char *explain = 0;
 Word *target1;
-int nreps = 1;
 Job *jobs;
+Rule *rules, *patrule, *metarules;
 Biobuf bout;
-Rule *patrule;
+
 void badusage(void);
-#ifdef	PROF
-short buf[10000];
-#endif
 
 void
 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;
+	Word **link, *flags, *files, *args;
 	Bufblock *whatif;
+	char *s;
+	int i;
 
 	/*
 	 *  start with a copy of the current environment variables
@@ -40,13 +33,14 @@
 	 */
 
 	Binit(&bout, 1, OWRITE);
-	buf = newbuf();
 	whatif = 0;
 	USED(argc);
-	for(argv++; *argv && (**argv == '-'); argv++)
+
+	flags = 0;
+	link = &flags;
+	for(argv++; *argv && (**argv == '-'); argv++, link = &(*link)->next)
 	{
-		bufcpy(buf, argv[0], strlen(argv[0]));
-		insert(buf, ' ');
+		*link = newword(*argv);
 		switch(argv[0][1])
 		{
 		case 'a':
@@ -67,11 +61,12 @@
 			explain = &argv[0][2];
 			break;
 		case 'f':
-			if(*++argv == 0)
+			argv++;
+			if(*argv == 0 || **argv == 0)
 				badusage();
-			*f++ = *argv;
-			bufcpy(buf, argv[0], strlen(argv[0]));
-			insert(buf, ' ');
+			link = &(*link)->next;
+			*link = newword(*argv);
+			wadd(&files, *argv);
 			break;
 		case 'i':
 			iflag = 1;
@@ -97,11 +92,13 @@
 			else
 				insert(whatif, ' ');
 			if(argv[0][2])
-				bufcpy(whatif, &argv[0][2], strlen(&argv[0][2]));
+				bufcpy(whatif, &argv[0][2]);
 			else {
 				if(*++argv == 0)
 					badusage();
-				bufcpy(whatif, &argv[0][0], strlen(&argv[0][0]));
+				bufcpy(whatif, &argv[0][0]);
+				link = &(*link)->next;
+				*link = newword(*argv);
 			}
 			break;
 		default:
@@ -108,12 +105,6 @@
 			badusage();
 		}
 	}
-#ifdef	PROF
-	{
-		extern etext();
-		monitor(main, etext, buf, sizeof buf, 300);
-	}
-#endif
 
 	if(aflag)
 		iflag = 1;
@@ -124,54 +115,30 @@
 	/*
 		assignment args become null strings
 	*/
-	temp = 0;
+	mkinfile = "command line args";
 	for(i = 0; argv[i]; i++) if(utfrune(argv[i], '=')){
-		bufcpy(buf, argv[i], strlen(argv[i]));
-		insert(buf, ' ');
-		if(tfd < 0){
-			temp = maketmp();
-			if(temp == 0) {
-				perror("temp file");
-				Exit();
-			}
-			if((tfd = create(temp, ORDWR, 0600)) < 0){
-				perror(temp);
-				Exit();
-			}
-			Binit(&tb, tfd, OWRITE);
-		}
-		Bprint(&tb, "%s\n", argv[i]);
+		if(!wadd(&flags, argv[i]))
+			varoverride(argv[i]);
 		*argv[i] = 0;
 	}
-	if(tfd >= 0){
-		Bflush(&tb);
-		LSEEK(tfd, 0L, 0);
-		parse("command line args", tfd, 1);
-		remove(temp);
-	}
+	setvar("MKFLAGS", flags);
 
-	if (buf->current != buf->start) {
-		buf->current--;
-		insert(buf, 0);
-	}
-	symlook("MKFLAGS", S_VAR, (void *) stow(buf->start));
-	buf->current = buf->start;
+	args = 0;
+	link = &args;
 	for(i = 0; argv[i]; i++){
 		if(*argv[i] == 0) continue;
-		if(i)
-			insert(buf, ' ');
-		bufcpy(buf, argv[i], strlen(argv[i]));
+		*link = newword(argv[i]);
+		link = &(*link)->next;
 	}
-	insert(buf, 0);
-	symlook("MKARGS", S_VAR, (void *) stow(buf->start));
-	freebuf(buf);
+	setvar("MKARGS", args);
 
-	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(files == 0){
+		if(access(MKFILE, AEXIST) == 0)
+			parse(MKFILE, open(MKFILE, OREAD|OCEXEC));
+	} else {
+		for(; files; files = popword(files))
+			parse(files->s, open(files->s, OREAD|OCEXEC));
+	}
 	if(DEBUG(D_PARSE)){
 		dumpw("default targets", target1);
 		dumpr("rules", rules);
@@ -184,44 +151,26 @@
 		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 {
+	if(args == 0){
+		if(target1 == 0){
 			fprint(2, "mk: nothing to mk\n");
 			Exit();
 		}
+		for(; target1; target1 = popword(target1))
+			mk(target1->s);
 	} else {
+		args = wdup(args);
 		if(sflag){
-			for(; *argv; argv++)
-				if(**argv)
-					mk(*argv);
+			for(; args; args = popword(args))
+				mk(args->s);
 		} 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);
+			if(args->next == 0)
+				mk(args->s);
 			else {
-				head = newword("command line arguments");
-				addrules(head, tail, strdup(""), VIR, mkinline, 0);
+				Word *head = newword(mkinfile);
+				addrules(head, args, Strdup(""), VIR, 0, 0);
 				mk(head->s);
 			}
 		}
@@ -234,7 +183,6 @@
 void
 badusage(void)
 {
-
 	fprint(2, "usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n");
 	Exit();
 }
@@ -242,7 +190,7 @@
 void *
 Malloc(int n)
 {
-	register void *s;
+	void *s;
 
 	s = malloc(n);
 	if(!s) {
@@ -249,6 +197,7 @@
 		fprint(2, "mk: cannot alloc %d bytes\n", n);
 		Exit();
 	}
+	setmalloctag(s, getcallerpc(&n));
 	return(s);
 }
 
@@ -263,9 +212,20 @@
 		fprint(2, "mk: cannot alloc %d bytes\n", n);
 		Exit();
 	}
+	setrealloctag(s, getcallerpc(&s));
 	return(s);
 }
 
+char *
+Strdup(char *s)
+{
+	int n = strlen(s)+1;
+	char *d = Malloc(n);
+	memcpy(d, s, n);
+	setmalloctag(d, getcallerpc(&s));
+	return d;
+}
+
 void
 regerror(char *s)
 {
@@ -274,6 +234,6 @@
 			patrule->file, patrule->line, s);
 	else
 		fprint(2, "mk: %s:%d: regular expression error; %s\n",
-			infile, mkinline, s);
+			mkinfile, mkinline, s);
 	Exit();
 }
--- a/sys/src/cmd/mk/mk.c
+++ b/sys/src/cmd/mk/mk.c
@@ -172,7 +172,7 @@
 	Arc *a;
 
 	MADESET(node, fake? BEINGMADE : MADE);
-	if(((node->flags&VIRTUAL) == 0) && (access(node->name, 0) == 0)){
+	if(((node->flags&VIRTUAL) == 0) && (access(node->name, AEXIST) == 0)){
 		node->time = timeof(node->name, 1);
 		node->flags &= ~(CANPRETEND|PRETENDING);
 		for(a = node->prereqs; a; a = a->next)
@@ -188,14 +188,12 @@
 }
 
 static
-pcmp(char *prog, char *p, char *q)
+pcmp(char *cmd)
 {
-	char buf[3*NAMEBLOCK];
 	int pid;
 
 	Bflush(&bout);
-	snprint(buf, sizeof buf, "%s '%s' '%s'\n", prog, p, q);
-	pid = pipecmd(buf, 0, 0);
+	pid = pipecmd(cmd, 0, 0, 0);
 	while(waitup(-3, &pid) >= 0)
 		;
 	return(pid? 2:1);
@@ -204,25 +202,28 @@
 int
 outofdate(Node *node, Arc *arc, int eval)
 {
-	char buf[3*NAMEBLOCK], *str;
-	Symtab *sym;
-	int ret;
-
-	str = 0;
 	if(arc->prog){
-		snprint(buf, sizeof buf, "%s%c%s", node->name, 0377,
-			arc->n->name);
-		sym = symlook(buf, S_OUTOFDATE, 0);
+		Bufblock *cmd;
+		Symtab *sym;
+		int ret;
+
+		cmd = newbuf();
+		bufcpy(cmd, arc->prog);
+		insert(cmd, ' ');
+		bufcpyq(cmd, node->name);
+		insert(cmd, ' ');
+		bufcpyq(cmd, arc->n->name);
+		insert(cmd, '\n');
+		insert(cmd, 0);
+		sym = symlook(cmd->start, S_OUTOFDATE, 0);
 		if(sym == 0 || eval){
+			ret = pcmp(cmd->start);
 			if(sym == 0)
-				str = strdup(buf);
-			ret = pcmp(arc->prog, node->name, arc->n->name);
-			if(sym)
-				sym->u.value = ret;
-			else
-				symlook(str, S_OUTOFDATE, (void *)ret);
+				sym = symlook(cmd->start, S_OUTOFDATE, 1);
+			sym->u.value = ret;
 		} else
 			ret = sym->u.value;
+		freebuf(cmd);
 		return(ret-1);
 	} else if(strchr(arc->n->name, '(') && arc->n->time == 0)  /* missing archive member */
 		return 1;
--- a/sys/src/cmd/mk/mk.h
+++ b/sys/src/cmd/mk/mk.h
@@ -15,21 +15,39 @@
 
 typedef struct Word
 {
-	char 		*s;
 	struct Word 	*next;
+	char 		s[1];
 } Word;
 
-typedef struct Envy
+typedef struct Symtab
 {
-	char 		*name;
-	Word 		*values;
-} Envy;
+	union{
+		void	*ptr;
+		uintptr	value;
+	} u;
+	struct Symtab	*next;
+	unsigned char	space;
+	char		name[1];
+} Symtab;
 
-extern Envy *envy;
+enum {
+	S_VAR,		/* variable -> value */
+	S_TARGET,	/* target -> rule */
+	S_TIME,		/* file -> time */
+	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,	/* "cmp 'file1' 'file2'\n" -> 2(outofdate) or 1(not outofdate) */
+	S_BULKED,	/* directory; we have bulked */
+	S_WESET,	/* variable; we set in the mkfile */
+	S_INTERNAL,	/* variable -> value; an internal mk variable (e.g., stem, target) */
+};
 
 typedef struct Rule
 {
-	char 		*target;	/* one target */
+	Symtab 		*target;	/* one target */
 	Word 		*tail;		/* constituents of targets */
 	char 		*recipe;	/* do it ! */
 	short 		attr;		/* attributes */
@@ -112,50 +130,17 @@
 } Job;
 extern Job *jobs;
 
-typedef struct Symtab
-{
-	short		space;
-	char		*name;
-	union{
-		void		*ptr;
-		uintptr	value;
-	} u;
-	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_BULKED,	/* we have bulked this dir */
-	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	char	*mkinfile;
 extern	int	nreps;
 extern	char	*explain;
-extern	char	*termchars;
-extern	char 	*shell;
-extern	char 	*shellname;
-extern	char 	*shflags;
-extern	int	IWS;
+extern	char	termchars[];
+extern	char 	shell[];
+extern	char 	shellname[];
 
-#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	SYNERR(l)	(fprint(2, "mk: %s:%d: syntax error; ", mkinfile, ((l)>=0)?(l):mkinline))
 #define	NAMEBLOCK	1000
 #define	BIGBLOCK	20000
 
--- a/sys/src/cmd/mk/parse.c
+++ b/sys/src/cmd/mk/parse.c
@@ -1,31 +1,30 @@
 #include	"mk.h"
 
-char *infile;
+char *mkinfile;
 int mkinline;
+
+static void ipush(char *file);
+static void ipop(void);
+static void doassign(Word *, Word *, int, int);
 static int rhead(char *, Word **, Word **, int *, char **);
 static char *rbody(Biobuf*);
 extern Word *target1;
 
 void
-parse(char *f, int fd, int varoverride)
+parse(char *f, int fd)
 {
-	int hline;
-	char *body;
+	int hline, attr, pid, newfd;
 	Word *head, *tail;
-	int attr, set, pid;
 	char *prog, *p;
-	int newfd;
-	Biobuf in;
 	Bufblock *buf;
+	Biobuf in;
 
 	if(fd < 0){
 		perror(f);
 		Exit();
 	}
-	ipush();
-	infile = strdup(f);
-	mkinline = 1;
 	Binit(&in, fd, OREAD);
+	ipush(f);
 	buf = newbuf();
 	while(assline(&in, buf)){
 		hline = mkinline;
@@ -32,33 +31,33 @@
 		switch(rhead(buf->start, &head, &tail, &attr, &prog))
 		{
 		case '<':
-			p = wtos(tail, ' ');
+			p = wtos(tail);
 			if(*p == 0){
 				SYNERR(-1);
 				fprint(2, "missing include file name\n");
 				Exit();
 			}
-			newfd = open(p, OREAD);
+			newfd = open(p, OREAD|OCEXEC);
 			if(newfd < 0){
 				fprint(2, "warning: skipping missing include file: ");
 				perror(p);
 			} else
-				parse(p, newfd, 0);
+				parse(p, newfd);
+			free(p);
 			break;
 		case '|':
-			p = wtos(tail, ' ');
+			p = wtos(tail);
 			if(*p == 0){
 				SYNERR(-1);
 				fprint(2, "missing include program name\n");
 				Exit();
 			}
-			execinit();
-			pid=pipecmd(p, envy, &newfd);
+			pid=pipecmd(p, 0, execinit(), &newfd);
 			if(newfd < 0){
 				fprint(2, "warning: skipping missing program file: ");
 				perror(p);
 			} else
-				parse(p, newfd, 0);
+				parse(p, newfd);
 			while(waitup(-3, &pid) >= 0)
 				;
 			if(pid != 0){
@@ -65,36 +64,14 @@
 				fprint(2, "bad include program status\n");
 				Exit();
 			}
+			free(p);
 			break;
 		case ':':
-			body = rbody(&in);
-			addrules(head, tail, body, attr, hline, prog);
-			break;
+			addrules(head, tail, rbody(&in), attr, hline, prog);
+			continue;	/* don't free head and tail */
 		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;
+			doassign(head, tail, attr, 0);
+			continue;
 		default:
 			SYNERR(hline);
 			fprint(2, "expected one of :<=\n");
@@ -101,13 +78,59 @@
 			Exit();
 			break;
 		}
+		delword(head);
+		delword(tail);
 	}
-	close(fd);
 	freebuf(buf);
 	ipop();
+	close(fd);
 }
 
+static void
+doassign(Word *head, Word *tail, int attr, int override)
+{
+	int set;
+
+	if(head->next){
+		SYNERR(-1);
+		fprint(2, "multiple vars on left side of assignment\n");
+		Exit();
+	}
+	if(symlook(head->s, S_OVERRIDE, 0)){
+		set = override;
+	} else {
+		set = 1;
+		if(override)
+			symlook(head->s, S_OVERRIDE, 1);
+	}
+	if(set){
+		setvar(head->s, tail);
+		symlook(head->s, S_WESET, 1);
+		tail = 0;	/* don't free */
+	}
+	if(attr)
+		symlook(head->s, S_NOEXPORT, 1);
+	delword(head);
+	delword(tail);
+}
+
 void
+varoverride(char *line)
+{
+	Word *head, *tail;
+	char *dummy;
+	int attr;
+	
+	head = tail = 0;
+	if(rhead(line, &head, &tail, &attr, &dummy) == '='){
+		doassign(head, tail, attr, 1);
+		return;
+	}
+	delword(head);
+	delword(tail);
+}
+
+void
 addrules(Word *head, Word *tail, char *body, int attr, int hline, char *prog)
 {
 	Word *w;
@@ -135,6 +158,10 @@
 	int n;
 	Word *w;
 
+	*h = *t = 0;
+	*attr = 0;
+	*prog = 0;
+
 	p = charin(line,":=<");
 	if(p == 0)
 		return('?');
@@ -144,8 +171,6 @@
 		sep = '|';
 		p++;
 	}
-	*attr = 0;
-	*prog = 0;
 	if(sep == '='){
 		pp = charin(p, termchars);	/* termchars is shell-dependent */
 		if (pp && *pp == '=') {
@@ -195,7 +220,7 @@
 				if (pp == 0 || *pp == 0)
 					goto eos;
 				*pp = 0;
-				*prog = strdup(p);
+				*prog = Strdup(p);
 				*pp = ':';
 				p = pp;
 				break;
@@ -221,7 +246,7 @@
 		}
 	}
 	*h = w = stow(line);
-	if(*w->s == 0 && sep != '<' && sep != '|') {
+	if(empty(w) && sep != '<' && sep != '|') {
 		SYNERR(mkinline-1);
 		fprint(2, "no var on left side of assignment/rule\n");
 		Exit();
@@ -257,7 +282,7 @@
 			mkinline++;
 	}
 	insert(buf, 0);
-	p = strdup(buf->start);
+	p = Strdup(buf->start);
 	freebuf(buf);
 	return p;
 }
@@ -270,40 +295,29 @@
 };
 static struct input *inputs = 0;
 
-void
-ipush(void)
+static void
+ipush(char *file)
 {
-	struct input *in, *me;
+	struct input *i;
 
-	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;
-	}
+	i = (struct input *)Malloc(sizeof(*i));
+	i->file = mkinfile;
+	i->line = mkinline;
+	i->next = inputs;
+	inputs = i;
+
+	mkinfile = Strdup(file);
+	mkinline = 1;
 }
 
-void
+static void
 ipop(void)
 {
-	struct input *in, *me;
+	struct input *i;
 
-	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);
+	i = inputs;
+	inputs = i->next;
+	mkinfile = i->file;
+	mkinline = i->line;
+	free(i);
 }
--- a/sys/src/cmd/mk/plan9.c
+++ b/sys/src/cmd/mk/plan9.c
@@ -1,7 +1,8 @@
 #include	"mk.h"
 
-char 	*shell =	"/bin/rc";
-char 	*shellname =	"rc";
+char 	shell[] =	"/bin/rc";
+char 	shellname[] =	"rc";
+char	envdir[] =	"/env/";
 
 static	Word	*encodenulls(char*, int);
 
@@ -11,128 +12,119 @@
 	char *p;
 	int envf, f;
 	Dir *e;
-	char nam[1024];
-	int i, n, len;
+	Bufblock *path;
+	int i, n, len, npath;
 	Word *w;
 
 	rfork(RFENVG);	/*  use copy of the current environment variables */
 
-	envf = open("/env", OREAD);
+	envf = open(envdir, OREAD);
 	if(envf < 0)
 		return;
+
+	path = newbuf();
+	bufcpy(path, envdir);
+	npath = path->current - path->start;
+
 	while((n = dirread(envf, &e)) > 0){
 		for(i = 0; i < n; i++){
-			len = e[i].length;
 				/* don't import funny names, NULL values,
 				 * or internal mk variables
 				 */
+			len = e[i].length;
 			if(len <= 0 || *shname(e[i].name) != '\0')
 				continue;
 			if (symlook(e[i].name, S_INTERNAL, 0))
 				continue;
-			snprint(nam, sizeof nam, "/env/%s", e[i].name);
-			f = open(nam, OREAD);
+
+			path->current = path->start + npath;
+			bufcpy(path, e[i].name);
+			insert(path, 0);
+			f = open(path->start, OREAD);
 			if(f < 0)
 				continue;
 			p = Malloc(len+1);
 			if(read(f, p, len) != len){
-				perror(nam);
+				perror(path->start);
 				close(f);
 				continue;
 			}
+			p[len] = '\0';
 			close(f);
-			if (p[len-1] == 0)
-				len--;
-			else
-				p[len] = 0;
 			w = encodenulls(p, len);
 			free(p);
-			p = strdup(e[i].name);
-			setvar(p, (void *) w);
-			symlook(p, S_EXPORTED, (void*)"")->u.ptr = "";
+			setvar(e[i].name, w);
 		}
 		free(e);
 	}
+	freebuf(path);
 	close(envf);
 }
 
-/* break string of values into words at 01's or nulls*/
 static Word *
 encodenulls(char *s, int n)
 {
-	Word *w, *head;
-	char *cp;
+	Word *head, **link;
+	int m;
 
-	head = w = 0;
-	while (n-- > 0) {
-		for (cp = s; *cp && *cp != '\0'; cp++)
-				n--;
-		*cp = 0;
-		if (w) {
-			w->next = newword(s);
-			w = w->next;
-		} else
-			head = w = newword(s);
-		s = cp+1;
+	head = 0;
+	link = &head;
+	while(n > 0){
+		m = strlen(s)+1;
+		n -= m;
+		*link = newword(s);
+		s += m;
+		link = &(*link)->next;
 	}
-	if (!head)
-		head = newword("");
 	return head;
 }
 
-/* as well as 01's, change blanks to nulls, so that rc will
- * treat the words as separate arguments
- */
 void
-exportenv(Envy *e)
+exportenv(Symtab **e)
 {
-	int f, n, hasvalue, first;
+	int f, n, npath;
+	Bufblock *path;
 	Word *w;
-	Symtab *sy;
-	char nam[256];
 
-	for(;e->name; e++){
-		sy = symlook(e->name, S_VAR, 0);
-		if (e->values == 0 || e->values->s == 0 || e->values->s[0] == 0)
-			hasvalue = 0;
-		else
-			hasvalue = 1;
-		if(sy == 0 && !hasvalue)	/* non-existant null symbol */
+	path = newbuf();
+	bufcpy(path, envdir);
+	npath = path->current - path->start;
+
+	for(;*e; e++){
+		w = (*e)->u.ptr;
+		path->current = path->start + npath;
+		bufcpy(path, (*e)->name);
+		insert(path, 0);
+		if(w == 0){
+			remove(path->start);
 			continue;
-		snprint(nam, sizeof nam, "/env/%s", e->name);
-		if (sy != 0 && !hasvalue) {	/* Remove from environment */
-				/* we could remove it from the symbol table
-				 * too, but we're in the child copy, and it
-				 * would still remain in the parent's table.
-				 */
-			remove(nam);
-			delword(e->values);
-			e->values = 0;		/* memory leak */
-			continue;
 		}
-	
-		f = create(nam, OWRITE, 0666L);
+		f = create(path->start, OWRITE, 0666L);
 		if(f < 0) {
-			fprint(2, "can't create %s, f=%d\n", nam, f);
-			perror(nam);
+			fprint(2, "can't create %s\n", path->start);
+			perror(path->start);
 			continue;
 		}
-		first = 1;
-		for (w = e->values; w; w = w->next) {
+		if(w->next == 0){
 			n = strlen(w->s);
-			if (n) {
-				if(first)
-					first = 0;
-				else{
-					if (write (f, "\0", 1) != 1)
-						perror(nam);
-				}
-				if (write(f, w->s, n) != n)
-					perror(nam);
-			}
+			if(n == 0) n = 1;
+			if(write(f, w->s, n) != n)
+				perror(path->start);
+		} else {
+			Bufblock *buf = newbuf();
+			do {
+				bufcpy(buf, w->s);
+				insert(buf, 0);
+				w = w->next;
+			} while(w);
+			n = buf->current - buf->start;
+			if(write(f, buf->start, n) != n)
+				perror(path->start);
+			freebuf(buf);
 		}
 		close(f);
 	}
+	freebuf(path);
 }
 
 int
@@ -156,83 +148,31 @@
 }
 
 int
-execsh(char *args, char *cmd, Bufblock *buf, Envy *e)
+execsh(char *cmd, char *args, Symtab **env, Bufblock *buf)
 {
-	char *p;
-	int tot, n, pid, in[2], out[2];
+	int fd, tot, n, pid;
 
-	if(buf && pipe(out) < 0){
-		perror("pipe");
-		Exit();
-	}
-	pid = rfork(RFPROC|RFFDG|RFENVG);
-	if(pid < 0){
-		perror("mk rfork");
-		Exit();
-	}
-	if(pid == 0){
-		if(buf)
-			close(out[0]);
-		if(pipe(in) < 0){
-			perror("pipe");
-			Exit();
-		}
-		pid = fork();
-		if(pid < 0){
-			perror("mk fork");
-			Exit();
-		}
-		if(pid != 0){
-			dup(in[0], 0);
-			if(buf){
-				dup(out[1], 1);
-				close(out[1]);
-			}
-			close(in[0]);
-			close(in[1]);
-			if (e)
-				exportenv(e);
-			if(shflags)
-				execl(shell, shellname, shflags, args, nil);
-			else
-				execl(shell, shellname, args, nil);
-			perror(shell);
-			_exits("exec");
-		}
-		if(buf)
-			close(out[1]);
-		close(in[0]);
-		p = cmd+strlen(cmd);
-		while(cmd < p){
-			n = write(in[1], cmd, p-cmd);
-			if(n < 0)
-				break;
-			cmd += n;
-		}
-		close(in[1]);
-		_exits(0);
-	}
+	pid = pipecmd(cmd, args, env, buf? &fd: 0);
 	if(buf){
-		close(out[1]);
 		tot = 0;
 		for(;;){
-			if (buf->current >= buf->end)
+			if(buf->current >= buf->end)
 				growbuf(buf);
-			n = read(out[0], buf->current, buf->end-buf->current);
+			n = read(fd, buf->current, buf->end-buf->current);
 			if(n <= 0)
 				break;
 			buf->current += n;
 			tot += n;
 		}
-		if (tot && buf->current[-1] == '\n')
+		if(tot && buf->current[-1] == '\n')
 			buf->current--;
-		close(out[0]);
+		close(fd);
 	}
 	return pid;
 }
 
 int
-pipecmd(char *cmd, Envy *e, int *fd)
+pipecmd(char *cmd, char *args, Symtab **env, int *fd)
 {
 	int pid, pfd[2];
 
@@ -254,18 +194,22 @@
 			dup(pfd[1], 1);
 			close(pfd[1]);
 		}
-		if(e)
-			exportenv(e);
-		if(shflags)
-			execl(shell, shellname, shflags, "-c", cmd, nil);
+		if(env)
+			exportenv(env);
+		if(args)
+			execl(shell, shellname, args, "-Ic", cmd, nil);
 		else
-			execl(shell, shellname, "-c", cmd, nil);
+			execl(shell, shellname, "-Ic", cmd, nil);
 		perror(shell);
 		_exits("exec");
 	}
 	if(fd){
+		char name[32];
+
 		close(pfd[1]);
-		*fd = pfd[0];
+		snprint(name, sizeof(name), "/fd/%d", pfd[0]);
+		*fd = open(name, OREAD|OCEXEC);
+		close(pfd[0]);
 	}
 	return pid;
 }
@@ -301,15 +245,6 @@
 	atnotify(notifyf, 1);
 }
 
-char*
-maketmp(void)
-{
-	static char temp[] = "/tmp/mkargXXXXXX";
-
-	mktemp(temp);
-	return temp;
-}
-
 int
 chgtime(char *name)
 {
@@ -335,7 +270,7 @@
 			p = match->ep;
 			c = *p;
 			*p = 0;
-			*to = strdup(match->sp);
+			*to = Strdup(match->sp);
 			*p = c;
 		}
 		else
@@ -344,95 +279,75 @@
 }
 
 void
-dirtime(char *dir, char *path)
+dirtime(char *dir)
 {
-	int i, fd, n;
-	long mtime;
+	int i, fd, n, npath;
+	Bufblock *path;
+	ulong t;
 	Dir *d;
-	char buf[4096];
 
-	fd = open(dir, OREAD);
+	if(symlook(dir, S_BULKED, 0))
+		return;
+	symlook(dir, S_BULKED, 1);
+
+	path = newbuf();
+	bufcpy(path, dir);
+	if(strcmp(dir, ".") == 0)
+		npath = 0;
+	else {
+		insert(path, '/');
+		npath = path->current - path->start;
+	}
+	insert(path, 0);
+	fd = open(path->start, OREAD);
 	if(fd >= 0){
 		while((n = dirread(fd, &d)) > 0){
 			for(i=0; i<n; i++){
-				mtime = d[i].mtime;
+				t = d[i].mtime;
 				/* defensive driving: this does happen */
-				if(mtime == 0)
-					mtime = 1;
-				snprint(buf, sizeof buf, "%s%s", path,
-					d[i].name);
-				if(symlook(buf, S_TIME, 0) == nil)
-					symlook(strdup(buf), S_TIME,
-						(void*)mtime)->u.value = mtime;
+				if(t == 0) t = 1;
+				path->current = path->start + npath;
+				bufcpy(path, d[i].name);
+				insert(path, 0);
+				symlook(path->start, S_TIME, 1)->u.value = t;
 			}
 			free(d);
 		}
 		close(fd);
 	}
+	freebuf(path);
 }
 
-void
-bulkmtime(char *dir)
-{
-	char buf[4096];
-	char *ss, *s, *sym;
-
-	if(dir){
-		sym = dir;
-		s = dir;
-		if(strcmp(dir, "/") == 0)
-			strecpy(buf, buf + sizeof buf - 1, dir);
-		else
-			snprint(buf, sizeof buf, "%s/", dir);
-	}else{
-		s = ".";
-		sym = "";
-		buf[0] = 0;
-	}
-	if(symlook(sym, S_BULKED, 0))
-		return;
-	ss = strdup(sym);
-	symlook(ss, S_BULKED, (void*)ss);
-	dirtime(s, buf);
-}
-
 ulong
 mkmtime(char *name, int force)
 {
-	Dir *d;
-	char *s, *ss, carry;
+	char *a, *s;
 	ulong t;
-	Symtab *sym;
-	char buf[4096];
 
-	strecpy(buf, buf + sizeof buf - 1, name);
-	cleanname(buf);
-	name = buf;
-
-	s = utfrrune(name, '/');
-	if(s == name)
-		s++;
+	t = 0;
+	/* cleanname() needs at least 2 characters */
+	a = Malloc(strlen(name)+2+1);
+	strcpy(a, name);
+	cleanname(a);
+	s = utfrrune(a, '/');
 	if(s){
-		ss = name;
-		carry = *s;
 		*s = 0;
+		dirtime(a);
+		*s = '/';
 	}else{
-		ss = 0;
-		carry = 0;
+		dirtime(".");
 	}
-	bulkmtime(ss);
-	if(carry)
-		*s = carry;
 	if(!force){
-		sym = symlook(name, S_TIME, 0);
+		Symtab *sym = symlook(a, S_TIME, 0);
 		if(sym)
-			return sym->u.value;
-		return 0;
+			t = sym->u.value;
+	} else {
+		Dir *d = dirstat(a);
+		if(d){
+			t = d->mtime;
+			free(d);
+		}
 	}
-	if((d = dirstat(name)) == nil)
-		return 0;
-	t = d->mtime;
-	free(d);
+	free(a);
 	return t;
 }
-
--- a/sys/src/cmd/mk/rc.c
+++ b/sys/src/cmd/mk/rc.c
@@ -1,8 +1,6 @@
 #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 */
+char	termchars[] = "'= \t";	/*used in parse.c to isolate assignment attribute*/
 
 /*
  *	This file contains functions that depend on rc's syntax.  Most
@@ -172,4 +170,41 @@
 			s = copysingle(s, buf);	/* copy quoted string */
 	}
 	return s;
+}
+
+static int
+needquotes(char *s)
+{
+	Rune r;
+
+	if(*s == 0)
+		return 1;
+	while(*s){
+		s += chartorune(&r, s);
+		if(needsrcquote(r))
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ *	append string s into buffer buf with rc quoting as neccessary.
+ */
+void
+bufcpyq(Bufblock *buf, char *s)
+{
+	Rune r;
+
+	if(!needquotes(s)){
+		bufcpy(buf, s);
+		return;
+	}
+	insert(buf, '\'');
+	while(*s){
+		s += chartorune(&r, s);
+		if(r == '\'')
+			rinsert(buf, r);
+		rinsert(buf, r);
+	}
+	insert(buf, '\'');
 }
--- a/sys/src/cmd/mk/recipe.c
+++ b/sys/src/cmd/mk/recipe.c
@@ -9,7 +9,7 @@
 	Node *n;
 	Rule *r = 0;
 	Symtab *s;
-	Word head, ahead, lp, ln, *w, *ww, *aw;
+	Word *head, *ahead, *lp, *ln, *w, **ww, **aw;
 
 	aa = 0;
 	/*
@@ -45,13 +45,11 @@
 		build the node list
 	*/
 	node->next = 0;
-	head.next = 0;
-	ww = &head;
-	ahead.next = 0;
-	aw = &ahead;
+	head = 0, ww = &head;
+	ahead = 0, aw = &ahead;
 	if(r->attr&REGEXP){
-		ww->next = newword(node->name);
-		aw->next = newword(node->name);
+		*ww = newword(node->name);
+		*aw = newword(node->name);
 	} else {
 		for(w = r->alltargets; w; w = w->next){
 			if(r->attr&META)
@@ -58,11 +56,10 @@
 				subst(aa->stem, w->s, buf, sizeof(buf));
 			else
 				strecpy(buf, buf + sizeof buf - 1, w->s);
-			aw->next = newword(buf);
-			aw = aw->next;
+			*aw = newword(buf), aw = &(*aw)->next;
 			if((s = symlook(buf, S_NODE, 0)) == 0)
 				continue;	/* not a node we are interested in */
-			n = s->u.ptr;
+			n = (Node*)s->u.ptr;
 			if(aflag == 0 && n->time) {
 				for(a = n->prereqs; a; a = a->next)
 					if(a->n && outofdate(n, a, 0))
@@ -70,8 +67,7 @@
 				if(a == 0)
 					continue;
 			}
-			ww->next = newword(buf);
-			ww = ww->next;
+			*ww = newword(buf), ww = &(*ww)->next;
 			if(n == node) continue;
 			n->next = node->next;
 			node->next = n;
@@ -83,13 +79,13 @@
 	/*
 		gather the params for the job
 	*/
-	lp.next = ln.next = 0;
+	lp = ln = 0;
 	for(n = node; n; n = n->next){
 		for(a = n->prereqs; a; a = a->next){
 			if(a->n){
-				addw(&lp, a->n->name);
+				wadd(&lp, a->n->name);
 				if(outofdate(n, a, 0)){
-					addw(&ln, a->n->name);
+					wadd(&ln, a->n->name);
 					if(explain)
 						fprint(1, "%s(%ld) < %s(%ld)\n",
 							n->name, n->time, a->n->name, a->n->time);
@@ -96,25 +92,11 @@
 				}
 			} else {
 				if(explain)
-					fprint(1, "%s has no prerequisites\n",
-							n->name);
+					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));
+	run(newjob(r, node, aa->stem, aa->match, lp, ln, head, ahead));
 	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);
 }
--- a/sys/src/cmd/mk/rule.c
+++ b/sys/src/cmd/mk/rule.c
@@ -1,33 +1,33 @@
 #include	"mk.h"
 
 static Rule *lr, *lmr;
-static 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)
+addrule(char *head, Word *tail, char *body, Word *ahead, int attr, int line, char *prog)
 {
-	Rule *r;
-	Rule *rr;
+	Rule *r, *rr;
 	Symtab *sym;
 	int reuse;
 
 	r = 0;
 	reuse = 0;
-	if(sym = symlook(head, S_TARGET, 0)){
-		for(r = sym->u.ptr; r; r = r->chain)
-			if(rcmp(r, head, tail) == 0){
+	sym = symlook(head, S_TARGET, 0);
+	if(sym){
+		for(r = sym->u.ptr; r; r = r->chain){
+			if(wcmp(r->tail, 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->line = line;
+	r->file = mkinfile;
 	r->attr = attr;
 	r->alltargets = ahead;
 	r->prog = prog;
@@ -34,15 +34,19 @@
 	r->rule = nrules++;
 
 	if(!reuse){
-		rr = symlook(head, S_TARGET, r)->u.ptr;
-		if(rr != r){
+		r->next = 0;
+		r->chain = 0;
+		if(sym == 0){
+			sym = symlook(head, S_TARGET, 1);
+			sym->u.ptr = r;
+		} else {
+			rr = sym->u.ptr;
 			r->chain = rr->chain;
 			rr->chain = r;
-		} else
-			r->chain = 0;
+		}
 	}
-	if(!reuse)
-		r->next = 0;
+	r->target = sym;
+
 	if((attr&REGEXP) || charin(head, "%&")){
 		r->attr |= META;
 		if(reuse)
@@ -73,28 +77,19 @@
 void
 dumpr(char *s, Rule *r)
 {
+	char *t;
+
 	Bprint(&bout, "%s: start=%p\n", s, r);
 	for(; r; r = r->next){
 		Bprint(&bout, "\tRule %p: %s:%d attr=%x next=%p chain=%p alltarget='%s'",
-			r, r->file, r->line, r->attr, r->next, r->chain, wtos(r->alltargets, ' '));
+			r, r->file, r->line, r->attr, r->next, r->chain,
+			t = wtos(r->alltargets)), free(t);
 		if(r->prog)
 			Bprint(&bout, " prog='%s'", r->prog);
-		Bprint(&bout, "\n\ttarget=%s: %s\n", r->target, wtos(r->tail,' '));
+		Bprint(&bout, "\n\ttarget=%s: %s\n", r->target->name,
+			t = wtos(r->tail)), free(t);
 		Bprint(&bout, "\trecipe@%p='%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 *
--- a/sys/src/cmd/mk/run.c
+++ b/sys/src/cmd/mk/run.c
@@ -40,12 +40,12 @@
 static void
 sched(void)
 {
-	char *flags;
-	Job *j;
+	char *t, *flags;
 	Bufblock *buf;
-	int slot;
+	Symtab **env;
 	Node *n;
-	Envy *e;
+	Job *j;
+	int slot;
 
 	if(jobs == 0){
 		usage();
@@ -54,14 +54,16 @@
 	j = jobs;
 	jobs = j->next;
 	if(DEBUG(D_EXEC))
-		fprint(1, "firing up job for target %s\n", wtos(j->t, ' '));
+		fprint(1, "firing up job for target %s\n",
+			t = wtos(j->t)),
+			free(t);
 	slot = nextslot();
 	events[slot].job = j;
 	buf = newbuf();
-	e = buildenv(j, slot);
-	shprint(j->r->recipe, e, buf);
+	env = buildenv(j, slot);
+	shprint(j->r->recipe, buf);
 	if(!tflag && (nflag || !(j->r->attr&QUIET)))
-		Bwrite(&bout, buf->start, (long)strlen(buf->start));
+		Bwrite(&bout, buf->start, strlen(buf->start));
 	freebuf(buf);
 	if(nflag||tflag){
 		for(n = j->n; n; n = n->next){
@@ -82,11 +84,13 @@
 			flags = 0;
 		else
 			flags = "-e";
-		events[slot].pid = execsh(flags, j->r->recipe, 0, e);
+		events[slot].pid = execsh(j->r->recipe, flags, env, 0);
 		usage();
 		nrunning++;
 		if(DEBUG(D_EXEC))
-			fprint(1, "pid for target %s = %d\n", wtos(j->t, ' '), events[slot].pid);
+			fprint(1, "pid for target %s = %d\n",
+				t = wtos(j->t), events[slot].pid),
+				free(t);
 	}
 }
 
@@ -93,7 +97,6 @@
 int
 waitup(int echildok, int *retstatus)
 {
-	Envy *e;
 	int pid;
 	int slot;
 	Symtab *s;
@@ -140,13 +143,14 @@
 		goto again;
 	}
 	j = events[slot].job;
+	events[slot].job = 0;
+	events[slot].pid = -1;
 	usage();
 	nrunning--;
-	events[slot].pid = -1;
 	if(buf[0]){
-		e = buildenv(j, slot);
+		buildenv(j, slot);
 		bp = newbuf();
-		shprint(j->r->recipe, e, bp);
+		shprint(j->r->recipe, bp);
 		front(bp->start);
 		fprint(2, "mk: %s: exit status=%s", bp->start, buf);
 		freebuf(bp);
@@ -169,8 +173,9 @@
 	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, s->u.ptr);
+		update(uarg, (Node*)s->u.ptr);
 	}
+	freejob(j);
 	if(nrunning < nproclimit)
 		sched();
 	return(0);
@@ -179,14 +184,10 @@
 void
 nproc(void)
 {
-	Symtab *sym;
 	Word *w;
 
-	if(sym = symlook("NPROC", S_VAR, 0)) {
-		w = sym->u.ptr;
-		if (w && w->s && w->s[0])
-			nproclimit = atoi(w->s);
-	}
+	if(!empty(w = getvar("NPROC")))
+		nproclimit = atoi(w->s);
 	if(nproclimit < 1)
 		nproclimit = 1;
 	if(DEBUG(D_EXEC))
@@ -193,7 +194,7 @@
 		fprint(1, "nprocs = %d\n", nproclimit);
 	if(nproclimit > nevents){
 		if(nevents)
-			events = (Event *)Realloc((char *)events, nproclimit*sizeof(Event));
+			events = (Event *)Realloc(events, nproclimit*sizeof(Event));
 		else
 			events = (Event *)Malloc(nproclimit*sizeof(Event));
 		while(nevents < nproclimit)
@@ -281,7 +282,7 @@
 	long t;
 
 	time(&t);
-	if(tick)
+	if(tick && nrunning < nelem(tslot))
 		tslot[nrunning] += (t-tick);
 	tick = t;
 }
@@ -292,6 +293,6 @@
 	int i;
 
 	usage();
-	for(i = 0; i <= nevents; i++)
+	for(i = 0; i <= nevents && i < nelem(tslot); i++)
 		fprint(1, "%d: %ld\n", i, tslot[i]);
 }
--- a/sys/src/cmd/mk/shprint.c
+++ b/sys/src/cmd/mk/shprint.c
@@ -1,9 +1,9 @@
 #include	"mk.h"
 
-static char *vexpand(char*, Envy*, Bufblock*);
+static char *vexpand(char*, Bufblock*);
 
 void
-shprint(char *s, Envy *env, Bufblock *buf)
+shprint(char *s, Bufblock *buf)
 {
 	int n;
 	Rune r;
@@ -11,7 +11,7 @@
 	while(*s) {
 		n = chartorune(&r, s);
 		if (r == '$')
-			s = vexpand(s, env, buf);
+			s = vexpand(s, buf);
 		else {
 			rinsert(buf, r);
 			s += n;
@@ -21,25 +21,26 @@
 	insert(buf, 0);
 }
 
-static char *
-mygetenv(char *name, Envy *env)
+static Symtab*
+mygetenv(char *name)
 {
-	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, ' ');
+	Symtab *s;
+
+	/* only resolve internal variables and variables we've set */
+	s = symlook(name, S_INTERNAL, 0);
+	if(s == 0){
+		s = symlook(name, S_VAR, 0);
+		if(s == 0 || !symlook(name, S_WESET, 0))
+			return  0;
 	}
-	return 0;
+	return s;
 }
 
 static char *
-vexpand(char *w, Envy *env, Bufblock *buf)
+vexpand(char *w, Bufblock *buf)
 {
-	char *s, carry, *p, *q;
+	char carry, *p, *q;
+	Symtab *s;
 
 	assert(/*vexpand no $*/ *w == '$');
 	p = w+1;	/* skip dollar sign */
@@ -52,15 +53,14 @@
 		q = shname(p);
 	carry = *q;
 	*q = 0;
-	s = mygetenv(p, env);
+	s = mygetenv(p);
 	*q = carry;
 	if (carry == '}')
 		q++;
-	if (s) {
-		bufcpy(buf, s, strlen(s));
-		free(s);
-	} else 		/* copy name intact*/
-		bufcpy(buf, w, q-w);
+	if(s)
+		bufcpyw(buf, s->u.ptr);
+	else 		/* copy $name intact */
+		bufncpy(buf, w, q-w);
 	return(q);
 }
 
@@ -71,7 +71,7 @@
 	int i, j;
 	char *flds[512];
 
-	q = strdup(s);
+	q = Strdup(s);
 	i = getfields(q, flds, nelem(flds), 0, " \t\n");
 	if(i > 5){
 		flds[4] = flds[i-1];
--- a/sys/src/cmd/mk/symtab.c
+++ b/sys/src/cmd/mk/symtab.c
@@ -5,7 +5,7 @@
 static Symtab *hash[NHASH];
 
 Symtab *
-symlook(char *sym, int space, void *install)
+symlook(char *sym, int space, int install)
 {
 	long h;
 	char *p;
@@ -17,17 +17,17 @@
 		h = ~h;
 	h %= NHASH;
 	for(s = hash[h]; s; s = s->next)
-		if((s->space == space) && (strcmp(s->name, sym) == 0))
-			return(s);
+		if(s->space == space && strcmp(s->name, sym) == 0)
+			return s;
 	if(install == 0)
-		return(0);
-	s = (Symtab *)Malloc(sizeof(Symtab));
+		return 0;
+	s = (Symtab *)Malloc(sizeof(Symtab) + (++p - sym));
 	s->space = space;
-	s->name = sym;
-	s->u.ptr = install;
+	s->u.ptr = 0;
+	memcpy(s->name, sym, p - sym);
 	s->next = hash[h];
 	hash[h] = s;
-	return(s);
+	return s;
 }
 
 void
--- a/sys/src/cmd/mk/var.c
+++ b/sys/src/cmd/mk/var.c
@@ -1,10 +1,20 @@
 #include	"mk.h"
 
+Word*
+getvar(char *name)
+{
+	Symtab *sym = symlook(name, S_VAR, 0);
+	if(sym)
+		return sym->u.ptr;
+	return 0;
+}
+
 void
-setvar(char *name, void *value)
+setvar(char *name, Word *value)
 {
-	symlook(name, S_VAR, value)->u.ptr = value;
-	symlook(name, S_MAKEVAR, (void*)"");
+	Symtab *sym = symlook(name, S_VAR, 1);
+	delword(sym->u.ptr);
+	sym->u.ptr = value;
 }
 
 static void
--- a/sys/src/cmd/mk/varsub.c
+++ b/sys/src/cmd/mk/varsub.c
@@ -5,7 +5,6 @@
 static	Bufblock	*varname(char**);
 static	Word		*extractpat(char*, char**, char*, char*);
 static	int		submatch(char*, Word*, Word*, int*, char**);
-static	Word		*varmatch(char *);
 
 Word *
 varsub(char **s)
@@ -15,14 +14,12 @@
 
 	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);
+	w = getvar(b->start);
 	freebuf(b);
-	return w;
+	return wdup(w);
 }
 
 /*
@@ -45,7 +42,7 @@
 		rinsert(b, r);
 		cp += n;
 	}
-	if (b->current == b->start){
+	if(b->current == b->start){
 		SYNERR(-1);
 		fprint(2, "missing variable name <%s>\n", *s);
 		freebuf(b);
@@ -57,42 +54,25 @@
 }
 
 static Word*
-varmatch(char *name)
-{
-	Word *w;
-	Symtab *sym;
-	
-	sym = symlook(name, S_VAR, 0);
-	if(sym){
-			/* check for at least one non-NULL value */
-		for (w = sym->u.ptr; w; w = w->next)
-			if(w->s && *w->s)
-				return wdup(w);
-	}
-	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)
+	if(buf == 0)
 		return 0;
 	cp = *s;
-	if (*cp == '}') {				/* ${name} variant*/
+	if(*cp == '}') {				/* ${name} variant*/
 		(*s)++;					/* skip the '}' */
-		w = varmatch(buf->start);
+		w = getvar(buf->start);
 		freebuf(buf);
-		return w;
+		return wdup(w);
 	}
-	if (*cp != ':') {
+	if(*cp != ':') {
 		SYNERR(-1);
 		fprint(2, "bad variable name <%s>\n", buf->start);
 		freebuf(buf);
@@ -99,7 +79,7 @@
 		return 0;
 	}
 	cp++;
-	end = charin(cp , "}");
+	end = charin(cp, "}");
 	if(end == 0){
 		SYNERR(-1);
 		fprint(2, "missing '}': %s\n", begin);
@@ -107,13 +87,10 @@
 	}
 	*end = 0;
 	*s = end+1;
-	
-	sym = symlook(buf->start, S_VAR, 0);
-	if(sym == 0 || sym->u.value == 0)
-		w = newword(buf->start);
-	else
-		w = subsub(sym->u.ptr, cp, end);
+	w = getvar(buf->start);
 	freebuf(buf);
+	if(w)
+		w = subsub(w, cp, end);
 	return w;
 }
 
@@ -120,8 +97,7 @@
 static Word*
 extractpat(char *s, char **r, char *term, char *end)
 {
-	int save;
-	char *cp;
+	char save, *cp;
 	Word *w;
 
 	cp = charin(s, term);
@@ -144,7 +120,7 @@
 subsub(Word *v, char *s, char *end)
 {
 	int nmid;
-	Word *head, *tail, *w, *h;
+	Word *head, *tail, *w, *h, **l;
 	Word *a, *b, *c, *d;
 	Bufblock *buf;
 	char *cp, *enda;
@@ -164,47 +140,52 @@
 	buf = newbuf();
 	for(; v; v = v->next){
 		h = w = 0;
+		l = &h;
 		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)
+				*l = w = wdup(c);
+				while(w->next){
+					l = &w->next;
 					w = w->next;
+				}
 			}
 			if(PERCENT(*cp) && nmid > 0){	
 				if(w){
-					bufcpy(buf, w->s, strlen(w->s));
-					bufcpy(buf, enda, nmid);
+					bufcpy(buf, w->s);
+					bufncpy(buf, enda, nmid);
 					insert(buf, 0);
-					free(w->s);
-					w->s = strdup(buf->start);
+					delword(w);
+					*l = w = newword(buf->start);
 				} else {
-					bufcpy(buf, enda, nmid);
+					bufncpy(buf, enda, nmid);
 					insert(buf, 0);
-					h = w = newword(buf->start);
+					*l = w = newword(buf->start);
 				}
 				buf->current = buf->start;
 			}
-			if(d && *d->s){
+			if(!empty(d)){
 				if(w){
-
-					bufcpy(buf, w->s, strlen(w->s));
-					bufcpy(buf, d->s, strlen(d->s));
+					bufcpy(buf, w->s);
+					bufcpy(buf, d->s);
 					insert(buf, 0);
-					free(w->s);
-					w->s = strdup(buf->start);
+					delword(w);
+					*l = w = newword(buf->start);
 					w->next = wdup(d->next);
-					while(w->next)
-						w = w->next;
 					buf->current = buf->start;
-				} else
-					h = w = wdup(d);
+				} else {
+					*l = w = wdup(d);
+				}
+				while(w->next){
+					l = &w->next;
+					w = w->next;
+				}
 			}
 		}
 		if(w == 0)
-			h = w = newword(v->s);
+			*l = w = newword(v->s);
 	
 		if(head == 0)
 			head = h;
@@ -223,9 +204,9 @@
 static int
 submatch(char *s, Word *a, Word *b, int *nmid, char **enda)
 {
+	char *end;
 	Word *w;
 	int n;
-	char *end;
 
 	n = 0;
 	for(w = a; w; w = w->next){
--- a/sys/src/cmd/mk/word.c
+++ b/sys/src/cmd/mk/word.c
@@ -5,85 +5,109 @@
 Word*
 newword(char *s)
 {
-	Word *w;
-
-	w = (Word *)Malloc(sizeof(Word));
-	w->s = strdup(s);
+	int n = strlen(s)+1;
+	Word *w = (Word *)Malloc(sizeof(Word) + n);
+	memcpy(w->s, s, n);
 	w->next = 0;
-	return(w);
+	return w;
 }
 
+Word*
+popword(Word *w)
+{
+	Word *x = w->next;
+	free(w);
+	return x;
+}
+
 Word *
 stow(char *s)
 {
-	Word *head, *w, *new;
+	Word *w, *h, **l;
 
-	w = head = 0;
-	while(*s){
-		new = nextword(&s);
-		if(new == 0)
-			break;
-		if (w)
-			w->next = new;
-		else
-			head = w = new;
+	h = 0;
+	l = &h;
+	while(*s && (*l = w = nextword(&s))){
 		while(w->next)
 			w = w->next;
-		
+		l = &w->next;
 	}
-	if (!head)
-		head = newword("");
-	return(head);
+	return h;
 }
 
 char *
-wtos(Word *w, int sep)
+wtos(Word *w)
 {
 	Bufblock *buf;
-	char *cp;
+	char *s;
 
 	buf = newbuf();
+	bufcpyw(buf, w);
+	insert(buf, 0);
+	s = Strdup(buf->start);
+	freebuf(buf);
+	return s;
+}
+
+void
+bufcpyw(Bufblock *buf, Word *w)
+{
 	for(; w; w = w->next){
-		for(cp = w->s; *cp; cp++)
-			insert(buf, *cp);
+		bufcpyq(buf, w->s);
 		if(w->next)
-			insert(buf, sep);
+			insert(buf, ' ');
 	}
-	insert(buf, 0);
-	cp = strdup(buf->start);
-	freebuf(buf);
-	return(cp);
 }
 
+int
+empty(Word *w)
+{
+	return w == 0 || w->s[0] == 0;
+}
+
+int
+wadd(Word **l, char *s)
+{
+	Word *w;
+
+	while(w = *l){
+		if(strcmp(w->s, s) == 0)
+			return 1;
+		l = &w->next;
+	}
+	*l = newword(s);
+	return 0;
+}
+
+int
+wcmp(Word *a, Word *b)
+{
+	for(; a && b; a = a->next, b = b->next)
+		if(strcmp(a->s, b->s))
+			return 1;
+	return(a || b);
+}
+
 Word*
 wdup(Word *w)
 {
-	Word *v, *new, *base;
+	Word *h, **l;
 
-	v = base = 0;
+	h = 0;
+	l = &h;
 	while(w){
-		new = newword(w->s);
-		if(v)
-			v->next = new;
-		else
-			base = new;
-		v = new;
+		*l = newword(w->s);
+		l = &(*l)->next;
 		w = w->next;
 	}
-	return base;
+	return h;
 }
 
 void
 delword(Word *w)
 {
-	Word *v;
-
-	while(v = w){
-		w = w->next;
-		if(v->s)
-			free(v->s);
-		free(v);
-	}
+	while(w)
+		w = popword(w);
 }
 
 /*
@@ -93,19 +117,20 @@
 static Word*
 nextword(char **s)
 {
+	Word *head, *tail, **link, *w, *t;
 	Bufblock *b;
-	Word *head, *tail, *w;
-	Rune r;
-	char *cp;
 	int empty;
+	char *cp;
+	Rune r;
 
 	cp = *s;
 	b = newbuf();
-restart:
+	empty = 1;
 	head = tail = 0;
+	link = &head;
+restart:
 	while(*cp == ' ' || *cp == '\t')		/* leading white space */
 		cp++;
-	empty = 1;
 	while(*cp){
 		cp += chartorune(&r, cp);
 		switch(r)
@@ -117,12 +142,12 @@
 		case '\\':
 		case '\'':
 		case '"':
-			empty = 0;
 			cp = expandquote(cp, r, b);
 			if(cp == 0){
 				fprint(2, "missing closing quote: %s\n", *s);
 				Exit();
 			}
+			empty = 0;
 			break;
 		case '$':
 			w = varsub(&cp);
@@ -133,29 +158,29 @@
 			}
 			empty = 0;
 			if(b->current != b->start){
-				bufcpy(b, w->s, strlen(w->s));
+				bufcpy(b, w->s);
 				insert(b, 0);
-				free(w->s);
-				w->s = strdup(b->start);
+				t = popword(w);
+				w = newword(b->start);
+				w->next = t;
 				b->current = b->start;
 			}
-			if(head){
-				bufcpy(b, tail->s, strlen(tail->s));
-				bufcpy(b, w->s, strlen(w->s));
+			if(tail){
+				bufcpy(b, tail->s);
+				bufcpy(b, w->s);
 				insert(b, 0);
-				free(tail->s);
-				tail->s = strdup(b->start);
-				tail->next = w->next;
-				free(w->s);
-				free(w);
+				delword(tail);
+				*link = tail = newword(b->start);
+				tail->next = popword(w);
 				b->current = b->start;
 			} else
-				tail = head = w;
-			while(tail->next)
+				*link = tail = w;
+			while(tail->next){
+				link = &tail->next;
 				tail = tail->next;
+			}
 			break;
 		default:
-			empty = 0;
 			rinsert(b, r);
 			break;
 		}
@@ -162,18 +187,19 @@
 	}
 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);
+	if(b->current != b->start || !empty){
+		insert(b, 0);
+		if(tail){
+			cp = Strdup(b->start);
+			b->current = b->start;
+			bufcpy(b, tail->s);
+			bufcpy(b, cp);
+			free(cp);
 			insert(b, 0);
-			free(tail->s);
-			tail->s = strdup(cp);
-		} else {
-			insert(b, 0);
-			head = newword(b->start);
-		}
+			delword(tail);
+			*link = newword(b->start);
+		} else
+			*link = newword(b->start);
 	}
 	freebuf(b);
 	return head;