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