shithub: lu9-p9

Download patch

ref: 68af611454400ea7ef8617c7c8b35d817e9caa50
parent: 5e9c1b1c102811e94d8d8c30bbdef259c9910a0a
author: kvik <kvik@a-b.xyz>
date: Wed Apr 14 14:17:23 EDT 2021

p9: implement directory iterator

--- a/fs.c
+++ b/fs.c
@@ -120,3 +120,207 @@
 	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){
+		lua_pushnil(L);
+		seterror(L, "stat: %r");
+		pusherror(L);
+		return 2;
+	}
+	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;
+	Dir *d;
+	Walk *w;
+	
+	nargs = lua_gettop(L);
+	w = lua_newuserdatauv(L, sizeof(Walk), 1);
+	w->fd = -1;
+	w->nleft = 0;
+	w->dirs = w->p = nil;
+	luaL_setmetatable(L, "p9-Walk");
+	if(nargs == 2){
+		lua_insert(L, 2);
+		lua_setiuservalue(L, -2, 1);
+	}
+	if(lua_isnumber(L, 1))
+		w->fd = lua_tointeger(L, 1);
+	else{
+		if((w->fd = open(luaL_checkstring(L, 1), OREAD|OCEXEC)) == -1){
+			seterror(L, "open: %r");
+			goto Error;
+		}
+	}
+	if((d = dirfstat(w->fd)) == nil){
+		seterror(L, "stat: %r");
+		goto Error;
+	}
+	int isdir = d->mode & DMDIR;
+	free(d);
+	if(!isdir){
+		seterror(L, "walk in a non-directory");
+		goto Error;
+	}
+	/* return p9_walknext, p9-Walk, nil, p9-Walk */
+	int i = lua_gettop(L);
+	lua_pushcfunction(L, p9_walknext);
+	lua_pushvalue(L, i);
+	lua_pushnil(L);
+	lua_pushvalue(L, i);
+	return 4;
+Error:
+	lua_getiuservalue(L, -1, 1);
+	if(lua_istable(L, -1)){
+		pusherror(L);
+		lua_setfield(L, -2, "error");
+		lua_pushcfunction(L, p9_walkout);
+		return 1;
+	}
+	pusherror(L);
+	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){
+			seterror(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:
+	pusherror(L);
+	if(lua_istable(L, lua_upvalueindex(1))){
+		lua_setfield(L, lua_upvalueindex(1), "error");
+		lua_pushnil(L);
+		return 1;
+	}
+	return lua_error(L);
+}
+
+static int
+p9_walkclose(lua_State *L)
+{
+	Walk *w;
+	
+	w = luaL_checkudata(L, 1, "p9-Walk");
+	free(w->dirs);
+	w->dirs = nil;
+	close(w->fd);
+	w->fd = -1;
+	return 0;
+}
+
--- a/mod/init.lua
+++ b/mod/init.lua
@@ -181,7 +181,7 @@
 	if not perm or #perm == 0 then perm = "644" end
 	mode = parsemode(mode)
 	perm = parseperm(perm)
-	if perm & raw.DMDIR then
+	if perm & raw.DMDIR > 0 then
 		mode = mode & ~(raw.OWRITE)
 	end
 	return raw.create(file, mode, perm)
@@ -316,6 +316,18 @@
 -- 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
 
 
--- a/p9.c
+++ b/p9.c
@@ -6,6 +6,29 @@
 #include <lauxlib.h>
 
 static void
+seterror(lua_State *L, char *fmt, ...)
+{
+	va_list varg;
+	int n;
+	char *buf;
+	luaL_Buffer b;
+	
+	buf = luaL_buffinitsize(L, &b, 512);
+	va_start(varg, fmt);
+	n = vsnprint(buf, 512, fmt, varg);
+	va_end(varg);
+	luaL_pushresultsize(&b, n);
+	lua_setfield(L, LUA_REGISTRYINDEX, "p9-error");
+}
+
+static const char*
+pusherror(lua_State *L)
+{
+	lua_getfield(L, LUA_REGISTRYINDEX, "p9-error");
+	return lua_tostring(L, -1);
+}
+
+static void
 lerror(lua_State *L, char *call)
 {
 	char err[ERRMAX];
@@ -104,6 +127,13 @@
 	{"DMREAD", DMREAD},
 	{"DMWRITE", DMWRITE},
 	{"DMEXEC", DMEXEC},
+	{"QTDIR", QTDIR},
+	{"QTAPPEND", QTAPPEND},
+	{"QTEXCL", QTEXCL},
+	{"QTMOUNT", QTMOUNT},
+	{"QTAUTH", QTAUTH},
+	{"QTTMP", QTTMP},
+	{"QTFILE", QTFILE},
 	
 	{"MREPL", MREPL},
 	{"MBEFORE", MBEFORE},
@@ -137,6 +167,9 @@
 	{"remove", p9_remove},
 	{"fd2path", p9_fd2path},
 	
+	{"stat", p9_stat},
+	{"walk", p9_walk},
+	
 	{"bind", p9_bind},
 	{"mount", p9_mount},
 	{"unmount", p9_unmount},
@@ -155,6 +188,13 @@
 	buf = resizebuffer(L, nil, 8192);
 	lua_pushlightuserdata(L, buf);
 	lua_setfield(L, LUA_REGISTRYINDEX, "p9-buffer");
+	
+	static luaL_Reg walkmt[] = {
+		{"__close", p9_walkclose},
+		{nil, nil},
+	};
+	luaL_newmetatable(L, "p9-Walk");
+	luaL_setfuncs(L, walkmt, 0);
 	
 	luaL_newlib(L, p9func);
 	for(d = p9data; d->key != nil; d++){
--- a/test.lua
+++ b/test.lua
@@ -1,6 +1,11 @@
-#!/bin/lua9
+#!/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))
@@ -88,6 +93,80 @@
 	f:write(data)
 	f:seek(0)
 	assert(f:slurp() == data)
+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 = "d755", children = children
+	} end
+	local function mkfs(path, d)
+		assert(d.type == "dir")
+		p9.createfile(path, nil, d.perm):close()
+		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)
+				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",
+	}
+	mkfs("/tmp/fs", fs)
+	
+	-- 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