shithub: lu9-p9

Download patch

ref: bc1f00d4e76ce69cff7b632cadf0df867d1bf5eb
parent: 4d4deaa134b151e364f06c5731790b23dcd35233
author: kvik <kvik@a-b.xyz>
date: Sun Apr 18 15:53:24 EDT 2021

p9: rewrite everything in C

This is done to minimize the dependencies a library user must
think about if they want to use it in their own programs.
With everything in a single library archive all that needs to
be done is link or load it.

During the conversion the API of several functions got changed
and some new functions got added as well.

If you have installed this library previously you will want to
remove the Lua module path files that got installed in
	/sys/lib/lua/5.4/p9
It doesn't hurt to keep them since this library is preloaded
but someone might get confused in the future.

A rebuild of lu9 and any of your programs will obviously be
needed as well.

diff: cannot open a/mod//null: 'a/mod//null' does not exist
--- /dev/null
+++ b/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);
+}
--- a/fs.c
+++ b/fs.c
@@ -1,4 +1,151 @@
+/*
+ * 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, 2);
+	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;
@@ -6,11 +153,10 @@
 	int fd;
 	
 	file = luaL_checkstring(L, 1);
-	mode = luaL_checkinteger(L, 2);
+	mode = openmode(L, luaL_optstring(L, 2, "read"));
 	if((fd = open(file, mode)) == -1)
 		return error(L, "open: %r");
-	lua_pushinteger(L, fd);
-	return 1;
+	return filenew(L, fd);
 }
 
 static int
@@ -21,31 +167,58 @@
 	ulong perm;
 	
 	file = luaL_checkstring(L, 1);
-	mode = luaL_checkinteger(L, 2);
-	perm = luaL_checkinteger(L, 3);
+	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");
-	lua_pushinteger(L, fd);
-	return 1;
+	return filenew(L, fd);
 }
 
 static int
 p9_close(lua_State *L)
 {
-	if(close(luaL_checkinteger(L, 1)) == -1)
+	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_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_read(lua_State *L)
 {
-	lua_Integer fd, nbytes, offset;
-	long n;
+	int fd;
+	long n, nbytes;
+	vlong offset;
 	char *buf;
 	
-	fd = luaL_checkinteger(L, 1);
-	nbytes = luaL_checkinteger(L, 2);
+	fd = filefd(L, 1);
+	nbytes = luaL_optinteger(L, 2, Iosize);
 	offset = luaL_optinteger(L, 3, -1);
 	buf = getbuffer(L, nbytes);
 	if(offset == -1)
@@ -59,6 +232,41 @@
 }
 
 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_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_write(lua_State *L)
 {
 	lua_Integer fd, offset;
@@ -66,7 +274,7 @@
 	const char *buf;
 	long n;
 
-	fd = luaL_checkinteger(L, 1);
+	fd = filefd(L, 1);
 	buf = luaL_checklstring(L, 2, &nbytes);
 	nbytes = luaL_optinteger(L, 3, nbytes);
 	offset = luaL_optinteger(L, 4, -1);
@@ -81,21 +289,6 @@
 }
 
 static int
-p9_seek(lua_State *L)
-{
-	lua_Integer fd, n, type;
-	vlong off;
-	
-	fd = luaL_checkinteger(L, 1);
-	n = luaL_checkinteger(L, 2);
-	type = luaL_checkinteger(L, 3);
-	if((off = seek(fd, n, type)) == -1)
-		return error(L, "seek: %r");
-	lua_pushinteger(L, off);
-	return 1;
-}
-
-static int
 p9_remove(lua_State *L)
 {
 	const char *file;
@@ -114,211 +307,9 @@
 	char *buf;
 	
 	fd = luaL_checkinteger(L, 1);
-	buf = getbuffer(L, 8192);
-	if(fd2path(fd, buf, 8192) != 0)
+	buf = getbuffer(L, Iosize);
+	if(fd2path(fd, buf, Iosize) != 0)
 		return error(L, "fd2path: %r");
 	lua_pushstring(L, buf);
 	return 1;
 }
-
-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;
-}
-
--- a/mkfile
+++ b/mkfile
@@ -1,8 +1,5 @@
 </$objtype/mkfile
 
-MOD=p9
-MODPATH=/sys/lib/lua
-
 CFLAGS=-FTVw -p -I../lua/shim -I../lua
 
 LIB=libp9.a.$O
@@ -11,13 +8,7 @@
 
 all:V: $LIB
 
-install:V: all
-	if(~ $#luav 0)
-		luav=`{lu9 -v}
-	for(p in $MODPATH/$luav)
-		mkdir -p $p/$MOD && dircp mod $p/$MOD
-
-clean:V:
+clean:QV:
 	rm -f *.[$OS] *.a.[$OS]
 
 $LIB: $OBJS
@@ -26,5 +17,4 @@
 %.$O: %.c
 	$CC $CFLAGS $stem.c
 
-p9.$O: p9.c fs.c proc.c
-
+p9.$O: p9.c fs.c walk.c env.c ns.c proc.c
--- a/mod/init.lua
+++ /dev/null
@@ -1,416 +1,0 @@
-local p9 = {}
-local raw = require "p9.raw"
-p9.raw = raw
-
--- Defaults &c
-p9.iosize = 8192
-
-
-
-
--- Utility / debugging
-
-local function fwrite(fmt, ...)
-	io.write(string.format(fmt, ...))
-end
-
--- typecheck(values... : any, types : string) -> string...
--- typecheck matches the types of values against a string
--- of space-separated type names.
--- If a value without a matching type is found an error
--- describing the type mismatch is thrown.
--- Otherwise the function returns a type of each value.
-local function typecheck(...)
-	local values = table.pack(...)
-	local t = table.remove(values)
-	local accept = {}
-	for w in t:gmatch("%w+") do
-		accept[w] = true
-	end
-	local types = {}
-	for _, v in ipairs(values) do
-		local tv = type(v)
-		types[#types + 1] = tv
-		if not accept[tv] then
-			error(("expected type(s) [%s], got %s"):format(t, tv), 2)
-		end
-	end
-	return table.unpack(types)
-end
-
-
-
--- Namespace
-
-local mntmap <const> = {
-	["a"] = raw.MAFTER,
-	["b"] = raw.MBEFORE,
-	["c"] = raw.MCREATE,
-	["C"] = raw.MCACHE,
-}
-local function parsemntflags(s)
-	local f = raw.MREPL
-	for c in s:gmatch("%w") do
-		f = f | (mntmap[c] | 0)
-	end
-	return f
-end
-
---- bind(string, string, [string]) -> int
-function p9.bind(name, over, flags)
-	return raw.bind(name, over, parsemntflags(flags or ""))
-end
-
---- mount(int | string, int, string, [string, [string]]) -> int
-function p9.mount(fd, afd, over, flags, tree)
-	if type(fd) == string then
-		fd = p9.open(fd, "rw")
-	end
-	flags = parsemntflags(flags or "")
-	return raw.mount(fd, afd or -1, over, flags, tree or "")
-end
-
---- unmount(string, [string]) -> int
-function p9.unmount(name, over)
-	if over == nil then
-		return raw.unmount(nil, name)
-	end
-	return raw.unmount(name, over)
-end
-
-
-
-
--- Processes
-
-local rforkmap <const> = {
-	-- these are the same as in rc(1)
-	["n"] = raw.RFNAMEG,
-	["N"] = raw.RFCNAMEG,
-	["e"] = raw.RFENVG,
-	["E"] = raw.RFCENVG,
-	["s"] = raw.RFNOTEG,
-	["f"] = raw.RFFDG,
-	["F"] = raw.RFCFDG,
-	["m"] = raw.RFNOMNT,
-	-- these are new
-	["p"] = raw.RFPROC,
-	["&"] = raw.RFNOWAIT,
-	-- this is likely useless
-	["r"] = raw.RFREND,
-	-- this is gonna panic
-	["M"] = raw.RFMEM,
-}
---- rfork(string) -> int
-function p9.rfork(flags)
-	local f = 0
-	for c in flags:gmatch("%w") do
-		f = f | (rforkmap[c] or 0)
-	end
-	return raw.rfork(f)
-end
-
-
-
-
--- File I/O
--- These are built on top of the raw API written in C and
--- where applicable they provide a more polished interface
--- with reasonable defaults.
-
-local modemap <const> = {
-	["r"] = raw.OREAD,
-	["w"] = raw.OWRITE,
-	["x"] = raw.OEXEC,
-	["T"] = raw.OTRUNC,
-	["C"] = raw.OCEXEC,
-	["R"] = raw.ORCLOSE,
-	["E"] = raw.OEXCL,
-}
-local function parsemode(s)
-	local m, o, c
-	
-	m = 0;
-	o = {r = false, w = false, x = false}
-	for c in s:gmatch("%w") do
-		if o[c] ~= nil then
-			o[c] = true
-		else
-			m = m | (modemap[c] or 0)
-		end
-	end
-	if o.r and o.w then
-		m = m | raw.ORDWR
-	elseif o.r then
-		m = m | raw.OREAD
-	elseif o.w then
-		m = m | raw.OWRITE
-	elseif o.x then
-		m = m | raw.OEXEC
-	end
-	return m
-end
-
-local permmap <const> = {
-	["d"] = raw.DMDIR,
-	["a"] = raw.DMAPPEND,
-	["e"] = raw.DMEXCL,
-	["t"] = raw.DMTMP,
-}
-local function parseperm(s)
-	local perm, m, p, c
-	
-	m, p = s:match("([daet]*)([0-7]*)")
-	perm = tonumber(p, 8) or 0644
-	for c in m:gmatch("%w") do
-		perm = perm | (permmap[c] or 0)
-	end
-	return perm
-end
-
---- open(string, string) -> int
-function p9.open(file, ...)
-	local mode = ...
-	mode = parsemode(mode or "r")
-	return raw.open(file, mode)
-end
-
---- create(string, [string, [string]]) -> int
-function p9.create(file, mode, perm)
-	if not mode or #mode == 0 then mode = "rw" end
-	if not perm or #perm == 0 then perm = "644" end
-	mode = parsemode(mode)
-	perm = parseperm(perm)
-	if perm & raw.DMDIR > 0 then
-		mode = mode & ~(raw.OWRITE)
-	end
-	return raw.create(file, mode, perm)
-end
-
---- read(int, [int, [int]]) -> string
-function p9.read(fd, n, off)
-	return raw.read(fd, n or p9.iosize, off or -1)
-end
-
---- slurp(int, [int]) -> string
-function p9.slurp(fd, max)
-	max = max or math.huge
-	local tot, n = 0, 0
-	local buf = {}
-	while true do
-		n = math.min(max - tot, p9.iosize)
-		local r = p9.read(fd, n)
-		if #r == 0 then
-			break
-		end
-		buf[#buf + 1] = r
-		tot = tot + #r
-		if tot == max then
-			break
-		end
-	end
-	return table.concat(buf)
-end
-
---- write(int, string, [int, [int]]) -> int
-function p9.write(fd, buf, n, off)
-	return raw.write(fd, buf, n or #buf, off or -1)
-end
-
-local whencemap <const> = {
-	["set"] = 0,
-	["cur"] = 1,
-	["end"] = 2
-}
---- seek(int, int, [string]) -> int
-function p9.seek(fd, n, whence)
-	whence = whence or "set"
-	if whencemap[whence] == nil then
-		error("whence must be one of [cur, set, end]")
-	end
-	return raw.seek(fd, n, whencemap[whence])
-end
-
---- close(int) -> int
-function p9.close(fd)
-	return raw.close(fd)
-end
-
---- remove(string) -> int
-function p9.remove(file)
-	return raw.remove(file)
-end
-
---- fd2path(int) -> string
-function p9.fd2path(fd)
-	return raw.fd2path(fd)
-end
-
---- pipe() -> int, int
-function p9.pipe()
-end
-
-
-
--- The File object
-
--- A file descriptor wrapper.
-
--- 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.openfile and p9.createfile take a file name and open
--- or create a file. They accept the same arguments as
--- regular open and close.
-
--- 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.
-
--- The f.keep field can be set to true to prevent the finalizer
--- from closing the file.
-
-local fileproto = {
-	fd = -1,
-	name = nil,
-	path = nil,
-	
-	read = function(self, ...) return p9.read(self.fd, ...) end,
-	write = function(self, ...) return p9.write(self.fd, ...) end,
-	slurp = function(self, ...) return p9.slurp(self.fd, ...) end,
-	seek = function(self, ...) return p9.seek(self.fd, ...) end,
-	close = function(self)
-		if self.fd == -1 then
-			return
-		end
-		p9.close(self.fd)
-		self.fd = -1
-	end,
-	
-	__close = function(f, _)
-		f:close()
-	end
-}
-
---- file(int, [string]) -> file{}
-function p9.file(fd, name)
-	local f = {}
-	for k, v in pairs(fileproto) do
-		f[k] = v
-	end
-	f.fd = fd
-	f.name = name
-	f.path = p9.fd2path(fd)
-	return setmetatable(f, f)
-end
-
--- openfile(string, ...) -> file{}
-function p9.openfile(file, ...)
-	return p9.file(p9.open(file, ...), file)
-end
-
--- createfile(string, ...) -> file{}
-function p9.createfile(file, ...)
-	return p9.file(p9.create(file, ...), file)
-end
-
-
-
--- Filesystem
-function p9.stat(path)
-	return p9.raw.stat(path)
-end
-
-function p9.walk(path, err)
-	return p9.raw.walk(path, err)
-end
-
-
-
-
--- 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.
-
---- getenv(string) -> string | list
-function p9.getenv(key)
-	typecheck(key, "string")
-	
-	local f, err = io.open("/env/" .. key)
-	if err then
-		return nil, err
-	end
-	local buf = f:read("a")
-	f:close()
-	-- a value
-	if #buf == 0 or buf:sub(-1) ~= "\0" then
-		return buf
-	end
-	-- a list (as encoded by rc(1) shell)
-	local t, p = {}, 1
-	while p <= #buf do
-		t[#t + 1], p = string.unpack("z", buf, p)
-	end
-	return t
-end
-
---- setenv(string, string | list)
-function p9.setenv(key, val)
-	local tk = typecheck(key, "string")
-	local tv = typecheck(val, "nil string table")
-	if val == nil then
-		pcall(p9.remove, "/env/" .. key)
-		return
-	end
-	local buf
-	if tv == "string" then
-		buf = val
-	elseif tv == "table" then
-		local t = {}
-		for i = 1, #val do
-			t[#t + 1] = string.pack("z", val[i])
-		end
-		buf = table.concat(t)
-	end
-	local f = assert(io.open("/env/" .. key, "w"))
-	if not f:write(buf) then
-		error("can't write environment")
-	end
-	f:close()
-end
-
-p9.env = setmetatable({}, {
-	__index = function(_, key)
-		return p9.getenv(key)
-	end,
-	__newindex = function(_, key, val)
-		p9.setenv(key, val)
-	end,
-})
-
-return p9
--- a/ns.c
+++ b/ns.c
@@ -1,14 +1,38 @@
+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;
-	lua_Integer flag;
-	int r;
+	int flags, r;
 	
 	this = luaL_checkstring(L, 1);
 	over = luaL_checkstring(L, 2);
-	flag = luaL_checkinteger(L, 3);
-	if((r = bind(this, over, flag)) == -1)
+	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;
@@ -18,15 +42,29 @@
 p9_mount(lua_State *L)
 {
 	const char *over, *aname;
-	lua_Integer fd, afd, flag, r;
+	int fd, afd, closefd, flags, r;
 	
-	fd = luaL_checkinteger(L, 1);
-	afd = luaL_checkinteger(L, 2);
-	over = luaL_checkstring(L, 3);
-	flag = luaL_checkinteger(L, 4);
-	aname = luaL_checkstring(L, 5);
-	if((r = mount(fd, afd, over, flag, aname)) == -1)
+	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;
 }
@@ -37,8 +75,12 @@
 	const char *name, *over;
 	int r;
 	
-	name = luaL_optstring(L, 1, nil);
-	over = luaL_checkstring(L, 2);
+	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);
--- a/p9.c
+++ b/p9.c
@@ -5,6 +5,13 @@
 #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, ...)
 {
@@ -14,9 +21,9 @@
 	luaL_Buffer b;
 	
 	lua_pushnil(L);
-	buf = luaL_buffinitsize(L, &b, 512);
+	buf = luaL_buffinitsize(L, &b, Smallbuf);
 	va_start(varg, fmt);
-	n = vsnprint(buf, 512, fmt, varg);
+	n = vsnprint(buf, Smallbuf, fmt, varg);
 	va_end(varg);
 	luaL_pushresultsize(&b, n);
 	return 2;
@@ -84,6 +91,8 @@
 }
 
 #include "fs.c"
+#include "walk.c"
+#include "env.c"
 #include "ns.c"
 #include "proc.c"
 
@@ -144,12 +153,12 @@
 static luaL_Reg p9func[] = {
 	{"open", p9_open},
 	{"create", p9_create},
-	{"close", p9_close},
-	{"read", p9_read},
-	{"write", p9_write},
-	{"seek", p9_seek},
+	{"file", p9_file},
+	
 	{"remove", p9_remove},
 	{"fd2path", p9_fd2path},
+	{"getenv", p9_getenv},
+	{"setenv", p9_setenv},
 	
 	{"stat", p9_stat},
 	{"walk", p9_walk},
@@ -166,13 +175,26 @@
 int
 luaopen_p9(lua_State *L)
 {
+	int lib;
 	Buf *buf;
 	Data *d;
 	
-	buf = resizebuffer(L, nil, 8192);
+	buf = resizebuffer(L, nil, Iosize);
 	lua_pushlightuserdata(L, buf);
 	lua_setfield(L, LUA_REGISTRYINDEX, "p9-buffer");
 	
+	static luaL_Reg filemt[] = {
+		{"close", p9_close},
+		{"read", p9_read},
+		{"slurp", p9_slurp},
+		{"write", p9_write},
+		{"seek", p9_seek},
+		{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},
@@ -179,11 +201,25 @@
 	};
 	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
+++ b/proc.c
@@ -1,10 +1,39 @@
 static int
 p9_rfork(lua_State *L)
 {
-	lua_Integer flags;
-	int r;
+	int flags, i, n, r;
+	char *f[12];
 	
-	flags = luaL_checkinteger(L, 1);
+	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);
--- a/test.lua
+++ b/test.lua
@@ -11,88 +11,67 @@
 	return string.format("/tmp/lua.%x", math.random(1e10))
 end
 
-p9.rfork("en")
-os.execute("ramfs")
-
-local t = {
-	{nil, nil}, {"", nil}, {nil, ""},
-	{"r", nil}, {"r", ""},
-	{"w", "d644"},
-	{"rw", "d644"},
-	{nil, "d644"},
-	{"", "d644"},
-	{"r", "d644"},
-	{"w", "d644"},
-	{"rw", "d644"}
-}
-
-for i = 1, #t do
-	local mode = t[i][1]
-	local perm = t[i][2]
-	p9.close(p9.create(tmp(), mode, perm))
+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, "rw")
-	p9.write(fd, s)
-	p9.close(fd)
+	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(p9.slurp(fd) == s)
-	p9.close(fd)
+	assert(fd:slurp(2048) == string.rep("ABCD", 512))
+	fd:close()
+	
 	fd = p9.open(f, "r")
-	assert(p9.slurp(fd, 2048) == string.rep("ABCD", 512))
-	p9.close(fd)
-	fd = p9.open(f, "r")
-	assert(p9.slurp(fd, 16*1024 + 999) == s)
-	p9.close(fd)
+	assert(fd:slurp(16*1024 + 999) == s)
+	fd:close()
 	
 	fd = p9.open(f, "r")
-	assert(p9.seek(fd, 0, "end") == 16*1024)
-	assert(p9.seek(fd, 8192, "set") == 8192
-		and p9.slurp(fd) == string.rep("ABCD", 2*1024))
-	p9.seek(fd, 0)
-	assert(p9.seek(fd, 16*1024 - 4, "cur") == 16*1024 - 4
-		and p9.slurp(fd) == "ABCD")
-	p9.close(fd)
+	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
 
--- fd2path
-do
-	local fd = p9.create("/tmp/fd2path")
-	assert(p9.fd2path(fd) == "/tmp/fd2path")
-end
-
 -- File objects
 -- Closing
 -- Make sure it's closed
 local fd
 do
-	local f <close> = p9.createfile(tmp())
+	local f <close> = p9.create(tmp())
 	fd = f.fd
 end
-assert(p9.seek(fd, 0) == nil)
+assert(p9.file(fd):close() == nil)
 -- Make sure it's not closed
 local fd
 do
-	local f = p9.createfile(tmp())
+	local f = p9.create(tmp())
 	fd = f.fd
 end
-assert(p9.seek(fd, 0))
-p9.close(fd)
+assert(p9.file(fd):seek(0))
+p9.file(fd):close()
 
--- Basic operations. These are the same as regular
--- function calls so no need for much testing.
+-- fd2path
 do
-	local f <close> = p9.createfile(tmp(), "rw")
-	local data = string.rep("ABCD", 1024)
-	f:write(data)
-	f:seek(0)
-	assert(f:slurp() == data)
+	local fd = p9.create("/tmp/fd2path")
+	assert(p9.fd2path(fd.fd) == "/tmp/fd2path")
 end
 
 -- Filesystem
@@ -102,17 +81,16 @@
 		type = "file", perm = "644", data = data
 	} end
 	local function Dir(children) return {
-		type = "dir", perm = "d755", children = children
+		type = "dir", perm = "dir 755", children = children
 	} end
 	local function mkfs(path, d)
-		assert(d.type == "dir")
-		p9.createfile(path, nil, d.perm):close()
+		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> = p9.createfile(new, "w", c.perm)
+				local f <close> = assert(p9.create(new, "w", c.perm))
 				f:write(c.data)
 			end
 		end
@@ -129,7 +107,16 @@
 		},
 		d = File "d",
 	}
-	mkfs("/tmp/fs", fs)
+	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)
@@ -173,19 +160,18 @@
 
 -- Namespaces
 -- bind and unmount work
-assert(function()
+do
 	local f
 	assert(p9.bind("#|", "/n/pipe"))
-	f = assert(p9.openfile("/n/pipe/data"))
+	f = assert(p9.open("/n/pipe/data"))
 	assert(p9.unmount("/n/pipe"))
-	assert(p9.openfile("/n/pipe/data") == nil)
-end)
+	assert(p9.open("/n/pipe/data") == nil)
+end
 -- mount works
-assert(function()
-	assert(p9.mount(p9.open("/srv/cwfs", "rw"), nil, "/n/test"))
-	assert(p9.openfile("/n/test/lib/glass"))
-end)
-
+do
+	assert(p9.mount("/srv/cwfs", "/n/test"))
+	assert(p9.open("/n/test/lib/glass"))
+end
 
 -- Process control
 -- No idea how to test this properly.
--- /dev/null
+++ b/walk.c
@@ -1,0 +1,200 @@
+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;
+}