Various additions and fixes.
diff --git a/src/lib9/9proc.h b/src/lib9/9proc.h
new file mode 100644
index 0000000..713b955
--- /dev/null
+++ b/src/lib9/9proc.h
@@ -0,0 +1,23 @@
+enum
+{
+	NPRIV = 16,
+	RENDHASH = 33,
+	PIDHASH = 33,
+};
+
+typedef struct Uproc Uproc;
+struct Uproc
+{
+	Uproc *next;
+	int pid;
+	int pipe[2];
+	int state;
+	void *priv[NPRIV];
+	ulong rendval;
+	ulong rendtag;
+	Uproc *rendhash;
+	p9jmp_buf notejb;
+};
+
+extern Uproc *_p9uproc(void);
+extern void _p9uprocdie(void);
diff --git a/src/lib9/Makefile b/src/lib9/Makefile
deleted file mode 100644
index 5411bd9..0000000
--- a/src/lib9/Makefile
+++ /dev/null
@@ -1,38 +0,0 @@
-PLAN9=../..
-include $(PLAN9)/src/Makehdr
-
-LIB=lib9.a
-
-OFILES=\
-	_exits.$O\
-	argv0.$O\
-	await.$O\
-	cleanname.$O\
-	dirstat.$O\
-	encodefmt.$O\
-	errstr.$O\
-	exits.$O\
-	ffork-$(SYSNAME).$O\
-	getcallerpc-$(OBJTYPE).$O\
-	getfields.$O\
-	lock.$O\
-	malloctag.$O\
-	mallocz.$O\
-	nrand.$O\
-	qlock.$O\
-	readn.$O\
-	rendez-$(SYSNAME).$O\
-	strecpy.$O\
-	sysfatal.$O\
-	tas-$(OBJTYPE).$O\
-	tokenize.$O\
-	u16.$O\
-	u32.$O\
-	u64.$O\
-	wait.$O\
-
-HFILES=\
-	$(PLAN9)/include/lib9.h\
-
-include $(PLAN9)/src/Makesyslib
-
diff --git a/src/lib9/_exits.c b/src/lib9/_exits.c
index 35ff4e6..9affe94 100644
--- a/src/lib9/_exits.c
+++ b/src/lib9/_exits.c
@@ -1,8 +1,12 @@
-#include <lib9.h>
+#include <u.h>
+#include <libc.h>
+#include "9proc.h"
 
 void
 _exits(char *s)
 {
+	_p9uprocdie();
+
 	if(s && *s)
 		_exit(1);
 	_exit(0);
diff --git a/src/lib9/_p9dialparse.c b/src/lib9/_p9dialparse.c
new file mode 100644
index 0000000..93c9842
--- /dev/null
+++ b/src/lib9/_p9dialparse.c
@@ -0,0 +1,151 @@
+#include <u.h>
+#include <libc.h>
+
+#include <netdb.h>
+#include <sys/un.h>
+
+static char *nets[] = { "tcp", "udp", nil };
+#define CLASS(p) ((*(uchar*)(p))>>6)
+
+static int
+parseip(char *host, u32int *pip)
+{
+	uchar addr[4];
+	int x, i;
+	char *p;
+
+	p = host;
+	for(i=0; i<4 && *p; i++){
+		x = strtoul(p, &p, 0);
+		if(x < 0 || x >= 256)
+			return -1;
+		if(*p != '.' && *p != 0)
+			return -1;
+		if(*p == '.')
+			p++;
+		addr[i] = x;
+	}
+
+	switch(CLASS(addr)){
+	case 0:
+	case 1:
+		if(i == 3){
+			addr[3] = addr[2];
+			addr[2] = addr[1];
+			addr[1] = 0;
+		}else if(i == 2){
+			addr[3] = addr[1];
+			addr[2] = 0;
+			addr[1] = 0;
+		}else if(i != 4)
+			return -1;
+		break;
+	case 2:
+		if(i == 3){
+			addr[3] = addr[2];
+			addr[2] = 0;
+		}else if(i != 4)
+			return -1;
+		break;
+	}
+	*pip = *(u32int*)addr;
+	return 0;
+}
+
+int
+_p9dialparse(char *addr, char **pnet, char **punix, u32int *phost, int *pport)
+{
+	char *net, *host, *port, *e;
+	int i;
+	struct servent *se;
+	struct hostent *he;
+	struct sockaddr_un *sun;
+
+	if(strncmp(addr, "/net/", 5) == 0)
+		addr += 5;
+
+	net = addr;
+	if((host = strchr(net, '!')) == nil){
+		werrstr("malformed address");
+		return -1;
+	}
+	*host++ = 0;
+	if((port = strchr(host, '!')) == nil){
+		if(strcmp(net, "unix")==0 || strcmp(net, "net")==0){
+		Unix:
+			if(strlen(host)+1 > sizeof sun->sun_path){
+				werrstr("unix socket name too long");
+				return -1;
+			}
+			*punix = host;
+			*pnet = "unix";
+			*phost = 0;
+			*pport = 0;
+			return 0;
+		}
+		werrstr("malformed address");
+		return -1;
+	}
+	*port++ = 0;
+
+	if(*host == 0){
+		werrstr("malformed address (empty host)");
+		return -1;
+	}
+	if(*port == 0){
+		werrstr("malformed address (empty port)");
+		return -1;
+	}
+
+	if(strcmp(net, "unix") == 0)
+		goto Unix;
+
+	if(strcmp(net, "tcp")!=0 && strcmp(net, "udp")!=0){
+		werrstr("bad network %s!%s!%s", net, host, port);
+		return -1;
+	}
+
+	/* translate host */
+	if(strcmp(host, "*") == 0)
+		*phost = 0;
+	else if(parseip(host, phost) == 0)
+		{}
+	else if((he = gethostbyname(host)) != nil)
+		*phost = *(u32int*)(he->h_addr);
+	else{
+		werrstr("unknown host %s", host);
+		return -1;
+	}
+
+	/* translate network and port; should return list rather than first */
+	if(strcmp(net, "net") == 0){
+		for(i=0; nets[i]; i++){
+			if((se = getservbyname(port, nets[i])) != nil){
+				*pnet = nets[i];
+				*pport = ntohs(se->s_port);
+				return 0;
+			}
+		}
+		werrstr("unknown service %s", port);
+		return -1;
+	}
+
+	if(strcmp(net, "tcp") != 0 && strcmp(net, "udp") != 0){
+		werrstr("unknown network %s", net);
+		return -1;
+	}
+
+	*pnet = net;
+	i = strtol(port, &e, 0);
+	if(*e == 0){
+		*pport = i;
+		return 0;
+	}
+
+	if((se = getservbyname(port, net)) != nil){
+		*pport = ntohs(se->s_port);
+		return 0;
+	}
+	werrstr("unknown service %s", port);
+	return -1;
+}
diff --git a/src/lib9/_p9dir.c b/src/lib9/_p9dir.c
new file mode 100644
index 0000000..459bd15
--- /dev/null
+++ b/src/lib9/_p9dir.c
@@ -0,0 +1,121 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+#include <sys/stat.h>
+#include <sys/disklabel.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <grp.h>
+
+int
+_p9dir(struct stat *st, char *name, Dir *d, char **str, char *estr)
+{
+	char *s;
+	char tmp[20];
+	struct group *g;
+	struct passwd *p;
+	int sz;
+
+	sz = 0;
+	if(d)
+		memset(d, 0, sizeof *d);
+
+	/* name */
+	s = strrchr(name, '/');
+	if(s)
+		s++;
+	if(!s || !*s)
+		s = name;
+	if(*s == '/')
+		s++;
+	if(*s == 0)
+		s = "/";
+	if(d){
+		if(*str + strlen(s)+1 > estr)
+			d->name = "oops";
+		else{
+			strcpy(*str, s);
+			d->name = *str;
+			*str += strlen(*str)+1;
+		}
+	}
+	sz += strlen(s)+1;
+
+	/* user */
+	p = getpwuid(st->st_uid);
+	if(p == nil){
+		snprint(tmp, sizeof tmp, "%d", (int)st->st_uid);
+		s = tmp;
+	}else
+		s = p->pw_name;
+	sz += strlen(s)+1;
+	if(d){
+		if(*str+strlen(s)+1 > estr)
+			d->uid = "oops";	
+		else{
+			strcpy(*str, s);
+			d->uid = *str;
+			*str += strlen(*str)+1;
+		}
+	}
+
+	/* group */
+	g = getgrgid(st->st_gid);
+	if(g == nil){
+		snprint(tmp, sizeof tmp, "%d", (int)st->st_gid);
+		s = tmp;
+	}else
+		s = g->gr_name;
+	sz += strlen(s)+1;
+	if(d){
+		if(*str + strlen(s)+1 > estr)
+			d->gid = "oops";	
+		else{
+			strcpy(*str, s);
+			d->gid = *str;
+			*str += strlen(*str)+1;
+		}
+	}
+
+	if(d){
+		d->type = 'M';
+
+		d->muid = "";
+		d->qid.path = ((uvlong)st->st_dev<<32) | st->st_ino;
+		d->qid.vers = st->st_gen;
+		d->mode = st->st_mode&0777;
+		d->atime = st->st_atime;
+		d->mtime = st->st_mtime;
+		d->length = st->st_size;
+
+		if(S_ISDIR(st->st_mode)){
+			d->length = 0;
+			d->mode |= DMDIR;
+			d->qid.type = QTDIR;
+		}
+
+		/* fetch real size for disks */
+		if(S_ISCHR(st->st_mode)){
+			int fd, n;
+			struct disklabel lab;
+
+			if((fd = open(name, O_RDONLY)) < 0)
+				goto nosize;
+			if(ioctl(fd, DIOCGDINFO, &lab) < 0)
+				goto nosize;
+			n = minor(st->st_rdev)&7;
+			if(n >= lab.d_npartitions)
+				goto nosize;
+
+			d->length = (vlong)(lab.d_partitions[n].p_size) * lab.d_secsize;
+
+		nosize:
+			if(fd >= 0)
+				close(fd);
+		}
+	}
+
+	return sz;
+}
+
diff --git a/src/lib9/_p9proc.c b/src/lib9/_p9proc.c
new file mode 100644
index 0000000..6e4010c
--- /dev/null
+++ b/src/lib9/_p9proc.c
@@ -0,0 +1,73 @@
+#include <u.h>
+#include <libc.h>
+#include "9proc.h"
+
+static Lock uproclock;
+static Uproc *phash[PIDHASH];
+
+Uproc*
+_p9uproc(void)
+{
+	/* for now, assume getpid is fast or cached */
+	int pid;
+	Uproc *up;
+
+	pid = getpid();
+again:
+if(0)print("find %d\n", pid);
+	lock(&uproclock);
+	for(up=phash[pid%PIDHASH]; up; up=up->next){
+		if(up->pid == pid){
+if(0)print("found %d\n", pid);
+			unlock(&uproclock);
+			return up;
+		}
+	}
+
+	up = mallocz(sizeof(Uproc), 1);
+	if(up == nil){
+if(0)print("again %d\n", pid);
+		unlock(&uproclock);
+		sleep(1000);
+		goto again;
+	}
+
+againpipe:
+	if(pipe(up->pipe) < 0){
+if(0)print("againpipe %d\n", pid);
+		sleep(1000);
+		goto againpipe;
+	}
+
+	up->pid = pid;
+	up->next = phash[pid%PIDHASH];
+	phash[pid%PIDHASH] = up;
+if(0)print("link %d\n", pid);
+	unlock(&uproclock);
+	return up;
+}
+
+void
+_p9uprocdie(void)
+{
+	Uproc **l, *up;
+	int pid;
+
+	pid = getpid();
+if(0)print("die %d\n", pid);
+	lock(&uproclock);
+	for(l=&phash[pid%33]; *l; l=&(*l)->next){
+		if((*l)->pid == pid){
+			up = *l;
+			*l = up->next;
+if(0)print("died %d\n", pid);
+			unlock(&uproclock);
+			close(up->pipe[0]);
+			close(up->pipe[1]);
+			free(up);
+			return;
+		}
+	}
+if(0)print("not started %d\n", pid);
+	unlock(&uproclock);
+}
diff --git a/src/lib9/announce.c b/src/lib9/announce.c
new file mode 100644
index 0000000..dc8cbbb
--- /dev/null
+++ b/src/lib9/announce.c
@@ -0,0 +1,137 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+
+extern int _p9dialparse(char*, char**, char**, u32int*, int*);
+
+static int
+getfd(char *dir)
+{
+	int fd;
+
+	if(strncmp(dir, "/dev/fd/", 8) != 0)
+		return -1;
+	fd = strtol(dir+8, &dir, 0);
+	if(*dir != 0)
+		return -1;
+	return fd;
+}
+
+static void
+putfd(char *dir, int fd)
+{
+	snprint(dir, NETPATHLEN, "/dev/fd/%d", fd);
+}
+
+#undef unix
+
+int
+p9announce(char *addr, char *dir)
+{
+	int proto;
+	char *buf, *unix;
+	char *net;
+	u32int host;
+	int port, s;
+	int n, sn;
+	struct sockaddr_in sa;
+	struct sockaddr_un sun;
+
+	buf = strdup(addr);
+	if(buf == nil)
+		return -1;
+
+	if(_p9dialparse(buf, &net, &unix, &host, &port) < 0){
+		free(buf);
+		return -1;
+	}
+	if(strcmp(net, "tcp") == 0)
+		proto = SOCK_STREAM;
+	else if(strcmp(net, "udp") == 0)
+		proto = SOCK_DGRAM;
+	else if(strcmp(net, "unix") == 0)
+		goto Unix;
+	else{
+		werrstr("can only handle tcp, udp, and unix: not %s", net);
+		free(buf);
+		return -1;
+	}
+	free(buf);
+
+	memset(&sa, 0, sizeof sa);
+	memmove(&sa.sin_addr, &host, 4);
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(port);
+	if((s = socket(AF_INET, proto, 0)) < 0)
+		return -1;
+	sn = sizeof n;
+	if(port && getsockopt(s, SOL_SOCKET, SO_TYPE, (char*)&n, &sn) >= 0
+	&& n == SOCK_STREAM){
+		n = 1;
+		setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&n, sizeof n);
+	}
+	if(bind(s, (struct sockaddr*)&sa, sizeof sa) < 0){
+		close(s);
+		return -1;
+	}
+	if(proto == SOCK_STREAM){
+		listen(s, 8);
+		putfd(dir, s);
+print("announce dir: %s\n", dir);
+	}
+	return s;
+
+Unix:
+	memset(&sun, 0, sizeof sun);
+	sun.sun_family = AF_UNIX;
+	sun.sun_len = sizeof sun;
+	strcpy(sun.sun_path, unix);
+	if((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+		return -1;
+	sn = sizeof sun;
+	if(bind(s, (struct sockaddr*)&sun, sizeof sun) < 0){
+		close(s);
+		return -1;
+	}
+	listen(s, 8);
+	putfd(dir, s);
+	return s;
+}
+
+int
+p9listen(char *dir, char *newdir)
+{
+	int fd;
+
+	if((fd = getfd(dir)) < 0){
+		werrstr("bad 'directory' in listen: %s", dir);
+		return -1;
+	}
+
+print("accept %d", fd);
+	if((fd = accept(fd, nil, nil)) < 0)
+		return -1;
+print(" -> %d\n", fd);
+
+	putfd(newdir, fd);
+print("listen dir: %s\n", newdir);
+	return fd;
+}
+
+int
+p9accept(int cfd, char *dir)
+{
+	int fd;
+
+	if((fd = getfd(dir)) < 0){
+		werrstr("bad 'directory' in accept");
+		return -1;
+	}
+	/* need to dup because the listen fd will be closed */
+	return dup(fd);
+}
+
diff --git a/src/lib9/atexit.c b/src/lib9/atexit.c
new file mode 100644
index 0000000..b1d8b97
--- /dev/null
+++ b/src/lib9/atexit.c
@@ -0,0 +1,54 @@
+#include <u.h>
+#include <libc.h>
+
+#define	NEXIT	33
+
+static Lock onexlock;
+static struct
+{
+	void	(*f)(void);
+	int	pid;
+}onex[NEXIT];
+
+int
+atexit(void (*f)(void))
+{
+	int i;
+
+	lock(&onexlock);
+	for(i=0; i<NEXIT; i++)
+		if(onex[i].f == 0) {
+			onex[i].pid = getpid();
+			onex[i].f = f;
+			unlock(&onexlock);
+			return 1;
+		}
+	unlock(&onexlock);
+	return 0;
+}
+
+void
+atexitdont(void (*f)(void))
+{
+	int i, pid;
+
+	pid = getpid();
+	for(i=0; i<NEXIT; i++)
+		if(onex[i].f == f && onex[i].pid == pid)
+			onex[i].f = 0;
+}
+
+void
+exits(char *s)
+{
+	int i, pid;
+	void (*f)(void);
+
+	pid = getpid();
+	for(i = NEXIT-1; i >= 0; i--)
+		if((f = onex[i].f) && pid == onex[i].pid) {
+			onex[i].f = 0;
+			(*f)();
+		}
+	_exits(s);
+}
diff --git a/src/lib9/atnotify.c b/src/lib9/atnotify.c
new file mode 100644
index 0000000..60e8ad0
--- /dev/null
+++ b/src/lib9/atnotify.c
@@ -0,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+
+#define	NFN	33
+static	int	(*onnot[NFN])(void*, char*);
+static	Lock	onnotlock;
+
+static
+void
+notifier(void *v, char *s)
+{
+	int i;
+
+	for(i=0; i<NFN; i++)
+		if(onnot[i] && ((*onnot[i])(v, s))){
+			noted(NCONT);
+			return;
+		}
+	noted(NDFLT);
+}
+
+int
+atnotify(int (*f)(void*, char*), int in)
+{
+	int i, n, ret;
+	static int init;
+
+	if(!init){
+		notify(notifier);
+		init = 1;		/* assign = */
+	}
+	ret = 0;
+	lock(&onnotlock);
+	if(in){
+		for(i=0; i<NFN; i++)
+			if(onnot[i] == 0) {
+				onnot[i] = f;
+				ret = 1;
+				break;
+			}
+	}else{
+		n = 0;
+		for(i=0; i<NFN; i++)
+			if(onnot[i]){
+				if(ret==0 && onnot[i]==f){
+					onnot[i] = 0;
+					ret = 1;
+				}else
+					n++;
+			}
+		if(n == 0){
+			init = 0;
+			notify(0);
+		}
+	}
+	unlock(&onnotlock);
+	return ret;
+}
diff --git a/src/lib9/await.c b/src/lib9/await.c
index 49160e7..67e99b8 100644
--- a/src/lib9/await.c
+++ b/src/lib9/await.c
@@ -1,12 +1,12 @@
+#define NOPLAN9DEFINES
+#include <u.h>
+#include <libc.h>
+
 #include <signal.h>
 #include <sys/types.h>
-#include <sys/time.h>
 #include <sys/resource.h>
 #include <sys/wait.h>
 #include <sys/time.h>
-#include <string.h>
-#include <errno.h>
-#include <lib9.h>
 
 static struct {
 	int sig;
@@ -18,9 +18,7 @@
 	SIGILL,		"sys: trap: illegal instruction",
 	SIGTRAP,		"sys: trace trap",
 	SIGABRT,		"sys: abort",
-#ifdef SIGEMT
 	SIGEMT,		"sys: emulate instruction executed",
-#endif
 	SIGFPE,		"sys: fp: trap",
 	SIGKILL,		"sys: kill",
 	SIGBUS,		"sys: bus error",
@@ -40,14 +38,12 @@
 	SIGVTALRM,	"sys: virtual time alarm",
 	SIGPROF,		"sys: profiling timer alarm",
 	SIGWINCH,	"sys: window size change",
-#ifdef SIGINFO
 	SIGINFO,		"sys: status request",
-#endif
 	SIGUSR1,		"sys: usr1",
 	SIGUSR2,		"sys: usr2",
 };
 	
-static char*
+char*
 _p9sigstr(int sig, char *tmp)
 {
 	int i;
@@ -59,8 +55,7 @@
 	return tmp;
 }
 
-/*
-static int
+int
 _p9strsig(char *s)
 {
 	int i;
@@ -70,7 +65,6 @@
 			return tab[i].sig;
 	return 0;
 }
-*/
 
 int
 await(char *str, int n)
@@ -89,16 +83,16 @@
 		if(WIFEXITED(status)){
 			status = WEXITSTATUS(status);
 			if(status)
-				snprint(buf, sizeof buf, "%d %lu %lu %lu %d", pid, u, s, u+s, status);
+				snprint(buf, sizeof buf, "%d %lud %lud %lud %d", pid, u, s, u+s, status);
 			else
-				snprint(buf, sizeof buf, "%d %lu %lu %lu ''", pid, u, s, u+s);
+				snprint(buf, sizeof buf, "%d %lud %lud %lud ''", pid, u, s, u+s, status);
 			strecpy(str, str+n, buf);
 			return strlen(str);
 		}
 		if(WIFSIGNALED(status)){
 			cd = WCOREDUMP(status);
 			USED(cd);
-			snprint(buf, sizeof buf, "%d %lu %lu %lu '%s'", pid, u, s, u+s, _p9sigstr(WTERMSIG(status), tmp));
+			snprint(buf, sizeof buf, "%d %lud %lud %lud '%s'", pid, u, s, u+s, _p9sigstr(WTERMSIG(status), tmp));
 			strecpy(str, str+n, buf);
 			return strlen(str);
 		}
diff --git a/src/lib9/cistrcmp.c b/src/lib9/cistrcmp.c
new file mode 100644
index 0000000..a545615
--- /dev/null
+++ b/src/lib9/cistrcmp.c
@@ -0,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+
+int
+cistrcmp(char *s1, char *s2)
+{
+	int c1, c2;
+
+	while(*s1){
+		c1 = *(uchar*)s1++;
+		c2 = *(uchar*)s2++;
+
+		if(c1 == c2)
+			continue;
+
+		if(c1 >= 'A' && c1 <= 'Z')
+			c1 -= 'A' - 'a';
+
+		if(c2 >= 'A' && c2 <= 'Z')
+			c2 -= 'A' - 'a';
+
+		if(c1 != c2)
+			return c1 - c2;
+	}
+	return -*s2;
+}
diff --git a/src/lib9/cistrncmp.c b/src/lib9/cistrncmp.c
new file mode 100644
index 0000000..8f24d41
--- /dev/null
+++ b/src/lib9/cistrncmp.c
@@ -0,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+
+int
+cistrncmp(char *s1, char *s2, int n)
+{
+	int c1, c2;
+
+	while(*s1 && n-- > 0){
+		c1 = *(uchar*)s1++;
+		c2 = *(uchar*)s2++;
+
+		if(c1 == c2)
+			continue;
+
+		if(c1 >= 'A' && c1 <= 'Z')
+			c1 -= 'A' - 'a';
+
+		if(c2 >= 'A' && c2 <= 'Z')
+			c2 -= 'A' - 'a';
+
+		if(c1 != c2)
+			return c1 - c2;
+	}
+	if(n <= 0)
+		return 0;
+	return -*s2;
+}
diff --git a/src/lib9/cistrstr.c b/src/lib9/cistrstr.c
new file mode 100644
index 0000000..0a11322
--- /dev/null
+++ b/src/lib9/cistrstr.c
@@ -0,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+
+char*
+cistrstr(char *s, char *sub)
+{
+	int c, csub, n;
+
+	csub = *sub;
+	if(csub == '\0')
+		return s;
+	if(csub >= 'A' && csub <= 'Z')
+		csub -= 'A' - 'a';
+	sub++;
+	n = strlen(sub);
+	for(; c = *s; s++){
+		if(c >= 'A' && c <= 'Z')
+			c -= 'A' - 'a';
+		if(c == csub && cistrncmp(s+1, sub, n) == 0)
+			return s;
+	}
+	return nil;
+}
diff --git a/src/lib9/create.c b/src/lib9/create.c
new file mode 100644
index 0000000..abef0c3
--- /dev/null
+++ b/src/lib9/create.c
@@ -0,0 +1,8 @@
+#include <u.h>
+#include <libc.h>
+
+int
+create(char *path, int mode, ulong perm)
+{
+	return open(path, mode|O_CREAT|O_TRUNC, perm);
+}
diff --git a/src/lib9/ctime.c b/src/lib9/ctime.c
new file mode 100644
index 0000000..e9d971b
--- /dev/null
+++ b/src/lib9/ctime.c
@@ -0,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+
+static
+void
+ct_numb(char *cp, int n)
+{
+
+	cp[0] = ' ';
+	if(n >= 10)
+		cp[0] = (n/10)%10 + '0';
+	cp[1] = n%10 + '0';
+}
+
+char*
+asctime(Tm *t)
+{
+	char *ncp;
+	static char cbuf[30];
+
+	strcpy(cbuf, "Thu Jan 01 00:00:00 GMT 1970\n");
+	ncp = &"SunMonTueWedThuFriSat"[t->wday*3];
+	cbuf[0] = *ncp++;
+	cbuf[1] = *ncp++;
+	cbuf[2] = *ncp;
+	ncp = &"JanFebMarAprMayJunJulAugSepOctNovDec"[t->mon*3];
+	cbuf[4] = *ncp++;
+	cbuf[5] = *ncp++;
+	cbuf[6] = *ncp;
+	ct_numb(cbuf+8, t->mday);
+	ct_numb(cbuf+11, t->hour+100);
+	ct_numb(cbuf+14, t->min+100);
+	ct_numb(cbuf+17, t->sec+100);
+	ncp = t->zone;
+	cbuf[20] = *ncp++;
+	cbuf[21] = *ncp++;
+	cbuf[22] = *ncp;
+	if(t->year >= 100) {
+		cbuf[24] = '2';
+		cbuf[25] = '0';
+	}
+	ct_numb(cbuf+26, t->year+100);
+	return cbuf;
+}
+
+char*
+ctime(long t)
+{
+	return asctime(localtime(t));
+}
+
diff --git a/src/lib9/date.c b/src/lib9/date.c
new file mode 100644
index 0000000..2e84fd0
--- /dev/null
+++ b/src/lib9/date.c
@@ -0,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+
+#undef gmtime
+#undef localtime
+#undef asctime
+#undef ctime
+#undef cputime
+#undef times
+#undef tm2sec
+#undef nsec
+
+#include <time.h>
+
+static Tm bigtm;
+
+static void
+tm2Tm(struct tm *tm, Tm *bigtm)
+{
+	memset(bigtm, 0, sizeof *bigtm);
+	bigtm->sec = tm->tm_sec;
+	bigtm->min = tm->tm_min;
+	bigtm->hour = tm->tm_hour;
+	bigtm->mday = tm->tm_mday;
+	bigtm->mon = tm->tm_mon;
+	bigtm->year = tm->tm_year;
+	bigtm->wday = tm->tm_wday;
+	strecpy(bigtm->zone, bigtm->zone+4, tm->tm_zone);
+	bigtm->tzoff = tm->tm_gmtoff;
+}
+
+static void
+Tm2tm(Tm *bigtm, struct tm *tm)
+{
+	memset(tm, 0, sizeof *tm);
+	tm->tm_sec = bigtm->sec;
+	tm->tm_min = bigtm->min;
+	tm->tm_hour = bigtm->hour;
+	tm->tm_mday = bigtm->mday;
+	tm->tm_mon = bigtm->mon;
+	tm->tm_year = bigtm->year;
+	tm->tm_wday = bigtm->wday;
+	tm->tm_zone = bigtm->zone;
+	tm->tm_gmtoff = bigtm->tzoff;
+}
+
+Tm*
+p9gmtime(long t)
+{
+	struct tm tm;
+
+	tm = *gmtime(&t);
+	tm2Tm(&tm, &bigtm);
+	return &bigtm;
+}
+
+Tm*
+p9localtime(long t)
+{
+	struct tm tm;
+
+	tm = *localtime(&t);
+	tm2Tm(&tm, &bigtm);
+	return &bigtm;
+}
+
+long
+p9tm2sec(Tm *bigtm)
+{
+	struct tm tm;
+
+	Tm2tm(bigtm, &tm);
+	if(strcmp(bigtm->zone, "GMT") == 0 || strcmp(bigtm->zone, "UCT") == 0)
+		return timegm(&tm);
+	return mktime(&tm);	/* local time zone */
+}
+
diff --git a/src/lib9/dial.c b/src/lib9/dial.c
new file mode 100644
index 0000000..1d961b4
--- /dev/null
+++ b/src/lib9/dial.c
@@ -0,0 +1,92 @@
+#include <u.h>
+#include <libc.h>
+
+#undef	accept
+#undef	announce
+#undef	dial
+#undef	setnetmtpt
+#undef	hangup
+#undef	listen
+#undef	netmkaddr
+#undef	reject
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <netdb.h>
+
+
+extern int _p9dialparse(char*, char**, char**, u32int*, int*);
+#undef unix
+
+int
+p9dial(char *addr, char *dummy1, char *dummy2, int *dummy3)
+{
+	char *buf;
+	char *net, *unix;
+	u32int host;
+	int port;
+	int proto;
+	struct sockaddr_in sa;	
+	struct sockaddr_un su;
+	int s;
+
+	if(dummy1 || dummy2 || dummy3){
+		werrstr("cannot handle extra arguments in dial");
+		return -1;
+	}
+
+	buf = strdup(addr);
+	if(buf == nil)
+		return -1;
+
+	if(_p9dialparse(buf, &net, &unix, &host, &port) < 0){
+		free(buf);
+		return -1;
+	}
+
+	if(strcmp(net, "tcp") == 0)
+		proto = SOCK_STREAM;
+	else if(strcmp(net, "udp") == 0)
+		proto = SOCK_DGRAM;
+	else if(strcmp(net, "unix") == 0)
+		goto Unix;
+	else{
+		werrstr("can only handle tcp, udp, and unix: not %s", net);
+		free(buf);
+		return -1;
+	}
+	free(buf);
+
+	memset(&sa, 0, sizeof sa);
+	memmove(&sa.sin_addr, &host, 4);
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(port);
+	if((s = socket(AF_INET, proto, 0)) < 0)
+		return -1;
+	if(connect(s, (struct sockaddr*)&sa, sizeof sa) < 0){
+		close(s);
+		return -1;
+	}
+	return s;
+
+Unix:
+	memset(&su, 0, sizeof su);
+	su.sun_len = sizeof su;
+	su.sun_family = AF_UNIX;
+	if(strlen(unix)+1 > sizeof su.sun_path){
+		werrstr("unix socket name too long");
+		free(buf);
+		return -1;
+	}
+	strcpy(su.sun_path, unix);
+	free(buf);
+	if((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+		return -1;
+	if(connect(s, (struct sockaddr*)&su, sizeof su) < 0){
+		close(s);
+		return -1;
+	}
+	return s;
+}
+
diff --git a/src/lib9/dirfstat.c b/src/lib9/dirfstat.c
new file mode 100644
index 0000000..e617bc2
--- /dev/null
+++ b/src/lib9/dirfstat.c
@@ -0,0 +1,29 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+#include <sys/stat.h>
+
+extern int _p9dir(struct stat*, char*, Dir*, char**, char*);
+
+Dir*
+dirfstat(int fd)
+{
+	struct stat st;
+	int nstr;
+	Dir *d;
+	char *str, tmp[100];
+
+	if(fstat(fd, &st) < 0)
+		return nil;
+
+	snprint(tmp, sizeof tmp, "/dev/fd/%d", fd);
+	nstr = _p9dir(&st, tmp, nil, nil, nil);
+	d = mallocz(sizeof(Dir)+nstr, 1);
+	if(d == nil)
+		return nil;
+	str = (char*)&d[1];
+	_p9dir(&st, tmp, d, &str, str+nstr);
+	return d;
+}
+
diff --git a/src/lib9/dirfwstat.c b/src/lib9/dirfwstat.c
new file mode 100644
index 0000000..7f6c54a
--- /dev/null
+++ b/src/lib9/dirfwstat.c
@@ -0,0 +1,21 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+#include <sys/time.h>
+
+int
+dirfwstat(int fd, Dir *dir)
+{
+	struct timeval tv[2];
+
+	/* BUG handle more */
+	if(dir->mtime == ~0ULL)
+		return 0;
+
+	tv[0].tv_sec = dir->mtime;
+	tv[0].tv_usec = 0;
+	tv[1].tv_sec = dir->mtime;
+	tv[1].tv_usec = 0;
+	return futimes(fd, tv);
+}
diff --git a/src/lib9/dirmodefmt.c b/src/lib9/dirmodefmt.c
new file mode 100644
index 0000000..8d79635
--- /dev/null
+++ b/src/lib9/dirmodefmt.c
@@ -0,0 +1,47 @@
+#include <u.h>
+#include <libc.h>
+
+static char *modes[] =
+{
+	"---",
+	"--x",
+	"-w-",
+	"-wx",
+	"r--",
+	"r-x",
+	"rw-",
+	"rwx",
+};
+
+static void
+rwx(long m, char *s)
+{
+	strncpy(s, modes[m], 3);
+}
+
+int
+dirmodefmt(Fmt *f)
+{
+	static char buf[16];
+	ulong m;
+
+	m = va_arg(f->args, ulong);
+
+	if(m & DMDIR)
+		buf[0]='d';
+	else if(m & DMAPPEND)
+		buf[0]='a';
+	else if(m & DMAUTH)
+		buf[0]='A';
+	else
+		buf[0]='-';
+	if(m & DMEXCL)
+		buf[1]='l';
+	else
+		buf[1]='-';
+	rwx((m>>6)&7, buf+2);
+	rwx((m>>3)&7, buf+5);
+	rwx((m>>0)&7, buf+8);
+	buf[11] = 0;
+	return fmtstrcpy(f, buf);
+}
diff --git a/src/lib9/dirread.c b/src/lib9/dirread.c
new file mode 100644
index 0000000..f4610f6
--- /dev/null
+++ b/src/lib9/dirread.c
@@ -0,0 +1,155 @@
+#include <u.h>
+#include <libc.h>
+
+#undef asctime
+#undef ctime
+#undef gmtime
+#undef localtime
+
+#include <sys/stat.h>
+#include <dirent.h>
+
+extern int _p9dir(struct stat*, char*, Dir*, char**, char*);
+
+static int
+countde(char *p, int n)
+{
+	char *e;
+	int m;
+	struct dirent *de;
+
+	e = p+n;
+	m = 0;
+	while(p < e){
+		de = (struct dirent*)p;
+		if(de->d_reclen <= 4+2+2+1 || p+de->d_reclen > e)
+			break;
+		if(de->d_namlen == 1 && de->d_name[0]=='.')
+			de->d_namlen = 0;
+		else if(de->d_namlen == 2 && de->d_name[0]=='.' && de->d_name[1]=='.')
+			de->d_namlen = 0;
+		else
+			m++;
+		p += de->d_reclen;
+	}
+	return m;
+}
+
+static int
+dirpackage(int fd, char *buf, int n, Dir **dp)
+{
+	int oldwd;
+	char *p, *str, *estr;
+	int i, nstr, m;
+	struct dirent *de;
+	struct stat st;
+	Dir *d;
+
+	n = countde(buf, n);
+	if(n <= 0)
+		return n;
+
+	if((oldwd = open(".", O_RDONLY)) < 0)
+		return -1;
+	if(fchdir(fd) < 0)
+		return -1;
+		
+	p = buf;
+	nstr = 0;
+	for(i=0; i<n; i++){
+		de = (struct dirent*)p;
+		if(stat(de->d_name, &st) < 0)
+			de->d_namlen = 0;
+		else
+			nstr += _p9dir(&st, de->d_name, nil, nil, nil);
+		p += de->d_reclen;
+	}
+
+	d = malloc(sizeof(Dir)*n+nstr);
+	if(d == nil){
+		fchdir(oldwd);
+		close(oldwd);
+		return -1;
+	}
+	str = (char*)&d[n];
+	estr = str+nstr;
+
+	p = buf;
+	m = 0;
+	for(i=0; i<n; i++){
+		de = (struct dirent*)p;
+		if(de->d_namlen != 0 && stat(de->d_name, &st) >= 0)
+			_p9dir(&st, de->d_name, &d[m++], &str, estr);
+		p += de->d_reclen;
+	}
+
+	fchdir(oldwd);
+	close(oldwd);
+	*dp = d;
+	return m;
+}
+
+long
+dirread(int fd, Dir **dp)
+{
+	char *buf;
+	struct stat st;
+	int n;
+
+	*dp = 0;
+
+	if(fstat(fd, &st) < 0)
+		return -1;
+
+	if(st.st_blksize < 8192)
+		st.st_blksize = 8192;
+
+	buf = malloc(st.st_blksize);
+	if(buf == nil)
+		return -1;
+
+	n = getdents(fd, buf, st.st_blksize);
+	if(n < 0){
+		free(buf);
+		return -1;
+	}
+	n = dirpackage(fd, buf, n, dp);
+	free(buf);
+	return n;
+}
+
+
+long
+dirreadall(int fd, Dir **d)
+{
+	uchar *buf, *nbuf;
+	long n, ts;
+	struct stat st;
+
+	if(fstat(fd, &st) < 0)
+		return -1;
+
+	if(st.st_blksize < 8192)
+		st.st_blksize = 8192;
+
+	buf = nil;
+	ts = 0;
+	for(;;){
+		nbuf = realloc(buf, ts+st.st_blksize);
+		if(nbuf == nil){
+			free(buf);
+			return -1;
+		}
+		buf = nbuf;
+		n = getdents(fd, buf+ts, st.st_blksize);
+		if(n <= 0)
+			break;
+		ts += n;
+	}
+	if(ts >= 0)
+		ts = dirpackage(fd, buf, ts, d);
+	free(buf);
+	if(ts == 0 && n < 0)
+		return -1;
+	return ts;
+}
diff --git a/src/lib9/dirstat.c b/src/lib9/dirstat.c
index fb9cd0a..253a905 100644
--- a/src/lib9/dirstat.c
+++ b/src/lib9/dirstat.c
@@ -1,83 +1,28 @@
-#include "u.h"
-#include "libc.h"
-#include <sys/types.h>
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
 #include <sys/stat.h>
-#include <pwd.h>
-#include <grp.h>
 
-static void
-statconv(Dir *dir, struct stat *s)
+extern int _p9dir(struct stat*, char*, Dir*, char**, char*);
+
+Dir*
+dirstat(char *file)
 {
-	struct passwd *p;
-	struct group *g;
-	ulong q;
+	struct stat st;
+	int nstr;
+	Dir *d;
+	char *str;
 
-	p = getpwuid(s->st_uid);
-	if (p)
-		strncpy(dir->uid, p->pw_name, NAMELEN);
-	g = getgrgid(s->st_gid);
-	if (g)
-		strncpy(dir->gid, g->gr_name, NAMELEN);
-	q = 0;
-	if(S_ISDIR(s->st_mode))
-		q = CHDIR;
-	q |= s->st_ino & 0x00FFFFFFUL;
-	dir->qid.path = q;
-	dir->qid.vers = s->st_mtime;
-	dir->mode = (dir->qid.path&CHDIR)|(s->st_mode&0777);
-	dir->atime = s->st_atime;
-	dir->mtime = s->st_mtime;
-	dir->length = s->st_size;
-	dir->dev = s->st_dev;
-	dir->type = 'M';
-	if(S_ISFIFO(s->st_mode))
-		dir->type = '|';
+	if(stat(file, &st) < 0)
+		return nil;
+
+	nstr = _p9dir(&st, file, nil, nil, nil);
+	d = mallocz(sizeof(Dir)+nstr, 1);
+	if(d == nil)
+		return nil;
+	str = (char*)&d[1];
+	_p9dir(&st, file, d, &str, str+nstr);
+	return d;
 }
 
-int
-dirfstat(int fd, Dir *d)
-{
-	struct stat sbuf;
-
-	if(fstat(fd, &sbuf) < 0)
-		return -1;
-	statconv(d, &sbuf);
-	return 0;
-}
-
-static char *
-lelem(char *path)
-{	
-	char *pr;
-
-	pr = utfrrune(path, '/');
-	if(pr)
-		pr++;
-	else
-		pr = path;
-	return pr;
-}
-
-int
-dirstat(char *f, Dir *d)
-{
-	struct stat sbuf;
-
-	if(stat(f, &sbuf) < 0)
-		return -1;
-	statconv(d, &sbuf);
-	strncpy(d->name, lelem(f), NAMELEN);
-	return 0;
-}
-
-int
-dirfwstat(int fd, Dir *d)
-{
-	return -1;
-}
-
-int
-dirwstat(char *name, Dir *d)
-{
-	return -1;
-}
diff --git a/src/lib9/dirwstat.c b/src/lib9/dirwstat.c
new file mode 100644
index 0000000..573dd37
--- /dev/null
+++ b/src/lib9/dirwstat.c
@@ -0,0 +1,21 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+#include <sys/time.h>
+
+int
+dirwstat(char *file, Dir *dir)
+{
+	struct timeval tv[2];
+
+	/* BUG handle more */
+	if(dir->mtime == ~0ULL)
+		return 0;
+
+	tv[0].tv_sec = dir->mtime;
+	tv[0].tv_usec = 0;
+	tv[1].tv_sec = dir->mtime;
+	tv[1].tv_usec = 0;
+	return utimes(file, tv);
+}
diff --git a/src/lib9/dup.c b/src/lib9/dup.c
new file mode 100644
index 0000000..feec1b7
--- /dev/null
+++ b/src/lib9/dup.c
@@ -0,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+
+#undef dup
+
+int
+p9dup(int old, int new)
+{
+	if(new == -1)
+		return dup(old);
+	return dup2(old, new);
+}
diff --git a/src/lib9/errstr.c b/src/lib9/errstr.c
index 504ec6e..0c6ab31 100644
--- a/src/lib9/errstr.c
+++ b/src/lib9/errstr.c
@@ -78,3 +78,16 @@
 	va_end(arg);
 	errstr(buf, ERRMAX);
 }
+
+char*
+gerrstr(void)
+{
+	char *s;
+
+	s = getsyserr();
+	if(errno != EPLAN9)
+		strcpy(s, strerror(errno));
+	return s;
+}
+
+
diff --git a/src/lib9/exec.c b/src/lib9/exec.c
new file mode 100644
index 0000000..ffe8134
--- /dev/null
+++ b/src/lib9/exec.c
@@ -0,0 +1,9 @@
+#include <u.h>
+#include <libc.h>
+
+int
+exec(char *prog, char *argv[])
+{
+	/* to mimic plan 9 should be just exec, but execvp is a better fit for unix */
+	return execvp(prog, argv);
+}
diff --git a/src/lib9/exits.c b/src/lib9/exits.c
index a449f68..f4206e9 100644
--- a/src/lib9/exits.c
+++ b/src/lib9/exits.c
@@ -1,10 +1,22 @@
-#include <lib9.h>
+#include <u.h>
+#include <libc.h>
+
+extern void _privdie(void);
 
 void
 exits(char *s)
 {
+	_privdie();
 	if(s && *s)
 		exit(1);
 	exit(0);
 }
 
+void
+_exits(char *s)
+{
+	_privdie();
+	if(s && *s)
+		_exit(1);
+	_exit(0);
+}
diff --git a/src/lib9/ffork-Darwin.c b/src/lib9/ffork-Darwin.c
index 189ac94..5e677f7 100644
--- a/src/lib9/ffork-Darwin.c
+++ b/src/lib9/ffork-Darwin.c
@@ -1,26 +1 @@
-#include <lib9.h>
-#include <pthread.h>
-
-extern int __isthreaded;
-int
-ffork(int flags, void(*fn)(void*), void *arg)
-{
-	void *p;
-	pthread_t tid;
-
-	if(flags != (RFMEM|RFNOWAIT)){
-		werrstr("ffork unsupported");
-		return -1;
-	}
-
-	if(pthread_create(&tid, NULL, (void*(*)(void*))fn, arg) < 0)
-		return -1;
-	return (int)tid;
-}
-
-int
-getfforkid(void)
-{
-	return (int)pthread_self();
-}
-
+#include "ffork-pthread.c"
diff --git a/src/lib9/ffork-SunOS.c b/src/lib9/ffork-SunOS.c
new file mode 100644
index 0000000..5e677f7
--- /dev/null
+++ b/src/lib9/ffork-SunOS.c
@@ -0,0 +1 @@
+#include "ffork-pthread.c"
diff --git a/src/lib9/ffork-pthread.c b/src/lib9/ffork-pthread.c
new file mode 100644
index 0000000..189ac94
--- /dev/null
+++ b/src/lib9/ffork-pthread.c
@@ -0,0 +1,26 @@
+#include <lib9.h>
+#include <pthread.h>
+
+extern int __isthreaded;
+int
+ffork(int flags, void(*fn)(void*), void *arg)
+{
+	void *p;
+	pthread_t tid;
+
+	if(flags != (RFMEM|RFNOWAIT)){
+		werrstr("ffork unsupported");
+		return -1;
+	}
+
+	if(pthread_create(&tid, NULL, (void*(*)(void*))fn, arg) < 0)
+		return -1;
+	return (int)tid;
+}
+
+int
+getfforkid(void)
+{
+	return (int)pthread_self();
+}
+
diff --git a/src/lib9/getcallerpc-sun4u.s b/src/lib9/getcallerpc-sun4u.s
new file mode 100644
index 0000000..f28e57f
--- /dev/null
+++ b/src/lib9/getcallerpc-sun4u.s
@@ -0,0 +1,5 @@
+.text
+.globl getcallerpc
+getcallerpc:
+	retl
+	or %o7, %r0, %o0
diff --git a/src/lib9/getenv.c b/src/lib9/getenv.c
new file mode 100644
index 0000000..c6ff716
--- /dev/null
+++ b/src/lib9/getenv.c
@@ -0,0 +1,15 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+char*
+p9getenv(char *s)
+{
+	char *t;
+
+	t = getenv(s);
+	if(t == 0)
+		return 0;
+	return strdup(t);
+}
+
diff --git a/src/lib9/getuser.c b/src/lib9/getuser.c
new file mode 100644
index 0000000..b53a389
--- /dev/null
+++ b/src/lib9/getuser.c
@@ -0,0 +1,17 @@
+#include <pwd.h>
+
+#include <u.h>
+#include <libc.h>
+
+char*
+getuser(void)
+{
+	static char user[64];
+	struct passwd *pw;
+
+	pw = getpwuid(getuid());
+	if(pw == nil)
+		return "none";
+	strecpy(user, user+sizeof user, pw->pw_name);
+	return user;
+}
diff --git a/src/lib9/getwd.c b/src/lib9/getwd.c
new file mode 100644
index 0000000..d6f7a13
--- /dev/null
+++ b/src/lib9/getwd.c
@@ -0,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+
+#undef getwd
+
+char*
+p9getwd(char *s, int ns)
+{
+	return getcwd(s, ns);
+}
diff --git a/src/lib9/jmp-FreeBSD.s b/src/lib9/jmp-FreeBSD.s
new file mode 100644
index 0000000..6e52b68
--- /dev/null
+++ b/src/lib9/jmp-FreeBSD.s
@@ -0,0 +1,3 @@
+.globl sigsetjmp, p9setjmp
+p9setjmp:
+	jmp sigsetjmp
diff --git a/src/lib9/jmp.c b/src/lib9/jmp.c
new file mode 100644
index 0000000..6f928ba
--- /dev/null
+++ b/src/lib9/jmp.c
@@ -0,0 +1,17 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+void
+p9longjmp(p9jmp_buf buf, int val)
+{
+	siglongjmp((void*)buf, val);
+}
+
+void
+p9notejmp(void *x, p9jmp_buf buf, int val)
+{
+	USED(x);
+	siglongjmp((void*)buf, val);
+}
+
diff --git a/src/lib9/main.c b/src/lib9/main.c
new file mode 100644
index 0000000..4a42905
--- /dev/null
+++ b/src/lib9/main.c
@@ -0,0 +1,13 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+extern void p9main(int, char**);
+
+int
+main(int argc, char **argv)
+{
+	p9main(argc, argv);
+	exits("main");
+	return 99;
+}
diff --git a/src/lib9/mkfile b/src/lib9/mkfile
new file mode 100644
index 0000000..9f5971a
--- /dev/null
+++ b/src/lib9/mkfile
@@ -0,0 +1,73 @@
+PLAN9=../..
+<$PLAN9/src/mkhdr
+
+LIB=lib9.a
+
+OFILES=\
+	_exits.$O\
+	_p9dialparse.$O\
+	_p9dir.$O\
+	_p9proc.$O\
+	announce.$O\
+	argv0.$O\
+	atexit.$O\
+	atnotify.$O\
+	await.$O\
+	cistrcmp.$O\
+	cistrncmp.$O\
+	cistrstr.$O\
+	cleanname.$O\
+	create.$O\
+	ctime.$O\
+	date.$O\
+	dial.$O\
+	dirfstat.$O\
+	dirfwstat.$O\
+	dirmodefmt.$O\
+	dirread.$O\
+	dirstat.$O\
+	dirwstat.$O\
+	dup.$O\
+	encodefmt.$O\
+	errstr.$O\
+	exec.$O\
+	ffork-$SYSNAME.$O\
+	getcallerpc-$OBJTYPE.$O\
+	getenv.$O\
+	getfields.$O\
+	getuser.$O\
+	getwd.$O\
+	jmp.$O\
+	jmp-FreeBSD.$O\
+	lock.$O\
+	main.$O\
+	malloctag.$O\
+	mallocz.$O\
+	needsrcquote.$O\
+	netmkaddr.$O\
+	notify.$O\
+	nrand.$O\
+	nulldir.$O\
+	postnote.$O\
+	qlock.$O\
+	quote.$O\
+	readn.$O\
+	rendez-$SYSNAME.$O\
+	rfork.$O\
+	seek.$O\
+	sleep.$O\
+	strecpy.$O\
+	sysfatal.$O\
+	tas-$OBJTYPE.$O\
+	time.$O\
+	tokenize.$O\
+	u16.$O\
+	u32.$O\
+	u64.$O\
+	wait.$O\
+	waitpid.$O\
+
+HFILES=\
+	$PLAN9/include/lib9.h\
+
+<$PLAN9/src/mksyslib
diff --git a/src/lib9/needsrcquote.c b/src/lib9/needsrcquote.c
new file mode 100644
index 0000000..f4cf460
--- /dev/null
+++ b/src/lib9/needsrcquote.c
@@ -0,0 +1,12 @@
+#include <u.h>
+#include <libc.h>
+
+int
+needsrcquote(int c)
+{
+	if(c <= ' ')
+		return 1;
+	if(strchr("`^#*[]=|\\?${}()'<>&;", c))
+		return 1;
+	return 0;
+}
diff --git a/src/lib9/netmkaddr.c b/src/lib9/netmkaddr.c
new file mode 100644
index 0000000..fd53f46
--- /dev/null
+++ b/src/lib9/netmkaddr.c
@@ -0,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+/*
+ *  make an address, add the defaults
+ */
+char *
+netmkaddr(char *linear, char *defnet, char *defsrv)
+{
+	static char addr[256];
+	char *cp;
+
+	/*
+	 *  dump network name
+	 */
+	cp = strchr(linear, '!');
+	if(cp == 0){
+		if(defnet==0){
+			if(defsrv)
+				snprint(addr, sizeof(addr), "net!%s!%s",
+					linear, defsrv);
+			else
+				snprint(addr, sizeof(addr), "net!%s", linear);
+		}
+		else {
+			if(defsrv)
+				snprint(addr, sizeof(addr), "%s!%s!%s", defnet,
+					linear, defsrv);
+			else
+				snprint(addr, sizeof(addr), "%s!%s", defnet,
+					linear);
+		}
+		return addr;
+	}
+
+	/*
+	 *  if there is already a service, use it
+	 */
+	cp = strchr(cp+1, '!');
+	if(cp)
+		return linear;
+
+	/*
+	 *  add default service
+	 */
+	if(defsrv == 0)
+		return linear;
+	snprint(addr, sizeof(addr), "%s!%s", linear, defsrv);
+
+	return addr;
+}
diff --git a/src/lib9/notify.c b/src/lib9/notify.c
new file mode 100644
index 0000000..095a3f5
--- /dev/null
+++ b/src/lib9/notify.c
@@ -0,0 +1,83 @@
+#include <signal.h>
+
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+#include "9proc.h"
+
+extern char *_p9sigstr(int, char*);
+
+static int sigs[] = {
+	SIGHUP,
+	SIGINT,
+	SIGQUIT,
+	SIGILL,
+	SIGTRAP,
+	SIGABRT,
+	SIGEMT,
+	SIGFPE,
+	SIGBUS,
+	SIGSEGV,
+	SIGSYS,
+	SIGPIPE,
+	SIGALRM,
+	SIGTERM,
+	SIGTSTP,
+	SIGTTIN,
+	SIGTTOU,
+	SIGXCPU,
+	SIGXFSZ,
+	SIGVTALRM,
+	SIGUSR1,
+	SIGUSR2,
+};
+
+static void (*notifyf)(void*, char*);
+
+static void
+notifysigf(int sig)
+{
+	int v;
+	char tmp[64];
+	Uproc *up;
+
+	up = _p9uproc();
+	v = p9setjmp(up->notejb);
+	if(v == 0 && notifyf)
+		(*notifyf)(nil, _p9sigstr(sig, tmp));
+	else if(v == 2){
+if(0)print("HANDLED %d\n", sig);
+		return;
+	}
+if(0)print("DEFAULT %d\n", sig);
+	signal(sig, SIG_DFL);
+	kill(getpid(), sig);
+}
+	
+int
+notify(void (*f)(void*, char*))
+{
+	int i;
+	void (*sf)(int);
+
+	if(f == nil)
+		sf = SIG_DFL;
+	else{
+		notifyf = f;
+		sf = notifysigf;
+	}
+	for(i=0; i<nelem(sigs); i++)
+		signal(sigs[i], sf);
+	return 0;
+}
+
+int
+noted(int v)
+{
+	Uproc *up;
+
+	up = _p9uproc();
+	p9longjmp(up->notejb, v==NCONT ? 2 : 1);
+	abort();
+	return 0;
+}
diff --git a/src/lib9/nulldir.c b/src/lib9/nulldir.c
new file mode 100644
index 0000000..612725d
--- /dev/null
+++ b/src/lib9/nulldir.c
@@ -0,0 +1,9 @@
+#include <u.h>
+#include <libc.h>
+
+void
+nulldir(Dir *d)
+{
+	memset(d, ~0, sizeof(Dir));
+	d->name = d->uid = d->gid = d->muid = "";
+}
diff --git a/src/lib9/postnote.c b/src/lib9/postnote.c
new file mode 100644
index 0000000..9124bd5
--- /dev/null
+++ b/src/lib9/postnote.c
@@ -0,0 +1,34 @@
+#include <signal.h>
+
+#include <u.h>
+#define _NO9DEFINES_
+#include <libc.h>
+
+
+extern int _p9strsig(char*);
+
+int
+postnote(int who, int pid, char *msg)
+{
+	int sig;
+
+	sig = _p9strsig(msg);
+	if(sig == 0){
+		werrstr("unknown note");
+		return -1;
+	}
+
+	switch(who){
+	default:
+		werrstr("bad who in postnote");
+		return -1;
+	case PNPROC:
+		return kill(pid, sig);
+	case PNGROUP:
+		if((pid = getpgid(pid)) < 0)
+			return -1;
+		return killpg(pid, sig);
+	}
+}
+
+		
diff --git a/src/lib9/priv.c b/src/lib9/priv.c
new file mode 100644
index 0000000..651c48c
--- /dev/null
+++ b/src/lib9/priv.c
@@ -0,0 +1,32 @@
+#include <u.h>
+#include <libc.h>
+#include "9proc.h"
+
+static Lock privlock;
+static ulong privmap;
+
+int
+privalloc(void)
+{
+	int i;
+
+	lock(&privlock);
+	for(i=0; i<NPRIV; i++)
+		if((privmap&(1<<i)) == 0){
+			privmap |= (1<<i);
+			unlock(&privlock);
+			return i;
+		}
+	unlock(&privlock);
+	return -1;
+}
+
+void**
+privmem(int i)
+{
+	Uproc *up;
+
+	up = _p9uproc();
+	return &up->priv[i];
+}
+
diff --git a/src/lib9/quote.c b/src/lib9/quote.c
new file mode 100644
index 0000000..4467377
--- /dev/null
+++ b/src/lib9/quote.c
@@ -0,0 +1,136 @@
+#include <u.h>
+#include <libc.h>
+
+int	(*doquote)(int);
+
+/* in libfmt */
+extern int __needsquotes(char*, int*);
+extern int __runeneedsquotes(Rune*, int*);
+
+char*
+unquotestrdup(char *s)
+{
+	char *t, *ret;
+	int quoting;
+
+	ret = s = strdup(s);	/* return unquoted copy */
+	if(ret == nil)
+		return ret;
+	quoting = 0;
+	t = s;	/* s is output string, t is input string */
+	while(*t!='\0' && (quoting || (*t!=' ' && *t!='\t'))){
+		if(*t != '\''){
+			*s++ = *t++;
+			continue;
+		}
+		/* *t is a quote */
+		if(!quoting){
+			quoting = 1;
+			t++;
+			continue;
+		}
+		/* quoting and we're on a quote */
+		if(t[1] != '\''){
+			/* end of quoted section; absorb closing quote */
+			t++;
+			quoting = 0;
+			continue;
+		}
+		/* doubled quote; fold one quote into two */
+		t++;
+		*s++ = *t++;
+	}
+	if(t != s)
+		memmove(s, t, strlen(t)+1);
+	return ret;
+}
+
+Rune*
+unquoterunestrdup(Rune *s)
+{
+	Rune *t, *ret;
+	int quoting;
+
+	ret = s = runestrdup(s);	/* return unquoted copy */
+	if(ret == nil)
+		return ret;
+	quoting = 0;
+	t = s;	/* s is output string, t is input string */
+	while(*t!='\0' && (quoting || (*t!=' ' && *t!='\t'))){
+		if(*t != '\''){
+			*s++ = *t++;
+			continue;
+		}
+		/* *t is a quote */
+		if(!quoting){
+			quoting = 1;
+			t++;
+			continue;
+		}
+		/* quoting and we're on a quote */
+		if(t[1] != '\''){
+			/* end of quoted section; absorb closing quote */
+			t++;
+			quoting = 0;
+			continue;
+		}
+		/* doubled quote; fold one quote into two */
+		t++;
+		*s++ = *t++;
+	}
+	if(t != s)
+		memmove(s, t, (runestrlen(t)+1)*sizeof(Rune));
+	return ret;
+}
+
+char*
+quotestrdup(char *s)
+{
+	char *t, *u, *ret;
+	int quotelen;
+	Rune r;
+
+	if(__needsquotes(s, &quotelen) == 0)
+		return strdup(s);
+	
+	ret = malloc(quotelen+1);
+	if(ret == nil)
+		return nil;
+	u = ret;
+	*u++ = '\'';
+	for(t=s; *t; t++){
+		r = *t;
+		if(r == L'\'')
+			*u++ = r;	/* double the quote */
+		*u++ = r;
+	}
+	*u++ = '\'';
+	*u = '\0';
+	return ret;
+}
+
+Rune*
+quoterunestrdup(Rune *s)
+{
+	Rune *t, *u, *ret;
+	int quotelen;
+	Rune r;
+
+	if(__runeneedsquotes(s, &quotelen) == 0)
+		return runestrdup(s);
+	
+	ret = malloc((quotelen+1)*sizeof(Rune));
+	if(ret == nil)
+		return nil;
+	u = ret;
+	*u++ = '\'';
+	for(t=s; *t; t++){
+		r = *t;
+		if(r == L'\'')
+			*u++ = r;	/* double the quote */
+		*u++ = r;
+	}
+	*u++ = '\'';
+	*u = '\0';
+	return ret;
+}
diff --git a/src/lib9/rendez-SunOS.c b/src/lib9/rendez-SunOS.c
new file mode 100644
index 0000000..eabb9a7
--- /dev/null
+++ b/src/lib9/rendez-SunOS.c
@@ -0,0 +1 @@
+#include "rendez-pthread.c"
diff --git a/src/lib9/rendez-pthread.c b/src/lib9/rendez-pthread.c
index 9b5d434..b4b95f2 100644
--- a/src/lib9/rendez-pthread.c
+++ b/src/lib9/rendez-pthread.c
@@ -33,6 +33,7 @@
  */
 
 #include <pthread.h>
+#include <signal.h>
 #include <lib9.h>
 
 enum
diff --git a/src/lib9/rendez.c b/src/lib9/rendez.c
new file mode 100644
index 0000000..cf23a4e
--- /dev/null
+++ b/src/lib9/rendez.c
@@ -0,0 +1,42 @@
+#include <u.h>
+#include <libc.h>
+#include "9proc.h"
+
+static Lock rendlock;
+static Uproc *rendhash[RENDHASH];
+
+ulong
+rendezvous(ulong tag, ulong val)
+{
+	char c;
+	ulong ret;
+	Uproc *t, *self, **l;
+
+	self = _p9uproc();
+	lock(&rendlock);
+	l = &rendhash[tag%RENDHASH];
+	for(t=*l; t; l=&t->rendhash, t=*l){
+		if(t->rendtag==tag){
+			*l = t->rendhash;
+			ret = t->rendval;
+			t->rendval = val;
+			t->rendtag++;
+			c = 0;
+			unlock(&rendlock);
+			write(t->pipe[1], &c, 1);
+			return ret;
+		}
+	}
+
+	/* Going to sleep here. */
+	t = self;
+	t->rendtag = tag;
+	t->rendval = val;
+	t->rendhash = *l;
+	*l = t;
+	unlock(&rendlock);
+	do
+		read(t->pipe[0], &c, 1);
+	while(t->rendtag == tag);
+	return t->rendval;
+}
diff --git a/src/lib9/rfork.c b/src/lib9/rfork.c
new file mode 100644
index 0000000..e248cd7
--- /dev/null
+++ b/src/lib9/rfork.c
@@ -0,0 +1,20 @@
+#define NOPLAN9DEFINES
+#include <lib9.h>
+
+int
+p9rfork(int flags)
+{
+	if(flags&RFPROC){
+		werrstr("cannot use rfork to fork -- use ffork");
+		return -1;
+	}
+	if(flags&RFNOTEG){
+		setpgrp(0, 0);
+		flags &= ~RFNOTEG;
+	}
+	if(flags){
+		werrstr("unknown flags %08ux in rfork", flags);
+		return -1;
+	}
+	return 0;
+}
diff --git a/src/lib9/seek.c b/src/lib9/seek.c
new file mode 100644
index 0000000..b626355
--- /dev/null
+++ b/src/lib9/seek.c
@@ -0,0 +1,8 @@
+#include <u.h>
+#include <libc.h>
+
+vlong
+seek(int fd, vlong offset, int whence)
+{
+	return lseek(fd, offset, whence);
+}
diff --git a/src/lib9/sleep.c b/src/lib9/sleep.c
new file mode 100644
index 0000000..9cafdcf
--- /dev/null
+++ b/src/lib9/sleep.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+#include <sys/time.h>
+#include <sched.h>
+
+int
+p9sleep(long milli)
+{
+	struct timeval tv;
+
+	if(milli == 0){
+		sched_yield();
+		return 0;
+	}
+
+	tv.tv_sec = milli/1000;
+	tv.tv_usec = (milli%1000)*1000;
+	return select(0, 0, 0, 0, &tv);
+}
+
+long
+p9alarm(ulong milli)
+{
+	struct itimerval itv;
+	struct itimerval oitv;
+
+	itv.it_interval.tv_sec = 0;
+	itv.it_interval.tv_usec = 0;
+	itv.it_value.tv_sec = milli/1000;
+	itv.it_value.tv_usec = (milli%1000)*1000;
+	if(setitimer(ITIMER_REAL, &itv, &oitv) < 0)
+		return -1;
+	return oitv.it_value.tv_sec*1000+oitv.it_value.tv_usec/1000;
+}
diff --git a/src/lib9/stat2dir.c b/src/lib9/stat2dir.c
new file mode 100644
index 0000000..51f5bf2
--- /dev/null
+++ b/src/lib9/stat2dir.c
@@ -0,0 +1,116 @@
+#include <u.h>
+#include <libc.h>
+
+#include <sys/stat.h>
+#include <sys/disklabel.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <grp.h>
+
+int
+_p9dir(struct stat *st, char *name, Dir *d, char **str, char *estr)
+{
+	char *s;
+	char tmp[20];
+	struct group *g;
+	struct pwd *p;
+	int sz;
+
+	sz = 0;
+
+	/* name */
+	s = strrchr(name, '/');
+	if(s && s[1])
+		s++;
+	else
+		s = "/";
+	if(d){
+		if(*str + strlen(s)+1 > estr)
+			d->name = "oops";
+		else{
+			strcpy(*str, s);
+			d->name = *str;
+			*str += strlen(*str)+1;
+		}
+	}
+	sz += strlen(s)+1;
+
+	/* user */
+	p = getpwuid(st->st_uid);
+	if(p == nil){
+		snprint(tmp, sizeof tmp, "%d", (int)st->st_uid);
+		s = tmp;
+	}else
+		s = p->pw_name;
+	sz += strlen(s)+1;
+	if(d){
+		if(*str+strlen(s)+1 > estr)
+			d->uid = "oops";	
+		else{
+			strcpy(*str, s);
+			d->uid = *str;
+			*str += strlen(*str)+1;
+		}
+	}
+
+	/* group */
+	g = getgrgid(st->st_gid);
+	if(g == nil){
+		snprint(tmp, sizeof tmp, "%d", (int)st->st_gid);
+		s = tmp;
+	}else
+		s = g->gr_name;
+	sz += strlen(s)+1;
+	if(d){
+		if(*str + strlen(s)+1 > estr){
+			d->gid = "oops";	
+		else{
+			strcpy(*str, s);
+			d->gid = *str;
+			*str += strlen(*str)+1;
+		}
+	}
+
+	if(d){
+		d->muid = "";
+		d->qid.path = ((uvlong)st->st_dev<<32) | st->st_ino;
+		d->qid.vers = st->st_gen;
+		d->mode = st->st_mode&0777;
+		if(S_ISDIR(st->st_mode)){
+			d->mode |= DMDIR;
+			d->qid.type = QTDIR;
+		}
+		d->atime = st->st_atime;
+		d->mtime = st->st_mtime;
+		d->length = st->st_size;
+
+		/* fetch real size for disks */
+		if(S_ISCHR(st->st_mode)){
+			int fd, n;
+			struct disklabel lab;
+
+			if((fd = open(name, O_RDONLY)) < 0)
+				goto nosize;
+			if(ioctl(fd, DIOCGDINFO, &lab) < 0)
+				goto nosize;
+			n = minor(st->st_rdev)&0xFFFF;
+			if(n >= lab.d_npartitions)
+				goto nosize;
+			d->length = (vlong)lab.d_npartitions[n].p_size * lab.d_secsize;
+		nosize:
+			if(fd >= 0)
+				close(fd);
+		}
+	}
+
+	return sz;
+}
+
+Dir*
+_dirfstat(char *name, int fd)
+{
+	Dir *d;
+	int size;
+
+	
+}
diff --git a/src/lib9/tas-sun4u.s b/src/lib9/tas-sun4u.s
new file mode 100644
index 0000000..b960a26
--- /dev/null
+++ b/src/lib9/tas-sun4u.s
@@ -0,0 +1,4 @@
+.globl _tas
+_tas:
+	retl
+	ldstub [%o0], %o0
diff --git a/src/lib9/time.c b/src/lib9/time.c
new file mode 100644
index 0000000..169a82f
--- /dev/null
+++ b/src/lib9/time.c
@@ -0,0 +1,58 @@
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+
+long
+p9times(long *t)
+{
+	struct rusage ru, cru;
+
+	if(getrusage(0, &ru) < 0 || getrusage(-1, &cru) < 0)
+		return -1;
+
+	t[0] = ru.ru_utime.tv_sec*1000 + ru.ru_utime.tv_usec/1000;
+	t[1] = ru.ru_stime.tv_sec*1000 + ru.ru_stime.tv_usec/1000;
+	t[2] = cru.ru_utime.tv_sec*1000 + cru.ru_utime.tv_usec/1000;
+	t[3] = cru.ru_stime.tv_sec*1000 + cru.ru_stime.tv_usec/1000;
+
+	/* BUG */
+	return t[0]+t[1]+t[2]+t[3];
+}
+
+double
+p9cputime(void)
+{
+	long t[4];
+	double d;
+
+	if(p9times(t) < 0)
+		return -1.0;
+
+	d = (double)t[0]+(double)t[1]+(double)t[2]+(double)t[3];
+	return d/1000.0;
+}
+
+vlong
+p9nsec(void)
+{
+	struct timeval tv;
+
+	if(gettimeofday(&tv, 0) < 0)
+		return -1;
+
+	return (vlong)tv.tv_sec*1000*1000*1000 + tv.tv_usec*1000;
+}
+
+long
+p9time(long *tt)
+{
+	long t;
+	t = time(0);
+	if(tt)
+		*tt = t;
+	return t;
+}
+
diff --git a/src/lib9/udp.c b/src/lib9/udp.c
new file mode 100644
index 0000000..a116472
--- /dev/null
+++ b/src/lib9/udp.c
@@ -0,0 +1,52 @@
+#include <u.h>
+#define NOPLAN9DEFINES
+#include <libc.h>
+#include <ip.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+/*
+ *  prefix of all v4 addresses
+ *  copied from libip because libc cannot depend on libip
+ */
+static uchar v4prefix[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	0, 0, 0, 0
+};
+
+long
+udpread(int fd, Udphdr *hdr, void *buf, long n)
+{
+	struct sockaddr_in sin;
+	socklen_t len;
+
+	len = sizeof sin;
+	n = recvfrom(fd, buf, n, 0, (struct sockaddr*)&sin, &len);
+	if(len != sizeof sin){
+		werrstr("recvfrom acting weird");
+		return -1;
+	}
+	if(n < 0)
+		return -1;
+	memset(hdr, 0, sizeof *hdr);
+	memmove(hdr->raddr, v4prefix, IPaddrlen);
+	*(u32int*)(hdr->raddr+12) = *(u32int*)&sin.sin_addr;
+	*(u16int*)hdr->rport = *(u16int*)&sin.sin_port;
+	return n;
+}
+
+long
+udpwrite(int fd, Udphdr *hdr, void *buf, long n)
+{
+	struct sockaddr_in sin;
+
+	memset(&sin, 0, sizeof sin);
+	sin.sin_family = AF_INET;
+	*(u32int*)&sin.sin_addr = *(u32int*)(hdr->raddr+12);
+	*(u16int*)&sin.sin_port = *(u16int*)hdr->rport;
+	return sendto(fd, buf, n, 0, (struct sockaddr*)&sin, sizeof sin);
+}
+
diff --git a/src/lib9/wait.c b/src/lib9/wait.c
index 14af715..6dc137b 100644
--- a/src/lib9/wait.c
+++ b/src/lib9/wait.c
@@ -1,4 +1,5 @@
-#include <lib9.h>
+#include <u.h>
+#include <libc.h>
 
 Waitmsg*
 wait(void)
diff --git a/src/lib9/waitpid.c b/src/lib9/waitpid.c
new file mode 100644
index 0000000..3df8ef8
--- /dev/null
+++ b/src/lib9/waitpid.c
@@ -0,0 +1,20 @@
+#include <u.h>
+#include <libc.h>
+
+int
+waitpid(void)
+{
+	int n;
+	char buf[512], *fld[5];
+
+	n = await(buf, sizeof buf-1);
+	if(n <= 0)
+		return -1;
+	buf[n] = '\0';
+	if(tokenize(buf, fld, nelem(fld)) != nelem(fld)){
+		werrstr("couldn't parse wait message");
+		return -1;
+	}
+	return atoi(fld[0]);
+}
+