code: 9ferno

Download patch

ref: edafd783588d269cd0234e775b464be728bdcd92
parent: de5fee8aa45bf9af85583b87009c23a219efd155
author: 9ferno <gophone2015@gmail.com>
date: Fri Nov 5 16:39:15 EDT 2021

migrating 9front proc, queue and chan code

--- a/os/ip/plan9.c
+++ b/os/ip/plan9.c
@@ -27,10 +27,3 @@
 {
 	return up->env->errstr;
 }
-
-int
-postnote(Proc *p, int, char *, int)
-{
-	swiproc(p, 0);
-	return 0;
-}
--- a/os/pc64/fpu.c
+++ b/os/pc64/fpu.c
@@ -86,11 +86,6 @@
 	"precision loss",
 };
 
-enum
-{
-	NDebug,				/* print debug message */
-};
-
 static void
 mathnote(ulong status, uintptr pc)
 {
--- a/os/pc64/inferno.main.c
+++ b/os/pc64/inferno.main.c
@@ -179,8 +179,8 @@
 	 */
 	o = up->env;
 	o->pgrp->slash = namec("#/", Atodir, 0, 0);
-	cnameclose(o->pgrp->slash->name);
-	o->pgrp->slash->name = newcname("/");
+	cnameclose(o->pgrp->slash->path);
+	o->pgrp->slash->path = newcname("/");
 	o->pgrp->dot = cclone(o->pgrp->slash);
 
 	chandevinit();
--- a/os/pc64/main.c
+++ b/os/pc64/main.c
@@ -293,8 +293,8 @@
 	 */
 	o = up->env;
 	o->pgrp->slash = namec("#/", Atodir, 0, 0);
-	cnameclose(o->pgrp->slash->name);
-	o->pgrp->slash->name = newcname("/");
+	pathclose(o->pgrp->slash->path);
+	o->pgrp->slash->path = newpath("/");
 	o->pgrp->dot = cclone(o->pgrp->slash);
 
 	chandevinit();
--- a/os/pc64/trap.c
+++ b/os/pc64/trap.c
@@ -406,7 +406,7 @@
 linkproc(void)
 {
 	spllo();
-	up->kpfun(up->arg);
+	up->kpfun(up->kparg);
 	pexit("kproc dying", 0);
 }
 
@@ -422,7 +422,7 @@
 	p->sched.sp = (uintptr)p->kstack+KSTACK-BY2WD;
 
 	p->kpfun = func;
-	p->arg = arg;
+	p->kparg = arg;
 }
 
 
--- a/os/port/chan.c
+++ b/os/port/chan.c
@@ -5,21 +5,10 @@
 #include	"fns.h"
 #include	"../port/error.h"
 
-char*
-channame(Chan *c)		/* DEBUGGING */
-{
-	if(c == nil)
-		return "<nil chan>";
-	if(c->name == nil)
-		return "<nil name>";
-	if(c->name->s == nil)
-		return "<nil name.s>";
-	return c->name->s;
-}
-
 enum
 {
-	CNAMESLOP	= 20
+	PATHSLOP	= 20,
+	PATHMSLOP	= 20,
 };
 
 struct
@@ -34,13 +23,28 @@
 
 struct Elemlist
 {
+	char	*aname;	/* original name */
 	char	*name;	/* copy of name, so '/' can be overwritten */
 	int	nelems;
 	char	**elems;
 	int	*off;
 	int	mustbedir;
+	int	nerror;
+	int	prefix;
 };
 
+char*
+chanpath(Chan *c)
+{
+	if(c == nil)
+		return "<nil chan>";
+	if(c->path == nil)
+		return "<nil path>";
+	if(c->path->s == nil)
+		return "<nil path.s>";
+	return c->path->s;
+}
+
 #define SEP(c) ((c) == 0 || (c) == '/')
 void cleancname(Cname*);
 
@@ -53,26 +57,27 @@
 int
 incref(Ref *r)
 {
-	int x;
+	long old, new;
 
-	lock(&r->l);
-	x = ++r->ref;
-	unlock(&r->l);
-	return x;
+	do {
+		old = r->ref;
+		new = old+1;
+	} while(!cmpswap(&r->ref, old, new));
+	return new;
 }
 
 int
 decref(Ref *r)
 {
-	int x;
+	long old, new;
 
-	lock(&r->l);
-	x = --r->ref;
-	unlock(&r->l);
-	if(x < 0)
-		panic("decref, pc=0x%zux", getcallerpc(&r));
-
-	return x;
+	do {
+		old = r->ref;
+		if(old <= 0)
+			panic("decref pc=%#p", getcallerpc(&r));
+		new = old-1;
+	} while(!cmpswap(&r->ref, old, new));
+	return new;
 }
 
 /*
@@ -87,20 +92,19 @@
 	int nt;
 
 	nt = strlen(t);
-	if(nt+1 <= ns){
-		memmove(s, t, nt+1);
+	if(nt < ns){
+		memmove(s, t, nt);
+		s[nt] = '\0';
 		return;
 	}
-	/* too long */
-	if(ns < 4){
-		/* but very short! */
-		strncpy(s, t, ns);
-		return;
-	}
-	/* truncate with ... at character boundary (very rare case) */
-	memmove(s, t, ns-4);
+	/* too long, truncate */
+	nt = ns-1;
+	memmove(s, t, nt);
+	s[nt] = '\0';
+	/* append ... if there is space */
 	ns -= 4;
-	s[ns] = '\0';
+	if(ns < 0)
+		return;
 	/* look for first byte of UTF-8 sequence by skipping continuation bytes */
 	while(ns>0 && (s[--ns]&0xC0)==0x80)
 		;
@@ -126,17 +130,18 @@
 	int n;
 	char *t, *prev;
 
-	n = strlen(s)+1;
+	n = strlen(s);
 	/* if it's a user, we can wait for memory; if not, something's very wrong */
-	if(up){
-		t = smalloc(n);
-		setmalloctag(t, getcallerpc(&p));
-	}else{
-		t = malloc(n);
+	if(up != nil)
+		t = smalloc(n+1);
+	else{
+		t = malloc(n+1);
 		if(t == nil)
 			panic("kstrdup: no memory");
 	}
+	setmalloctag(t, getcallerpc(&p));
 	memmove(t, s, n);
+	t[n] = '\0';
 	prev = *p;
 	*p = t;
 	free(prev);
@@ -147,10 +152,17 @@
 {
 	int i;
 
+	todinit();	/* avoid later reentry causing infinite recursion */
 	for(i=0; devtab[i] != nil; i++)
 		devtab[i]->reset();
 }
 
+/*
+ * closeproc() kproc is used by 9front not inferno
+ * TODO not sure if closeproc() is needed for 9ferno
+ */
+static void closeproc(void*);
+
 void
 chandevinit(void)
 {
@@ -158,6 +170,7 @@
 
 	for(i=0; devtab[i] != nil; i++)
 		devtab[i]->init();
+	kproc("closeproc", closeproc, nil, 0);
 }
 
 void
@@ -164,7 +177,7 @@
 chandevshutdown(void)
 {
 	int i;
-	
+
 	/* shutdown in reverse order */
 	for(i=0; devtab[i] != nil; i++)
 		;
@@ -210,74 +223,166 @@
 	c->mqid.path = 0;
 	c->mqid.vers = 0;
 	c->mqid.type = 0;
-	c->name = 0;
+	c->path = nil;
 	return c;
 }
 
-static Ref ncname;
-
-Cname*
-newcname(char *s)
+Path*
+newpath(char *s)
 {
-	Cname *n;
 	int i;
+	Path *p;
 
-	n = smalloc(sizeof(Cname));
+	p = smalloc(sizeof(Path));
 	i = strlen(s);
-	n->len = i;
-	n->alen = i+CNAMESLOP;
-	n->s = smalloc(n->alen);
-	memmove(n->s, s, i+1);
-	n->ref = 1;
-	incref(&ncname);
-	return n;
+	p->len = i;
+	p->alen = i+PATHSLOP;
+	p->s = smalloc(p->alen);
+	memmove(p->s, s, i+1);
+	p->ref = 1;
+
+	/*
+	 * Cannot use newpath for arbitrary names because the mtpt
+	 * array will not be populated correctly.  The names #/ and / are
+	 * allowed, but other names with / in them draw warnings.
+	 */
+	if(strchr(s, '/') != nil && strcmp(s, "#/") != 0 && strcmp(s, "/") != 0)
+		print("newpath: %s from %#p\n", s, getcallerpc(&s));
+
+	p->mlen = 1;
+	p->malen = PATHMSLOP;
+	p->mtpt = smalloc(p->malen*sizeof p->mtpt[0]);
+	return p;
 }
 
+static Path*
+copypath(Path *p)
+{
+	int i;
+	Path *pp;
+
+	pp = smalloc(sizeof(Path));
+	pp->ref = 1;
+
+	pp->len = p->len;
+	pp->alen = p->alen;
+	pp->s = smalloc(p->alen);
+	memmove(pp->s, p->s, p->len+1);
+
+	pp->mlen = p->mlen;
+	pp->malen = p->malen;
+	pp->mtpt = smalloc(p->malen*sizeof pp->mtpt[0]);
+	for(i=0; i<pp->mlen; i++){
+		pp->mtpt[i] = p->mtpt[i];
+		if(pp->mtpt[i] != nil)
+			incref(pp->mtpt[i]);
+	}
+
+	return pp;
+}
+
 void
-cnameclose(Cname *n)
+pathclose(Path *p)
 {
-	if(n == nil)
+	int i;
+
+	if(p == nil || decref(p))
 		return;
-	if(decref(n))
-		return;
-	decref(&ncname);
-	free(n->s);
-	free(n);
+	for(i=0; i<p->mlen; i++)
+		if(p->mtpt[i] != nil)
+			cclose(p->mtpt[i]);
+	free(p->mtpt);
+	free(p->s);
+	free(p);
 }
 
-Cname*
-addelem(Cname *n, char *s)
+/*
+ * In place, rewrite name to compress multiple /, eliminate ., and process ..
+ * (Really only called to remove a trailing .. that has been added.
+ * Otherwise would need to update n->mtpt as well.)
+ */
+static void
+fixdotdotname(Path *p)
 {
-	int i, a;
-	char *t;
-	Cname *new;
+	char *r;
 
-	if(s[0]=='.' && s[1]=='\0')
-		return n;
+	if(p->s[0] == '#'){
+		r = strchr(p->s, '/');
+		if(r == nil)
+			return;
+		cleanname(r);
 
-	if(n->ref > 1){
+		/*
+		 * The correct name is #i rather than #i/,
+		 * but the correct name of #/ is #/.
+		 */
+		if(strcmp(r, "/")==0 && p->s[1] != '/')
+			*r = '\0';
+	}else
+		cleanname(p->s);
+	p->len = strlen(p->s);
+}
+
+static Path*
+uniquepath(Path *p)
+{
+	Path *new;
+
+	if(p->ref > 1){
 		/* copy on write */
-		new = newcname(n->s);
-		cnameclose(n);
-		n = new;
+		new = copypath(p);
+		pathclose(p);
+		p = new;
 	}
+	return p;
+}
 
+Path*
+addelem(Path *p, char *s, Chan *from)
+{
+	char *t;
+	int a, i;
+	Chan *c, **tt;
+
+	if(s[0]=='.' && s[1]=='\0')
+		return p;
+
+	p = uniquepath(p);
+
 	i = strlen(s);
-	if(n->len+1+i+1 > n->alen){
-		a = n->len+1+i+1 + CNAMESLOP;
+	a = p->len+1+i+1;
+	if(a > p->alen){
+		a += PATHSLOP;
 		t = smalloc(a);
-		memmove(t, n->s, n->len+1);
-		free(n->s);
-		n->s = t;
-		n->alen = a;
+		memmove(t, p->s, p->len+1);
+		free(p->s);
+		p->s = t;
+		p->alen = a;
 	}
-	if(n->len>0 && n->s[n->len-1]!='/' && s[0]!='/')	/* don't insert extra slash if one is present */
-		n->s[n->len++] = '/';
-	memmove(n->s+n->len, s, i+1);
-	n->len += i;
-	if(isdotdot(s))
-		cleancname(n);
-	return n;
+	/* don't insert extra slash if one is present */
+	if(p->len>0 && p->s[p->len-1]!='/' && s[0]!='/')
+		p->s[p->len++] = '/';
+	memmove(p->s+p->len, s, i+1);
+	p->len += i;
+	if(isdotdot(s)){
+		fixdotdotname(p);
+		if(p->mlen > 1 && (c = p->mtpt[--p->mlen]) != nil){
+			p->mtpt[p->mlen] = nil;
+			cclose(c);
+		}
+	}else{
+		if(p->mlen >= p->malen){
+			p->malen = p->mlen+1+PATHMSLOP;
+			tt = smalloc(p->malen*sizeof tt[0]);
+			memmove(tt, p->mtpt, p->mlen*sizeof tt[0]);
+			free(p->mtpt);
+			p->mtpt = tt;
+		}
+		p->mtpt[p->mlen++] = from;
+		if(from != nil)
+			incref(from);
+	}
+	return p;
 }
 
 void
@@ -285,6 +390,12 @@
 {
 	c->flag = CFREE;
 
+	if(c->dirrock != nil){
+		free(c->dirrock);
+		c->dirrock = nil;
+		c->nrock = 0;
+		c->mrock = 0;
+	}
 	if(c->umh != nil){
 		putmhead(c->umh);
 		c->umh = nil;
@@ -302,7 +413,8 @@
 		c->mchan = nil;
 	}
 
-	cnameclose(c->name);
+	pathclose(c->path);
+	c->path = nil;
 
 	lock(&chanalloc);
 	c->next = chanalloc.free;
@@ -310,18 +422,110 @@
 	unlock(&chanalloc);
 }
 
+/*
+ * Queue a chan to be closed by one of the clunk procs.
+ */
+struct {
+	Chan *head;
+	Chan *tail;
+	ulong nqueued;
+	ulong nclosed;
+	Lock l;
+	QLock q;
+	Rendez r;
+} clunkq;
+
+static int
+clunkwork(void*)
+{
+	return clunkq.head != nil;
+}
+
+static void
+closechanq(Chan *c)
+{
+	lock(&clunkq.l);
+	clunkq.nqueued++;
+	c->next = nil;
+	if(clunkq.head != nil)
+		clunkq.tail->next = c;
+	else
+		clunkq.head = c;
+	clunkq.tail = c;
+	unlock(&clunkq.l);
+	wakeup(&clunkq.r);
+}
+
+static Chan*
+closechandeq(void)
+{
+	Chan *c;
+
+	lock(&clunkq.l);
+	c = clunkq.head;
+	if(c != nil) {
+		clunkq.head = c->next;
+		clunkq.nclosed++;
+	}
+	unlock(&clunkq.l);
+	return c;
+}
+
+static void
+closeproc(void *)
+{
+	Chan *c;
+
+	for(;;){
+		c = closechandeq();
+		if(c == nil) {
+			qlock(&clunkq.q);
+			if(!waserror()) {
+				tsleep(&clunkq.r, clunkwork, nil, 500);
+				poperror();
+			}
+			c = closechandeq();
+			if(c == nil) {
+				if(clunkq.q.head != nil) {
+					qunlock(&clunkq.q);
+					pexit("no work", 1);
+				}
+				qunlock(&clunkq.q);
+				continue;
+			}
+			if(clunkq.q.head == nil) {
+				if(!waserror()) {
+					kproc("closeproc", closeproc, nil, 0);
+					poperror();
+				}
+			}
+			qunlock(&clunkq.q);
+		}
+		if(!waserror()){
+			devtab[c->type]->close(c);
+			poperror();
+		}
+		chanfree(c);
+	}
+}
+
 void
 cclose(Chan *c)
 {
-	if(c == 0)
-		return;
+	if(c == nil || c->ref < 1 || c->flag&CFREE)
+		panic("cclose %#p", getcallerpc(&c));
 
-	if(c->flag&CFREE)
-		panic("cclose %zux", getcallerpc(&c));
-
 	if(decref(c))
 		return;
 
+	if(devtab[c->type]->dc == L'M')
+	if((c->flag&COPEN) == 0 || (c->flag&(CRCLOSE|CCACHE)) == CCACHE)
+	if((c->qid.type&(QTEXCL|QTMOUNT|QTAUTH)) == 0)
+	if((clunkq.nqueued - clunkq.nclosed) < 64){
+		closechanq(c);
+		return;
+	}
+
 	if(!waserror()){
 		devtab[c->type]->close(c);
 		poperror();
@@ -329,6 +533,16 @@
 	chanfree(c);
 }
 
+void
+ccloseq(Chan *c)
+{
+	if(c == nil || c->ref < 1 || c->flag&CFREE)
+		panic("ccloseq %#p", getcallerpc(&c));
+
+	if(decref(c) == 0)
+		closechanq(c);
+}
+
 /*
  * Make sure we have the only copy of c.  (Copy on write.)
  */
@@ -337,12 +551,18 @@
 {
 	Chan *nc;
 
-	if(c->ref != 1) {
+	if(c->ref != 1){
 		nc = cclone(c);
 		cclose(c);
 		c = nc;
 	}
 
+	if(c->umh != nil){	//BUG
+		print("cunique umh != nil from %#p\n", getcallerpc(&c));
+		putmhead(c->umh);
+		c->umh = nil;
+	}
+
 	return c;
 }
 
@@ -353,11 +573,11 @@
 }
 
 int
-eqchan(Chan *a, Chan *b, int pathonly)
+eqchan(Chan *a, Chan *b, int skipvers)
 {
 	if(a->qid.path != b->qid.path)
 		return 0;
-	if(!pathonly && a->qid.vers!=b->qid.vers)
+	if(!skipvers && a->qid.vers!=b->qid.vers)
 		return 0;
 	if(a->type != b->type)
 		return 0;
@@ -367,11 +587,11 @@
 }
 
 int
-eqchantdqid(Chan *a, int type, int dev, Qid qid, int pathonly)
+eqchantdqid(Chan *a, int type, int dev, Qid qid, int skipvers)
 {
 	if(a->qid.path != qid.path)
 		return 0;
-	if(!pathonly && a->qid.vers!=qid.vers)
+	if(!skipvers && a->qid.vers!=qid.vers)
 		return 0;
 	if(a->type != type)
 		return 0;
@@ -389,130 +609,150 @@
 	mh->ref = 1;
 	mh->from = from;
 	incref(from);
+	setmalloctag(mh, getcallerpc(&from));
+	return mh;
+}
 
 /*
-	n = from->name->len;
-	if(n >= sizeof(mh->fromname))
-		n = sizeof(mh->fromname)-1;
-	memmove(mh->fromname, from->name->s, n);
-	mh->fromname[n] = 0;
-*/
-	return mh;
+ * This is necessary because there are many
+ * pointers to the top of a given mount list:
+ *
+ *	- the mhead in the namespace hash table
+ *	- the mhead in chans returned from findmount:
+ *	  used in namec and then by unionread.
+ *	- the mhead in chans returned from createdir:
+ *	  used in the open/create race protect, which is gone.
+ *
+ * The RWlock in the Mhead protects the mount list it contains.
+ * The mount list is deleted in cunmount() and closepgrp().
+ * The RWlock ensures that nothing is using the mount list at that time.
+ *
+ * It is okay to replace c->mh with whatever you want as
+ * long as you are sure you have a unique reference to it.
+ *
+ * This comment might belong somewhere else.
+ */
+void
+putmhead(Mhead *m)
+{
+	if(m == nil)
+		return;
+	if(decref(m))
+		return;
+	assert(m->mount == nil);
+	cclose(m->from);
+	free(m);
 }
 
 int
 cmount(Chan *new, Chan *old, int flag, char *spec)
 {
-	Pgrp *pg;
-	int order, flg;
+	int order;
 	Mhead *m, **l, *mh;
-	Mount *nm, *f, *um, **h;
+	Mount *nm, *f, *um;
+	Pgrp *pg;
 
+	if(old->umh != nil)
+		print("cmount: unexpected umh, caller %#p\n", getcallerpc(&new));
+
 	if(QTDIR & (old->qid.type^new->qid.type))
 		error(Emount);
 
-if(old->umh)
-	print("cmount old extra umh\n");
-
 	order = flag&MORDER;
 
-	if((old->qid.type&QTDIR)==0 && order != MREPL)
+	if((old->qid.type&QTDIR) == 0 && order != MREPL)
 		error(Emount);
 
+	nm = newmount(new, flag, spec);
 	mh = new->umh;
+	if(mh != nil) {
+		rlock(&mh->lock);
+		if(waserror()) {
+			runlock(&mh->lock);
+			mountfree(nm);
+			nexterror();
+		}
+		um = mh->mount;
+		if(um != nil){
+			/*
+			 * Not allowed to bind when the old directory is itself a union.
+			 * (Maybe it should be allowed, but I don't see what the semantics
+			 * would be.)
+			 *
+			 * We need to check mh->mount->next to tell unions apart from
+			 * simple mount points, so that things like
+			 *	mount -c fd /root
+			 *	bind -c /root /
+			 * work.
+			 *
+			 * The check of mount->mflag allows things like
+			 *	mount fd /root
+			 *	bind -c /root /
+			 *
+			 * This is far more complicated than it should be, but I don't
+			 * see an easier way at the moment.
+			 */
+			if((flag&MCREATE) != 0 && (um->next != nil || (um->mflag&MCREATE) == 0))
+				error(Emount);
 
-	/*
-	 * Not allowed to bind when the old directory
-	 * is itself a union.  (Maybe it should be allowed, but I don't see
-	 * what the semantics would be.)
-	 *
-	 * We need to check mh->mount->next to tell unions apart from
-	 * simple mount points, so that things like
-	 *	mount -c fd /root
-	 *	bind -c /root /
-	 * work.  The check of mount->mflag catches things like
-	 *	mount fd /root
-	 *	bind -c /root /
-	 * 
-	 * This is far more complicated than it should be, but I don't
-	 * see an easier way at the moment.		-rsc
-	 */
-	if((flag&MCREATE) && mh && mh->mount
-	&& (mh->mount->next || !(mh->mount->mflag&MCREATE)))
-		error(Emount);
+			/*
+			 *  copy a union when binding it onto a directory
+			 */
+			f = nm;
+			for(um = um->next; um != nil; um = um->next){
+				f->next = newmount(um->to, order==MREPL? MAFTER: order, um->spec);
+				f = f->next;
+			}
+		}
+		runlock(&mh->lock);
+		poperror();
+	}
 
 	pg = up->env->pgrp;
 	wlock(&pg->ns);
-
 	l = &MOUNTH(pg, old->qid);
-	for(m = *l; m; m = m->hash) {
+	for(m = *l; m != nil; m = m->hash){
 		if(eqchan(m->from, old, 1))
 			break;
 		l = &m->hash;
 	}
-
-	if(m == nil) {
+	if(m == nil){
 		/*
 		 *  nothing mounted here yet.  create a mount
 		 *  head and add to the hash table.
 		 */
 		m = newmhead(old);
-		*l = m;
-
 		/*
 		 *  if this is a union mount, add the old
 		 *  node to the mount chain.
 		 */
 		if(order != MREPL)
-			m->mount = newmount(m, old, 0, 0);
+			m->mount = newmount(old, 0, nil);
+		*l = m;
 	}
 	wlock(&m->lock);
-	if(waserror()){
-		wunlock(&m->lock);
-		nexterror();
-	}
-	wunlock(&pg->ns);
-
-	nm = newmount(m, new, flag, spec);
-	if(mh != nil && mh->mount != nil) {
-		/*
-		 *  copy a union when binding it onto a directory
-		 */
-		flg = order;
-		if(order == MREPL)
-			flg = MAFTER;
-		h = &nm->next;
-		um = mh->mount;
-		for(um = um->next; um; um = um->next) {
-			f = newmount(m, um->to, flg, um->spec);
-			*h = f;
-			h = &f->next;
-		}
-	}
-
-	if(m->mount && order == MREPL) {
-		mountfree(m->mount);
-		m->mount = 0;
-	}
-
-	if(flag & MCREATE)
-		nm->mflag |= MCREATE;
-
-	if(m->mount && order == MAFTER) {
-		for(f = m->mount; f->next; f = f->next)
+	um = m->mount;
+	if(um != nil && order == MAFTER){
+		for(f = um; f->next != nil; f = f->next)
 			;
 		f->next = nm;
-	}
-	else {
-		for(f = nm; f->next; f = f->next)
-			;
-		f->next = m->mount;
+		um = nil;
+	} else {
+		if(order != MREPL){
+			for(f = nm; f->next != nil; f = f->next)
+				;
+			f->next = um;
+			um = nil;
+		}
 		m->mount = nm;
 	}
-
+	order = nm->mountid;
 	wunlock(&m->lock);
-	poperror();
-	return nm->mountid;
+	wunlock(&pg->ns);
+
+	mountfree(um);
+
+	return order;
 }
 
 void
@@ -522,11 +762,11 @@
 	Mhead *m, **l;
 	Mount *f, **p;
 
-	if(mnt->umh)	/* should not happen */
+	if(mnt->umh != nil)	/* should not happen */
 		print("cunmount newp extra umh %p has %p\n", mnt, mnt->umh);
 
 	/*
-	 * It _can_ happen that mounted->umh is non-nil, 
+	 * It _can_ happen that mounted->umh is non-nil,
 	 * because mounted is the result of namec(Aopen)
 	 * (see sysfile.c:/^sysunmount).
 	 * If we open a union directory, it will have a umh.
@@ -538,47 +778,44 @@
 	wlock(&pg->ns);
 
 	l = &MOUNTH(pg, mnt->qid);
-	for(m = *l; m; m = m->hash) {
+	for(m = *l; m != nil; m = m->hash){
 		if(eqchan(m->from, mnt, 1))
 			break;
 		l = &m->hash;
 	}
 
-	if(m == 0) {
+	if(m == nil){
 		wunlock(&pg->ns);
 		error(Eunmount);
 	}
 
 	wlock(&m->lock);
-	if(mounted == 0) {
+	f = m->mount;
+	if(mounted == nil){
 		*l = m->hash;
-		wunlock(&pg->ns);
-		mountfree(m->mount);
 		m->mount = nil;
-		cclose(m->from);
 		wunlock(&m->lock);
+		wunlock(&pg->ns);
+		mountfree(f);
 		putmhead(m);
 		return;
 	}
-
-	p = &m->mount;
-	for(f = *p; f; f = f->next) {
-		/* BUG: Needs to be 2 pass */
+	for(p = &m->mount; f != nil; f = f->next){
 		if(eqchan(f->to, mounted, 1) ||
-		  (f->to->mchan && eqchan(f->to->mchan, mounted, 1))) {
+		  (f->to->mchan != nil && eqchan(f->to->mchan, mounted, 1))){
 			*p = f->next;
-			f->next = 0;
-			mountfree(f);
-			if(m->mount == nil) {
+			f->next = nil;
+			if(m->mount == nil){
 				*l = m->hash;
-				cclose(m->from);
 				wunlock(&m->lock);
 				wunlock(&pg->ns);
+				mountfree(f);
 				putmhead(m);
 				return;
 			}
 			wunlock(&m->lock);
 			wunlock(&pg->ns);
+			mountfree(f);
 			return;
 		}
 		p = &f->next;
@@ -594,105 +831,119 @@
 	Chan *nc;
 	Walkqid *wq;
 
+	if(c == nil || c->ref < 1 || c->flag&CFREE)
+		panic("cclone: %#p", getcallerpc(&c));
 	wq = devtab[c->type]->walk(c, nil, nil, 0);
 	if(wq == nil)
 		error("clone failed");
 	nc = wq->clone;
 	free(wq);
-	nc->name = c->name;
-	if(c->name)
-		incref(c->name);
+	if((nc->path = c->path) != nil)
+		incref(c->path);
 	return nc;
 }
 
+/* also used by sysfile.c:/^mountfix */
 int
 findmount(Chan **cp, Mhead **mp, int type, int dev, Qid qid)
 {
+	Chan *to;
 	Pgrp *pg;
 	Mhead *m;
 
 	pg = up->env->pgrp;
 	rlock(&pg->ns);
-	for(m = MOUNTH(pg, qid); m; m = m->hash){
-		rlock(&m->lock);
-if(m->from == nil){
-	print("m %p m->from 0\n", m);
-	runlock(&m->lock);
-	continue;
-}
-		if(eqchantdqid(m->from, type, dev, qid, 1)) {
+	for(m = MOUNTH(pg, qid); m != nil; m = m->hash){
+		if(eqchantdqid(m->from, type, dev, qid, 1)){
+			if(mp != nil)
+				incref(m);
+			rlock(&m->lock);
+			to = m->mount->to;
+			incref(to);
+			runlock(&m->lock);
 			runlock(&pg->ns);
 			if(mp != nil){
-				incref(m);
-				if(*mp != nil)
-					putmhead(*mp);
+				putmhead(*mp);
 				*mp = m;
 			}
 			if(*cp != nil)
 				cclose(*cp);
-			incref(m->mount->to);
-			*cp = m->mount->to;
-			runlock(&m->lock);
+			*cp = to;
 			return 1;
 		}
-		runlock(&m->lock);
 	}
-
 	runlock(&pg->ns);
 	return 0;
 }
 
-int
-domount(Chan **cp, Mhead **mp)
+/*
+ * Calls findmount but also updates path.
+ */
+static int
+domount(Chan **cp, Mhead **mp, Path **path)
 {
-	return findmount(cp, mp, (*cp)->type, (*cp)->dev, (*cp)->qid);
+	Chan **lc, *from;
+	Path *p;
+
+	if(findmount(cp, mp, (*cp)->type, (*cp)->dev, (*cp)->qid) == 0)
+		return 0;
+
+	if(path != nil){
+		p = *path;
+		p = uniquepath(p);
+		if(p->mlen <= 0)
+			print("domount: path %s has mlen==%d\n", p->s, p->mlen);
+		else{
+			from = (*mp)->from;
+			incref(from);
+			lc = &p->mtpt[p->mlen-1];
+			if(*lc != nil)
+				cclose(*lc);
+			*lc = from;
+		}
+		*path = p;
+	}
+	return 1;
 }
 
-Chan*
-undomount(Chan *c, Cname *name)
+/*
+ * If c is the right-hand-side of a mount point, returns the left hand side.
+ * Changes name to reflect the fact that we've uncrossed the mountpoint,
+ * so name had better be ours to change!
+ */
+static Chan*
+undomount(Chan *c, Path *path)
 {
 	Chan *nc;
-	Pgrp *pg;
-	Mount *t;
-	Mhead **h, **he, *f;
 
-	pg = up->env->pgrp;
-	rlock(&pg->ns);
-	if(waserror()) {
-		runlock(&pg->ns);
-		nexterror();
-	}
+	if(path->ref != 1 || path->mlen == 0)
+		print("undomount: path %s ref %d mlen %d caller %#p\n",
+			path->s, path->ref, path->mlen, getcallerpc(&c));
 
-	he = &pg->mnthash[MNTHASH];
-	for(h = pg->mnthash; h < he; h++) {
-		for(f = *h; f; f = f->hash) {
-			if(strcmp(f->from->name->s, name->s) != 0)
-				continue;
-			for(t = f->mount; t; t = t->next) {
-				if(eqchan(c, t->to, 1)) {
-					/*
-					 * We want to come out on the left hand side of the mount
-					 * point using the element of the union that we entered on.
-					 * To do this, find the element that has a from name of
-					 * c->name->s.
-					 */
-					if(strcmp(t->head->from->name->s, name->s) != 0)
-						continue;
-					nc = t->head->from;
-					incref(nc);
-					cclose(c);
-					c = nc;
-					break;
-				}
-			}
-		}
+	if(path->mlen > 0 && (nc = path->mtpt[path->mlen-1]) != nil){
+		cclose(c);
+		path->mtpt[path->mlen-1] = nil;
+		c = nc;
 	}
-	poperror();
-	runlock(&pg->ns);
 	return c;
 }
 
 /*
+ * Call dev walk but catch errors.
+ */
+static Walkqid*
+ewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	Walkqid *wq;
+
+	if(waserror())
+		return nil;
+	wq = devtab[c->type]->walk(c, nc, name, nname);
+	poperror();
+	return wq;
+}
+
+/*
  * Either walks all the way or not at all.  No partial results in *cp.
  * *nerror is the number of names to display in an error message.
  */
@@ -700,17 +951,17 @@
 int
 walk(Chan **cp, char **names, int nnames, int nomount, int *nerror)
 {
-	int dev, dotdot, i, n, nhave, ntry, type;
-	Chan *c, *nc;
-	Cname *cname;
-	Mount *f;
+	int dev, didmount, dotdot, i, n, nhave, ntry, type;
+	Chan *c, *nc, *mtpt;
+	Path *path;
 	Mhead *mh, *nmh;
+	Mount *f;
 	Walkqid *wq;
 
 	c = *cp;
 	incref(c);
-	cname = c->name;
-	incref(cname);
+	path = c->path;
+	incref(path);
 	mh = nil;
 
 	/*
@@ -720,18 +971,21 @@
 	 *    3. move to the first mountpoint along the way.
 	 *    4. repeat.
 	 *
-	 * An invariant is that each time through the loop, c is on the undomount
-	 * side of the mount point, and c's name is cname.
+	 * Each time through the loop:
+	 *
+	 *	If didmount==0, c is on the undomount side of the mount point.
+	 *	If didmount==1, c is on the domount side of the mount point.
+	 * 	Either way, c's full path is path.
 	 */
+	didmount = 0;
 	for(nhave=0; nhave<nnames; nhave+=n){
-		if((c->qid.type&QTDIR)==0){
+		if((c->qid.type&QTDIR) == 0){
 			if(nerror)
 				*nerror = nhave;
-			cnameclose(cname);
+			pathclose(path);
 			cclose(c);
-			strcpy(up->env->errstr, Enotdir);
-			if(mh != nil)
-				putmhead(mh);
+			kstrcpy(up->env->errstr, Enotdir, ERRMAX);
+			putmhead(mh);
 			return -1;
 		}
 		ntry = nnames - nhave;
@@ -740,83 +994,89 @@
 		dotdot = 0;
 		for(i=0; i<ntry; i++){
 			if(isdotdot(names[nhave+i])){
-				if(i==0) {
+				if(i==0){
 					dotdot = 1;
 					ntry = 1;
-				} else
+				}else
 					ntry = i;
 				break;
 			}
 		}
 
-		if(!dotdot && !nomount)
-			domount(&c, &mh);
+		if(!dotdot && !nomount && !didmount)
+			domount(&c, &mh, &path);
 
 		type = c->type;
 		dev = c->dev;
 
-		if((wq = devtab[type]->walk(c, nil, names+nhave, ntry)) == nil){
+		if((wq = ewalk(c, nil, names+nhave, ntry)) == nil){
 			/* try a union mount, if any */
-			if(mh && !nomount){
+			if(mh != nil && !nomount){
 				/*
-				 * mh->mount == c, so start at mh->mount->next
+				 * mh->mount->to == c, so start at mh->mount->next
 				 */
 				rlock(&mh->lock);
-				for(f = mh->mount->next; f; f = f->next)
-					if((wq = devtab[f->to->type]->walk(f->to, nil, names+nhave, ntry)) != nil)
+				if((f = mh->mount) != nil)
+					f = f->next;
+				for(; f != nil; f = f->next)
+					if((wq = ewalk(f->to, nil, names+nhave, ntry)) != nil){
+						type = f->to->type;
+						dev = f->to->dev;
 						break;
+					}
 				runlock(&mh->lock);
-				if(f != nil){
-					type = f->to->type;
-					dev = f->to->dev;
-				}
 			}
 			if(wq == nil){
 				cclose(c);
-				cnameclose(cname);
+				pathclose(path);
 				if(nerror)
 					*nerror = nhave+1;
-				if(mh != nil)
-					putmhead(mh);
+				putmhead(mh);
 				return -1;
 			}
 		}
 
-		nmh = nil;
-		if(dotdot) {
+		didmount = 0;
+		if(dotdot){
 			assert(wq->nqid == 1);
 			assert(wq->clone != nil);
 
-			cname = addelem(cname, "..");
-			nc = undomount(wq->clone, cname);
+			path = addelem(path, "..", nil);
+			nc = undomount(wq->clone, path);
+			nmh = nil;
 			n = 1;
-		} else {
+		}else{
 			nc = nil;
-			if(!nomount)
-				for(i=0; i<wq->nqid && i<ntry-1; i++)
-					if(findmount(&nc, &nmh, type, dev, wq->qid[i]))
+			nmh = nil;
+			if(!nomount){
+				for(i=0; i<wq->nqid && i<ntry-1; i++){
+					if(findmount(&nc, &nmh, type, dev, wq->qid[i])){
+						didmount = 1;
 						break;
+					}
+				}
+			}
 			if(nc == nil){	/* no mount points along path */
 				if(wq->clone == nil){
 					cclose(c);
-					cnameclose(cname);
-					if(wq->nqid==0 || (wq->qid[wq->nqid-1].type&QTDIR)){
+					pathclose(path);
+					if(wq->nqid == 0 || (wq->qid[wq->nqid-1].type&QTDIR) != 0){
 						if(nerror)
 							*nerror = nhave+wq->nqid+1;
-						strcpy(up->env->errstr, Edoesnotexist);
+						kstrcpy(up->env->errstr, Edoesnotexist, ERRMAX);
 					}else{
 						if(nerror)
 							*nerror = nhave+wq->nqid;
-						strcpy(up->env->errstr, Enotdir);
+						kstrcpy(up->env->errstr, Enotdir, ERRMAX);
 					}
 					free(wq);
-					if(mh != nil)
-						putmhead(mh);
+					putmhead(mh);
 					return -1;
 				}
 				n = wq->nqid;
 				nc = wq->clone;
 			}else{		/* stopped early, at a mount point */
+				assert(didmount);
 				if(wq->clone != nil){
 					cclose(wq->clone);
 					wq->clone = nil;
@@ -823,8 +1083,12 @@
 				}
 				n = i+1;
 			}
-			for(i=0; i<n; i++)
-				cname = addelem(cname, names[nhave+i]);
+			for(i=0; i<n; i++){
+				mtpt = nil;
+				if(i==n-1 && nmh!=nil)
+					mtpt = nmh->from;
+				path = addelem(path, names[nhave+i], mtpt);
+			}
 		}
 		cclose(c);
 		c = nc;
@@ -832,24 +1096,16 @@
 		mh = nmh;
 		free(wq);
 	}
-
 	putmhead(mh);
-
 	c = cunique(c);
 
-	if(c->umh != nil){	//BUG
-		print("walk umh\n");
-		putmhead(c->umh);
-		c->umh = nil;
-	}
+	pathclose(c->path);
+	c->path = path;
 
-	cnameclose(c->name);
-	c->name = cname;
-
 	cclose(*cp);
 	*cp = c;
 	if(nerror)
-		*nerror = 0;
+		*nerror = nhave;
 	return 0;
 }
 
@@ -885,31 +1141,6 @@
 {
 }
 
-/*
- * In place, rewrite name to compress multiple /, eliminate ., and process ..
- */
-void
-cleancname(Cname *n)
-{
-	char *p;
-
-	if(n->s[0] == '#'){
-		p = strchr(n->s, '/');
-		if(p == nil)
-			return;
-		cleanname(p);
-
-		/*
-		 * The correct name is #i rather than #i/,
-		 * but the correct name of #/ is #/.
-		 */
-		if(strcmp(p, "/")==0 && n->s[1] != '/')
-			*p = '\0';
-	}else
-		cleanname(n->s);
-	n->len = strlen(n->s);
-}
-
 static void
 growparse(Elemlist *e)
 {
@@ -939,11 +1170,11 @@
  * rather than a directory.
  */
 static void
-parsename(char *name, Elemlist *e)
+parsename(char *aname, Elemlist *e)
 {
-	char *slash;
+	char *name, *slash;
 
-	kstrdup(&e->name, name);
+	kstrdup(&e->name, aname);
 	name = e->name;
 	e->nelems = 0;
 	e->elems = nil;
@@ -951,12 +1182,12 @@
 	e->off[0] = skipslash(name) - name;
 	for(;;){
 		name = skipslash(name);
-		if(*name=='\0'){
+		if(*name == '\0'){
+			e->off[e->nelems] = name+strlen(name) - e->name;
 			e->mustbedir = 1;
 			break;
 		}
 		growparse(e);
-		
 		e->elems[e->nelems++] = name;
 		slash = utfrune(name, '/');
 		if(slash == nil){
@@ -970,6 +1201,59 @@
 	}
 }
 
+static void
+namelenerror(char *aname, int len, char *err)
+{
+	char *ename, *name, *next;
+	int i, errlen;
+
+	/*
+	 * If the name is short enough, just use the whole thing.
+	 */
+	errlen = strlen(err);
+	if(len < ERRMAX/3 || len+errlen < 2*ERRMAX/3)
+		snprint(up->genbuf, sizeof up->genbuf, "%.*s",
+			utfnlen(aname, len), aname);
+	else{
+		/*
+		 * Print a suffix of the name, but try to get a little info.
+		 */
+		ename = aname+len;
+		next = ename;
+		do{
+			name = next;
+			if(next == aname)
+				break;
+			while(next > aname)
+				if(*--next == '/')
+					break;
+			len = ename-next;
+		}while(len < ERRMAX/3 || len + errlen < 2*ERRMAX/3);
+
+		/*
+		 * If the name is ridiculously long, chop it.
+		 */
+		if(name == ename){
+			name = ename-ERRMAX/4;
+			if(name <= aname)
+				panic("bad math in namelenerror");
+			/* walk out of current UTF sequence */
+			for(i=0; (*name&0xC0)==0x80 && i<UTFmax; i++)
+				name++;
+		}
+		snprint(up->genbuf, sizeof up->genbuf, "...%.*s",
+			utfnlen(name, ename-name), name);
+	}
+	snprint(up->env->errstr, ERRMAX, "%#q %s", up->genbuf, err);
+	nexterror();
+}
+
+void
+nameerror(char *name, char *err)
+{
+	namelenerror(name, strlen(name), err);
+}
+
 void*
 memrchr(void *va, int c, long n)
 {
@@ -1001,19 +1285,24 @@
 Chan*
 namec(char *aname, int amode, int omode, ulong perm)
 {
-	int n, prefix, len, t, nomount, npath;
-	Chan *c, *cnew;
-	Cname *cname;
+	int len, n, t, nomount;
+	Chan *c;
+	Chan *volatile cnew;
+	Path *volatile path;
 	Elemlist e;
 	Rune r;
 	Mhead *m;
-	char *createerr, tmperrbuf[ERRMAX];
+	char *err;
 	char *name;
 
-	name = aname;
-	if(name[0] == '\0')
+	if(aname[0] == '\0')
 		error("empty file name");
-	validname(name, 1);
+	aname = validnamedup(aname, 1);
+	if(waserror()){
+		free(aname);
+		nexterror();
+	}
+	name = aname;
 
 	/*
 	 * Find the starting off point (the current slash, the root of
@@ -1026,30 +1315,31 @@
 		c = up->env->pgrp->slash;
 		incref(c);
 		break;
-	
+
 	case '#':
 		nomount = 1;
 		up->genbuf[0] = '\0';
 		n = 0;
-		while(*name!='\0' && (*name != '/' || n < 2)){
+		while(*name != '\0' && (*name != '/' || n < 2)){
 			if(n >= sizeof(up->genbuf)-1)
 				error(Efilename);
 			up->genbuf[n++] = *name++;
 		}
 		up->genbuf[n] = '\0';
-		n = chartorune(&r, up->genbuf+1)+1;
-		if(r == 'M')
-			error(Enoattach);
 		/*
-		 *  the nodevs exceptions are
+		 *  noattach is sandboxing.
+		 *
+		 *  the OK exceptions are:
 		 *	|  it only gives access to pipes you create
+		 *	d  this process's file descriptors
 		 *	e  this process's environment
-		 *	s  private file2chan creation space
-		 *	D private secure sockets name space
-		 *	a private TLS name space
+		 *  the iffy exceptions are:
+		 *	c  time and pid, but also cons and consctl
+		 *	p  control of your own processes (and unfortunately
+		 *	   any others left unprotected)
 		 */
-		if(up->env->pgrp->nodevs &&
-		   (utfrune("|esDa", r) == nil || r == 's' && up->genbuf[n]!='\0'))
+		n = chartorune(&r, up->genbuf+1)+1;
+		if(up->env->pgrp->noattach && utfrune("|decp", r)==nil)
 			error(Enoattach);
 		t = devno(r, 1);
 		if(t == -1)
@@ -1062,23 +1352,36 @@
 		incref(c);
 		break;
 	}
-	prefix = name - aname;
 
+	e.aname = aname;
+	e.prefix = name - aname;
 	e.name = nil;
 	e.elems = nil;
 	e.off = nil;
 	e.nelems = 0;
+	e.nerror = 0;
 	if(waserror()){
 		cclose(c);
 		free(e.name);
 		free(e.elems);
+		/*
+		 * Prepare nice error, showing first e.nerror elements of name.
+		 */
+		if(e.nerror == 0)
+			nexterror();
+		if(e.off[e.nerror]==0)
+			print("nerror=%d but off=%d\n",
+				e.nerror, e.off[e.nerror]);
+		len = e.prefix+e.off[e.nerror];
 		free(e.off);
-//dumpmount();
-		nexterror();
+		err = up->env->errstr;
+		up->env->errstr = up->env->syserrstr;
+		up->env->syserrstr = err;
+		namelenerror(aname, len, err);
 	}
 
 	/*
-	 * Build a list of elements in the path.
+	 * Build a list of elements in the name.
 	 */
 	parsename(name, &e);
 
@@ -1088,9 +1391,8 @@
 	if(amode == Acreate){
 		/* perm must have DMDIR if last element is / or /. */
 		if(e.mustbedir && !(perm&DMDIR)){
-			npath = e.nelems;
-			strcpy(tmperrbuf, "create without DMDIR");
-			goto NameError;
+			e.nerror = e.nelems;
+			error("create without DMDIR");
 		}
 
 		/* don't try to walk the last path element just yet. */
@@ -1099,66 +1401,67 @@
 		e.nelems--;
 	}
 
-	if(walk(&c, e.elems, e.nelems, nomount, &npath) < 0){
-		if(npath < 0 || npath > e.nelems){
-			print("namec %s walk error npath=%d\n", aname, npath);
-			nexterror();
+	if(walk(&c, e.elems, e.nelems, nomount, &e.nerror) < 0){
+		if(e.nerror < 0 || e.nerror > e.nelems){
+			print("namec %s walk error nerror=%d\n", aname, e.nerror);
+			e.nerror = 0;
 		}
-		strcpy(tmperrbuf, up->env->errstr);
-	NameError:
-		len = prefix+e.off[npath];
-		if(len < ERRMAX/3 || (name=memrchr(aname, '/', len))==nil || name==aname)
-			snprint(up->genbuf, sizeof up->genbuf, "%.*s", len, aname);
-		else
-			snprint(up->genbuf, sizeof up->genbuf, "...%.*s", (int)(len-(name-aname)), name);
-		snprint(up->env->errstr, ERRMAX, "%#q %s", up->genbuf, tmperrbuf);
 		nexterror();
 	}
 
-	if(e.mustbedir && !(c->qid.type&QTDIR)){
-		npath = e.nelems;
-		strcpy(tmperrbuf, "not a directory");
-		goto NameError;
-	}
+	if(e.mustbedir && (c->qid.type&QTDIR) == 0)
+		error("not a directory");
 
-	if(amode == Aopen && (omode&3) == OEXEC && (c->qid.type&QTDIR)){
-		npath = e.nelems;
+	if(amode == Aopen && (omode&3) == OEXEC && (c->qid.type&QTDIR) != 0)
 		error("cannot exec directory");
-	}
 
 	switch(amode){
-	case Aaccess:
-		if(!nomount)
-			domount(&c, nil);
-		break;
-
 	case Abind:
+		/* no need to maintain path - cannot dotdot an Abind */
 		m = nil;
 		if(!nomount)
-			domount(&c, &m);
-		if(c->umh != nil)
-			putmhead(c->umh);
+			domount(&c, &m, nil);
+		if(waserror()){
+			putmhead(m);
+			nexterror();
+		}
+		c = cunique(c);
 		c->umh = m;
+		poperror();
 		break;
 
+	case Aaccess:
 	case Aremove:
 	case Aopen:
 	Open:
-		/* save the name; domount might change c */
-		cname = c->name;
-		incref(cname);
+		/* save&update the name; domount might change c */
+		path = c->path;
+		incref(path);
+		if(waserror()){
+			pathclose(path);
+			nexterror();
+		}
 		m = nil;
 		if(!nomount)
-			domount(&c, &m);
-
+			domount(&c, &m, &path);
+		if(waserror()){
+			putmhead(m);
+			nexterror();
+		}
 		/* our own copy to open or remove */
 		c = cunique(c);
+		poperror();
 
 		/* now it's our copy anyway, we can put the name back */
-		cnameclose(c->name);
-		c->name = cname;
+		pathclose(c->path);
+		c->path = path;
+		poperror();
 
+		/* record whether c is on a mount point */
+		c->ismtpt = m!=nil;
+
 		switch(amode){
+		case Aaccess:
 		case Aremove:
 			putmhead(m);
 			break;
@@ -1165,28 +1468,22 @@
 
 		case Aopen:
 		case Acreate:
-if(c->umh != nil){
-	print("cunique umh\n");
-	putmhead(c->umh);
-	c->umh = nil;
-}
-
 			/* only save the mount head if it's a multiple element union */
-			if(m && m->mount && m->mount->next)
-				c->umh = m;
-			else
-				putmhead(m);
+			if(m != nil) {
+				rlock(&m->lock);
+				if(m->mount != nil && m->mount->next != nil) {
+					c->umh = m;
+					runlock(&m->lock);
+				} else {
+					runlock(&m->lock);
+					putmhead(m);
+				}
+			}
 
 			/* save registers else error() in open has wrong value of c saved */
 			saveregisters();
 
-			if(omode == OEXEC)
-				c->flag &= ~CCACHE;
-
 			c = devtab[c->type]->open(c, omode&~OCEXEC);
-
-			if(omode & OCEXEC)
-				c->flag |= CCEXEC;
 			if(omode & ORCLOSE)
 				c->flag |= CRCLOSE;
 			break;
@@ -1198,7 +1495,7 @@
 		 * Directories (e.g. for cd) are left before the mount point,
 		 * so one may mount on / or . and see the effect.
 		 */
-		if(!(c->qid.type & QTDIR))
+		if((c->qid.type&QTDIR) == 0)
 			error(Enotdir);
 		break;
 
@@ -1217,6 +1514,7 @@
 		 * If omode&OEXCL is set, just give up.
 		 */
 		e.nelems++;
+		e.nerror++;
 		if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) == 0){
 			if(omode&OEXCL)
 				error(Eexist);
@@ -1228,11 +1526,11 @@
 		 * The semantics of the create(2) system call are that if the
 		 * file exists and can be written, it is to be opened with truncation.
 		 * On the other hand, the create(5) message fails if the file exists.
-		 * If we get two create(2) calls happening simultaneously, 
-		 * they might both get here and send create(5) messages, but only 
+		 * If we get two create(2) calls happening simultaneously,
+		 * they might both get here and send create(5) messages, but only
 		 * one of the messages will succeed.  To provide the expected create(2)
 		 * semantics, the call with the failed message needs to try the above
-		 * walk again, opening for truncation.  This correctly solves the 
+		 * walk again, opening for truncation.  This correctly solves the
 		 * create/create race, in the sense that any observable outcome can
 		 * be explained as one happening before the other.
 		 * The create/create race is quite common.  For example, it happens
@@ -1242,7 +1540,7 @@
 		 * The implementation still admits a create/create/remove race:
 		 * (A) walk to file, fails
 		 * (B) walk to file, fails
-		 * (A) create file, succeeds, returns 
+		 * (A) create file, succeeds, returns
 		 * (B) create file, fails
 		 * (A) remove file, succeeds, returns
 		 * (B) walk to file, return failure.
@@ -1281,48 +1579,44 @@
 			 * if findmount gave us a new Chan.
 			 */
 			cnew = cunique(cnew);
-			cnameclose(cnew->name);
-			cnew->name = c->name;
-			incref(cnew->name);
+			pathclose(cnew->path);
+			cnew->path = c->path;
+			incref(cnew->path);
 
 			devtab[cnew->type]->create(cnew, e.elems[e.nelems-1], omode&~(OEXCL|OCEXEC), perm);
-			poperror();
-			if(omode & OCEXEC)
-				cnew->flag |= CCEXEC;
 			if(omode & ORCLOSE)
 				cnew->flag |= CRCLOSE;
-			if(m)
-				putmhead(m);
+			poperror();
+			putmhead(m);
 			cclose(c);
 			c = cnew;
-			c->name = addelem(c->name, e.elems[e.nelems-1]);
+			c->path = addelem(c->path, e.elems[e.nelems-1], nil);
 			break;
 		}
 
 		/* create failed */
 		cclose(cnew);
-		if(m)
-			putmhead(m);
+		putmhead(m);
 		if(omode & OEXCL)
 			nexterror();
 		/* save error */
-		createerr = up->env->errstr;
-		up->env->errstr = tmperrbuf;
+		err = up->env->errstr;
+		up->env->errstr = up->env->syserrstr;
+		up->env->syserrstr = err;
 		/* note: we depend that walk does not error */
-		if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) < 0){
-			up->env->errstr = createerr;
-			error(createerr);	/* report true error */
-		}
-		up->env->errstr = createerr;
+		if(walk(&c, e.elems+e.nelems-1, 1, nomount, nil) < 0)
+			error(err);	/* report true error */
+		/* restore error */
+		err = up->env->syserrstr;
+		up->env->syserrstr = up->env->errstr;
+		up->env->errstr = err;
 		omode |= OTRUNC;
 		goto Open;
 
 	default:
-		panic("unknown namec access %d\n", amode);
+		panic("unknown namec access %d", amode);
 	}
 
-	poperror();
-
 	/* place final element in genbuf for e.g. exec */
 	if(e.nelems > 0)
 		kstrcpy(up->genbuf, e.elems[e.nelems-1], sizeof up->genbuf);
@@ -1331,6 +1625,9 @@
 	free(e.name);
 	free(e.elems);
 	free(e.off);
+	poperror();	/* e c */
+	free(aname);
+	poperror();	/* aname */
 
 	return c;
 }
@@ -1364,12 +1661,17 @@
  * routine works for kernel and user memory both.
  * The parameter slashok flags whether a slash character is an error
  * or a valid character.
+ *
+ * The parameter dup flags whether the string should be copied
+ * out of user space before being scanned the second time.
+ * (Otherwise a malicious thread could remove the NUL, causing us
+ * to access unchecked addresses.)
  */
-void
-validname(char *aname, int slashok)
+static char*
+validname0(char *aname, int slashok, int dup, uintptr pc)
 {
-	char *ename, *name;
-	int c;
+	char *ename, *name, *s;
+	int c, n;
 	Rune r;
 
 	name = aname;
@@ -1376,8 +1678,19 @@
 	ename = memchr(name, 0, (1<<16));
 
 	if(ename==nil || ename-name>=(1<<16))
-		error("name too long");
+		error(Etoolong);
 
+	s = nil;
+	if(dup){
+		n = ename-name;
+		s = smalloc(n+1);
+		memmove(s, name, n);
+		s[n] = 0;
+		aname = s;
+		name = s;
+		setmalloctag(s, pc);
+	}
+	
 	while(*name){
 		/* all characters above '~' are ok */
 		c = *(uchar*)name;
@@ -1385,47 +1698,33 @@
 			name += chartorune(&r, name);
 		else{
 			if(isfrog[c])
-				if(!slashok || c!='/'){
-					snprint(up->genbuf, sizeof(up->genbuf), "%s: %q", Ebadchar, aname);
-					error(up->genbuf);
+			if(!slashok || c!='/'){
+				snprint(up->genbuf, sizeof(up->genbuf), "%s: %q", Ebadchar, aname);
+				free(s);
+				error(up->genbuf);
 			}
 			name++;
 		}
 	}
+	return s;
 }
 
 void
-isdir(Chan *c)
+validname(char *aname, int slashok)
 {
-	if(c->qid.type & QTDIR)
-		return;
-	error(Enotdir);
+	validname0(aname, slashok, 0, getcallerpc(&aname));
 }
 
-/*
- * This is necessary because there are many
- * pointers to the top of a given mount list:
- *
- *	- the mhead in the namespace hash table
- *	- the mhead in chans returned from findmount:
- *	  used in namec and then by unionread.
- *	- the mhead in chans returned from createdir:
- *	  used in the open/create race protect, which is gone.
- *
- * The RWlock in the Mhead protects the mount list it contains.
- * The mount list is deleted when we cunmount.
- * The RWlock ensures that nothing is using the mount list at that time.
- *
- * It is okay to replace c->mh with whatever you want as 
- * long as you are sure you have a unique reference to it.
- *
- * This comment might belong somewhere else.
- */
+char*
+validnamedup(char *aname, int slashok)
+{
+	return validname0(aname, slashok, 1, getcallerpc(&aname));
+}
+
 void
-putmhead(Mhead *m)
+isdir(Chan *c)
 {
-	if(m && decref(m) == 0){
-		m->mount = (Mount*)0xCafeBeef;
-		free(m);
-	}
+	if(c->qid.type & QTDIR)
+		return;
+	error(Enotdir);
 }
--- a/os/port/dev.c
+++ b/os/port/dev.c
@@ -135,7 +135,7 @@
 		spec = "";
 	buf = smalloc(4+strlen(spec)+1);
 	sprint(buf, "#%C%s", tc, spec);
-	c->name = newcname(buf);
+	c->path = newpath(buf);
 	free(buf);
 	return c;
 }
@@ -157,7 +157,6 @@
 	nc->qid = c->qid;
 	nc->offset = c->offset;
 	nc->umh = nil;
-	nc->mountid = c->mountid;
 	nc->aux = c->aux;
 	nc->mqid = c->mqid;
 	nc->mcp = c->mcp;
@@ -269,12 +268,12 @@
 		switch((*gen)(c, nil, tab, ntab, i, &dir)){
 		case -1:
 			if(c->qid.type & QTDIR){
-				if(c->name == nil)
+				if(c->path == nil)
 					elem = "???";
-				else if(strcmp(c->name->s, "/") == 0)
+				else if(strcmp(c->path->s, "/") == 0)
 					elem = "/";
 				else
-					for(elem=p=c->name->s; *p; p++)
+					for(elem=p=c->path->s; *p; p++)
 						if(*p == '/')
 							elem = p+1;
 				devdir(c, c->qid, elem, 0, eve, DMDIR|0555, &dir);
--- a/os/port/devcons.c
+++ b/os/port/devcons.c
@@ -498,7 +498,7 @@
 	for(i = 0; i <= o->fgrp->maxfd; i++) {
 		if((c = o->fgrp->fd[i]) == nil)
 			continue;
-		print("%d: %s\n", i, c->name == nil? "???": c->name->s);
+		print("%d: %s\n", i, c->path == nil? "???": c->path->s);
 	}
 }
 
--- a/os/port/devmnt.c
+++ b/os/port/devmnt.c
@@ -740,7 +740,6 @@
 void
 mountrpc(Mnt *m, Mntrpc *r)
 {
-	char *sn, *cn;
 	int t;
 
 	r->reply.tag = 0;
@@ -757,14 +756,8 @@
 	default:
 		if(t == r->request.type+1)
 			break;
-		sn = "?";
-		if(m->c->name != nil)
-			sn = m->c->name->s;
-		cn = "?";
-		if(r->c != nil && r->c->name != nil)
-			cn = r->c->name->s;
-		print("mnt: proc %s %ud: mismatch from %s %s rep 0x%lux tag %d fid %ld T%d R%d rp %d\n",
-			up->text, up->pid, sn, cn,
+		print("mnt: proc %s %lud: mismatch from %s %s rep %#p tag %d fid %d T%d R%d rp %d\n",
+			up->text, up->pid, chanpath(m->c), chanpath(r->c),
 			r, r->request.tag, r->request.fid, r->request.type,
 			r->reply.type, r->reply.tag);
 		error(Emountrpc);
@@ -1142,12 +1135,12 @@
 	/* This routine is mostly vestiges of prior lives; now it's just sanity checking */
 
 	if(c->mchan == nil)
-		panic("mntchk 1: nil mchan c %s\n", channame(c));
+		panic("mntchk 1: nil mchan c %s\n", chanpath(c));
 
 	m = c->mchan->mux;
 
 	if(m == nil)
-		print("mntchk 2: nil mux c %s c->mchan %s \n", channame(c), channame(c->mchan));
+		print("mntchk 2: nil mux c %s c->mchan %s \n", chanpath(c), chanpath(c->mchan));
 
 	/*
 	 * Was it closed and reused (was error(Eshutdown); now, it can't happen)
--- a/os/port/devprog.c
+++ b/os/port/devprog.c
@@ -487,7 +487,7 @@
 		&"r w rw"[(c->mode&3)<<1],
 		devtab[c->type]->dc, c->dev,
 		c->qid.path, w, c->qid.vers, c->qid.type,
-		c->iounit, c->offset, c->name->s);
+		c->iounit, c->offset, c->path->s);
 	return n;
 }
 
@@ -499,7 +499,7 @@
 	int n, i, w, ww;
 
 	f = o->fgrp;	/* f is not locked because we've acquired */
-	n = readstr(0, va, count, o->pgrp->dot->name->s);
+	n = readstr(0, va, count, o->pgrp->dot->path->s);
 	n += snprint(va+n, count-n, "\n");
 	offset = progoffset(offset, va, &n);
 	/* compute width of qid.path */
@@ -887,19 +887,19 @@
 		mntscan(mw, o->pgrp);
 		if(mw->mh == 0) {
 			mw->cddone = 1;
-			i = snprint(a, n, "cd %s\n", o->pgrp->dot->name->s);
+			i = snprint(a, n, "cd %s\n", o->pgrp->dot->path->s);
 			poperror();
 			release();
 			return i;
 		}
 		int2flag(mw->cm->mflag, flag);
-		if(strcmp(mw->cm->to->name->s, "#M") == 0){
+		if(strcmp(mw->cm->to->path->s, "#M") == 0){
 			i = snprint(a, n, "mount %s %s %s %s\n", flag,
-				mw->cm->to->mchan->name->s,
-				mw->mh->from->name->s, mw->cm->spec? mw->cm->spec : "");
+				mw->cm->to->mchan->path->s,
+				mw->mh->from->path->s, mw->cm->spec? mw->cm->spec : "");
 		}else
 			i = snprint(a, n, "bind %s %s %s\n", flag,
-				mw->cm->to->name->s, mw->mh->from->name->s);
+				mw->cm->to->path->s, mw->mh->from->path->s);
 		poperror();
 		release();
 		return i;
--- a/os/port/dis.c
+++ b/os/port/dis.c
@@ -314,11 +314,11 @@
 	q = proctab(0);
 	for(eq = q+conf.nproc; q < eq; q++) {
 		if(q->iprog == p) {
-			swiproc(q, 1);
+			postnote(q, 1, "interrupted", NUser);
 			return;
 		}
 	}
-	/*print("didn't find\n");*/
+	print("swigrog: didn't find proc\n");
 }
 
 static Prog*
--- a/os/port/exportfs.c
+++ b/os/port/exportfs.c
@@ -406,7 +406,8 @@
 			q->flusht = fq;
 			if(q->busy){
 				pid = q->slave->pid;
-				swiproc(q->slave, 0);
+				/* swiproc(q->slave, 0); */
+				postnote(q->slave, 1, "exflushed", NExit);
 			}
 			unlock(q);
 			unlock(fs);
@@ -455,7 +456,8 @@
 		fs->work = q->next;
 		lock(q);
 		q->shut = 1;
-		swiproc(q->slave, 0);	/* whether busy or not */
+		/* swiproc(q->slave, 0);*/	/* whether busy or not */
+		postnote(q->slave, 1, "exshutdown", NExit);
 		unlock(q);
 	}
 	unlock(fs);
@@ -761,7 +763,7 @@
 exmount(Chan *c, Mhead **mp, int doname)
 {
 	Chan *nc;
-	Cname *oname;
+	Path *oname;
 
 	nc = nil;
 	if((c->flag & COPEN) == 0 && findmount(&nc, mp, c->type, c->dev, c->qid)){
@@ -772,10 +774,10 @@
 		nc = cunique(nc);
 		poperror();
 		if(doname){
-			oname = c->name;
+			oname = c->path;
 			incref(oname);
-			cnameclose(nc->name);
-			nc->name = oname;
+			pathclose(nc->path);
+			nc->path = oname;
 		}
 		return nc;
 	}
@@ -981,7 +983,7 @@
 {
 	Fid *f;
 	volatile struct {Chan *c;} c, dc;
-	Cname *oname;
+	Path *oname;
 	Uqid *qid;
 	Mhead *m;
 
@@ -1009,10 +1011,10 @@
 		nexterror();
 	}
 	if(m != nil){
-		oname = c.c->name;
+		oname = c.c->path;
 		incref(oname);
 		if(waserror()){
-			cnameclose(oname);
+			pathclose(oname);
 			nexterror();
 		}
 		dc.c = createdir(c.c, m);
@@ -1022,12 +1024,12 @@
 		}
 		c.c = cunique(dc.c);
 		poperror();
-		cnameclose(c.c->name);
+		pathclose(c.c->path);
 		poperror();
-		c.c->name = oname;
+		c.c->path = oname;
 	}
 	devtab[c.c->type]->create(c.c, t->name, t->mode, t->perm);
-	c.c->name = addelem(c.c->name, t->name);
+	c.c->path = addelem(c.c->path, t->name, c.c);
 	if(t->mode & ORCLOSE)
 		c.c->flag |= CRCLOSE;
 	qid = uqidalloc(fs, c.c);
--- a/os/port/inferno.c
+++ b/os/port/inferno.c
@@ -869,8 +869,8 @@
 		dot = o->pgrp->dot;
 		np.np->dot = cclone(dot);
 		np.np->slash = cclone(dot);
-		cnameclose(np.np->slash->name);
-		np.np->slash->name = newcname("/");
+		pathclose(np.np->slash->path);
+		np.np->slash->path = newpath("/");
 		np.np->nodevs = o->pgrp->nodevs;
 		opg = o->pgrp;
 		o->pgrp = np.np;
--- a/os/port/lib.h
+++ b/os/port/lib.h
@@ -154,7 +154,7 @@
 #define	ORDWR	2	/* read and write */
 #define	OEXEC	3	/* execute, == read but check execute permission */
 #define	OTRUNC	16	/* or'ed in (except for exec), truncate file first */
-#define	OCEXEC	32	/* or'ed in, close on exec */
+#define	OCEXEC	32	/* or'ed in (per file descriptor), close on exec */
 #define	ORCLOSE	64	/* or'ed in, remove on close */
 #define OEXCL   0x1000	/* or'ed in, exclusive create */
 
--- a/os/port/pgrp.c
+++ b/os/port/pgrp.c
@@ -8,6 +8,14 @@
 static Ref pgrpid;
 static Ref mountid;
 
+enum {
+	Whinesecs = 10,		/* frequency of out-of-resources printing */
+};
+
+/* TODO code here is different from 9front. Need to understand why. */
+
+static Ref mountid;
+
 Pgrp*
 newpgrp(void)
 {
@@ -20,7 +28,24 @@
 	return p;
 }
 
+Rgrp*
+newrgrp(void)
+{
+	Rgrp *r;
+
+	r = smalloc(sizeof(Rgrp));
+	r->ref = 1;
+	return r;
+}
+
 void
+closergrp(Rgrp *r)
+{
+	if(decref(r) == 0)
+		free(r);
+}
+
+void
 closepgrp(Pgrp *p)
 {
 	Mhead **h, **e, *f, *next;
@@ -78,40 +103,31 @@
 {
 	int i;
 	Mount *n, *m, **link, *order;
-	Mhead *f, **tom, **l, *mh;
+	Mhead *f, **l, *mh;
 
-	wlock(&from->ns);
-	if(waserror()){
-		wunlock(&from->ns);
-		nexterror();
-	}
-	order = 0;
-	tom = to->mnthash;
+	wlock(&to->ns);
+	rlock(&from->ns);
+	order = nil;
 	for(i = 0; i < MNTHASH; i++) {
-		l = tom++;
-		for(f = from->mnthash[i]; f; f = f->hash) {
+		l = &to->mnthash[i];
+		for(f = from->mnthash[i]; f != nil; f = f->hash) {
 			rlock(&f->lock);
-			if(waserror()){
-				runlock(&f->lock);
-				nexterror();
-			}
-			mh = malloc(sizeof(Mhead));
-			if(mh == nil)
-				error(Enomem);
-			mh->from = f->from;
-			mh->ref = 1;
-			incref(mh->from);
+			mh = newmhead(f->from);
 			*l = mh;
 			l = &mh->hash;
 			link = &mh->mount;
-			for(m = f->mount; m; m = m->next) {
-				n = newmount(mh, m->to, m->mflag, m->spec);
-				m->copy = n;
-				pgrpinsert(&order, m);
+			for(m = f->mount; m != nil; m = m->next) {
+				n = smalloc(sizeof(Mount));
+				n->mountid = m->mountid;
+				n->mflag = m->mflag;
+				n->to = m->to;
+				incref(n->to);
+				if(m->spec != nil)
+					kstrdup(&n->spec, m->spec);
+				pgrpinsert(&order, n);
 				*link = n;
 				link = &n->next;
 			}
-			poperror();
 			runlock(&f->lock);
 		}
 	}
@@ -118,10 +134,8 @@
 	/*
 	 * Allocate mount ids in the same sequence as the parent group
 	 */
-	lock(&mountid.l);
-	for(m = order; m; m = m->order)
-		m->copy->mountid = mountid.ref++;
-	unlock(&mountid.l);
+	for(m = order; m != nil; m = m->order)
+		m->mountid = incref(&mountid);
 
 	to->progmode = from->progmode;
 	to->slash = cclone(from->slash);
@@ -128,10 +142,11 @@
 	to->dot = cclone(from->dot);
 	to->nodevs = from->nodevs;
 
-	poperror();
-	wunlock(&from->ns);
+	runlock(&from->ns);
+	wunlock(&to->ns);
 }
 
+/* not used by 9front. why? */
 Fgrp*
 newfgrp(Fgrp *old)
 {
@@ -156,30 +171,46 @@
 Fgrp*
 dupfgrp(Fgrp *f)
 {
-	int i;
-	Chan *c;
 	Fgrp *new;
-	int n;
+	Chan *c;
+	int i;
 
 	new = smalloc(sizeof(Fgrp));
-	new->ref = 1;
+	if(f == nil){
+		new->flag = smalloc(DELTAFD*sizeof(new->flag[0]));
+		new->fd = smalloc(DELTAFD*sizeof(new->fd[0]));
+		new->nfd = DELTAFD;
+		new->ref = 1;
+		return new;
+	}
+
 	lock(f);
-	n = DELTAFD;
-	if(f->maxfd >= n)
-		n = (f->maxfd+1 + DELTAFD-1)/DELTAFD * DELTAFD;
-	new->nfd = n;
-	new->fd = malloc(n*sizeof(Chan*));
+	/* Make new fd list shorter if possible, preserving quantization */
+	new->nfd = f->maxfd+1;
+	i = new->nfd%DELTAFD;
+	if(i != 0)
+		new->nfd += DELTAFD - i;
+	new->fd = malloc(new->nfd*sizeof(new->fd[0]));
 	if(new->fd == nil){
 		unlock(f);
 		free(new);
-		error(Enomem);
+		error("no memory for fgrp");
 	}
+	new->flag = malloc(new->nfd*sizeof(new->flag[0]));
+	if(new->flag == nil){
+		unlock(f);
+		free(new->fd);
+		free(new);
+		error("no memory for fgrp");
+	}
+	new->ref = 1;
+
 	new->maxfd = f->maxfd;
-	new->minfd = f->minfd;
 	for(i = 0; i <= f->maxfd; i++) {
-		if(c = f->fd[i]){
+		if((c = f->fd[i]) != nil){
 			incref(c);
 			new->fd[i] = c;
+			new->flag[i] = f->flag[i];
 		}
 	}
 	unlock(f);
@@ -193,46 +224,81 @@
 	int i;
 	Chan *c;
 
-	if(f == nil || decref(f) != 0)
+	if(f == nil || decref(f))
 		return;
 
+	/*
+	 * If we get into trouble, forceclosefgrp
+	 * will bail us out.
+	 */
+	up->env->closingfgrp = f;
 	for(i = 0; i <= f->maxfd; i++)
-		if(c = f->fd[i])
+		if((c = f->fd[i]) != nil){
+			f->fd[i] = nil;
 			cclose(c);
+		}
+	up->env->closingfgrp = nil;
 
 	free(f->fd);
+	free(f->flag);
 	free(f);
 }
 
+/*
+ * Called from interrupted() because up is in the middle
+ * of closefgrp and just got a kill ctl message.
+ * This usually means that up has wedged because
+ * of some kind of deadly embrace with mntclose
+ * trying to talk to itself.  To break free, hand the
+ * unclosed channels to the close queue.  Once they
+ * are finished, the blocked cclose that we've 
+ * interrupted will finish by itself.
+ */
+void
+forceclosefgrp(void)
+{
+	int i;
+	Chan *c;
+	Fgrp *f;
+
+	if(up->procctl != Proc_exitme || up->env->closingfgrp == nil){
+		print("bad forceclosefgrp call");
+		return;
+	}
+
+	f = up->env->closingfgrp;
+	for(i = 0; i <= f->maxfd; i++)
+		if((c = f->fd[i]) != nil){
+			f->fd[i] = nil;
+			ccloseq(c);
+		}
+}
+
 Mount*
-newmount(Mhead *mh, Chan *to, int flag, char *spec)
+newmount(Chan *to, int flag, char *spec)
 {
 	Mount *m;
 
 	m = smalloc(sizeof(Mount));
 	m->to = to;
-	m->head = mh;
 	incref(to);
 	m->mountid = incref(&mountid);
 	m->mflag = flag;
-	if(spec != 0)
+	if(spec != nil)
 		kstrdup(&m->spec, spec);
-
+	setmalloctag(m, getcallerpc(&to));
 	return m;
 }
-
 void
 mountfree(Mount *m)
 {
 	Mount *f;
 
-	while(m) {
-		f = m->next;
-		cclose(m->to);
-		m->mountid = 0;
-		free(m->spec);
-		free(m);
-		m = f;
+	while((f = m) != nil) {
+		m = m->next;
+		cclose(f->to);
+		free(f->spec);
+		free(f);
 	}
 }
 
@@ -239,19 +305,32 @@
 void
 resrcwait(char *reason)
 {
+	static ulong lastwhine;
+	ulong now;
 	char *p;
 
-	if(up == 0)
-		panic("resrcwait");
+	if(up == nil)
+		panic("resrcwait: %s", reason);
 
 	p = up->psstate;
-	if(reason) {
+	if(reason != nil) {
+		if(waserror()){
+			up->psstate = p;
+			nexterror();
+		}
 		up->psstate = reason;
-		print("%s\n", reason);
+		now = seconds();
+		/* don't tie up the console with complaints */
+		if(now - lastwhine > Whinesecs) {
+			lastwhine = now;
+			print("%s\n", reason);
+		}
 	}
-
-	tsleep(&up->sleep, return0, 0, 300);
-	up->psstate = p;
+	tsleep(&up->sleep, return0, 0, 100+nrand(200));
+	if(reason != nil) {
+		up->psstate = p;
+		poperror();
+	}
 }
 
 void
--- a/os/port/portdat.h
+++ b/os/port/portdat.h
@@ -24,7 +24,9 @@
 typedef struct Mntwalk	Mntwalk;
 typedef struct Mnt	Mnt;
 typedef struct Mhead	Mhead;
+typedef struct Note	Note;
 typedef struct Osenv	Osenv;
+typedef struct Path	Path;
 typedef struct Perf	Perf;
 typedef struct Pgrp	Pgrp;
 typedef struct Proc	Proc;
@@ -35,6 +37,7 @@
 typedef struct Rendez	Rendez;
 typedef struct Rept	Rept;
 typedef struct Rootdata	Rootdata;
+typedef struct Rgrp	Rgrp;
 typedef struct RWlock	RWlock;
 typedef struct Schedq	Schedq;
 typedef struct Signerkey Signerkey;
@@ -100,6 +103,10 @@
 	s32	gid;		/* Numeric group id for system */
 	char*	user;		/* Inferno user name */
 	int	fpuostate;
+
+	/* from 9front */
+	Rgrp	*rgrp;		/* Rendez group */
+	Fgrp	*closingfgrp;	/* used during teardown */
 };
 
 enum
@@ -117,10 +124,13 @@
 
 struct RWlock
 {
-	Lock;				/* Lock modify lock */
-	QLock	x;			/* Mutual exclusion lock */
-	QLock	k;			/* Lock for waiting writers */
-	s32	readers;		/* Count of readers in lock */
+	Lock	use;
+	Proc	*head;		/* list of waiting processes */
+	Proc	*tail;
+	uintptr	wpc;		/* pc of writer */
+	Proc	*wproc;		/* writing proc */
+	int	readers;	/* number of readers */
+	int	writer;		/* number of writers */
 };
 
 struct Talarm
@@ -191,11 +201,12 @@
 
 struct Chan
 {
-	Lock;
 	Ref;
+	Lock;
 	Chan*	next;			/* allocation */
 	Chan*	link;
-	s64	offset;			/* in file */
+	s64	offset;			/* in fd */
+	s64	devoffset;		/* in underlying device; see read */
 	u16	type;
 	u32	dev;
 	u16	mode;			/* read/write */
@@ -202,30 +213,37 @@
 	u16	flag;
 	Qid	qid;
 	s32	fid;			/* for devmnt */
-	u32	iounit;	/* chunk size for i/o; 0==default */
+	u32	iounit;			/* chunk size for i/o; 0==default */
 	Mhead*	umh;			/* mount point that derived Chan; used in unionread */
 	Chan*	umc;			/* channel in union; held for union read */
 	QLock	umqlock;		/* serialize unionreads */
 	s32	uri;			/* union read index */
 	s32	dri;			/* devdirread index */
-	u32	mountid;
-	Mntcache *mcp;			/* Mount cache pointer */
-	Mnt		*mux;		/* Mnt for clients using me for messages */
+	uchar*	dirrock;		/* directory entry rock for translations */
+	int	nrock;
+	int	mrock;
+	QLock	rockqlock;
+	int	ismtpt;
+	Mntcache*mcp;			/* Mount cache pointer */
+	Mnt*	mux;			/* Mnt for clients using me for messages */
 	union {
 		void*	aux;
-		char	tag[4];		/* for iproute */
+		u32	mid;		/* for ns in devproc */
 	};
 	Chan*	mchan;			/* channel to mounted server */
 	Qid	mqid;			/* qid of root of mount point */
-	Cname	*name;
+	Path*	path;
 };
 
-struct Cname
+struct Path
 {
 	Ref;
-	s32	alen;			/* allocated length */
-	s32	len;			/* strlen(s) */
 	char	*s;
+	Chan	**mtpt;			/* mtpt history */
+	int	len;			/* strlen(s) */
+	int	alen;			/* allocated length of s */
+	int	mlen;			/* number of path elements */
+	int	malen;			/* allocated length of mtpt */
 };
 
 struct Dev
@@ -286,8 +304,6 @@
 {
 	u32	mountid;
 	Mount*	next;
-	Mhead*	head;
-	Mount*	copy;
 	Mount*	order;
 	Chan*	to;			/* channel replacing channel */
 	s32	mflag;
@@ -320,6 +336,19 @@
 
 enum
 {
+	NUser,				/* note provided externally */
+	NExit,				/* deliver note quietly */
+	NDebug,				/* print debug message */
+};
+
+struct Note
+{
+	char	msg[ERRMAX];
+	int	flag;			/* whether system posted it */
+};
+
+enum
+{
 	RENDLOG	=	5,
 	RENDHASH =	1<<RENDLOG,		/* Hash to lookup rendezvous tags */
 	MNTLOG	=	5,
@@ -328,6 +357,7 @@
 	MAXNFD =		4000,		/* max per process file descriptors */
 	MAXKEY =		8,	/* keys for signed modules */
 };
+#define REND(p,s)	((p)->rendhash[(s)&((1<<RENDLOG)-1)])
 #define MOUNTH(p,qid)	((p)->mnthash[(qid).path&((1<<MNTLOG)-1)])
 
 struct Mntparam {
@@ -340,11 +370,12 @@
 struct Pgrp
 {
 	Ref;				/* also used as a lock when mounting */
+	RWlock	ns;			/* Namespace n read/one write lock */
+	int	noattach;
+	Mhead*	mnthash[MNTHASH];
 	u32	pgrpid;
 	QLock	debug;			/* single access via devproc.c */
-	RWlock	ns;			/* Namespace n read/one write lock */
 	QLock	nsh;
-	Mhead*	mnthash[MNTHASH];
 	s32	progmode;
 	Chan*	dot;
 	Chan*	slash;
@@ -356,6 +387,7 @@
 {
 	Ref;
 	Lock;
+	Proc	*rendhash[RENDHASH];	/* Rendezvous tag hash */
 	Chan	**fd;
 	uchar	*flag;			/* per file-descriptor flags (CCEXEC) */
 	s32	nfd;			/* number allocated */
@@ -364,6 +396,13 @@
 	s32	exceed;			/* debugging */
 };
 
+struct Rgrp
+{
+	Ref;
+	Lock;
+	Proc	*rendhash[RENDHASH];	/* Rendezvous tag hash */
+};
+
 struct Evalue
 {
 	union {
@@ -448,6 +487,8 @@
 	Scheding,
 	Running,
 	Queueing,
+	QueueingR,
+	QueueingW,
 	Wakeme,
 	Broken,
 	Stopped,
@@ -459,7 +500,15 @@
 	Proc_traceme,
 	Proc_exitbig,
 
+	TUser = 0, 		/* Proc.time */
+	TSys,
+	TReal,
+	TCUser,
+	TCSys,
+	TCReal,
+
 	NERR		= 30,
+	NNOTE = 5,
 
 	Unknown		= 0,
 	IdleGC,
@@ -488,64 +537,61 @@
 	int	n;
 };
 
+/* inferno uses Osenv for environment information. It is cheaper to create
+ * new processes with a default environment
+ */
 struct Proc
 {
-	Label		sched;		/* known to l.s */
-	char*		kstack;		/* known to l.s */
-	Mach*		mach;		/* machine running this proc */
-	char		text[KNAMELEN];
-	Proc*		rnext;		/* next process in run queue */
-	Proc*		qnext;		/* next process on queue for a QLock */
-	QLock*		qlock;		/* addrof qlock being queued for DEBUG */
+	Label	sched;		/* known to l.s */
+	char	*kstack;	/* known to l.s */
+	Mach	*mach;		/* machine running this proc */
+	char	text[KNAMELEN];
+	/*	char	*user; 9front only */
+
+	/*
+	 * below 3 args fields are not used by 9ferno
+	 * leaving them alone to stay compatible with 9front
+	 */
+/*	char	*args;*/
+/*	int	nargs;		*//* number of bytes of args */
+/*	int	setargs;	*//* process changed its args */
+
+	Proc	*rnext;		/* next process in run queue */
+	Proc	*qnext;		/* next process on queue for a QLock */
+							/* check notes in proc.c for how these 2 fields are used */
+	void	*blockinglock;	/* address of QLock or RWLock being queued for, DEBUG */
+						/* not in 9front as we can reason that info from qpc */
+	uintptr	qpc;		/* last call that blocked in QLock or RWLock */
+
+	char	*psstate;	/* What /proc/#/status reports */
 	s32		state;
-	s32		type;
-	void*		prog;		/* Dummy Prog for interp release */
-	void*		iprog;
-	Osenv*		env;
-	Osenv		defenv;
-	s32		swipend;	/* software interrupt pending for Prog TODO replace with notepending? */
-	Lock		sysio;		/* note handler lock */
-	char*		psstate;	/* What /proc/#/status reports */
-	s32		pid;
-	s32		procctl;	/* Control for /proc debugging */
-	uintptr		pc;		/* DEBUG only */
-	Lock	rlock;	/* sync between sleep/swiproc for r */
-	Rendez*		r;		/* rendezvous point slept on */
-	Rendez		sleep;		/* place for syssleep/debug */
-	s32		killed;		/* by swiproc */
-	s32		kp;		/* true if a kernel process */
-	Proc	*palarm;	/* Next alarm time */
-	u32		alarm;		/* Time of call */
-	s32		priority;		/* scheduler priority */
-	u32		twhen;
 
-	Timer;			/* For tsleep and real-time */
-	Rendez*		trend;
-	Proc*		tlink;
-	s32		(*tfn)(void*);
-	void		(*kpfun)(void*);
-	void*		arg;
-	PFPU;				/* machine specific fpu state */
-	s32		scallnr;
-	s32		nerrlab;
-	Label		errlab[NERR];
-	char	genbuf[128];	/* buffer used e.g. for last name element from namec */
+	u32	pid;
+	u32	noteid;		/* Equivalent of note group */
+/*	u32	parentpid; *//* no single parent in inferno, send it to the process group */
 
-	Lock	*lockwait;
-	Lock	*lastlock;	/* debugging */
-	Lock	*lastilock;	/* debugging */
+/*	Proc	*parent;	*//* Process to send wait record on exit */
+/*	Lock	exl;		*//* Lock count and waitq */
+/*	Waitq	*waitq;		*//* Exited processes wait children */
+/*	int	nchild;		*//* Number of living children */
+/*	int	nwait;		*//* Number of uncollected wait records */
+/*	QLock	qwaitr;*/
+/*	Rendez	waitr;		*//* Place to hang out in wait */
 
-	Mach*		mp;		/* machine this process last ran on */
-	Mach*		wired;
-	int	nlocks;		/* number of locks held by proc */
-	/* obsoleted u32		movetime; */	/* next time process should switch processors */
-	u32		delaysched;
-	s32			preempted;	/* process yielding in interrupt */
-	uintptr		qpc;		/* last call that blocked in qlock */
-	void*		dbgreg;		/* User registers for devproc */
- 	s32		dbgstop;		/* don't run this kproc */
-	Edf*	edf;	/* if non-null, real-time proc, edf contains scheduling params */
+/*	QLock	seglock;	*//* locked whenever seg[] changes */
+/*	Segment	*seg[NSEG]; */
 
+/*	Pgrp	*pgrp;	*/	/* Process group for namespace */
+/*	Egrp 	*egrp;	*/	/* Environment group */
+/*	Fgrp	*fgrp;	*/	/* File descriptor group */
+/*	Rgrp	*rgrp;	*/	/* Rendez group */
+
+/*	Fgrp	*closingfgrp;*/	/* used during teardown */
+
+/*	int	insyscall;*/
+	u32	time[6];	/* User, Sys, Real; child U, S, R */
+
+/*	uvlong	kentry;	*/	/* Kernel entry time stamp (for profiling) */
 	/*
 	 * pcycles: cycles spent in this process (updated on procswitch)
 	 * when this is the current proc and we're in the kernel
@@ -554,19 +600,108 @@
 	 * when this is not the current process or we're in user mode
 	 * (procrestores and procsaves balance), it is pcycles.
 	 */
-	s64	pcycles;
+	vlong	pcycles;
 
-	PMMU;				/* TODO obsolete? machine specific mmu state */
-
-	/* TODO 9front fields that need to incorporated into 9ferno */
-	/* TODO replace swiproc() with postnote() */
-	u32	noteid;		/* Equivalent of note group */
 	QLock	debug;		/* to access debugging elements of User */
-	int	trace;		/* process being traced? */
-	ulong	procmode;	/* proc device default file mode */
+	Proc	*pdbg;		/* the debugging process */
+	/*
+	 * Pgrp.progmode is used by inferno and procmode by 9front
+	 * Leaving them different, for now
+	 */
+	u32	procmode;	/* proc device default file mode */
 	int	privatemem;	/* proc does not let anyone read mem */
 	int	noswap;		/* process is not swappable */
 	int	hang;		/* hang at next exec for debug */
+	int	procctl;	/* Control for /proc debugging */
+	uintptr	pc;		/* DEBUG only */
+
+	Lock	rlock;		/* sync sleep/wakeup with postnote */
+	Rendez	*r;		/* rendezvous point slept on */
+	Rendez	sleep;		/* place for syssleep/debug */
+	int	notepending;	/* note issued but not acted on */
+	int	kp;		/* true if a kernel process */
+	Proc	*palarm;	/* Next alarm time */
+	ulong	alarm;		/* Time of call */
+/*	int	newtlb;	*/	/* Pager has changed my pte's, I must flush */
+
+	uintptr	rendtag;	/* Tag for rendezvous */
+	uintptr	rendval;	/* Value for rendezvous */
+	Proc	*rendhash;	/* Hash list for tag values */
+
+	Timer;			/* For tsleep and real-time, has twhen of inferno */
+	Rendez	*trend;
+	int	(*tfn)(void*);
+	void	(*kpfun)(void*);
+	void	*kparg;
+
+/*	Sargs	s;		*//* syscall arguments */
+/*	int	scallnr;	*//* sys call number */
+	int	nerrlab;
+	Label	errlab[NERR];
+						/* below fields are in Osenv */
+/*	char	*syserrstr;	*//* last error from a system call, errbuf0 or 1 */
+/*	char	*errstr;	*//* reason we're unwinding the error stack, errbuf1 or 0 */
+/*	char	errbuf0[ERRMAX];*/
+/*	char	errbuf1[ERRMAX];*/
+	char	genbuf[128];	/* buffer used e.g. for last name element from namec */
+/*	Chan	*slash; part of Pgrp in inferno */
+/*	Chan	*dot; part of Pgrp in inferno */
+
+	Note	note[NNOTE];
+	short	nnote;
+	short	notified;	/* sysnoted is due */
+	Note	lastnote;
+	int	(*notify)(void*, char*);
+
+	Lock	*lockwait;
+	Lock	*lastlock;	/* debugging */
+	Lock	*lastilock;	/* debugging */
+
+	Mach	*wired;
+	Mach	*mp;		/* machine this process last ran on */
+	int	nlocks;		/* number of locks held by proc */
+	ulong	delaysched;
+	ulong	priority;	/* priority level */
+/*	ulong	basepri;	*//* base priority level */
+/*	uchar	fixedpri;	*//* priority level deson't change */
+/*	ulong	cpu;		*//* cpu average */
+/*	ulong	lastupdate;*/
+	uchar	yield;		/* non-zero if the process just did a sleep(0) */
+	ulong	readytime;	/* time process came ready */
+	int	preempted;	/* true if this process hasn't finished the interrupt
+				 *  that last preempted it
+				 */
+	Edf	*edf;		/* if non-null, real-time proc, edf contains scheduling params */
+	int	trace;		/* process being traced? */
+
+	QLock	*eql;		/* interruptable eqlock */
+
+	void	*ureg;		/* User registers for notes */
+	void	*dbgreg;	/* User registers for devproc */
+
+	PFPU;			/* machine specific fpu state */
+	PMMU;			/* machine specific mmu state, obsolete on 9ferno amd64? */
+
+/*	char	*syscalltrace;	*//* syscall trace */
+	
+	Watchpt	*watchpt;	/* watchpoints */
+	int	nwatchpt;
+
+	/* inferno specific fields */
+	s32		type;
+	void*		prog;		/* Dummy Prog for interp release */
+	void*		iprog;
+	Osenv*		env;
+	Osenv		defenv;
+	s32		swipend;	/* software interrupt pending for Prog TODO replace with notepending? */
+	Lock		sysio;		/* note handler lock */
+
+	/* inferno specific fields that are obsolete? */
+	int		fpstate;
+	int		killed;		/* by swiproc */
+	Proc*		tlink;
+	ulong		movetime;	/* next time process should switch processors */
+ 	int		dbgstop;		/* don't run this kproc */
 };
 
 enum
--- a/os/port/portfns.h
+++ b/os/port/portfns.h
@@ -1,5 +1,5 @@
 Timer*		addclock0link(void (*)(void), int);
-Cname*		addelem(Cname*, char*);
+Path*	addelem(Path *p, char *s, Chan *from);
 void		addprog(Proc*);
 void		addrootfile(char*, uchar*, ulong);
 Block*		adjustblock(Block*, int);
@@ -10,7 +10,7 @@
 int		blocklen(Block*);
 int	breakhit(Ureg *ur, Proc*);
 void		callwithureg(void(*)(Ureg*));
-char*		channame(Chan*);
+char*		chanpath(Chan*);
 int		canlock(Lock*);
 int		canqlock(QLock*);
 void		cclose(Chan*);
@@ -26,6 +26,7 @@
 void		cinit(void);
 Chan*		cclone(Chan*);
 void		cclose(Chan*);
+void		ccloseq(Chan*);
 void		closeegrp(Egrp*);
 void		closefgrp(Fgrp*);
 void		closemount(Mount*);
@@ -33,7 +34,6 @@
 void		closesigs(Skeyset*);
 void		cmderror(Cmdbuf*, char*);
 int		cmount(Chan*, Chan*, int, char*);
-void		cnameclose(Cname*);
 Block*		concatblock(Block*);
 void		confinit(void);
 void		(*consdebug)(void);
@@ -75,7 +75,6 @@
 int		devwstat(Chan*, uchar*, int);
 void		disinit(void*);
 void		disfault(void*, char*);
-int		domount(Chan**, Mhead**);
 void		drawactive(int);
 void		drawcmap(void);
 void		dumpaproc(Proc*);
@@ -93,8 +92,6 @@
 void		excinit(void);
 void		exhausted(char*);
 void		exit(int);
-void		reboot(void);
-void		halt(void);
 int		export(int, char*, int);
 uvlong		fastticks(uvlong*);
 uvlong		fastticks2ns(uvlong);
@@ -101,6 +98,7 @@
 void		fdclose(Fgrp*, int);
 Chan*		fdtochan(Fgrp*, int, int, int, int);
 int		findmount(Chan**, Mhead**, int, int, Qid);
+void		forceclosefgrp(void);
 void		free(void*);
 void		freeb(Block*);
 void		freeblist(Block*);
@@ -110,6 +108,7 @@
 uintptr	getrealloctag(void*);
 void		gotolabel(Label*);
 char*		getconfenv(void);
+void		halt(void);
 void 		(*hwrandbuf)(void*, u32);
 void		hnputl(void*, u32);
 void		hnputs(void*, u16);
@@ -117,6 +116,7 @@
 void		iallocsummary(void);
 void		ilock(Lock*);
 s32		incref(Ref*);
+void		interrupted(void);
 void		iomapinit(u32);
 s32		ioreservewin(u32, u32, u32, u32, char*);
 int		iprint(char*, ...);
@@ -184,12 +184,15 @@
 Chan*		newchan(void);
 Egrp*		newegrp(void);
 Fgrp*		newfgrp(Fgrp*);
-Mount*		newmount(Mhead*, Chan*, int, char*);
+int		newfd(Chan*, int);
+Mhead*		newmhead(Chan*);
+Mount*		newmount(Chan*, int, char*);
+Path*		newpath(char*);
 Pgrp*		newpgrp(void);
 Proc*		newproc(void);
 char*		nextelem(char*, char*);
 void		nexterror(void);
-Cname*		newcname(char*);
+Path*		newpath(char*);
 int		notify(Ureg*);
 void	notkilled(void);
 int		nrand(int);
@@ -200,6 +203,7 @@
 Block*		padblock(Block*, int);
 void		panic(char*, ...);
 Cmdbuf*		parsecmd(char*, int);
+void		pathclose(Path*);
 ulong		perfticks(void);
 void		pexit(char*, int);
 void		pgrpcpy(Pgrp*, Pgrp*);
@@ -264,6 +268,7 @@
 int		readnum_vlong(ulong, char*, ulong, vlong, int);
 int		readstr(ulong, char*, ulong, char*);
 void		ready(Proc*);
+void		reboot(void);
 void		renameproguser(char*, char*);
 void		renameuser(char*, char*);
 void		resrcwait(char*);
@@ -322,6 +327,7 @@
 void		userinit(void);
 ulong		userpc(void);
 void		validname(char*, int);
+char*		validnamedup(char*, int);
 void		validstat(uchar*, int);
 void		validwstatname(char*);
 Proc*		wakeup(Rendez*);
--- a/os/port/proc.c
+++ b/os/port/proc.c
@@ -393,6 +393,7 @@
 	if(pt != nil)
 		pt(p, SRun, 0);
 */
+/* for debugging */
 /*	if(p->pid != prevpid){
 		prevpid = p->pid;
 		if(p->type == Interp && p->iprog != nil){
@@ -406,19 +407,6 @@
 	return p;
 }
 
-int
-setpri(int priority)
-{
-	int p;
-
-	/* called by up so not on run queue */
-	p = up->priority;
-	up->priority = priority;
-	if(up->state == Running && anyhigher())
-		sched();
-	return p;
-}
-
 /*
  * not using memset 0 on the Proc structure
  * to avoid leaking KSTACK
@@ -449,7 +437,7 @@
 	p->mach = 0;
 	p->qnext = 0;
 	p->kp = 0;
-	p->killed = 0;
+	p->killed = 0; /* TODO replace these 2 with notepending */
 	p->swipend = 0;
 	p->nlocks = 0;
 	p->delaysched = 0;
@@ -477,7 +465,54 @@
 	return p;
 }
 
+/*
+ * wire this proc to a machine
+ */
 void
+procwired(Proc *p, int bm)
+{
+	Proc *pp;
+	int i;
+	char nwired[MAXMACH];
+	Mach *wm;
+
+	if(bm < 0){
+		/* pick a machine to wire to */
+		memset(nwired, 0, sizeof(nwired));
+		p->wired = nil;
+		for(i=0; i<conf.nproc; i++){
+			pp = proctab(i);
+			wm = pp->wired;
+			if(wm != nil && pp->pid)
+				nwired[wm->machno]++;
+		}
+		bm = 0;
+		for(i=0; i<conf.nmach; i++)
+			if(nwired[i] < nwired[bm])
+				bm = i;
+	} else {
+		/* use the virtual machine requested */
+		bm = bm % conf.nmach;
+	}
+
+	p->wired = MACHP(bm);
+	p->mp = p->wired;
+}
+
+int
+setpri(int priority)
+{
+	int p;
+
+	/* called by up so not on run queue */
+	p = up->priority;
+	up->priority = priority;
+	if(up->state == Running && anyhigher())
+		sched();
+	return p;
+}
+
+void
 procinit(void)
 {
 	Proc *p;
@@ -487,7 +522,8 @@
 	p = xalloc(conf.nproc*sizeof(Proc));
 	if(p == nil){
 		xsummary();
-		panic("cannot allocate %ud procs (%udMB)", conf.nproc, conf.nproc*sizeof(Proc)/(1024*1024));
+		panic("cannot allocate %ud procs (%udMB)",
+			conf.nproc, conf.nproc*sizeof(Proc)/(1024*1024));
 	}
 	procalloc.arena = p;
 	procalloc.free = p;
@@ -508,13 +544,12 @@
  *
  *  we lock both the process and the rendezvous to keep r->p
  *  and p->r synchronized.
- * TODO
- *  9front checks up->notepending instead of up->swipend
  */
 void
 sleep(Rendez *r, int (*f)(void*), void *arg)
 {
 	int s;
+/*	void (*pt)(Proc*, int, vlong);*/
 
 	if(up == nil)
 		panic("sleep() not in process (%zux)", getcallerpc(&r));
@@ -546,10 +581,11 @@
 	 */
 	r->p = up;
 
-	/*
-	 * if killed or condition happened, never mind
-	 */
-	if(up->killed || f(arg)){
+	if(up->notepending || f(arg)){
+		/*
+		 *  if condition happened or a note is pending
+		 *  never mind
+		 */
 		r->p = nil;
 		unlock(&up->rlock);
 		unlock(r);
@@ -568,15 +604,22 @@
 		procswitch();
 	}
 
-	if(up->killed || up->swipend) {
-		up->killed = 0;
-		up->swipend = 0;
+	if(up->notepending) {
+		up->notepending = 0;
 		splx(s);
-		error(Eintr);
+		interrupted();
 	}
 	splx(s);
 }
 
+void
+interrupted(void)
+{
+	if(up->procctl == Proc_exitme && up->env->closingfgrp != nil)
+		forceclosefgrp();
+	error(Eintr);
+}
+
 static int
 tfn(void *arg)
 {
@@ -659,37 +702,129 @@
 	return p;
 }
 
-/* TODO replace with postnote()
- * man 9 tsleep of inferno
- * man 9 postnote of 9front
+/*
+ *  if waking a sleeping process, this routine must hold both
+ *  p->rlock and r->lock.  However, it can't know them in
+ *  the same order as wakeup causing a possible lock ordering
+ *  deadlock.  We break the deadlock by giving up the p->rlock
+ *  lock if we can't get the r->lock and retrying.
  */
-void
-swiproc(Proc *p, int interp)
+int
+postnote(Proc *p, int dolock, char *n, int flag)
 {
-	ulong s;
-	Rendez *r;
+	int s, ret;
+	QLock *q;
 
 	if(p == nil)
-		return;
+		return 0;
 
-	s = splhi();
-	lock(&p->rlock);
-	if(!interp)
-		p->killed = 1;
-	r = p->r;
-	if(r != nil) {
-		lock(r);
-		if(r->p == p){
-			p->swipend = 1;
+	if(dolock)
+		qlock(&p->debug);
+
+	if(p->pid == 0){
+		if(dolock)
+			qunlock(&p->debug);
+		return 0;
+	}
+
+	if(n != nil && flag != NUser && (p->notify == nil || p->notified))
+		p->nnote = 0;
+
+	ret = 0;
+	if(p->nnote < NNOTE && n != nil) {
+		kstrcpy(p->note[p->nnote].msg, n, ERRMAX);
+		p->note[p->nnote++].flag = flag;
+		ret = 1;
+	}
+	p->notepending = 1;
+	if(dolock)
+		qunlock(&p->debug);
+
+	/* this loop is to avoid lock ordering problems. */
+	for(;;){
+		Rendez *r;
+
+		s = splhi();
+		lock(&p->rlock);
+		r = p->r;
+
+		/* waiting for a wakeup? */
+		if(r == nil)
+			break;	/* no */
+
+		/* try for the second lock */
+		if(canlock(r)){
+			if(p->state != Wakeme || r->p != p)
+				panic("postnote: state %d %d %d", r->p != p, p->r != r, p->state);
+			p->r = nil;
 			r->p = nil;
 			ready(p);
+			unlock(r);
+			break;
 		}
-		unlock(r);
+
+		/* give other process time to get out of critical section and try again */
+		unlock(&p->rlock);
+		splx(s);
+		sched();
 	}
 	unlock(&p->rlock);
 	splx(s);
+
+	switch(p->state){
+	case Queueing:
+		/* Try and pull out of a eqlock */
+		if((q = p->eql) != nil){
+			lock(&q->use);
+			if(p->state == Queueing && p->eql == q){
+				Proc *d, *l;
+
+				for(l = nil, d = q->head; d != nil; l = d, d = d->qnext){
+					if(d == p){
+						if(l != nil)
+							l->qnext = p->qnext;
+						else
+							q->head = p->qnext;
+						if(p->qnext == nil)
+							q->tail = l;
+						p->qnext = nil;
+						p->eql = nil;	/* not taken */
+						ready(p);
+						break;
+					}
+				}
+			}
+			unlock(&q->use);
+		}
+		break;
+	case Rendezvous:
+		/* Try and pull out of a rendezvous */
+		lock(p->env->rgrp);
+		if(p->state == Rendezvous) {
+			Proc *d, **l;
+
+			l = &REND(p->env->rgrp, p->rendtag);
+			for(d = *l; d != nil; d = d->rendhash) {
+				if(d == p) {
+					*l = p->rendhash;
+					p->rendval = ~0;
+					ready(p);
+					break;
+				}
+				l = &d->rendhash;
+			}
+		}
+		unlock(p->env->rgrp);
+		break;
+	}
+	return ret;
 }
 
+/*
+ * 9front maintains broken processes. Not bothering with them
+ * as there should not be any broken proc's in inferno
+ */
+
 void
 notkilled(void)
 {
@@ -782,7 +917,7 @@
 	p->kp = 1;
 
 	p->fpsave = up->fpsave;
-	p->scallnr = up->scallnr;
+/*	p->scallnr = up->scallnr; */
 	p->nerrlab = 0;
 
 	kstrdup(&p->env->user, up->env->user);
@@ -803,10 +938,10 @@
 		p->env->egrp = eg;
 	}
 
-/*	p->nnote = 0;
+	p->nnote = 0;
 	p->notify = nil;
 	p->notified = 0;
-	p->notepending = 0;*/
+	p->notepending = 0;
 
 	p->procmode = 0640;
 	p->privatemem = 1;
@@ -813,14 +948,32 @@
 	p->noswap = 1;
 	p->hang = 0;
 	p->kp = 1;
+
+/*	p->kpfun = func;
+	p->kparg = arg;
+	kprocchild(p, linkproc);*/
+/* this does all of the above 3 lines */
 	kprocchild(p, func, arg);
 
 	strcpy(p->text, name);
 
+/*	if(kpgrp == nil)
+		kpgrp = newpgrp();
+	p->pgrp = kpgrp;
+	incref(kpgrp);*/
+
+	memset(p->time, 0, sizeof(p->time));
+	p->time[TReal] = MACHP(0)->ticks;
+/*	cycles(&p->kentry);
+	p->pcycles = -p->kentry;*/
+
 	pidalloc(p);
 
 	qunlock(&p->debug);
 
+/*	procpriority(p, PriKproc, 0);*/
+
+	p->psstate = nil;
 	ready(p);
 }
 
--- a/os/port/qlock.c
+++ b/os/port/qlock.c
@@ -4,13 +4,50 @@
 #include "dat.h"
 #include "fns.h"
 
+struct {
+	ulong rlock;
+	ulong rlockq;
+	ulong wlock;
+	ulong wlockq;
+	ulong qlock;
+	ulong qlockq;
+} rwstats;
+
+/*
+	lock()
+		blockinglock = qpc = nil
+	unlock()
+		blockinglock = qpc = nil
+
+	lock()
+		blockinglock = qpc = nil
+	placed in the queue
+		blockinglock = lock address
+		qpc = pc that called lock()
+	out of the queue, ready to run
+		blockinglock = nil
+		qpc = pc that called lock()
+	unlock()
+		blockinglock = qpc = nil
+ */
+
 void
 eqlock(QLock *q)
 {
 	Proc *p;
 
+	if(m->ilockdepth != 0)
+		print("eqlock: %#p: ilockdepth %d\n", getcallerpc(&q), m->ilockdepth);
+	if(up != nil && up->nlocks)
+		print("eqlock: %#p: nlocks %d\n", getcallerpc(&q), up->nlocks);
+	if(up != nil && up->eql != nil)
+		print("eqlock: %#p: eql %p\n", getcallerpc(&q), up->eql);
+	if(q->use.key == 0x55555555)
+		panic("eqlock: q %#p, key 5*", q);
+
 	lock(&q->use);
-	if(!q->locked) {
+	rwstats.qlock++;
+	if(q->locked == 0) {
 		q->locked = 1;
 		unlock(&q->use);
 		return;
@@ -17,6 +54,12 @@
 	}
 	if(up == nil)
 		panic("eqlock");
+	if(up->notepending){
+		up->notepending = 0;
+		unlock(&q->use);
+		interrupted();
+	}
+	rwstats.qlockq++;
 	p = q->tail;
 	if(p == nil)
 		q->head = up;
@@ -24,33 +67,54 @@
 		p->qnext = up;
 	q->tail = up;
 	up->qnext = nil;
+	up->blockinglock = q;
+	up->eql = q;
 	up->qpc = getcallerpc(&q);
 	up->state = Queueing;
 	unlock(&q->use);
 	sched();
-	/* up->eql = nil; */
+	up->blockinglock = nil;
+	if(up->eql == nil){
+		up->notepending = 0;
+		interrupted();
+	}
+	up->eql = nil;
+	up->qpc = 0;
 }
 
 void
 qlock(QLock *q)
 {
-	Proc *p, *mp;
+	Proc *p;
 
+	if(m->ilockdepth != 0)
+		print("qlock: %#p: ilockdepth %d\n", getcallerpc(&q), m->ilockdepth);
+	if(up != nil && up->nlocks)
+		print("qlock: %#p: nlocks %d\n", getcallerpc(&q), up->nlocks);
+	if(up != nil && up->eql != nil)
+		print("qlock: %#p: eql %p\n", getcallerpc(&q), up->eql);
+	if(q->use.key == 0x55555555)
+		panic("qlock: q %#p, key 5*", q);
 	lock(&q->use);
-	if(!q->locked) {
+	rwstats.qlock++;
+	if(q->locked == 0) {
 		q->locked = 1;
 		unlock(&q->use);
 		return;
 	}
+	if(up == nil)
+		panic("qlock");
+	rwstats.qlockq++;
 	p = q->tail;
-	mp = up;
-	if(p == 0)
-		q->head = mp;
+	if(p == nil)
+		q->head = up;
 	else
-		p->qnext = mp;
-	q->tail = mp;
-	mp->qnext = 0;
-	mp->state = Queueing;
+		p->qnext = up;
+	up->qnext = nil;
+	q->tail = up;
+	up->blockinglock = q;
+	up->eql = nil;
+	up->state = Queueing;
 	up->qpc = getcallerpc(&q);
 	unlock(&q->use);
 	sched();
@@ -70,6 +134,7 @@
 	return 1;
 }
 
+/* blockinglock should not be nil */
 void
 qunlock(QLock *q)
 {
@@ -76,11 +141,21 @@
 	Proc *p;
 
 	lock(&q->use);
+	if (q->locked == 0)
+		print("qunlock called with qlock not held, from %#p\n",
+			getcallerpc(&q));
+	if (up != nil && up->blockinglock != nil)
+		print("qunlock called with blockinglock %#p, from %#p\n",
+			up->blockinglock, getcallerpc(&q));
+	if (up != nil)
+			up->qpc = 0;
 	p = q->head;
-	if(p) {
+	if(p != nil) {
+		/* some other process is waiting for this lock */
 		q->head = p->qnext;
-		if(q->head == 0)
-			q->tail = 0;
+		if(q->head == nil)
+			q->tail = nil;
+		p->blockinglock = nil;
 		unlock(&q->use);
 		ready(p);
 		return;
@@ -89,50 +164,160 @@
 	unlock(&q->use);
 }
 
+/* why is there no interruptible rlock()? */
 void
-rlock(RWlock *l)
+rlock(RWlock *q)
 {
-	qlock(&l->x);		/* wait here for writers and exclusion */
-	lock(l);
-	l->readers++;
-	canqlock(&l->k);	/* block writers if we are the first reader */
-	unlock(l);
-	qunlock(&l->x);
+	Proc *p;
+
+	lock(&q->use);
+	rwstats.rlock++;
+	if(q->writer == 0 && q->head == nil){
+		/* no writer, go for it */
+		q->readers++;
+		unlock(&q->use);
+		return;
+	}
+
+	rwstats.rlockq++;
+	p = q->tail;
+	if(up == nil)
+		panic("rlock");
+	if(p == nil)
+		q->head = up;
+	else
+		p->qnext = up;
+	q->tail = up;
+	up->qnext = nil;
+	up->blockinglock = q;
+	up->eql = nil;
+	up->state = QueueingR;
+	up->qpc = getcallerpc(&q);
+	unlock(&q->use);
+	sched();
 }
 
 /* same as rlock but punts if there are any writers waiting */
 int
-canrlock(RWlock *l)
+canrlock(RWlock *q)
 {
-	if (!canqlock(&l->x))
-		return 0;
-	lock(l);
-	l->readers++;
-	canqlock(&l->k);	/* block writers if we are the first reader */
-	unlock(l);
-	qunlock(&l->x);
-	return 1;
+	lock(&q->use);
+	rwstats.rlock++;
+	if(q->writer == 0 && q->head == nil){
+		/* no writer, go for it */
+		q->readers++;
+		unlock(&q->use);
+		return 1;
+	}
+	unlock(&q->use);
+	return 0;
 }
 
 void
-runlock(RWlock *l)
+runlock(RWlock *q)
 {
-	lock(l);
-	if(--l->readers == 0)	/* last reader out allows writers */
-		qunlock(&l->k);
-	unlock(l);
+	Proc *p;
+
+	lock(&q->use);
+	if (up != nil && up->blockinglock != nil)
+		print("runlock called with blockinglock %#p, from %#p\n",
+			up->blockinglock, getcallerpc(&q));
+	if (up != nil)
+			up->qpc = 0;
+	p = q->head;
+	if(--(q->readers) > 0 || p == nil){
+		unlock(&q->use);
+		return;
+	}
+	/* last reader out allows writers */
+	/* start waiting writer */
+	if(p->state != QueueingW)
+		panic("runlock");
+	q->head = p->qnext;
+	if(q->head == nil)
+		q->tail = nil;
+	q->writer = 1;
+	p->blockinglock = nil;
+	unlock(&q->use);
+	ready(p);
 }
 
 void
-wlock(RWlock *l)
+wlock(RWlock *q)
 {
-	qlock(&l->x);		/* wait here for writers and exclusion */
-	qlock(&l->k);		/* wait here for last reader */
+	Proc *p;
+
+	lock(&q->use);
+	rwstats.wlock++;
+	if(q->readers == 0 && q->writer == 0){
+		/* noone waiting, go for it */
+		q->wpc = getcallerpc(&q);
+		q->wproc = up;
+		q->writer = 1;
+		unlock(&q->use);
+		return;
+	}
+
+	/* wait */
+	rwstats.wlockq++;
+	p = q->tail;
+	if(up == nil)
+		panic("wlock");
+	if(p == nil)
+		q->head = up;
+	else
+		p->qnext = up;
+	q->tail = up;
+	up->qnext = nil;
+	up->blockinglock = q;
+	up->eql = nil;
+	up->state = QueueingW;
+	up->qpc = getcallerpc(&q);
+	unlock(&q->use);
+	sched();
 }
 
 void
-wunlock(RWlock *l)
+wunlock(RWlock *q)
 {
-	qunlock(&l->k);
-	qunlock(&l->x);
+	Proc *p;
+
+	lock(&q->use);
+	if (up != nil && up->blockinglock != nil)
+		print("runlock called with blockinglock %#p, from %#p\n",
+			up->blockinglock, getcallerpc(&q));
+	if (up != nil)
+			up->qpc = 0;
+	p = q->head;
+	if(p == nil){
+		q->writer = 0;
+		unlock(&q->use);
+		return;
+	}
+	if(p->state == QueueingW){
+		/* start waiting writer */
+		q->head = p->qnext;
+		if(q->head == nil)
+			q->tail = nil;
+		p->blockinglock = nil;
+		unlock(&q->use);
+		ready(p);
+		return;
+	}
+
+	if(p->state != QueueingR)
+		panic("wunlock");
+
+	/* waken waiting readers */
+	while(q->head != nil && q->head->state == QueueingR){
+		p = q->head;
+		q->head = p->qnext;
+		q->readers++;
+		p->blockinglock = nil;
+		ready(p);
+	}
+	if(q->head == nil)
+		q->tail = nil;
+	q->writer = 0;
+	unlock(&q->use);
 }
--- a/os/port/sysfile.c
+++ b/os/port/sysfile.c
@@ -5,53 +5,134 @@
 #include	"fns.h"
 #include	"../port/error.h"
 
-static int
-growfd(Fgrp *f, int fd)
+static void
+unlockfgrp(Fgrp *f)
 {
-	int n;
-	Chan **nfd, **ofd;
+	int ex;
 
-	if(fd < f->nfd)
+	ex = f->exceed;
+	f->exceed = 0;
+	unlock(f);
+	if(ex)
+		pprint("warning: process exceeds %d file descriptors\n", ex);
+}
+
+int
+growfd(Fgrp *f, int fd)	/* fd is always >= 0 */
+{
+	Chan **newfd, **oldfd;
+	uchar *newflag, *oldflag;
+	int nfd;
+
+	nfd = f->nfd;
+	if(fd < nfd)
 		return 0;
-	n = f->nfd+DELTAFD;
-	if(n > MAXNFD)
-		n = MAXNFD;
-	if(fd >= n)
+	if(fd >= nfd+DELTAFD)
+		return -1;	/* out of range */
+	/*
+	 * Unbounded allocation is unwise; besides, there are only 16 bits
+	 * of fid in 9P
+	 */
+	if(nfd >= MAXNFD){
+    Exhausted:
+		print("no free file descriptors\n");
 		return -1;
-	nfd = malloc(n*sizeof(Chan*));
-	if(nfd == nil)
+	}
+	oldfd = f->fd;
+	oldflag = f->flag;
+	newfd = malloc((nfd+DELTAFD)*sizeof(newfd[0]));
+	if(newfd == nil)
+		goto Exhausted;
+	memmove(newfd, oldfd, nfd*sizeof(newfd[0]));
+	newflag = malloc((nfd+DELTAFD)*sizeof(newflag[0]));
+	if(newflag == nil){
+		free(newfd);
+		goto Exhausted;
+	}
+	memmove(newflag, oldflag, nfd*sizeof(newflag[0]));
+	f->fd = newfd;
+	f->flag = newflag;
+	f->nfd = nfd+DELTAFD;
+	if(fd > f->maxfd){
+		if(fd/100 > f->maxfd/100)
+			f->exceed = (fd/100)*100;
+		f->maxfd = fd;
+	}
+	free(oldfd);
+	free(oldflag);
+	return 1;
+}
+
+/*
+ *  this assumes that the fgrp is locked
+ */
+int
+findfreefd(Fgrp *f, int start)
+{
+	int fd;
+
+	for(fd=start; fd<f->nfd; fd++)
+		if(f->fd[fd] == nil)
+			break;
+	if(fd >= f->nfd && growfd(f, fd) < 0)
 		return -1;
-	ofd = f->fd;
-	memmove(nfd, ofd, f->nfd*sizeof(Chan *));
-	f->fd = nfd;
-	f->nfd = n;
-	free(ofd);
-	return 0;
+	return fd;
 }
 
 int
-newfd(Chan *c)
+newfd(Chan *c, int mode)
 {
-	int i;
-	Fgrp *f = up->env->fgrp;
+	int fd, flag;
+	Fgrp *f;
 
+	f = up->env->fgrp;
 	lock(f);
-	for(i=f->minfd; i<f->nfd; i++)
-		if(f->fd[i] == 0)
-			break;
-	if(i >= f->nfd && growfd(f, i) < 0){
-		unlock(f);
-		exhausted("file descriptors");
+	fd = findfreefd(f, 0);
+	if(fd < 0){
+		unlockfgrp(f);
 		return -1;
 	}
-	f->minfd = i + 1;
-	if(i > f->maxfd)
-		f->maxfd = i;
-	f->fd[i] = c;
-	unlock(f);
-	return i;
+	if(fd > f->maxfd)
+		f->maxfd = fd;
+	f->fd[fd] = c;
+
+	/* per file-descriptor flags */
+	flag = 0;
+	if(mode & OCEXEC)
+		flag |= CCEXEC;
+	f->flag[fd] = flag;
+
+	unlockfgrp(f);
+	return fd;
 }
 
+int
+newfd2(int fd[2], Chan *c[2])
+{
+	Fgrp *f;
+
+	f = up->env->fgrp;
+	lock(f);
+	fd[0] = findfreefd(f, 0);
+	if(fd[0] < 0){
+		unlockfgrp(f);
+		return -1;
+	}
+	fd[1] = findfreefd(f, fd[0]+1);
+	if(fd[1] < 0){
+		unlockfgrp(f);
+		return -1;
+	}
+	if(fd[1] > f->maxfd)
+		f->maxfd = fd[1];
+	f->fd[fd[0]] = c[0];
+	f->fd[fd[1]] = c[1];
+	f->flag[fd[0]] = 0;
+	f->flag[fd[1]] = 0;
+	unlockfgrp(f);
+	return 0;
+}
+
 Chan*
 fdtochan(Fgrp *f, int fd, int mode, int chkmnt, int iref)
 {
@@ -205,7 +286,7 @@
 		cclose(c);
 		nexterror();
 	}
-	fd = newfd(c);
+	fd = newfd(c, mode);
 	if(fd < 0)
 		error(Enofd);
 	poperror();
@@ -231,7 +312,7 @@
 	if(fd != -1){
 		lock(f);
 		if(fd<0 || growfd(f, fd) < 0) {
-			unlock(f);
+			unlockfgrp(f);
 			cclose(c);
 			error(Ebadfd);
 		}
@@ -239,7 +320,8 @@
 			f->maxfd = fd;
 		oc = f->fd[fd];
 		f->fd[fd] = c;
-		unlock(f);
+		f->flag[fd] = 0;
+		unlockfgrp(f);
 		if(oc)
 			cclose(oc);
 	}else{
@@ -247,7 +329,7 @@
 			cclose(c);
 			nexterror();
 		}
-		fd = newfd(c);
+		fd = newfd(c, 0);
 		if(fd < 0)
 			error(Enofd);
 		poperror();
@@ -288,13 +370,13 @@
 		return nil;
 	c = fdtochan(up->env->fgrp, fd, -1, 0, 1);
 	s = nil;
-	if(c->name != nil){
-		s = malloc(c->name->len+1);
+	if(c->path != nil){
+		s = malloc(c->path->len+1);
 		if(s == nil){
 			cclose(c);
 			error(Enomem);
 		}
-		memmove(s, c->name->s, c->name->len+1);
+		memmove(s, c->path->s, c->path->len+1);
 		cclose(c);
 	}
 	poperror();
@@ -327,7 +409,7 @@
 		nexterror();
 	}
 
-	fd = newfd(ac);
+	fd = newfd(ac, 0);
 	if(fd < 0)
 		error(Enofd);
 	poperror();	/* ac */
@@ -398,12 +480,8 @@
 		error(Egreg);
 	c[0] = d->open(c[0], ORDWR);
 	c[1] = d->open(c[1], ORDWR);
-	fd[0] = newfd(c[0]);
-	if(fd[0] < 0)
+	if(newfd2(fd, c) < 0)
 		error(Enofd);
-	fd[1] = newfd(c[1]);
-	if(fd[1] < 0)
-		error(Enofd);
 	poperror();
 	return 0;
 }
@@ -556,7 +634,7 @@
 		cclose(c);
 		nexterror();
 	}
-	fd = newfd(c);
+	fd = newfd(c, mode);
 	if(fd < 0)
 		error(Enofd);
 	poperror();