shithub: lu9-p9

Download patch

ref: 5168236aa96a83b623a142c0d0e365c7ff0d96e6
parent: 970e4fcbaacb7511d45eaa2890501a0cc1edb45c
author: kvik <[email protected]>
date: Thu Apr 22 18:23:16 EDT 2021

all: introduce nicer project structure

This should allow easier maintenance and cherry linking of
modules planned in the future.

diff: cannot open b/base//null: file does not exist: 'b/base//null' diff: cannot open b/note//null: file does not exist: 'b/note//null' diff: cannot open b/test//null: file does not exist: 'b/test//null'
--- /dev/null
+++ b/base/base.c
@@ -1,0 +1,162 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+
+#include "../base/common.c"
+
+#include "fs.c"
+#include "walk.c"
+#include "env.c"
+#include "ns.c"
+#include "proc.c"
+#include "misc.c"
+
+typedef struct Data {
+	char *key;
+	lua_Integer val;
+} Data;
+
+static Data p9data[] = {
+	{"OREAD", OREAD},
+	{"OWRITE", OWRITE},
+	{"ORDWR", ORDWR},
+	{"OEXEC", OEXEC},
+	{"OTRUNC", OTRUNC},
+	{"OCEXEC", OCEXEC},
+	{"ORCLOSE", ORCLOSE},
+	{"OEXCL", OEXCL},
+	
+	{"DMDIR", DMDIR},
+	{"DMAPPEND", DMAPPEND},
+	{"DMEXCL", DMEXCL},
+	{"DMMOUNT", DMMOUNT},
+	{"DMAUTH", DMAUTH},
+	{"DMTMP", DMTMP},
+	{"DMREAD", DMREAD},
+	{"DMWRITE", DMWRITE},
+	{"DMEXEC", DMEXEC},
+	{"QTDIR", QTDIR},
+	{"QTAPPEND", QTAPPEND},
+	{"QTEXCL", QTEXCL},
+	{"QTMOUNT", QTMOUNT},
+	{"QTAUTH", QTAUTH},
+	{"QTTMP", QTTMP},
+	{"QTFILE", QTFILE},
+	
+	{"MREPL", MREPL},
+	{"MBEFORE", MBEFORE},
+	{"MAFTER", MAFTER},
+	{"MCREATE", MCREATE},
+	{"MCACHE", MCACHE},
+
+	{"RFPROC", RFPROC},
+	{"RFNOWAIT", RFNOWAIT},
+	{"RFNAMEG", RFNAMEG},
+	{"RFCNAMEG", RFCNAMEG},
+	{"RFNOMNT", RFNOMNT},
+	{"RFENVG", RFENVG},
+	{"RFCENVG", RFCENVG},
+	{"RFNOTEG", RFNOTEG},
+	{"RFFDG", RFFDG},
+	{"RFCFDG", RFCFDG},
+	{"RFREND", RFREND},
+	{"RFMEM", RFMEM},
+	
+	{nil, 0}
+};
+
+static luaL_Reg p9func[] = {
+	{"open", p9_open},
+	{"create", p9_create},
+	{"file", p9_file},
+	{"pipe", p9_pipe},
+	{"remove", p9_remove},
+	{"access", p9_access},
+	
+	{"stat", p9_stat},
+	{"wstat", p9_wstat},
+	{"walk", p9_walk},
+	
+	{"bind", p9_bind},
+	{"mount", p9_mount},
+	{"unmount", p9_unmount},
+	
+	{"getenv", p9_getenv},
+	{"setenv", p9_setenv},
+	
+	{"abort", p9_abort},
+	{"exits", p9_exits},
+	{"fatal", p9_fatal},
+	{"sleep", p9_sleep},
+	{"alarm", p9_alarm},
+	{"rfork", p9_rfork},
+	{"wait", p9_wait},
+	{"exec", p9_exec},
+	{"wdir", p9_wdir},
+	{"pid", p9_pid},
+	{"ppid", p9_ppid},
+	{"user", p9_user},
+	{"sysname", p9_sysname},
+	
+	{"cleanname", p9_cleanname},
+	
+	{nil, nil}
+};
+
+int
+luaopen_p9(lua_State *L)
+{
+	int lib;
+	Buf *buf;
+	Data *d;
+	
+	buf = resizebuffer(L, nil, Iosize);
+	lua_pushlightuserdata(L, buf);
+	lua_setfield(L, LUA_REGISTRYINDEX, "p9-buffer");
+	
+	static luaL_Reg filemt[] = {
+		{"close", p9_file_close},
+		{"read", p9_file_read},
+		{"slurp", p9_file_slurp},
+		{"write", p9_file_write},
+		{"seek", p9_file_seek},
+		{"iounit", p9_file_iounit},
+		{"path", p9_file_path},
+		{"dup", p9_file_dup},
+		{nil, nil},
+	};
+	luaL_newmetatable(L, "p9-File");
+	luaL_setfuncs(L, filemt, 0);
+	lua_pop(L, 1);
+	
+	static luaL_Reg walkmt[] = {
+		{"__close", p9_walkclose},
+		{nil, nil},
+	};
+	luaL_newmetatable(L, "p9-Walk");
+	luaL_setfuncs(L, walkmt, 0);
+	lua_pop(L, 1);
+	
+	luaL_newlib(L, p9func);
+	lib = lua_gettop(L);
+	for(d = p9data; d->key != nil; d++){
+		lua_pushinteger(L, d->val);
+		lua_setfield(L, -2, d->key);
+	}
+	
+	static luaL_Reg envmt[] = {
+		{"__index", p9_getenv_index},
+		{"__newindex", p9_setenv_newindex},
+		{nil, nil},
+	};
+	lua_createtable(L, 0, 2);
+	luaL_setfuncs(L, envmt, 0);
+	lua_pushvalue(L, -1);
+	lua_setmetatable(L, -2);
+	lua_setfield(L, lib, "env");
+	
+	return 1;
+}
--- /dev/null
+++ b/base/common.c
@@ -1,0 +1,86 @@
+enum {
+	Iosize = 8192,
+	Smallbuf = 512,
+};
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+static int
+error(lua_State *L, char *fmt, ...)
+{
+	va_list varg;
+	int n;
+	char *buf;
+	luaL_Buffer b;
+	
+	lua_pushnil(L);
+	buf = luaL_buffinitsize(L, &b, Smallbuf);
+	va_start(varg, fmt);
+	n = vsnprint(buf, Smallbuf, fmt, varg);
+	va_end(varg);
+	luaL_pushresultsize(&b, n);
+	return 2;
+}
+
+/* Memory allocator associated with Lua state */
+static void*
+lalloc(lua_State *L, void *ptr, usize sz)
+{
+	void *ud;
+	
+	if((ptr = (lua_getallocf(L, &ud))(ud, ptr, LUA_TUSERDATA, sz)) == nil){
+		lua_pushliteral(L, "out of memory");
+		lua_error(L);
+	}
+	memset(ptr, 0, sz);
+	setmalloctag(ptr, getcallerpc(&L));
+	return ptr;
+}
+
+/* 
+ * Various functions in this library require a
+ * variably sized buffer for their operation.
+ * Rather than allocating one for each call
+ * we preallocate a shared buffer of reasonable
+ * size and grow it as needed.
+ * The buffer gets associated with a Lua state
+ * at library load time.
+ * getbuffer(L, sz) returns a pointer to the
+ * memory area of at least sz bytes.
+ * 
+ * To avoid stepping on each other's toes the
+ * buffer use must be constrained to a single
+ * call.
+ */
+
+typedef struct Buf Buf;
+
+struct Buf {
+	usize sz;
+	char *b;
+};
+
+static Buf*
+resizebuffer(lua_State *L, Buf *buf, usize sz)
+{
+	if(buf == nil){
+		buf = lalloc(L, nil, sizeof(Buf));
+		buf->b = nil;
+		buf->sz = 0;
+	}
+	if(buf->sz < sz){
+		buf->b = lalloc(L, buf->b, sz);
+		buf->sz = sz;
+	}
+	return buf;
+}
+
+static char*
+getbuffer(lua_State *L, usize sz)
+{
+	Buf *buf;
+	
+	lua_getfield(L, LUA_REGISTRYINDEX, "p9-buffer");
+	buf = lua_touserdata(L, -1);
+	return resizebuffer(L, buf, sz)->b;
+}
--- /dev/null
+++ b/base/env.c
@@ -1,0 +1,133 @@
+/* Environment variables
+ *
+ * p9.env object provides a map between the /env device and Lua,
+ * with its dynamic fields representing the environment variables.
+ * Assigning a value to the field writes to the environment:
+ *
+ * 	p9.env.var = "value"
+ *
+ * while reading a value reads from the environment:
+ *
+ * 	assert(p9.env.var == "value")
+ *
+ * A value can be a string or a list.
+ * A list is encoded (decoded) to (from) the environment as a
+ * list of strings according to the encoding used by the rc(1)
+ * shell (0-byte separated fields).
+ *
+ *	lua> p9.env.list = {"a", "b", "c"}
+ *	rc> echo $#list  * $list
+ *	3  * a b c
+ *
+ * p9.getenv(name) and p9.setenv(name, val) provide the more
+ * usual API.
+ */
+
+static int
+p9_getenv(lua_State *L)
+{
+	int fd;
+	long len, elems, i;
+	char env[Smallbuf];
+	const char *buf, *s, *e;
+	
+	snprint(env, sizeof env, "/env/%s", luaL_checkstring(L, 1));
+	if((fd = open(env, OREAD)) == -1){
+		lua_pushnil(L);
+		return 1;
+	}
+	slurp(L, fd, -1);
+	close(fd);
+	len = luaL_len(L, -1);
+	buf = lua_tostring(L, -1);
+	/* Empty */
+	if(len == 0){
+		lua_pushnil(L);
+		return 1;
+	}
+	/* String (not a list) */
+	if(buf[len-1] != '\0')
+		return 1;
+	/* List */
+	for(elems = i = 0; i < len; i++)
+		if(buf[i] == '\0') elems++;
+	s = buf;
+	e = buf + len;
+	lua_createtable(L, elems, 0);
+	for(i = 1; s < e; i++){
+		lua_pushstring(L, s);
+		lua_rawseti(L, -2, i);
+		s = memchr(s, '\0', e - s);
+		s++;
+	}
+	return 1;
+}
+
+static int
+p9_getenv_index(lua_State *L)
+{
+	lua_remove(L, 1);
+	return p9_getenv(L);
+}
+
+static int
+p9_setenv(lua_State *L)
+{
+	int fd, ntab, n, t, i;
+	char env[Smallbuf];
+	luaL_Buffer b;
+	
+	snprint(env, sizeof env, "/env/%s", luaL_checkstring(L, 1));
+	t = lua_type(L, 2);
+	if(t != LUA_TNIL && t != LUA_TSTRING && t != LUA_TTABLE)
+		return luaL_argerror(L, 2, "nil, string, or table expected");
+	if((fd = create(env, OWRITE, 0644)) == -1)
+		return error(L, "open: %r");
+	/*
+	 * Writes below are not fully checked for
+	 * error (write(n) != n), because env(3)
+	 * may truncate the value at its dumb 16k
+	 * limit. Encoding a knowledge of this limit
+	 * sucks, as does env(3).
+	 */
+	if(t == LUA_TNIL){
+		close(fd);
+		return 0;
+	}else if(t == LUA_TSTRING){
+		n = luaL_len(L, 2);
+		if(write(fd, lua_tostring(L, 2), n) == -1){
+			close(fd);
+			return error(L, "write: %r");
+		}
+	}else{
+		ntab = luaL_len(L, 2);
+		luaL_buffinit(L, &b);
+		for(i = 1; i <= ntab; i++){
+			t = lua_geti(L, 2, i);
+			if(t != LUA_TSTRING){
+				if(luaL_callmeta(L, -1, "__tostring")
+				&& lua_type(L, -1) == LUA_TSTRING){
+					lua_replace(L, -2);
+				}else{
+					lua_pop(L, 1);
+					continue;
+				}
+			}
+			luaL_addvalue(&b);
+			luaL_addchar(&b, '\0');
+		}
+		if(write(fd, luaL_buffaddr(&b), luaL_bufflen(&b)) == -1){
+			close(fd);
+			return error(L, "write: %r");
+		}
+	}
+	close(fd);
+	return 0;
+}
+
+static int
+p9_setenv_newindex(lua_State *L)
+{
+	lua_remove(L, 1);
+	return p9_setenv(L);
+}
--- /dev/null
+++ b/base/fs.c
@@ -1,0 +1,398 @@
+/*
+ * The File object
+
+ * p9.file(fd) takes an open file descriptor and returns a
+ * File object f which provides a convenient method interface
+ * to the usual file operations.
+ * p9.open and p9.create take a file name to open or create.
+
+ * The file descriptor stored in f.fd is garbage collected,
+ * that is, it will be automatically closed once the File
+ * object becomes unreachable. Note how this means that f.fd
+ * should be used sparringly and with much care. In particular
+ * you shouldn't store it outside of f, since the actual file
+ * descriptor number might become invalid (closed) or refer
+ * to a completely different file after f is collected.
+ *
+ * Store the File object in some global place to prevent it
+ * from being collected.
+ */
+ 
+static int
+openmode(lua_State *L, char *s)
+{
+	int i, n, mode;
+	char r, w, x;
+	char buf[64], *f[10], *p;
+	
+	snprint(buf, sizeof buf, "%s", s);
+	mode = r = w = x = 0;
+	n = getfields(buf, f, sizeof f, 1, " \t\n");
+	if(n < 1)
+		return OREAD;
+	for(i = 0; p = f[i], i < n; i++){
+		if(strcmp(p, "r") == 0 || strcmp(p, "read") == 0)
+			r = 1;
+		else if(strcmp(p, "w") == 0 || strcmp(p, "write") == 0)
+			w = 1;
+		else if(strcmp(p, "rw") == 0 || strcmp(p, "rdwr") == 0)
+			r = w = 1;
+		else if(strcmp(p, "x") == 0 || strcmp(p, "exec") == 0)
+			x = 1;
+		else if(strcmp(p, "trunc") == 0)
+			mode |= OTRUNC;
+		else if(strcmp(p, "cexec") == 0)
+			mode |= OCEXEC;
+		else if(strcmp(p, "rclose") == 0)
+			mode |= ORCLOSE;
+		else if(strcmp(p, "excl") == 0)
+			mode |= OEXCL;
+		else
+			return luaL_error(L, "unknown mode flag '%s'", p);
+	}
+	if(x) mode |= OEXEC;
+	else if(r && w) mode |= ORDWR;
+	else if(r) mode |= OREAD;
+	else if(w) mode |= OWRITE;
+	return mode;
+}
+
+static ulong
+createperm(lua_State *L, char *s)
+{
+	int i, n;
+	ulong perm;
+	char buf[64], *f[10], *p;
+	
+	snprint(buf, sizeof buf, "%s", s);
+	perm = 0;
+	n = getfields(buf, f, sizeof f, 1, " \t\n");
+	if(n < 1)
+		return 0644;
+	for(i = 0; p = f[i], i < n; i++){
+		if(isdigit(p[0]))
+			perm |= strtol(p, nil, 8);
+		else if(strcmp(p, "dir") == 0)
+			perm |= DMDIR;
+		else if(strcmp(p, "tmp") == 0)
+			perm |= DMTMP;
+		else if(strcmp(p, "excl") == 0)
+			perm |= DMEXCL;
+		else if(strcmp(p, "append") == 0)
+			perm |= DMAPPEND;
+		else
+			return luaL_error(L, "unknown permission flag '%s'", p);
+	}
+	return perm;
+}
+
+static int filenew(lua_State*, int);
+static int fileclose(lua_State*);
+static int filefd(lua_State*, int);
+
+static int
+filenew(lua_State *L, int fd)
+{
+	int f;
+
+	lua_createtable(L, 0, 4);
+	f = lua_gettop(L);
+	lua_pushinteger(L, fd);
+		lua_setfield(L, f, "fd");
+	luaL_getmetatable(L, "p9-File");
+		lua_setfield(L, f, "__index");
+	lua_pushcfunction(L, fileclose);
+		lua_setfield(L, f, "__close");
+	lua_pushcfunction(L, fileclose);
+		lua_setfield(L, f, "__gc");
+	lua_pushvalue(L, f);
+		lua_setmetatable(L, f);
+	return 1;
+}
+
+static int
+fileclose(lua_State *L)
+{
+	int fd;
+	
+	fd = filefd(L, 1);
+	if(fd == -1)
+		return 0;
+	lua_pushinteger(L, -1);
+		lua_setfield(L, 1, "fd");
+	close(fd);
+	return 0;
+}
+
+static int
+filefd(lua_State *L, int idx)
+{
+	int fd;
+	
+	if(lua_getfield(L, idx, "fd") != LUA_TNUMBER)
+		return luaL_error(L, "fd must be integer");
+	fd = lua_tonumber(L, -1);
+	lua_pop(L, 1);
+	return fd;
+}
+
+static int
+p9_file(lua_State *L)
+{
+	int fd;
+	
+	fd = luaL_checkinteger(L, 1);
+	return filenew(L, fd);
+}
+
+static int
+p9_open(lua_State *L)
+{
+	const char *file;
+	int mode;
+	int fd;
+	
+	file = luaL_checkstring(L, 1);
+	mode = openmode(L, luaL_optstring(L, 2, "read"));
+	if((fd = open(file, mode)) == -1)
+		return error(L, "open: %r");
+	return filenew(L, fd);
+}
+
+static int
+p9_create(lua_State *L)
+{
+	const char *file;
+	int fd, mode;
+	ulong perm;
+	
+	file = luaL_checkstring(L, 1);
+	mode = openmode(L, luaL_optstring(L, 2, "rdwr"));
+	perm = createperm(L, luaL_optstring(L, 3, "644"));
+	if((fd = create(file, mode, perm)) == -1)
+		return error(L, "create: %r");
+	return filenew(L, fd);
+}
+
+static int
+p9_file_close(lua_State *L)
+{
+	if(close(filefd(L, 1)) == -1)
+		return error(L, "close: %r");
+	return 0;
+}
+
+static int
+seekmode(lua_State *L, char *s)
+{
+	if(strcmp(s, "set") == 0)
+		return 0;
+	if(strcmp(s, "cur") == 0)
+		return 1;
+	if(strcmp(s, "end") == 0)
+		return 2;
+	return luaL_error(L, "unknown seek mode '%s'", s);
+}
+
+static int
+p9_file_seek(lua_State *L)
+{
+	int fd, type;
+	vlong n, off;
+	
+	fd = filefd(L, 1);
+	n = luaL_checkinteger(L, 2);
+	type = seekmode(L, luaL_optstring(L, 3, "set"));
+	if((off = seek(fd, n, type)) == -1)
+		return error(L, "seek: %r");
+	lua_pushinteger(L, off);
+	return 1;
+}
+
+static int
+p9_file_read(lua_State *L)
+{
+	int fd;
+	long n, nbytes;
+	vlong offset;
+	char *buf;
+	
+	fd = filefd(L, 1);
+	nbytes = luaL_optinteger(L, 2, Iosize);
+	offset = luaL_optinteger(L, 3, -1);
+	buf = getbuffer(L, nbytes);
+	if(offset == -1)
+		n = read(fd, buf, nbytes);
+	else
+		n = pread(fd, buf, nbytes, offset);
+	if(n == -1)
+		return error(L, "read: %r");
+	lua_pushlstring(L, buf, n);
+	return 1;
+}
+
+static int
+slurp(lua_State *L, int fd, long nbytes)
+{
+	int all;
+	long n, nr, tot;
+	char *buf;
+	luaL_Buffer b;
+	
+	all = (nbytes == -1) ? 1 : 0;
+	luaL_buffinit(L, &b);
+	for(tot = 0; all || tot < nbytes; tot += nr){
+		n = all ? Iosize : min(Iosize, nbytes - tot);
+		buf = luaL_prepbuffsize(&b, n);
+		if((nr = read(fd, buf, n)) == -1)
+			return error(L, "read: %r");
+		if(nr == 0)
+			break;
+		luaL_addsize(&b, nr);
+	}
+	luaL_pushresult(&b);
+	return 1;
+}
+
+static int
+p9_file_slurp(lua_State *L)
+{
+	int fd;
+	long nbytes;
+	
+	fd = filefd(L, 1);
+	nbytes = luaL_optinteger(L, 2, -1);
+	slurp(L, fd, nbytes);
+	return 1;
+}
+
+static int
+p9_file_write(lua_State *L)
+{
+	lua_Integer fd, offset;
+	size_t nbytes;
+	const char *buf;
+	long n;
+
+	fd = filefd(L, 1);
+	buf = luaL_checklstring(L, 2, &nbytes);
+	nbytes = luaL_optinteger(L, 3, nbytes);
+	offset = luaL_optinteger(L, 4, -1);
+	if(offset == -1)
+		n = write(fd, buf, nbytes);
+	else
+		n = pwrite(fd, buf, nbytes, offset);
+	if(n != nbytes)
+		return error(L, "write: %r");
+	lua_pushinteger(L, n);
+	return 1;
+}
+
+static int
+p9_file_path(lua_State *L)
+{
+	int fd;
+	char *buf;
+	
+	fd = filefd(L, 1);
+	buf = getbuffer(L, Iosize);
+	if(fd2path(fd, buf, Iosize) != 0)
+		return error(L, "fd2path: %r");
+	lua_pushstring(L, buf);
+	return 1;
+}
+
+static int
+p9_file_iounit(lua_State *L)
+{
+	int fd;
+	
+	fd = filefd(L, 1);
+	lua_pushinteger(L, iounit(fd));
+	return 1;
+}
+
+static int
+p9_file_dup(lua_State *L)
+{
+	int fd, new, na;
+	
+	na = lua_gettop(L);
+	fd = filefd(L, 1);
+	if(na == 2)
+		new = filefd(L, 2);
+	else
+		new = -1;
+	if((new = dup(fd, new)) == -1)
+		return error(L, "dup: %r");
+	if(na == 2){
+		lua_pushinteger(L, new);
+		lua_setfield(L, 2, "fd");
+		return 1;
+	}
+	return filenew(L, new);
+}
+
+static int
+p9_remove(lua_State *L)
+{
+	const char *file;
+	
+	file = luaL_checkstring(L, 1);
+	if(remove(file) == -1)
+		return error(L, "remove: %r");
+	lua_pushboolean(L, 1);
+	return 1;
+}
+
+static int
+p9_pipe(lua_State *L)
+{
+	int fd[2];
+	
+	if(pipe(fd) == -1)
+		return error(L, "pipe: %r");
+	filenew(L, fd[0]);
+	filenew(L, fd[1]);
+	return 2;
+}
+
+static int
+accessmode(lua_State *L, const char *s)
+{
+	int i, n, mode;
+	char buf[64], *f[10], *p;
+	
+	snprint(buf, sizeof buf, "%s", s);
+	n = getfields(buf, f, sizeof f, 1, " \t\n");
+	mode = 0;
+	for(i = 0; p = f[i], i < n; i++){
+		if(strcmp(p, "exist") == 0 || strcmp(p, "exists") == 0)
+			mode |= AEXIST;
+		else if(strcmp(p, "r") == 0 || strcmp(p, "read") == 0)
+			mode |= AREAD;
+		else if(strcmp(p, "w") == 0 || strcmp(p, "write") == 0)
+			mode |= AWRITE;
+		else if(strcmp(p, "rw") == 0 || strcmp(p, "rdwr") == 0)
+			mode |= AREAD|AWRITE;
+		else if(strcmp(p, "x") == 0 || strcmp(p, "exec") == 0)
+			mode |= AEXEC;
+		else
+			return luaL_error(L, "unknown access flag '%s'", p);
+	}
+	return mode;
+}
+
+static int
+p9_access(lua_State *L)
+{
+	const char *path;
+	int mode;
+
+	path = luaL_checkstring(L, 1);
+	mode = accessmode(L, luaL_optstring(L, 2, "exists"));
+	lua_pushboolean(L, 
+		access(path, mode) == 0 ? 1 : 0
+	);
+	return 1;
+}
--- /dev/null
+++ b/base/misc.c
@@ -1,0 +1,17 @@
+/* Nowhere in particular */
+
+static int
+p9_cleanname(lua_State *L)
+{
+	const char *path, *p;
+	lua_Unsigned len;
+	luaL_Buffer b;
+	
+	path = luaL_checklstring(L, 1, &len);
+	luaL_buffinit(L, &b);
+	luaL_addlstring(&b, path, len);
+	luaL_addchar(&b, '\0');
+	p = cleanname(luaL_buffaddr(&b));
+	lua_pushstring(L, p);
+	return 1;
+}
--- /dev/null
+++ b/base/ns.c
@@ -1,0 +1,88 @@
+static int
+parsemntflags(lua_State *L, char *s)
+{
+	int flags, n;
+	char *f[4], buf[128];
+	
+	flags = MREPL;
+	n = getfields(s, f, sizeof f, 1, " \t\r\n");
+	if(n > 0) for(int i = 0; i < n; i++){
+		if     (strcmp(f[i], "after") == 0)
+			flags |= MAFTER;
+		else if(strcmp(f[i], "before") == 0)
+			flags |= MBEFORE;
+		else if(strcmp(f[i], "create") == 0)
+			flags |= MCREATE;
+		else if(strcmp(f[i], "cache") == 0)
+			flags |= MCACHE;
+		else{
+			snprint(buf, sizeof buf, "skipping unknown mount flag '%s'", f[i]);
+			lua_warning(L, buf, 0);
+		}
+	}
+	return flags;
+}
+
+static int
+p9_bind(lua_State *L)
+{
+	const char *this, *over;
+	int flags, r;
+	
+	this = luaL_checkstring(L, 1);
+	over = luaL_checkstring(L, 2);
+	flags = parsemntflags(L, luaL_optstring(L, 3, ""));
+	if((r = bind(this, over, flags)) == -1)
+		return error(L, "bind: %r");
+	lua_pushinteger(L, r);
+	return 1;
+}
+
+static int
+p9_mount(lua_State *L)
+{
+	const char *over, *aname;
+	int fd, afd, closefd, flags, r;
+	
+	closefd = -1;
+	switch(lua_type(L, 1)){
+	default:
+		return luaL_typeerror(L, 1, "file descriptor or path");
+	case LUA_TNUMBER:
+		fd = lua_tointeger(L, 1); break;
+	case LUA_TSTRING:
+		if((fd = open(lua_tostring(L, 1), ORDWR)) == -1)
+			return error(L, "open: %r");
+		closefd = fd;
+		break;
+	}
+	over = luaL_checkstring(L, 2);
+	flags = parsemntflags(L, luaL_optstring(L, 3, ""));
+	aname = luaL_optstring(L, 4, "");
+	afd = luaL_optinteger(L, 5, -1);
+	if((r = mount(fd, afd, over, flags, aname)) == -1){
+		close(closefd);
+		return error(L, "mount: %r");
+	}
+	close(closefd);
+	lua_pushinteger(L, r);
+	return 1;
+}
+
+static int
+p9_unmount(lua_State *L)
+{
+	const char *name, *over;
+	int r;
+	
+	name = luaL_checkstring(L, 1);
+	over = luaL_optstring(L, 2, nil);
+	if(over == nil){
+		over = name;
+		name = nil;
+	}
+	if((r = unmount(name, over)) == -1)
+		return error(L, "unmount: %r");
+	lua_pushinteger(L, r);
+	return 1;
+}
--- /dev/null
+++ b/base/proc.c
@@ -1,0 +1,193 @@
+static int
+p9_abort(lua_State*)
+{
+	abort();
+	/* never */ return 0;
+}
+
+static int
+p9_exits(lua_State *L)
+{
+	exits(luaL_optstring(L, 1, nil));
+	/* never */ return 0;
+}
+
+static int
+p9_fatal(lua_State *L)
+{
+	if(lua_gettop(L) > 1
+	&& lua_getglobal(L, "string") == LUA_TTABLE
+	&& lua_getfield(L, -1, "format") == LUA_TFUNCTION){
+		lua_remove(L, -2);
+		lua_insert(L, 1);
+		if(lua_pcall(L, lua_gettop(L) - 1, 1, 0) == LUA_OK)
+			sysfatal(lua_tostring(L, -1));
+	}
+	sysfatal(luaL_optstring(L, 1, "fatal"));
+	/* never */ return 0;
+}
+
+static int
+p9_sleep(lua_State *L)
+{
+	long t;
+	
+	t = luaL_checkinteger(L, 1);
+	lua_pushboolean(L,
+		sleep(t) == -1 ? 0 : 1
+	);
+	return 1;
+}
+
+static int
+p9_alarm(lua_State *L)
+{
+	long t, rem;
+	
+	t = luaL_checkinteger(L, 1);
+	rem = alarm(t);
+	lua_pushinteger(L, rem);
+	return 1;
+}
+
+static int
+p9_wdir(lua_State *L)
+{
+	const char *path;
+	char *buf;
+	luaL_Buffer b;
+	
+	path = luaL_optstring(L, 1, nil);
+	if(path != nil){
+		if(chdir(path) == -1)
+			return error(L, "chdir: %r");
+		lua_pushboolean(L, 1);
+		return 1;
+	}
+	luaL_buffinitsize(L, &b, Iosize);
+	buf = luaL_buffaddr(&b);
+	if(getwd(buf, Iosize) == nil)
+		return error(L, "getwd: %r");
+	luaL_pushresultsize(&b, strlen(buf));
+	return 1;
+}
+
+static int
+p9_user(lua_State *L)
+{
+	lua_pushstring(L, getuser());
+	return 1;
+}
+
+static int
+p9_sysname(lua_State *L)
+{
+	lua_pushstring(L, sysname());
+	return 1;
+}
+
+static int
+p9_pid(lua_State *L)
+{
+	lua_pushinteger(L, getpid());
+	return 1;
+}
+
+static int
+p9_ppid(lua_State *L)
+{
+	lua_pushinteger(L, getppid());
+	return 1;
+}
+
+static int
+p9_rfork(lua_State *L)
+{
+	int flags, i, n, r;
+	char *f[12];
+	
+	flags = RFENVG|RFNAMEG|RFNOTEG;
+	n = getfields(luaL_optstring(L, 1, ""), f, sizeof f, 0, " \t\n");
+	if(n > 0) for(flags = 0, i = 0; i < n; i++){
+		if     (strcmp(f[i], "name") == 0)
+			flags |= RFNAMEG;
+		else if(strcmp(f[i], "cname") == 0)
+			flags |= RFCNAMEG;
+		else if(strcmp(f[i], "env") == 0)
+			flags |= RFENVG;
+		else if(strcmp(f[i], "cenv") == 0)
+			flags |= RFCENVG;
+		else if(strcmp(f[i], "note") == 0)
+			flags |= RFNOTEG;
+		else if(strcmp(f[i], "fd") == 0)
+			flags |= RFFDG;
+		else if(strcmp(f[i], "cfd") == 0)
+			flags |= RFCFDG;
+		else if(strcmp(f[i], "nomnt") == 0)
+			flags |= RFNOMNT;
+		else if(strcmp(f[i], "proc") == 0)
+			flags |= RFPROC;
+		else if(strcmp(f[i], "nowait") == 0)
+			flags |= RFNOWAIT;
+		else if(strcmp(f[i], "rend") == 0)
+			flags |= RFREND;
+		else if(strcmp(f[i], "mem") == 0)
+			flags |= RFMEM;
+		else
+			return luaL_error(L, "unknown rfork flag '%s'", f[i]);
+	}
+	if((r = rfork(flags)) == -1)
+		return error(L, "rfork %r");
+	lua_pushinteger(L, r);
+	return 1;
+}
+
+static int
+p9_exec(lua_State *L)
+{
+	int argc, i;
+	const char **argv, *p;
+	char buf[Smallbuf];
+	
+	argc = lua_gettop(L);
+	if(argc < 1)
+		luaL_argerror(L, 1, "string arguments expected");
+	argv = lalloc(L, nil, (argc+1) * sizeof(char*));
+	for(i = 1; i <= argc; i++)
+		argv[i-1] = luaL_checkstring(L, i);
+	argv[argc] = nil;
+	p = argv[0];
+	if(p[0] != '/' && (p[0] != '.' && p[1] != '/')){
+		snprint(buf, sizeof buf, "/bin/%s", argv[0]);
+		argv[0] = buf;
+	}
+	exec(argv[0], argv);
+	free(argv);
+	return error(L, "exec: %r");
+}
+
+static int
+p9_wait(lua_State *L)
+{
+	Waitmsg *w;
+	
+	w = wait();
+	if(w == nil)
+		return error(L, "wait: %r");
+	lua_pushboolean(L, w->msg[0] == 0 ? 1 : 0);
+	lua_createtable(L, 0, 3);
+		lua_pushinteger(L, w->pid);
+			lua_setfield(L, -2, "pid");
+		lua_pushstring(L, w->msg);
+			lua_setfield(L, -2, "status");
+		lua_createtable(L, 3, 0);
+			lua_pushinteger(L, w->time[0]);
+				lua_setfield(L, -2, "user");
+			lua_pushinteger(L, w->time[1]);
+				lua_setfield(L, -2, "system");
+			lua_pushinteger(L, w->time[2]);
+				lua_setfield(L, -2, "real");
+		lua_setfield(L, -2, "time");
+	free(w);
+	return 2;
+}
--- /dev/null
+++ b/base/walk.c
@@ -1,0 +1,231 @@
+static char*
+perms(int p, char *buf)
+{
+	buf[0] = p & 04 ? 'r' : '-';
+	buf[1] = p & 02 ? 'w' : '-';
+	buf[2] = p & 01 ? 'x' : '-';
+	return buf;
+}
+
+static void
+createdirtable(lua_State *L, Dir *d)
+{
+	#define set(t, k, v) do { \
+		lua_pushstring(L, (k)); \
+		lua_push##t(L, (v)); \
+		lua_rawset(L, -3); \
+	} while(0)
+	
+	lua_createtable(L, 0, 11);
+	set(integer, "type", d->type);
+	set(integer, "dev", d->dev);
+	set(integer, "atime", d->atime);
+	set(integer, "mtime", d->mtime);
+	set(integer, "length", d->length);
+	set(string, "name", d->name);
+	set(string, "uid", d->uid);
+	set(string, "gid", d->gid);
+	set(string, "muid", d->muid);
+	
+	lua_pushstring(L, "qid");
+	lua_createtable(L, 0, 3);
+	set(integer, "path", d->qid.path);
+	set(integer, "vers", d->qid.vers);
+	set(integer, "type", d->qid.type);
+	lua_rawset(L, -3);
+	
+	lua_pushstring(L, "mode");
+	lua_createtable(L, 0, 7);
+	ulong m = d->mode;
+	set(integer, "raw", m);
+	if(m & DMDIR)
+		set(boolean, "dir", 1);
+	else
+		set(boolean, "file", 1);
+	if(m & DMAPPEND)
+		set(boolean, "append", 1);
+	if(m & DMTMP)
+		set(boolean, "tmp", 1);
+	if(m & DMMOUNT)
+		set(boolean, "mount", 1);
+	if(m & DMAUTH)
+		set(boolean, "auth", 1);
+	char buf[10] = {0};
+	set(string, "user", perms((m & 0700) >> 6, buf));
+	set(string, "group", perms((m & 0070) >> 3, buf+3));
+	set(string, "other", perms((m & 0007) >> 0, buf+6));
+	set(string, "perm", buf);
+	lua_rawset(L, -3);
+	
+	#undef set
+}
+
+static int
+p9_stat(lua_State *L)
+{
+	Dir *d;
+	
+	d = nil;
+	switch(lua_type(L, 1)){
+	default:
+		USED(d);
+		return luaL_typeerror(L, 1, "string or number");
+	case LUA_TSTRING:
+		d = dirstat(lua_tostring(L, 1)); break;
+	case LUA_TNUMBER:
+		d = dirfstat(lua_tonumber(L, 1)); break;
+	}
+	if(d == nil)
+		return error(L, "stat: %r");
+	createdirtable(L, d);
+	free(d);
+	return 1;
+}
+
+typedef struct Walk {
+	int fd;
+	int nleft;
+	Dir *dirs, *p;
+} Walk;
+
+static int
+p9_walk(lua_State *L)
+{
+	static int p9_walkout(lua_State*);
+	static int p9_walknext(lua_State*);
+	int nargs, wstate;
+	Dir *d;
+	Walk *w;
+	
+	nargs = lua_gettop(L);
+	w = lua_newuserdatauv(L, sizeof(Walk), 1);
+	wstate = nargs + 1;
+	w->fd = -1;
+	w->nleft = 0;
+	w->dirs = w->p = nil;
+	luaL_setmetatable(L, "p9-Walk");
+	if(nargs == 2){
+		lua_pushvalue(L, 2);
+		lua_setiuservalue(L, wstate, 1);
+	}
+	if(lua_isnumber(L, 1))
+		w->fd = lua_tointeger(L, 1);
+	else{
+		if((w->fd = open(luaL_checkstring(L, 1), OREAD|OCEXEC)) == -1){
+			error(L, "open: %r");
+			goto Error;
+		}
+	}
+	if((d = dirfstat(w->fd)) == nil){
+		error(L, "stat: %r");
+		goto Error;
+	}
+	int isdir = d->mode & DMDIR;
+	free(d);
+	if(!isdir){
+		error(L, "walk in a non-directory");
+		goto Error;
+	}
+	/* return p9_walknext, p9-Walk, nil, p9-Walk */
+	lua_pushcfunction(L, p9_walknext);
+	lua_pushvalue(L, wstate);
+	lua_pushnil(L);
+	lua_pushvalue(L, wstate);
+	return 4;
+Error:
+	if(nargs == 2){
+		lua_setfield(L, 2, "error");
+		lua_pushcfunction(L, p9_walkout);
+		return 1;
+	}
+	return lua_error(L);
+}
+
+static int
+p9_walkout(lua_State*)
+{
+	return 0;
+}
+
+static int
+p9_walknext(lua_State *L)
+{
+	Walk *w;
+	Dir *d;
+	
+	w = luaL_checkudata(L, 1, "p9-Walk");
+	if(w->nleft == 0){
+		if(w->dirs != nil){
+			free(w->dirs);
+			w->dirs = nil;
+		}
+		if((w->nleft = dirread(w->fd, &w->dirs)) == -1){
+			error(L, "dirread: %r");
+			goto Error;
+		}
+		w->p = w->dirs;
+		if(w->nleft == 0)
+			return 0; /* Last Walk state will be closed */
+	}
+	w->nleft--;
+	d = w->p++;
+	createdirtable(L, d);
+	return 1;
+Error:
+	if(lua_getiuservalue(L, 1, 1) == LUA_TTABLE){
+		lua_pushvalue(L, -2);
+		lua_setfield(L, -2, "error");
+		lua_pushnil(L);
+		return 1;
+	}
+	lua_pop(L, 1);
+	return 2;
+}
+
+static int
+p9_walkclose(lua_State *L)
+{
+	Walk *w;
+	
+	w = luaL_checkudata(L, 1, "p9-Walk");
+	if(w->dirs != nil){
+		free(w->dirs);
+		w->dirs = nil;
+	}
+	if(w->fd != -1){
+		close(w->fd);
+		w->fd = -1;
+	}
+	return 0;
+}
+
+static int
+p9_wstat(lua_State *L)
+{
+	static int createperm(lua_State*, char*); /* from fs.c */
+	const char *path, *k;
+	Dir new;
+	
+	path = luaL_checkstring(L, 1);
+	luaL_argexpected(L, lua_type(L, 2) == LUA_TTABLE, 2, "table");
+	nulldir(&new);
+	lua_pushnil(L);
+	while(lua_next(L, -2)){
+		k = lua_tostring(L, -2);
+		if(strcmp(k, "name") == 0)
+			new.name = (char*)lua_tostring(L, -1); /* dw */
+		else if(strcmp(k, "mode") == 0)
+			new.mode = createperm(L, lua_tostring(L, -1));
+		else if(strcmp(k, "mtime") == 0)
+			new.mtime = lua_tointeger(L, -1);
+		else if(strcmp(k, "gid") == 0)
+			new.gid = (char*)lua_tostring(L, -1);
+		else if(strcmp(k, "length") == 0)
+			new.length = lua_tointeger(L, -1);
+		lua_pop(L, 1);
+	}
+	if(dirwstat(path, &new) == -1)
+		return error(L, "wstat: %r");
+	lua_pushboolean(L, 1);
+	return 1;
+}
--- a/env.c
+++ /dev/null
@@ -1,133 +1,0 @@
-/* Environment variables
- *
- * p9.env object provides a map between the /env device and Lua,
- * with its dynamic fields representing the environment variables.
- * Assigning a value to the field writes to the environment:
- *
- * 	p9.env.var = "value"
- *
- * while reading a value reads from the environment:
- *
- * 	assert(p9.env.var == "value")
- *
- * A value can be a string or a list.
- * A list is encoded (decoded) to (from) the environment as a
- * list of strings according to the encoding used by the rc(1)
- * shell (0-byte separated fields).
- *
- *	lua> p9.env.list = {"a", "b", "c"}
- *	rc> echo $#list  * $list
- *	3  * a b c
- *
- * p9.getenv(name) and p9.setenv(name, val) provide the more
- * usual API.
- */
-
-static int
-p9_getenv(lua_State *L)
-{
-	int fd;
-	long len, elems, i;
-	char env[Smallbuf];
-	const char *buf, *s, *e;
-	
-	snprint(env, sizeof env, "/env/%s", luaL_checkstring(L, 1));
-	if((fd = open(env, OREAD)) == -1){
-		lua_pushnil(L);
-		return 1;
-	}
-	slurp(L, fd, -1);
-	close(fd);
-	len = luaL_len(L, -1);
-	buf = lua_tostring(L, -1);
-	/* Empty */
-	if(len == 0){
-		lua_pushnil(L);
-		return 1;
-	}
-	/* String (not a list) */
-	if(buf[len-1] != '\0')
-		return 1;
-	/* List */
-	for(elems = i = 0; i < len; i++)
-		if(buf[i] == '\0') elems++;
-	s = buf;
-	e = buf + len;
-	lua_createtable(L, elems, 0);
-	for(i = 1; s < e; i++){
-		lua_pushstring(L, s);
-		lua_rawseti(L, -2, i);
-		s = memchr(s, '\0', e - s);
-		s++;
-	}
-	return 1;
-}
-
-static int
-p9_getenv_index(lua_State *L)
-{
-	lua_remove(L, 1);
-	return p9_getenv(L);
-}
-
-static int
-p9_setenv(lua_State *L)
-{
-	int fd, ntab, n, t, i;
-	char env[Smallbuf];
-	luaL_Buffer b;
-	
-	snprint(env, sizeof env, "/env/%s", luaL_checkstring(L, 1));
-	t = lua_type(L, 2);
-	if(t != LUA_TNIL && t != LUA_TSTRING && t != LUA_TTABLE)
-		return luaL_argerror(L, 2, "nil, string, or table expected");
-	if((fd = create(env, OWRITE, 0644)) == -1)
-		return error(L, "open: %r");
-	/*
-	 * Writes below are not fully checked for
-	 * error (write(n) != n), because env(3)
-	 * may truncate the value at its dumb 16k
-	 * limit. Encoding a knowledge of this limit
-	 * sucks, as does env(3).
-	 */
-	if(t == LUA_TNIL){
-		close(fd);
-		return 0;
-	}else if(t == LUA_TSTRING){
-		n = luaL_len(L, 2);
-		if(write(fd, lua_tostring(L, 2), n) == -1){
-			close(fd);
-			return error(L, "write: %r");
-		}
-	}else{
-		ntab = luaL_len(L, 2);
-		luaL_buffinit(L, &b);
-		for(i = 1; i <= ntab; i++){
-			t = lua_geti(L, 2, i);
-			if(t != LUA_TSTRING){
-				if(luaL_callmeta(L, -1, "__tostring")
-				&& lua_type(L, -1) == LUA_TSTRING){
-					lua_replace(L, -2);
-				}else{
-					lua_pop(L, 1);
-					continue;
-				}
-			}
-			luaL_addvalue(&b);
-			luaL_addchar(&b, '\0');
-		}
-		if(write(fd, luaL_buffaddr(&b), luaL_bufflen(&b)) == -1){
-			close(fd);
-			return error(L, "write: %r");
-		}
-	}
-	close(fd);
-	return 0;
-}
-
-static int
-p9_setenv_newindex(lua_State *L)
-{
-	lua_remove(L, 1);
-	return p9_setenv(L);
-}
--- a/fs.c
+++ /dev/null
@@ -1,398 +1,0 @@
-/*
- * The File object
-
- * p9.file(fd) takes an open file descriptor and returns a
- * File object f which provides a convenient method interface
- * to the usual file operations.
- * p9.open and p9.create take a file name to open or create.
-
- * The file descriptor stored in f.fd is garbage collected,
- * that is, it will be automatically closed once the File
- * object becomes unreachable. Note how this means that f.fd
- * should be used sparringly and with much care. In particular
- * you shouldn't store it outside of f, since the actual file
- * descriptor number might become invalid (closed) or refer
- * to a completely different file after f is collected.
- *
- * Store the File object in some global place to prevent it
- * from being collected.
- */
- 
-static int
-openmode(lua_State *L, char *s)
-{
-	int i, n, mode;
-	char r, w, x;
-	char buf[64], *f[10], *p;
-	
-	snprint(buf, sizeof buf, "%s", s);
-	mode = r = w = x = 0;
-	n = getfields(buf, f, sizeof f, 1, " \t\n");
-	if(n < 1)
-		return OREAD;
-	for(i = 0; p = f[i], i < n; i++){
-		if(strcmp(p, "r") == 0 || strcmp(p, "read") == 0)
-			r = 1;
-		else if(strcmp(p, "w") == 0 || strcmp(p, "write") == 0)
-			w = 1;
-		else if(strcmp(p, "rw") == 0 || strcmp(p, "rdwr") == 0)
-			r = w = 1;
-		else if(strcmp(p, "x") == 0 || strcmp(p, "exec") == 0)
-			x = 1;
-		else if(strcmp(p, "trunc") == 0)
-			mode |= OTRUNC;
-		else if(strcmp(p, "cexec") == 0)
-			mode |= OCEXEC;
-		else if(strcmp(p, "rclose") == 0)
-			mode |= ORCLOSE;
-		else if(strcmp(p, "excl") == 0)
-			mode |= OEXCL;
-		else
-			return luaL_error(L, "unknown mode flag '%s'", p);
-	}
-	if(x) mode |= OEXEC;
-	else if(r && w) mode |= ORDWR;
-	else if(r) mode |= OREAD;
-	else if(w) mode |= OWRITE;
-	return mode;
-}
-
-static ulong
-createperm(lua_State *L, char *s)
-{
-	int i, n;
-	ulong perm;
-	char buf[64], *f[10], *p;
-	
-	snprint(buf, sizeof buf, "%s", s);
-	perm = 0;
-	n = getfields(buf, f, sizeof f, 1, " \t\n");
-	if(n < 1)
-		return 0644;
-	for(i = 0; p = f[i], i < n; i++){
-		if(isdigit(p[0]))
-			perm |= strtol(p, nil, 8);
-		else if(strcmp(p, "dir") == 0)
-			perm |= DMDIR;
-		else if(strcmp(p, "tmp") == 0)
-			perm |= DMTMP;
-		else if(strcmp(p, "excl") == 0)
-			perm |= DMEXCL;
-		else if(strcmp(p, "append") == 0)
-			perm |= DMAPPEND;
-		else
-			return luaL_error(L, "unknown permission flag '%s'", p);
-	}
-	return perm;
-}
-
-static int filenew(lua_State*, int);
-static int fileclose(lua_State*);
-static int filefd(lua_State*, int);
-
-static int
-filenew(lua_State *L, int fd)
-{
-	int f;
-
-	lua_createtable(L, 0, 4);
-	f = lua_gettop(L);
-	lua_pushinteger(L, fd);
-		lua_setfield(L, f, "fd");
-	luaL_getmetatable(L, "p9-File");
-		lua_setfield(L, f, "__index");
-	lua_pushcfunction(L, fileclose);
-		lua_setfield(L, f, "__close");
-	lua_pushcfunction(L, fileclose);
-		lua_setfield(L, f, "__gc");
-	lua_pushvalue(L, f);
-		lua_setmetatable(L, f);
-	return 1;
-}
-
-static int
-fileclose(lua_State *L)
-{
-	int fd;
-	
-	fd = filefd(L, 1);
-	if(fd == -1)
-		return 0;
-	lua_pushinteger(L, -1);
-		lua_setfield(L, 1, "fd");
-	close(fd);
-	return 0;
-}
-
-static int
-filefd(lua_State *L, int idx)
-{
-	int fd;
-	
-	if(lua_getfield(L, idx, "fd") != LUA_TNUMBER)
-		return luaL_error(L, "fd must be integer");
-	fd = lua_tonumber(L, -1);
-	lua_pop(L, 1);
-	return fd;
-}
-
-static int
-p9_file(lua_State *L)
-{
-	int fd;
-	
-	fd = luaL_checkinteger(L, 1);
-	return filenew(L, fd);
-}
-
-static int
-p9_open(lua_State *L)
-{
-	const char *file;
-	int mode;
-	int fd;
-	
-	file = luaL_checkstring(L, 1);
-	mode = openmode(L, luaL_optstring(L, 2, "read"));
-	if((fd = open(file, mode)) == -1)
-		return error(L, "open: %r");
-	return filenew(L, fd);
-}
-
-static int
-p9_create(lua_State *L)
-{
-	const char *file;
-	int fd, mode;
-	ulong perm;
-	
-	file = luaL_checkstring(L, 1);
-	mode = openmode(L, luaL_optstring(L, 2, "rdwr"));
-	perm = createperm(L, luaL_optstring(L, 3, "644"));
-	if((fd = create(file, mode, perm)) == -1)
-		return error(L, "create: %r");
-	return filenew(L, fd);
-}
-
-static int
-p9_file_close(lua_State *L)
-{
-	if(close(filefd(L, 1)) == -1)
-		return error(L, "close: %r");
-	return 0;
-}
-
-static int
-seekmode(lua_State *L, char *s)
-{
-	if(strcmp(s, "set") == 0)
-		return 0;
-	if(strcmp(s, "cur") == 0)
-		return 1;
-	if(strcmp(s, "end") == 0)
-		return 2;
-	return luaL_error(L, "unknown seek mode '%s'", s);
-}
-
-static int
-p9_file_seek(lua_State *L)
-{
-	int fd, type;
-	vlong n, off;
-	
-	fd = filefd(L, 1);
-	n = luaL_checkinteger(L, 2);
-	type = seekmode(L, luaL_optstring(L, 3, "set"));
-	if((off = seek(fd, n, type)) == -1)
-		return error(L, "seek: %r");
-	lua_pushinteger(L, off);
-	return 1;
-}
-
-static int
-p9_file_read(lua_State *L)
-{
-	int fd;
-	long n, nbytes;
-	vlong offset;
-	char *buf;
-	
-	fd = filefd(L, 1);
-	nbytes = luaL_optinteger(L, 2, Iosize);
-	offset = luaL_optinteger(L, 3, -1);
-	buf = getbuffer(L, nbytes);
-	if(offset == -1)
-		n = read(fd, buf, nbytes);
-	else
-		n = pread(fd, buf, nbytes, offset);
-	if(n == -1)
-		return error(L, "read: %r");
-	lua_pushlstring(L, buf, n);
-	return 1;
-}
-
-static int
-slurp(lua_State *L, int fd, long nbytes)
-{
-	int all;
-	long n, nr, tot;
-	char *buf;
-	luaL_Buffer b;
-	
-	all = (nbytes == -1) ? 1 : 0;
-	luaL_buffinit(L, &b);
-	for(tot = 0; all || tot < nbytes; tot += nr){
-		n = all ? Iosize : min(Iosize, nbytes - tot);
-		buf = luaL_prepbuffsize(&b, n);
-		if((nr = read(fd, buf, n)) == -1)
-			return error(L, "read: %r");
-		if(nr == 0)
-			break;
-		luaL_addsize(&b, nr);
-	}
-	luaL_pushresult(&b);
-	return 1;
-}
-
-static int
-p9_file_slurp(lua_State *L)
-{
-	int fd;
-	long nbytes;
-	
-	fd = filefd(L, 1);
-	nbytes = luaL_optinteger(L, 2, -1);
-	slurp(L, fd, nbytes);
-	return 1;
-}
-
-static int
-p9_file_write(lua_State *L)
-{
-	lua_Integer fd, offset;
-	size_t nbytes;
-	const char *buf;
-	long n;
-
-	fd = filefd(L, 1);
-	buf = luaL_checklstring(L, 2, &nbytes);
-	nbytes = luaL_optinteger(L, 3, nbytes);
-	offset = luaL_optinteger(L, 4, -1);
-	if(offset == -1)
-		n = write(fd, buf, nbytes);
-	else
-		n = pwrite(fd, buf, nbytes, offset);
-	if(n != nbytes)
-		return error(L, "write: %r");
-	lua_pushinteger(L, n);
-	return 1;
-}
-
-static int
-p9_file_path(lua_State *L)
-{
-	int fd;
-	char *buf;
-	
-	fd = filefd(L, 1);
-	buf = getbuffer(L, Iosize);
-	if(fd2path(fd, buf, Iosize) != 0)
-		return error(L, "fd2path: %r");
-	lua_pushstring(L, buf);
-	return 1;
-}
-
-static int
-p9_file_iounit(lua_State *L)
-{
-	int fd;
-	
-	fd = filefd(L, 1);
-	lua_pushinteger(L, iounit(fd));
-	return 1;
-}
-
-static int
-p9_file_dup(lua_State *L)
-{
-	int fd, new, na;
-	
-	na = lua_gettop(L);
-	fd = filefd(L, 1);
-	if(na == 2)
-		new = filefd(L, 2);
-	else
-		new = -1;
-	if((new = dup(fd, new)) == -1)
-		return error(L, "dup: %r");
-	if(na == 2){
-		lua_pushinteger(L, new);
-		lua_setfield(L, 2, "fd");
-		return 1;
-	}
-	return filenew(L, new);
-}
-
-static int
-p9_remove(lua_State *L)
-{
-	const char *file;
-	
-	file = luaL_checkstring(L, 1);
-	if(remove(file) == -1)
-		return error(L, "remove: %r");
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int
-p9_pipe(lua_State *L)
-{
-	int fd[2];
-	
-	if(pipe(fd) == -1)
-		return error(L, "pipe: %r");
-	filenew(L, fd[0]);
-	filenew(L, fd[1]);
-	return 2;
-}
-
-static int
-accessmode(lua_State *L, const char *s)
-{
-	int i, n, mode;
-	char buf[64], *f[10], *p;
-	
-	snprint(buf, sizeof buf, "%s", s);
-	n = getfields(buf, f, sizeof f, 1, " \t\n");
-	mode = 0;
-	for(i = 0; p = f[i], i < n; i++){
-		if(strcmp(p, "exist") == 0 || strcmp(p, "exists") == 0)
-			mode |= AEXIST;
-		else if(strcmp(p, "r") == 0 || strcmp(p, "read") == 0)
-			mode |= AREAD;
-		else if(strcmp(p, "w") == 0 || strcmp(p, "write") == 0)
-			mode |= AWRITE;
-		else if(strcmp(p, "rw") == 0 || strcmp(p, "rdwr") == 0)
-			mode |= AREAD|AWRITE;
-		else if(strcmp(p, "x") == 0 || strcmp(p, "exec") == 0)
-			mode |= AEXEC;
-		else
-			return luaL_error(L, "unknown access flag '%s'", p);
-	}
-	return mode;
-}
-
-static int
-p9_access(lua_State *L)
-{
-	const char *path;
-	int mode;
-
-	path = luaL_checkstring(L, 1);
-	mode = accessmode(L, luaL_optstring(L, 2, "exists"));
-	lua_pushboolean(L, 
-		access(path, mode) == 0 ? 1 : 0
-	);
-	return 1;
-}
--- a/mkfile
+++ b/mkfile
@@ -3,18 +3,22 @@
 CFLAGS=-FTVw -p -I../lua/shim -I../lua
 
 LIB=libp9.a.$O
+MOD=\
+	base\
+	note
 
-OBJS=p9.$O
+default:V: all
 
-all:V: $LIB
+obj/:
+	mkdir -p $target
 
-clean:QV:
-	rm -f *.[$OS] *.a.[$OS]
+obj/%.$O: obj/ %/%.c
+	$CC $CFLAGS -o $target $stem/$stem.c
 
-$LIB: $OBJS
+$LIB: ${MOD:%=obj/%.$O}
 	ar cr $target $prereq
 
-%.$O: %.c
-	$CC $CFLAGS $stem.c
+all:V: $LIB
 
-p9.$O: p9.c fs.c walk.c env.c ns.c proc.c note.c misc.c
+clean:V:
+	rm -rf *.a.[$OS] obj/
--- a/note.c
+++ /dev/null
@@ -1,198 +1,0 @@
-/*
- * The following global state designates the Lua state
- * responsible for registering and running note handler
- * functions -- the one (and only) loading this module.
- * Additionally the note itself is communicated to the
- * postponed handler here due to most of Lua API not
- * being safe to use in a note handler context.
- * 
- * This global state and nondeterministic nature of
- * postponed handling of notes means this module should
- * be used with care and will likely need to be heavily
- * adapted for use in any but the simplest of hosts.
- * Lu9 standalone interpreter is an example of a simple
- * program with a single Lua state and Lua code being
- * "the boss", that is, the note is very likely to
- * interrupt a Lua VM rather than host code, and if not
- * the VM will be entered shortly after. This lets
- * postponed note handlers run relatively close to
- * the actual note event.
- * In more involved programs, perhaps running multiple
- * separate Lua states, or spending more time in the
- * host, the postponed handlers may run only as soon
- * as the designated handler state gets a chance to
- * run, if at all.
- * 
- * In short, consider alternatives to Lua code doing
- * any direct note handling.
- * 
- * TODO: the note state, catcher and exit functions,
- * and notify registration should all be left for the
- * host to set up.
- */
-
-typedef struct Note Note;
-
-struct Note {
-	lua_State *L;
-	char note[ERRMAX+1];
-};
-
-static Note notestate;
-
-/* 
- * Acks the note so it can be handled outside note context
- * but only after the possibly interrupted Lua state
- * stabilizes. This is done by registering an instruction
- * hook and running handler functions inside it.
- * Note that another note may come just as we are doing
- * this. We do nothing about it currently: the newer
- * notes simply interrupt currently executing handlers.
- * One solution is to queue incoming notes and handle
- * all of them in order.
- * Also note that this catcher always acknowledges the
- * note, preventing any other catchers registered after
- * it from ever seeing the note. Therefore you most
- * likely want it to be the last or only note handler.
- */
-static int
-notecatcher(void*, char *note)
-{
-	static void noterunner(lua_State*, lua_Debug*);
-	
-	lua_sethook(notestate.L, noterunner,
-		LUA_MASKCALL|LUA_MASKRET|LUA_MASKCOUNT, 1);
-	strncpy(notestate.note, note, ERRMAX);
-	return 1;
-}
-
-static void
-noterunner(lua_State *L, lua_Debug*)
-{
-	int n, i;
-	
-	lua_sethook(notestate.L, nil, 0, 0);
-	if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
-		luaL_error(L, "missing note handlers table");
-	if((n = lua_rawlen(L, -1)) == 0)
-		return;
-	for(i = 1; i <= n; i++){
-		lua_rawgeti(L, -1, i); /* handler(note) */
-		lua_pushstring(L, notestate.note);
-		if(lua_pcall(L, 1, 1, 0) != LUA_OK)
-			break;
-		if(lua_toboolean(L, -1) == 1)
-			return; /* to where we got interrupted */
-		lua_pop(L, 1);
-	}
-	/* Emulate kernel handling of unacknowledged note. */
-	if(strncmp(notestate.note, "sys:", 4) == 0)
-		abort();
-	exits(notestate.note); /* threadexitsall when using thread.h */
-}
-
-static int
-noteset(lua_State *L)
-{
-	if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
-		return luaL_error(L, "missing note handlers table");
-	lua_insert(L, -2);
-	lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
-	lua_pop(L, 1);
-	return 0;
-}
-
-static int
-noteunset(lua_State *L)
-{
-	int n, pos, fn, t;
-	
-	fn = lua_gettop(L);
-	if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
-		return luaL_error(L, "missing note handlers table");
-	t = fn + 1;
-	n = lua_rawlen(L, t);
-	for(pos = 1; pos <= n; pos++){
-		lua_rawgeti(L, t, pos);
-		if(lua_rawequal(L, fn, -1))
-			goto remove;
-		lua_pop(L, 1);
-	}
-	lua_pop(L, 2);
-	return 0;
-remove:
-	lua_pop(L, 1);
-  for ( ; pos < n; pos++) {
-    lua_rawgeti(L, t, pos + 1);
-    lua_rawseti(L, t, pos);
-  }
-  lua_pushnil(L);
-  lua_rawseti(L, t, pos);
-  lua_pop(L, 2);
-  return 0;
-}
-
-static int
-p9_note_catch(lua_State *L)
-{
-	int fn;
-	const char *arg;
-	
-	fn = lua_gettop(L);
-	luaL_argexpected(L, fn > 0, 1, "function expected");
-	if(fn == 1)
-		arg = "set";
-	else
-		arg = luaL_checkstring(L, 1);
-	luaL_argexpected(L,
-		lua_type(L, fn) == LUA_TFUNCTION, fn, "function");
-	if(strcmp(arg, "set") == 0)
-		return noteset(L);
-	else if(strcmp(arg, "unset") == 0)
-		return noteunset(L);
-	return luaL_error(L, "'set' or 'unset' expected");
-}
-
-static int
-p9_note_post(lua_State *L)
-{
-	int pid, w;
-	const char *who, *note;
-	
-	who = luaL_checkstring(L, 1);
-	if(strcmp(who, "proc") == 0)
-		w = PNPROC;
-	else if(strcmp(who, "group") == 0)
-		w = PNGROUP;
-	else
-		return luaL_argerror(L, 1, "expected 'proc' or 'group'");
-	pid = luaL_checkinteger(L, 2);
-	note = luaL_checkstring(L, 3);
-	if(postnote(w, pid, note) == -1)
-		return error(L, "postnote: %r");
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static luaL_Reg p9_note_funcs[] = {
-	{"post", p9_note_post},
-	{"catch", p9_note_catch},
-	{nil, nil},
-};
-
-int
-luaopen_p9_note(lua_State *L)
-{
-	/* Only one Lua state may work with notes */
-	if(notestate.L != nil)
-		return 0;
-	notestate.L = L;
-	
-	lua_createtable(L, 1, 0);
-	lua_setfield(L, LUA_REGISTRYINDEX, "p9-note-handlers");
-	
-	luaL_newlib(L, p9_note_funcs);
-	
-	atnotify(notecatcher, 1);
-	return 1;
-}
--- /dev/null
+++ b/note/note.c
@@ -1,0 +1,206 @@
+#include <u.h>
+#include <libc.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+
+#include "../base/common.c"
+
+/*
+ * The following global state designates the Lua state
+ * responsible for registering and running note handler
+ * functions -- the one (and only) loading this module.
+ * Additionally the note itself is communicated to the
+ * postponed handler here due to most of Lua API not
+ * being safe to use in a note handler context.
+ * 
+ * This global state and nondeterministic nature of
+ * postponed handling of notes means this module should
+ * be used with care and will likely need to be heavily
+ * adapted for use in any but the simplest of hosts.
+ * Lu9 standalone interpreter is an example of a simple
+ * program with a single Lua state and Lua code being
+ * "the boss", that is, the note is very likely to
+ * interrupt a Lua VM rather than host code, and if not
+ * the VM will be entered shortly after. This lets
+ * postponed note handlers run relatively close to
+ * the actual note event.
+ * In more involved programs, perhaps running multiple
+ * separate Lua states, or spending more time in the
+ * host, the postponed handlers may run only as soon
+ * as the designated handler state gets a chance to
+ * run, if at all.
+ * 
+ * In short, consider alternatives to Lua code doing
+ * any direct note handling.
+ * 
+ * TODO: the note state, catcher and exit functions,
+ * and notify registration should all be left for the
+ * host to set up.
+ */
+
+typedef struct Note Note;
+
+struct Note {
+	lua_State *L;
+	char note[ERRMAX+1];
+};
+
+static Note notestate;
+
+/* 
+ * Acks the note so it can be handled outside note context
+ * but only after the possibly interrupted Lua state
+ * stabilizes. This is done by registering an instruction
+ * hook and running handler functions inside it.
+ * Note that another note may come just as we are doing
+ * this. We do nothing about it currently: the newer
+ * notes simply interrupt currently executing handlers.
+ * One solution is to queue incoming notes and handle
+ * all of them in order.
+ * Also note that this catcher always acknowledges the
+ * note, preventing any other catchers registered after
+ * it from ever seeing the note. Therefore you most
+ * likely want it to be the last or only note handler.
+ */
+static int
+notecatcher(void*, char *note)
+{
+	static void noterunner(lua_State*, lua_Debug*);
+	
+	lua_sethook(notestate.L, noterunner,
+		LUA_MASKCALL|LUA_MASKRET|LUA_MASKCOUNT, 1);
+	strncpy(notestate.note, note, ERRMAX);
+	return 1;
+}
+
+static void
+noterunner(lua_State *L, lua_Debug*)
+{
+	int n, i;
+	
+	lua_sethook(notestate.L, nil, 0, 0);
+	if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
+		luaL_error(L, "missing note handlers table");
+	if((n = lua_rawlen(L, -1)) == 0)
+		return;
+	for(i = 1; i <= n; i++){
+		lua_rawgeti(L, -1, i); /* handler(note) */
+		lua_pushstring(L, notestate.note);
+		if(lua_pcall(L, 1, 1, 0) != LUA_OK)
+			break;
+		if(lua_toboolean(L, -1) == 1)
+			return; /* to where we got interrupted */
+		lua_pop(L, 1);
+	}
+	/* Emulate kernel handling of unacknowledged note. */
+	if(strncmp(notestate.note, "sys:", 4) == 0)
+		abort();
+	exits(notestate.note); /* threadexitsall when using thread.h */
+}
+
+static int
+noteset(lua_State *L)
+{
+	if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
+		return luaL_error(L, "missing note handlers table");
+	lua_insert(L, -2);
+	lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+	lua_pop(L, 1);
+	return 0;
+}
+
+static int
+noteunset(lua_State *L)
+{
+	int n, pos, fn, t;
+	
+	fn = lua_gettop(L);
+	if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
+		return luaL_error(L, "missing note handlers table");
+	t = fn + 1;
+	n = lua_rawlen(L, t);
+	for(pos = 1; pos <= n; pos++){
+		lua_rawgeti(L, t, pos);
+		if(lua_rawequal(L, fn, -1))
+			goto remove;
+		lua_pop(L, 1);
+	}
+	lua_pop(L, 2);
+	return 0;
+remove:
+	lua_pop(L, 1);
+  for ( ; pos < n; pos++) {
+    lua_rawgeti(L, t, pos + 1);
+    lua_rawseti(L, t, pos);
+  }
+  lua_pushnil(L);
+  lua_rawseti(L, t, pos);
+  lua_pop(L, 2);
+  return 0;
+}
+
+static int
+p9_note_catch(lua_State *L)
+{
+	int fn;
+	const char *arg;
+	
+	fn = lua_gettop(L);
+	luaL_argexpected(L, fn > 0, 1, "function expected");
+	if(fn == 1)
+		arg = "set";
+	else
+		arg = luaL_checkstring(L, 1);
+	luaL_argexpected(L,
+		lua_type(L, fn) == LUA_TFUNCTION, fn, "function");
+	if(strcmp(arg, "set") == 0)
+		return noteset(L);
+	else if(strcmp(arg, "unset") == 0)
+		return noteunset(L);
+	return luaL_error(L, "'set' or 'unset' expected");
+}
+
+static int
+p9_note_post(lua_State *L)
+{
+	int pid, w;
+	const char *who, *note;
+	
+	who = luaL_checkstring(L, 1);
+	if(strcmp(who, "proc") == 0)
+		w = PNPROC;
+	else if(strcmp(who, "group") == 0)
+		w = PNGROUP;
+	else
+		return luaL_argerror(L, 1, "expected 'proc' or 'group'");
+	pid = luaL_checkinteger(L, 2);
+	note = luaL_checkstring(L, 3);
+	if(postnote(w, pid, note) == -1)
+		return error(L, "postnote: %r");
+	lua_pushboolean(L, 1);
+	return 1;
+}
+
+static luaL_Reg p9_note_funcs[] = {
+	{"post", p9_note_post},
+	{"catch", p9_note_catch},
+	{nil, nil},
+};
+
+int
+luaopen_p9_note(lua_State *L)
+{
+	/* Only one Lua state may work with notes */
+	if(notestate.L != nil)
+		return 0;
+	notestate.L = L;
+	
+	lua_createtable(L, 1, 0);
+	lua_setfield(L, LUA_REGISTRYINDEX, "p9-note-handlers");
+	
+	luaL_newlib(L, p9_note_funcs);
+	
+	atnotify(notecatcher, 1);
+	return 1;
+}
--- a/ns.c
+++ /dev/null
@@ -1,88 +1,0 @@
-int
-parsemntflags(lua_State *L, char *s)
-{
-	int flags, n;
-	char *f[4], buf[128];
-	
-	flags = MREPL;
-	n = getfields(s, f, sizeof f, 1, " \t\r\n");
-	if(n > 0) for(int i = 0; i < n; i++){
-		if     (strcmp(f[i], "after") == 0)
-			flags |= MAFTER;
-		else if(strcmp(f[i], "before") == 0)
-			flags |= MBEFORE;
-		else if(strcmp(f[i], "create") == 0)
-			flags |= MCREATE;
-		else if(strcmp(f[i], "cache") == 0)
-			flags |= MCACHE;
-		else{
-			snprint(buf, sizeof buf, "skipping unknown mount flag '%s'", f[i]);
-			lua_warning(L, buf, 0);
-		}
-	}
-	return flags;
-}
-
-static int
-p9_bind(lua_State *L)
-{
-	const char *this, *over;
-	int flags, r;
-	
-	this = luaL_checkstring(L, 1);
-	over = luaL_checkstring(L, 2);
-	flags = parsemntflags(L, luaL_optstring(L, 3, ""));
-	if((r = bind(this, over, flags)) == -1)
-		return error(L, "bind: %r");
-	lua_pushinteger(L, r);
-	return 1;
-}
-
-static int
-p9_mount(lua_State *L)
-{
-	const char *over, *aname;
-	int fd, afd, closefd, flags, r;
-	
-	closefd = -1;
-	switch(lua_type(L, 1)){
-	default:
-		return luaL_typeerror(L, 1, "file descriptor or path");
-	case LUA_TNUMBER:
-		fd = lua_tointeger(L, 1); break;
-	case LUA_TSTRING:
-		if((fd = open(lua_tostring(L, 1), ORDWR)) == -1)
-			return error(L, "open: %r");
-		closefd = fd;
-		break;
-	}
-	over = luaL_checkstring(L, 2);
-	flags = parsemntflags(L, luaL_optstring(L, 3, ""));
-	aname = luaL_optstring(L, 4, "");
-	afd = luaL_optinteger(L, 5, -1);
-	if((r = mount(fd, afd, over, flags, aname)) == -1){
-		close(closefd);
-		return error(L, "mount: %r");
-	}
-	close(closefd);
-	lua_pushinteger(L, r);
-	return 1;
-}
-
-static int
-p9_unmount(lua_State *L)
-{
-	const char *name, *over;
-	int r;
-	
-	name = luaL_checkstring(L, 1);
-	over = luaL_optstring(L, 2, nil);
-	if(over == nil){
-		over = name;
-		name = nil;
-	}
-	if((r = unmount(name, over)) == -1)
-		return error(L, "unmount: %r");
-	lua_pushinteger(L, r);
-	return 1;
-}
--- a/p9.c
+++ /dev/null
@@ -1,246 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <ctype.h>
-
-#include <lua.h>
-#include <lauxlib.h>
-
-enum {
-	Iosize = 8192,
-	Smallbuf = 512,
-};
-
-#define min(a, b) ((a) < (b) ? (a) : (b))
-
-static int
-error(lua_State *L, char *fmt, ...)
-{
-	va_list varg;
-	int n;
-	char *buf;
-	luaL_Buffer b;
-	
-	lua_pushnil(L);
-	buf = luaL_buffinitsize(L, &b, Smallbuf);
-	va_start(varg, fmt);
-	n = vsnprint(buf, Smallbuf, fmt, varg);
-	va_end(varg);
-	luaL_pushresultsize(&b, n);
-	return 2;
-}
-
-/* Memory allocator associated with Lua state */
-static void*
-lalloc(lua_State *L, void *ptr, usize sz)
-{
-	void *ud;
-	
-	if((ptr = (lua_getallocf(L, &ud))(ud, ptr, LUA_TUSERDATA, sz)) == nil){
-		lua_pushliteral(L, "out of memory");
-		lua_error(L);
-	}
-	memset(ptr, 0, sz);
-	setmalloctag(ptr, getcallerpc(&L));
-	return ptr;
-}
-
-/* 
- * Various functions in this library require a
- * variably sized buffer for their operation.
- * Rather than allocating one for each call
- * we preallocate a shared buffer of reasonable
- * size and grow it as needed.
- * The buffer gets associated with a Lua state
- * at library load time.
- * getbuffer(L, sz) returns a pointer to the
- * memory area of at least sz bytes.
- * 
- * To avoid stepping on each other's toes the
- * buffer use must be constrained to a single
- * call.
- */
-
-typedef struct Buf {
-	usize sz;
-	char *b;
-} Buf;
-
-static Buf*
-resizebuffer(lua_State *L, Buf *buf, usize sz)
-{
-	if(buf == nil){
-		buf = lalloc(L, nil, sizeof(Buf));
-		buf->b = nil;
-		buf->sz = 0;
-	}
-	if(buf->sz < sz){
-		buf->b = lalloc(L, buf->b, sz);
-		buf->sz = sz;
-	}
-	return buf;
-}
-
-static char*
-getbuffer(lua_State *L, usize sz)
-{
-	Buf *buf;
-	
-	lua_getfield(L, LUA_REGISTRYINDEX, "p9-buffer");
-	buf = lua_touserdata(L, -1);
-	return resizebuffer(L, buf, sz)->b;
-}
-
-#include "fs.c"
-#include "walk.c"
-#include "env.c"
-#include "ns.c"
-#include "proc.c"
-#include "misc.c"
-#include "note.c"
-
-typedef struct Data {
-	char *key;
-	lua_Integer val;
-} Data;
-
-static Data p9data[] = {
-	{"OREAD", OREAD},
-	{"OWRITE", OWRITE},
-	{"ORDWR", ORDWR},
-	{"OEXEC", OEXEC},
-	{"OTRUNC", OTRUNC},
-	{"OCEXEC", OCEXEC},
-	{"ORCLOSE", ORCLOSE},
-	{"OEXCL", OEXCL},
-	
-	{"DMDIR", DMDIR},
-	{"DMAPPEND", DMAPPEND},
-	{"DMEXCL", DMEXCL},
-	{"DMMOUNT", DMMOUNT},
-	{"DMAUTH", DMAUTH},
-	{"DMTMP", DMTMP},
-	{"DMREAD", DMREAD},
-	{"DMWRITE", DMWRITE},
-	{"DMEXEC", DMEXEC},
-	{"QTDIR", QTDIR},
-	{"QTAPPEND", QTAPPEND},
-	{"QTEXCL", QTEXCL},
-	{"QTMOUNT", QTMOUNT},
-	{"QTAUTH", QTAUTH},
-	{"QTTMP", QTTMP},
-	{"QTFILE", QTFILE},
-	
-	{"MREPL", MREPL},
-	{"MBEFORE", MBEFORE},
-	{"MAFTER", MAFTER},
-	{"MCREATE", MCREATE},
-	{"MCACHE", MCACHE},
-
-	{"RFPROC", RFPROC},
-	{"RFNOWAIT", RFNOWAIT},
-	{"RFNAMEG", RFNAMEG},
-	{"RFCNAMEG", RFCNAMEG},
-	{"RFNOMNT", RFNOMNT},
-	{"RFENVG", RFENVG},
-	{"RFCENVG", RFCENVG},
-	{"RFNOTEG", RFNOTEG},
-	{"RFFDG", RFFDG},
-	{"RFCFDG", RFCFDG},
-	{"RFREND", RFREND},
-	{"RFMEM", RFMEM},
-	
-	{nil, 0}
-};
-
-static luaL_Reg p9func[] = {
-	{"open", p9_open},
-	{"create", p9_create},
-	{"file", p9_file},
-	{"pipe", p9_pipe},
-	{"remove", p9_remove},
-	{"access", p9_access},
-	
-	{"stat", p9_stat},
-	{"wstat", p9_wstat},
-	{"walk", p9_walk},
-	
-	{"bind", p9_bind},
-	{"mount", p9_mount},
-	{"unmount", p9_unmount},
-	
-	{"getenv", p9_getenv},
-	{"setenv", p9_setenv},
-	
-	{"abort", p9_abort},
-	{"exits", p9_exits},
-	{"fatal", p9_fatal},
-	{"sleep", p9_sleep},
-	{"alarm", p9_alarm},
-	{"rfork", p9_rfork},
-	{"wait", p9_wait},
-	{"exec", p9_exec},
-	{"wdir", p9_wdir},
-	{"pid", p9_pid},
-	{"ppid", p9_ppid},
-	{"user", p9_user},
-	{"sysname", p9_sysname},
-	
-	{"cleanname", p9_cleanname},
-	
-	{nil, nil}
-};
-
-int
-luaopen_p9(lua_State *L)
-{
-	int lib;
-	Buf *buf;
-	Data *d;
-	
-	buf = resizebuffer(L, nil, Iosize);
-	lua_pushlightuserdata(L, buf);
-	lua_setfield(L, LUA_REGISTRYINDEX, "p9-buffer");
-	
-	static luaL_Reg filemt[] = {
-		{"close", p9_file_close},
-		{"read", p9_file_read},
-		{"slurp", p9_file_slurp},
-		{"write", p9_file_write},
-		{"seek", p9_file_seek},
-		{"iounit", p9_file_iounit},
-		{"path", p9_file_path},
-		{"dup", p9_file_dup},
-		{nil, nil},
-	};
-	luaL_newmetatable(L, "p9-File");
-	luaL_setfuncs(L, filemt, 0);
-	lua_pop(L, 1);
-	
-	static luaL_Reg walkmt[] = {
-		{"__close", p9_walkclose},
-		{nil, nil},
-	};
-	luaL_newmetatable(L, "p9-Walk");
-	luaL_setfuncs(L, walkmt, 0);
-	lua_pop(L, 1);
-	
-	luaL_newlib(L, p9func);
-	lib = lua_gettop(L);
-	for(d = p9data; d->key != nil; d++){
-		lua_pushinteger(L, d->val);
-		lua_setfield(L, -2, d->key);
-	}
-	
-	static luaL_Reg envmt[] = {
-		{"__index", p9_getenv_index},
-		{"__newindex", p9_setenv_newindex},
-		{nil, nil},
-	};
-	lua_createtable(L, 0, 2);
-	luaL_setfuncs(L, envmt, 0);
-	lua_pushvalue(L, -1);
-	lua_setmetatable(L, -2);
-	lua_setfield(L, lib, "env");
-	
-	return 1;
-}
--- a/proc.c
+++ /dev/null
@@ -1,193 +1,0 @@
-static int
-p9_abort(lua_State*)
-{
-	abort();
-	/* never */ return 0;
-}
-
-static int
-p9_exits(lua_State *L)
-{
-	exits(luaL_optstring(L, 1, nil));
-	/* never */ return 0;
-}
-
-static int
-p9_fatal(lua_State *L)
-{
-	if(lua_gettop(L) > 1
-	&& lua_getglobal(L, "string") == LUA_TTABLE
-	&& lua_getfield(L, -1, "format") == LUA_TFUNCTION){
-		lua_remove(L, -2);
-		lua_insert(L, 1);
-		if(lua_pcall(L, lua_gettop(L) - 1, 1, 0) == LUA_OK)
-			sysfatal(lua_tostring(L, -1));
-	}
-	sysfatal(luaL_optstring(L, 1, "fatal"));
-	/* never */ return 0;
-}
-
-static int
-p9_sleep(lua_State *L)
-{
-	long t;
-	
-	t = luaL_checkinteger(L, 1);
-	lua_pushboolean(L,
-		sleep(t) == -1 ? 0 : 1
-	);
-	return 1;
-}
-
-static int
-p9_alarm(lua_State *L)
-{
-	long t, rem;
-	
-	t = luaL_checkinteger(L, 1);
-	rem = alarm(t);
-	lua_pushinteger(L, rem);
-	return 1;
-}
-
-static int
-p9_wdir(lua_State *L)
-{
-	const char *path;
-	char *buf;
-	luaL_Buffer b;
-	
-	path = luaL_optstring(L, 1, nil);
-	if(path != nil){
-		if(chdir(path) == -1)
-			return error(L, "chdir: %r");
-		lua_pushboolean(L, 1);
-		return 1;
-	}
-	luaL_buffinitsize(L, &b, Iosize);
-	buf = luaL_buffaddr(&b);
-	if(getwd(buf, Iosize) == nil)
-		return error(L, "getwd: %r");
-	luaL_pushresultsize(&b, strlen(buf));
-	return 1;
-}
-
-static int
-p9_user(lua_State *L)
-{
-	lua_pushstring(L, getuser());
-	return 1;
-}
-
-static int
-p9_sysname(lua_State *L)
-{
-	lua_pushstring(L, sysname());
-	return 1;
-}
-
-static int
-p9_pid(lua_State *L)
-{
-	lua_pushinteger(L, getpid());
-	return 1;
-}
-
-static int
-p9_ppid(lua_State *L)
-{
-	lua_pushinteger(L, getppid());
-	return 1;
-}
-
-static int
-p9_rfork(lua_State *L)
-{
-	int flags, i, n, r;
-	char *f[12];
-	
-	flags = RFENVG|RFNAMEG|RFNOTEG;
-	n = getfields(luaL_optstring(L, 1, ""), f, sizeof f, 0, " \t\n");
-	if(n > 0) for(flags = 0, i = 0; i < n; i++){
-		if     (strcmp(f[i], "name") == 0)
-			flags |= RFNAMEG;
-		else if(strcmp(f[i], "cname") == 0)
-			flags |= RFCNAMEG;
-		else if(strcmp(f[i], "env") == 0)
-			flags |= RFENVG;
-		else if(strcmp(f[i], "cenv") == 0)
-			flags |= RFCENVG;
-		else if(strcmp(f[i], "note") == 0)
-			flags |= RFNOTEG;
-		else if(strcmp(f[i], "fd") == 0)
-			flags |= RFFDG;
-		else if(strcmp(f[i], "cfd") == 0)
-			flags |= RFCFDG;
-		else if(strcmp(f[i], "nomnt") == 0)
-			flags |= RFNOMNT;
-		else if(strcmp(f[i], "proc") == 0)
-			flags |= RFPROC;
-		else if(strcmp(f[i], "nowait") == 0)
-			flags |= RFNOWAIT;
-		else if(strcmp(f[i], "rend") == 0)
-			flags |= RFREND;
-		else if(strcmp(f[i], "mem") == 0)
-			flags |= RFMEM;
-		else
-			return luaL_error(L, "unknown rfork flag '%s'", f[i]);
-	}
-	if((r = rfork(flags)) == -1)
-		return error(L, "rfork %r");
-	lua_pushinteger(L, r);
-	return 1;
-}
-
-static int
-p9_exec(lua_State *L)
-{
-	int argc, i;
-	const char **argv, *p;
-	char buf[Smallbuf];
-	
-	argc = lua_gettop(L);
-	if(argc < 1)
-		luaL_argerror(L, 1, "string arguments expected");
-	argv = lalloc(L, nil, (argc+1) * sizeof(char*));
-	for(i = 1; i <= argc; i++)
-		argv[i-1] = luaL_checkstring(L, i);
-	argv[argc] = nil;
-	p = argv[0];
-	if(p[0] != '/' && (p[0] != '.' && p[1] != '/')){
-		snprint(buf, sizeof buf, "/bin/%s", argv[0]);
-		argv[0] = buf;
-	}
-	exec(argv[0], argv);
-	free(argv);
-	return error(L, "exec: %r");
-}
-
-static int
-p9_wait(lua_State *L)
-{
-	Waitmsg *w;
-	
-	w = wait();
-	if(w == nil)
-		return error(L, "wait: %r");
-	lua_pushboolean(L, w->msg[0] == 0 ? 1 : 0);
-	lua_createtable(L, 0, 3);
-		lua_pushinteger(L, w->pid);
-			lua_setfield(L, -2, "pid");
-		lua_pushstring(L, w->msg);
-			lua_setfield(L, -2, "status");
-		lua_createtable(L, 3, 0);
-			lua_pushinteger(L, w->time[0]);
-				lua_setfield(L, -2, "user");
-			lua_pushinteger(L, w->time[1]);
-				lua_setfield(L, -2, "system");
-			lua_pushinteger(L, w->time[2]);
-				lua_setfield(L, -2, "real");
-		lua_setfield(L, -2, "time");
-	free(w);
-	return 2;
-}
--- a/test.lua
+++ /dev/null
@@ -1,322 +1,0 @@
-#!/bin/lu9
-
-local p9 = require "p9"
-local dump = (function()
-	local ok, inspect = pcall(require, "inspect")
-	if ok then return function(v) print(inspect(v)) end end
-	return print
-end)()
-
-local function tmp()
-	return string.format("/tmp/lua.%x", math.random(1e10))
-end
-
-local function rc()
-	os.execute("prompt='p9; ' rc -i")
-end
-
-p9.rfork("env name")
-os.execute("ramfs")
-
-
-
--- File I/O
-do
-	local s = string.rep("ABCD", 2048*2) -- 16k > standard 8k buffer
-	local f = tmp()
-	local fd = p9.create(f, "w")
-	fd:write(s)
-	fd:close()
-	
-	fd = p9.open(f)
-	assert(fd:slurp() == s)
-	fd:close()
-	
-	fd = p9.open(f, "r")
-	assert(fd:slurp(2048) == string.rep("ABCD", 512))
-	fd:close()
-	
-	fd = p9.open(f, "r")
-	assert(fd:slurp(16*1024 + 999) == s)
-	fd:close()
-	
-	fd = p9.open(f, "r")
-	assert(fd:seek(0, "end") == 16*1024)
-	assert(fd:seek(8192, "set") == 8192
-		and fd:slurp() == string.rep("ABCD", 2*1024))
-	fd:seek(0)
-	assert(fd:seek(16*1024 - 4, "cur") == 16*1024 - 4
-		and fd:slurp() == "ABCD")
-	fd:close()
-end
-
--- File objects
--- Closing
--- Make sure it's closed
-local fd
-do
-	local f <close> = p9.create(tmp())
-	fd = f.fd
-end
-assert(p9.file(fd):close() == nil)
--- Make sure it's not closed
-local fd
-do
-	local f = p9.create(tmp())
-	fd = f.fd
-end
-assert(p9.file(fd):seek(0))
-p9.file(fd):close()
-
--- file:path()
-do
-	local f = p9.create("/tmp/fd2path")
-	assert(f:path() == "/tmp/fd2path")
-	f:close()
-end
-
--- file:iounit()
-do
-	local f = assert(p9.open("/srv/slashmnt"))
-	assert(f:iounit() ~= 0)
-	f:close()
-end
-
--- file:dup()
-do
-	local a, b = assert(p9.pipe())
-	local c = assert(a:dup())
-	a:write("hello")
-	assert(b:read() == "hello")
-	c:write("world")
-	assert(b:read() == "world")
-	a:close() b:close() c:close()
-	
-	a = assert(p9.open("/lib/glass"))
-	local buf = a:read()
-	b = assert(p9.open("/lib/bullshit"))
-	b = assert(a:dup(b))
-	b:seek(0)
-	assert(b:read() == buf)
-end
-
--- access
-do
-	assert(p9.access("/dev/null"))
-	assert(p9.access("/dev/null", "read write"))
-	assert(p9.access("/bin/rc", "exec"))
-end
-	
--- pipe
-do
-	local p₀, p₁ = assert(p9.pipe())
-	p₀:write("ABCD")
-	assert(p₁:read() == "ABCD")
-	p₀:close(); p₁:close()
-end
-
--- wstat
-do
-	local name = tmp()
-	assert(p9.create(name, nil, "644")):close()
-	assert(p9.wstat(name, {name = "notyourbusiness", mode = "append 777"}))
-	local st = assert(p9.stat("/tmp/notyourbusiness"))
-	assert(st.mode.file and st.mode.append and st.mode.perm == "rwxrwxrwx")
-end
-
--- Filesystem
-do
-	-- Create a test tree
-	local function File(data) return {
-		type = "file", perm = "644", data = data
-	} end
-	local function Dir(children) return {
-		type = "dir", perm = "dir 755", children = children
-	} end
-	local function mkfs(path, d)
-		assert(p9.create(path, nil, d.perm))
-		for name, c in pairs(d.children) do
-			local new = path .. "/" .. name
-			if c.type == "dir" then
-				mkfs(new, c)
-			else
-				local f <close> = assert(p9.create(new, "w", c.perm))
-				f:write(c.data)
-			end
-		end
-	end
-	local fs = Dir {
-		a = File "a",
-		b = Dir {},
-		c = Dir {
-			ca = File "ca",
-			cb = Dir {
-				cba = File "cba",
-			},
-			cc = File "cc",
-		},
-		d = File "d",
-	}
-	local function walk(fs)
-		dump(fs.perm)
-		for _, e in pairs(fs.children) do
-			if e.type == "dir" then
-				walk(e)
-			end
-		end
-	end
-	local ok, err = pcall(mkfs, "/tmp/fs", fs)
-	if not ok then print(err) end
-	
-	-- Stat a file
-	assert(p9.stat("/tmp/fs/a").mode.file)
-	
-	-- Walking
-	-- Walking a file (or any other error) must report an error
-	local e = {}
-	for w in p9.walk("/tmp/fs/a", e) do
-		assert(false)
-	end
-	assert(e.error == "walk in a non-directory")
-	-- Without error object an error must be thrown
-	assert(false == pcall(function()
-		for w in p9.walk("tmp/fs/a") do end
-	end))
-	-- Same should happen if the iterator function fails inside
-	-- the loop because of dirread(2) failure, but this kind of
-	-- failure is hard to simulate.
-	
-	-- Walking a directory
-	local function compare(path, fs)
-		assert(fs.type == "dir")
-		for f in p9.walk(path) do
-			local new = path .. "/" .. f.name
-			if f.mode.dir then
-				if compare(new, fs.children[f.name]) == false then
-					return false
-				end
-			else
-				if fs.children[f.name] == nil then
-					error("file does not exist in proto")
-				end
-			end
-		end
-		return true
-	end
-	assert(compare("/tmp/fs", fs) == true)
-end
-
-
-
--- Namespaces
--- bind and unmount work
-do
-	local f
-	assert(p9.bind("#|", "/n/pipe"))
-	f = assert(p9.open("/n/pipe/data"))
-	assert(p9.unmount("/n/pipe"))
-	assert(p9.open("/n/pipe/data") == nil)
-end
--- mount works
-do
-	assert(p9.mount("/srv/cwfs", "/n/test"))
-	assert(p9.open("/n/test/lib/glass"))
-end
-
--- Process control
--- No idea how to test some of this
-do
-	assert(p9.sleep(0) == true)
-end
-
--- wdir
-do
-	local cwd = assert(p9.wdir())
-	assert(p9.wdir("/dev") and p9.wdir() == "/dev")
-	assert(p9.wdir(cwd))
-end
-
--- proc info
-do
-	assert(p9.user() and p9.sysname())
-	assert(p9.pid() and p9.ppid())
-end
-
--- Fork & Exec
-do
-	local us, them = p9.pipe()
-	if p9.rfork("proc nowait fd") == 0 then
-		them:dup(p9.file(0))
-		them:dup(p9.file(1))
-		them:close()
-		p9.exec("cat")
-	else
-		them:close()
-		us:write("HELLO CAT")
-		us:write("")
-		assert(us:slurp() == "HELLO CAT")
-		us:close()
-	end
-	
-	local ok = p9.exec("/dev/mordor")
-	assert(not ok)
-	
-	-- Wait
-	local N = 9
-	for i = 1, N do
-		if p9.rfork("proc") == 0 then
-			p9.exec("sleep", "0")
-		end
-	end
-	for i = 1, N do
-		local ok, w = p9.wait()
-	end
-	local ok = p9.wait()
-	assert(not ok)
-	-- Wait (status)
-	if p9.rfork("proc") == 0 then
-		p9.sleep(0)
-		p9.exits("i failed you")
-	end
-	local ok, w = p9.wait()
-	assert(not ok and w.status:match("i failed you"))
-end
-
-
-
-
--- Environment variables
-do
-	local e
-	
-	assert(p9.env["sure-is-empty"] == nil)
-	-- Set and get a string variable
-	p9.env.test = "ABC"; assert(p9.env.test == "ABC")
-	-- Delete a variable
-	p9.env.test = nil; assert(p9.env.test == nil)
-	
-	-- Set and get a list variable
-	p9.env.test = {"a", "b", "c"}
-	e = p9.env.test
-	assert(type(e) == "table"
-		and #e == 3 and e[1] == "a" and e[2] == "b" and e[3] == "c")
-	-- Ensure it's understood by rc
-	os.execute("echo -n $#test $test >/env/res")
-	assert(p9.env.res == "3 a b c")
-	-- Ensure we understand rc
-	os.execute("test=(d e f)")
-	e = p9.env.test
-	assert(type(e) == "table"
-		and #e == 3 and e[1] == "d" and e[2] == "e" and e[3] == "f")
-	p9.env.test = nil
-end
-
-
-
-
--- Misc.
-
--- cleanname
-do
-	assert(p9.cleanname("/usr///./glenda/.") == "/usr/glenda")
-end
--- /dev/null
+++ b/test/test.lua
@@ -1,0 +1,322 @@
+#!/bin/lu9
+
+local p9 = require "p9"
+local dump = (function()
+	local ok, inspect = pcall(require, "inspect")
+	if ok then return function(v) print(inspect(v)) end end
+	return print
+end)()
+
+local function tmp()
+	return string.format("/tmp/lua.%x", math.random(1e10))
+end
+
+local function rc()
+	os.execute("prompt='p9; ' rc -i")
+end
+
+p9.rfork("env name")
+os.execute("ramfs")
+
+
+
+-- File I/O
+do
+	local s = string.rep("ABCD", 2048*2) -- 16k > standard 8k buffer
+	local f = tmp()
+	local fd = p9.create(f, "w")
+	fd:write(s)
+	fd:close()
+	
+	fd = p9.open(f)
+	assert(fd:slurp() == s)
+	fd:close()
+	
+	fd = p9.open(f, "r")
+	assert(fd:slurp(2048) == string.rep("ABCD", 512))
+	fd:close()
+	
+	fd = p9.open(f, "r")
+	assert(fd:slurp(16*1024 + 999) == s)
+	fd:close()
+	
+	fd = p9.open(f, "r")
+	assert(fd:seek(0, "end") == 16*1024)
+	assert(fd:seek(8192, "set") == 8192
+		and fd:slurp() == string.rep("ABCD", 2*1024))
+	fd:seek(0)
+	assert(fd:seek(16*1024 - 4, "cur") == 16*1024 - 4
+		and fd:slurp() == "ABCD")
+	fd:close()
+end
+
+-- File objects
+-- Closing
+-- Make sure it's closed
+local fd
+do
+	local f <close> = p9.create(tmp())
+	fd = f.fd
+end
+assert(p9.file(fd):close() == nil)
+-- Make sure it's not closed
+local fd
+do
+	local f = p9.create(tmp())
+	fd = f.fd
+end
+assert(p9.file(fd):seek(0))
+p9.file(fd):close()
+
+-- file:path()
+do
+	local f = p9.create("/tmp/fd2path")
+	assert(f:path() == "/tmp/fd2path")
+	f:close()
+end
+
+-- file:iounit()
+do
+	local f = assert(p9.open("/srv/slashmnt"))
+	assert(f:iounit() ~= 0)
+	f:close()
+end
+
+-- file:dup()
+do
+	local a, b = assert(p9.pipe())
+	local c = assert(a:dup())
+	a:write("hello")
+	assert(b:read() == "hello")
+	c:write("world")
+	assert(b:read() == "world")
+	a:close() b:close() c:close()
+	
+	a = assert(p9.open("/lib/glass"))
+	local buf = a:read()
+	b = assert(p9.open("/lib/bullshit"))
+	b = assert(a:dup(b))
+	b:seek(0)
+	assert(b:read() == buf)
+end
+
+-- access
+do
+	assert(p9.access("/dev/null"))
+	assert(p9.access("/dev/null", "read write"))
+	assert(p9.access("/bin/rc", "exec"))
+end
+	
+-- pipe
+do
+	local p₀, p₁ = assert(p9.pipe())
+	p₀:write("ABCD")
+	assert(p₁:read() == "ABCD")
+	p₀:close(); p₁:close()
+end
+
+-- wstat
+do
+	local name = tmp()
+	assert(p9.create(name, nil, "644")):close()
+	assert(p9.wstat(name, {name = "notyourbusiness", mode = "append 777"}))
+	local st = assert(p9.stat("/tmp/notyourbusiness"))
+	assert(st.mode.file and st.mode.append and st.mode.perm == "rwxrwxrwx")
+end
+
+-- Filesystem
+do
+	-- Create a test tree
+	local function File(data) return {
+		type = "file", perm = "644", data = data
+	} end
+	local function Dir(children) return {
+		type = "dir", perm = "dir 755", children = children
+	} end
+	local function mkfs(path, d)
+		assert(p9.create(path, nil, d.perm))
+		for name, c in pairs(d.children) do
+			local new = path .. "/" .. name
+			if c.type == "dir" then
+				mkfs(new, c)
+			else
+				local f <close> = assert(p9.create(new, "w", c.perm))
+				f:write(c.data)
+			end
+		end
+	end
+	local fs = Dir {
+		a = File "a",
+		b = Dir {},
+		c = Dir {
+			ca = File "ca",
+			cb = Dir {
+				cba = File "cba",
+			},
+			cc = File "cc",
+		},
+		d = File "d",
+	}
+	local function walk(fs)
+		dump(fs.perm)
+		for _, e in pairs(fs.children) do
+			if e.type == "dir" then
+				walk(e)
+			end
+		end
+	end
+	local ok, err = pcall(mkfs, "/tmp/fs", fs)
+	if not ok then print(err) end
+	
+	-- Stat a file
+	assert(p9.stat("/tmp/fs/a").mode.file)
+	
+	-- Walking
+	-- Walking a file (or any other error) must report an error
+	local e = {}
+	for w in p9.walk("/tmp/fs/a", e) do
+		assert(false)
+	end
+	assert(e.error == "walk in a non-directory")
+	-- Without error object an error must be thrown
+	assert(false == pcall(function()
+		for w in p9.walk("tmp/fs/a") do end
+	end))
+	-- Same should happen if the iterator function fails inside
+	-- the loop because of dirread(2) failure, but this kind of
+	-- failure is hard to simulate.
+	
+	-- Walking a directory
+	local function compare(path, fs)
+		assert(fs.type == "dir")
+		for f in p9.walk(path) do
+			local new = path .. "/" .. f.name
+			if f.mode.dir then
+				if compare(new, fs.children[f.name]) == false then
+					return false
+				end
+			else
+				if fs.children[f.name] == nil then
+					error("file does not exist in proto")
+				end
+			end
+		end
+		return true
+	end
+	assert(compare("/tmp/fs", fs) == true)
+end
+
+
+
+-- Namespaces
+-- bind and unmount work
+do
+	local f
+	assert(p9.bind("#|", "/n/pipe"))
+	f = assert(p9.open("/n/pipe/data"))
+	assert(p9.unmount("/n/pipe"))
+	assert(p9.open("/n/pipe/data") == nil)
+end
+-- mount works
+do
+	assert(p9.mount("/srv/cwfs", "/n/test"))
+	assert(p9.open("/n/test/lib/glass"))
+end
+
+-- Process control
+-- No idea how to test some of this
+do
+	assert(p9.sleep(0) == true)
+end
+
+-- wdir
+do
+	local cwd = assert(p9.wdir())
+	assert(p9.wdir("/dev") and p9.wdir() == "/dev")
+	assert(p9.wdir(cwd))
+end
+
+-- proc info
+do
+	assert(p9.user() and p9.sysname())
+	assert(p9.pid() and p9.ppid())
+end
+
+-- Fork & Exec
+do
+	local us, them = p9.pipe()
+	if p9.rfork("proc nowait fd") == 0 then
+		them:dup(p9.file(0))
+		them:dup(p9.file(1))
+		them:close()
+		p9.exec("cat")
+	else
+		them:close()
+		us:write("HELLO CAT")
+		us:write("")
+		assert(us:slurp() == "HELLO CAT")
+		us:close()
+	end
+	
+	local ok = p9.exec("/dev/mordor")
+	assert(not ok)
+	
+	-- Wait
+	local N = 9
+	for i = 1, N do
+		if p9.rfork("proc") == 0 then
+			p9.exec("sleep", "0")
+		end
+	end
+	for i = 1, N do
+		local ok, w = p9.wait()
+	end
+	local ok = p9.wait()
+	assert(not ok)
+	-- Wait (status)
+	if p9.rfork("proc") == 0 then
+		p9.sleep(0)
+		p9.exits("i failed you")
+	end
+	local ok, w = p9.wait()
+	assert(not ok and w.status:match("i failed you"))
+end
+
+
+
+
+-- Environment variables
+do
+	local e
+	
+	assert(p9.env["sure-is-empty"] == nil)
+	-- Set and get a string variable
+	p9.env.test = "ABC"; assert(p9.env.test == "ABC")
+	-- Delete a variable
+	p9.env.test = nil; assert(p9.env.test == nil)
+	
+	-- Set and get a list variable
+	p9.env.test = {"a", "b", "c"}
+	e = p9.env.test
+	assert(type(e) == "table"
+		and #e == 3 and e[1] == "a" and e[2] == "b" and e[3] == "c")
+	-- Ensure it's understood by rc
+	os.execute("echo -n $#test $test >/env/res")
+	assert(p9.env.res == "3 a b c")
+	-- Ensure we understand rc
+	os.execute("test=(d e f)")
+	e = p9.env.test
+	assert(type(e) == "table"
+		and #e == 3 and e[1] == "d" and e[2] == "e" and e[3] == "f")
+	p9.env.test = nil
+end
+
+
+
+
+-- Misc.
+
+-- cleanname
+do
+	assert(p9.cleanname("/usr///./glenda/.") == "/usr/glenda")
+end
--- a/walk.c
+++ /dev/null
@@ -1,231 +1,0 @@
-static char*
-perms(int p, char *buf)
-{
-	buf[0] = p & 04 ? 'r' : '-';
-	buf[1] = p & 02 ? 'w' : '-';
-	buf[2] = p & 01 ? 'x' : '-';
-	return buf;
-}
-
-static void
-createdirtable(lua_State *L, Dir *d)
-{
-	#define set(t, k, v) do { \
-		lua_pushstring(L, (k)); \
-		lua_push##t(L, (v)); \
-		lua_rawset(L, -3); \
-	} while(0)
-	
-	lua_createtable(L, 0, 11);
-	set(integer, "type", d->type);
-	set(integer, "dev", d->dev);
-	set(integer, "atime", d->atime);
-	set(integer, "mtime", d->mtime);
-	set(integer, "length", d->length);
-	set(string, "name", d->name);
-	set(string, "uid", d->uid);
-	set(string, "gid", d->gid);
-	set(string, "muid", d->muid);
-	
-	lua_pushstring(L, "qid");
-	lua_createtable(L, 0, 3);
-	set(integer, "path", d->qid.path);
-	set(integer, "vers", d->qid.vers);
-	set(integer, "type", d->qid.type);
-	lua_rawset(L, -3);
-	
-	lua_pushstring(L, "mode");
-	lua_createtable(L, 0, 7);
-	ulong m = d->mode;
-	set(integer, "raw", m);
-	if(m & DMDIR)
-		set(boolean, "dir", 1);
-	else
-		set(boolean, "file", 1);
-	if(m & DMAPPEND)
-		set(boolean, "append", 1);
-	if(m & DMTMP)
-		set(boolean, "tmp", 1);
-	if(m & DMMOUNT)
-		set(boolean, "mount", 1);
-	if(m & DMAUTH)
-		set(boolean, "auth", 1);
-	char buf[10] = {0};
-	set(string, "user", perms((m & 0700) >> 6, buf));
-	set(string, "group", perms((m & 0070) >> 3, buf+3));
-	set(string, "other", perms((m & 0007) >> 0, buf+6));
-	set(string, "perm", buf);
-	lua_rawset(L, -3);
-	
-	#undef set
-}
-
-static int
-p9_stat(lua_State *L)
-{
-	Dir *d;
-	
-	d = nil;
-	switch(lua_type(L, 1)){
-	default:
-		USED(d);
-		return luaL_typeerror(L, 1, "string or number");
-	case LUA_TSTRING:
-		d = dirstat(lua_tostring(L, 1)); break;
-	case LUA_TNUMBER:
-		d = dirfstat(lua_tonumber(L, 1)); break;
-	}
-	if(d == nil)
-		return error(L, "stat: %r");
-	createdirtable(L, d);
-	free(d);
-	return 1;
-}
-
-typedef struct Walk {
-	int fd;
-	int nleft;
-	Dir *dirs, *p;
-} Walk;
-
-static int
-p9_walk(lua_State *L)
-{
-	static int p9_walkout(lua_State*);
-	static int p9_walknext(lua_State*);
-	int nargs, wstate;
-	Dir *d;
-	Walk *w;
-	
-	nargs = lua_gettop(L);
-	w = lua_newuserdatauv(L, sizeof(Walk), 1);
-	wstate = nargs + 1;
-	w->fd = -1;
-	w->nleft = 0;
-	w->dirs = w->p = nil;
-	luaL_setmetatable(L, "p9-Walk");
-	if(nargs == 2){
-		lua_pushvalue(L, 2);
-		lua_setiuservalue(L, wstate, 1);
-	}
-	if(lua_isnumber(L, 1))
-		w->fd = lua_tointeger(L, 1);
-	else{
-		if((w->fd = open(luaL_checkstring(L, 1), OREAD|OCEXEC)) == -1){
-			error(L, "open: %r");
-			goto Error;
-		}
-	}
-	if((d = dirfstat(w->fd)) == nil){
-		error(L, "stat: %r");
-		goto Error;
-	}
-	int isdir = d->mode & DMDIR;
-	free(d);
-	if(!isdir){
-		error(L, "walk in a non-directory");
-		goto Error;
-	}
-	/* return p9_walknext, p9-Walk, nil, p9-Walk */
-	lua_pushcfunction(L, p9_walknext);
-	lua_pushvalue(L, wstate);
-	lua_pushnil(L);
-	lua_pushvalue(L, wstate);
-	return 4;
-Error:
-	if(nargs == 2){
-		lua_setfield(L, 2, "error");
-		lua_pushcfunction(L, p9_walkout);
-		return 1;
-	}
-	return lua_error(L);
-}
-
-static int
-p9_walkout(lua_State*)
-{
-	return 0;
-}
-
-static int
-p9_walknext(lua_State *L)
-{
-	Walk *w;
-	Dir *d;
-	
-	w = luaL_checkudata(L, 1, "p9-Walk");
-	if(w->nleft == 0){
-		if(w->dirs != nil){
-			free(w->dirs);
-			w->dirs = nil;
-		}
-		if((w->nleft = dirread(w->fd, &w->dirs)) == -1){
-			error(L, "dirread: %r");
-			goto Error;
-		}
-		w->p = w->dirs;
-		if(w->nleft == 0)
-			return 0; /* Last Walk state will be closed */
-	}
-	w->nleft--;
-	d = w->p++;
-	createdirtable(L, d);
-	return 1;
-Error:
-	if(lua_getiuservalue(L, 1, 1) == LUA_TTABLE){
-		lua_pushvalue(L, -2);
-		lua_setfield(L, -2, "error");
-		lua_pushnil(L);
-		return 1;
-	}
-	lua_pop(L, 1);
-	return 2;
-}
-
-static int
-p9_walkclose(lua_State *L)
-{
-	Walk *w;
-	
-	w = luaL_checkudata(L, 1, "p9-Walk");
-	if(w->dirs != nil){
-		free(w->dirs);
-		w->dirs = nil;
-	}
-	if(w->fd != -1){
-		close(w->fd);
-		w->fd = -1;
-	}
-	return 0;
-}
-
-static int
-p9_wstat(lua_State *L)
-{
-	static int createperm(lua_State*, char*); /* from fs.c */
-	const char *path, *k;
-	Dir new;
-	
-	path = luaL_checkstring(L, 1);
-	luaL_argexpected(L, lua_type(L, 2) == LUA_TTABLE, 2, "table");
-	nulldir(&new);
-	lua_pushnil(L);
-	while(lua_next(L, -2)){
-		k = lua_tostring(L, -2);
-		if(strcmp(k, "name") == 0)
-			new.name = (char*)lua_tostring(L, -1); /* dw */
-		else if(strcmp(k, "mode") == 0)
-			new.mode = createperm(L, lua_tostring(L, -1));
-		else if(strcmp(k, "mtime") == 0)
-			new.mtime = lua_tointeger(L, -1);
-		else if(strcmp(k, "gid") == 0)
-			new.gid = (char*)lua_tostring(L, -1);
-		else if(strcmp(k, "length") == 0)
-			new.length = lua_tointeger(L, -1);
-		lua_pop(L, 1);
-	}
-	if(dirwstat(path, &new) == -1)
-		return error(L, "wstat: %r");
-	lua_pushboolean(L, 1);
-	return 1;
-}