ref: 3654451f1948e59b1f99d7bb6efb1283714d246d
author: kvik <kvik@a-b.xyz>
date: Thu Apr 8 18:44:21 EDT 2021
Feed the git
--- /dev/null
+++ b/fs.c
@@ -1,0 +1,122 @@
+static int
+p9_open(lua_State *L)
+{
+ const char *file;
+ int mode;
+ int fd;
+
+ file = luaL_checkstring(L, 1);
+ mode = luaL_checkinteger(L, 2);
+ if((fd = open(file, mode)) == -1)
+ lerror(L, "open");
+ lua_pushinteger(L, fd);
+ return 1;
+}
+
+static int
+p9_create(lua_State *L)
+{
+ const char *file;
+ int fd, mode;
+ ulong perm;
+
+ file = luaL_checkstring(L, 1);
+ mode = luaL_checkinteger(L, 2);
+ perm = luaL_checkinteger(L, 3);
+ if((fd = create(file, mode, perm)) == -1)
+ lerror(L, "create");
+ lua_pushinteger(L, fd);
+ return 1;
+}
+
+static int
+p9_close(lua_State *L)
+{
+ if(close(luaL_checkinteger(L, 1)) == -1)
+ lerror(L, "close");
+ return 0;
+}
+
+static int
+p9_read(lua_State *L)
+{
+ lua_Integer fd, nbytes, offset;
+ long n;
+ char *buf;
+
+ fd = luaL_checkinteger(L, 1);
+ nbytes = luaL_checkinteger(L, 2);
+ 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)
+ lerror(L, "read");
+ lua_pushlstring(L, buf, n);
+ return 1;
+}
+
+static int
+p9_write(lua_State *L)
+{
+ lua_Integer fd, offset;
+ size_t nbytes;
+ const char *buf;
+ long n;
+
+ fd = luaL_checkinteger(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)
+ lerror(L, "write");
+ lua_pushinteger(L, n);
+ return 1;
+}
+
+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)
+ lerror(L, "seek");
+ lua_pushinteger(L, off);
+ return 1;
+}
+
+static int
+p9_remove(lua_State *L)
+{
+ const char *file;
+
+ file = luaL_checkstring(L, 1);
+ if(remove(file) == -1)
+ lerror(L, "remove");
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int
+p9_fd2path(lua_State *L)
+{
+ lua_Integer fd;
+ char *buf;
+
+ fd = luaL_checkinteger(L, 1);
+ buf = getbuffer(L, 8192);
+ if(fd2path(fd, buf, 8192) != 0)
+ lerror(L, "fd2path");
+ lua_pushstring(L, buf);
+ return 1;
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,30 @@
+</$objtype/mkfile
+
+MOD=p9
+MODPATH=/sys/lib/lua
+
+CFLAGS=-FTVw -p -I../lua/shim -I../lua
+
+LIB=libp9.a.$O
+
+OBJS=p9.$O
+
+all:V: $LIB
+
+install:V: all
+ if(~ $#luav 0)
+ luav=`{lua9 -v}
+ for(p in $MODPATH/$luav)
+ mkdir -p $p/$MOD && dircp mod $p/$MOD
+
+clean:V:
+ rm -f *.[$OS] *.a.[$OS]
+
+$LIB: $OBJS
+ ar cr $target $prereq
+
+%.$O: %.c
+ $CC $CFLAGS $stem.c
+
+p9.$O: p9.c fs.c proc.c
+
--- /dev/null
+++ b/mod/init.lua
@@ -1,0 +1,441 @@
+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 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
+
+
+
+
+-- 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,
+})
+
+
+
+
+-- Lethal API
+-- p9.lethal() returns a proxy table that is just like the
+-- regular p9 module table, except that each function field
+-- is wrapped such that any error raised during a call
+-- results in program termination. An error is printed to
+-- fd 2 and the same error is used for the program's exit status.
+-- Note that this only mechanism only works for the fields
+-- of the p9 module, like the p9.open and friends.
+-- In particular, it doesn't apply to methods of File objects
+-- nor to the p9.env object.
+-- This limitation should be removed in the future.
+
+local lethalproxy = setmetatable({}, {
+ __index = function(_, key)
+ if type(p9[key]) ~= "function" then
+ return p9[key]
+ end
+ return function(...)
+ local res = table.pack(pcall(p9[key], ...))
+ if not res[1] then
+ p9.write(2, string.format("%s: %s\n", arg[0] or "lua", res[2]))
+ os.exit(res[2])
+ end
+ table.remove(res, 1)
+ return table.unpack(res)
+ end
+ end
+})
+
+--- lethal() -> p9{}
+function p9.lethal()
+ return lethalproxy
+end
+
+return p9
--- /dev/null
+++ b/ns.c
@@ -1,0 +1,46 @@
+static int
+p9_bind(lua_State *L)
+{
+ const char *this, *over;
+ lua_Integer flag;
+ int r;
+
+ this = luaL_checkstring(L, 1);
+ over = luaL_checkstring(L, 2);
+ flag = luaL_checkinteger(L, 3);
+ if((r = bind(this, over, flag)) == -1)
+ lerror(L, "bind");
+ lua_pushinteger(L, r);
+ return 1;
+}
+
+static int
+p9_mount(lua_State *L)
+{
+ const char *over, *aname;
+ lua_Integer fd, afd, flag, 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)
+ lerror(L, "mount");
+ lua_pushinteger(L, r);
+ return 1;
+}
+
+static int
+p9_unmount(lua_State *L)
+{
+ const char *name, *over;
+ int r;
+
+ name = luaL_optstring(L, 1, nil);
+ over = luaL_checkstring(L, 2);
+ if((r = unmount(name, over)) == -1)
+ lerror(L, "unmount");
+ lua_pushinteger(L, r);
+ return 1;
+}
--- /dev/null
+++ b/p9.c
@@ -1,0 +1,165 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+
+static void
+lerror(lua_State *L, char *call)
+{
+ char err[ERRMAX];
+
+ rerrstr(err, sizeof err);
+ lua_pushfstring(L, "%s: %s", call, err);
+ lua_error(L);
+}
+
+/* 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 "ns.c"
+#include "proc.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},
+
+ {"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},
+ {"close", p9_close},
+ {"read", p9_read},
+ {"write", p9_write},
+ {"seek", p9_seek},
+ {"remove", p9_remove},
+ {"fd2path", p9_fd2path},
+
+ {"bind", p9_bind},
+ {"mount", p9_mount},
+ {"unmount", p9_unmount},
+
+ {"rfork", p9_rfork},
+
+ {nil, nil}
+};
+
+int
+luaopen_p9(lua_State *L)
+{
+ Buf *buf;
+ Data *d;
+
+ buf = resizebuffer(L, nil, 8192);
+ lua_pushlightuserdata(L, buf);
+ lua_setfield(L, LUA_REGISTRYINDEX, "p9-buffer");
+
+ luaL_newlib(L, p9func);
+ for(d = p9data; d->key != nil; d++){
+ lua_pushinteger(L, d->val);
+ lua_setfield(L, -2, d->key);
+ }
+ return 1;
+}
--- /dev/null
+++ b/proc.c
@@ -1,0 +1,12 @@
+static int
+p9_rfork(lua_State *L)
+{
+ lua_Integer flags;
+ int r;
+
+ flags = luaL_checkinteger(L, 1);
+ if((r = rfork(flags)) == -1)
+ lerror(L, "rfork");
+ lua_pushinteger(L, r);
+ return 1;
+}
--- /dev/null
+++ b/test.lua
@@ -1,0 +1,140 @@
+#!/bin/lua9
+
+local p9 = require "p9"
+
+local function tmp()
+ 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))
+end
+
+
+
+-- 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)
+ fd = p9.open(f, "r")
+ assert(p9.slurp(fd) == s)
+ p9.close(fd)
+ 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)
+
+ 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)
+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())
+ fd = f.fd
+end
+assert(pcall(p9.seek, fd, 0) == false)
+-- Make sure it's not closed
+local fd
+do
+ local f = p9.createfile(tmp())
+ fd = f.fd
+end
+assert(pcall(p9.seek, fd, 0) == true)
+p9.close(fd)
+
+-- Basic operations. These are the same as regular
+-- function calls so no need for much testing.
+do
+ local f <close> = p9.createfile(tmp(), "rw")
+ local data = string.rep("ABCD", 1024)
+ f:write(data)
+ f:seek(0)
+ assert(f:slurp() == data)
+end
+
+
+
+-- Namespaces
+-- bind and unmount work
+assert(pcall(function()
+ local f
+ p9.bind("#|", "/n/pipe")
+ f = p9.openfile("/n/pipe/data")
+ p9.unmount("/n/pipe")
+ assert(pcall(p9.openfile, "/n/pipe/data") == false)
+end))
+-- mount works
+assert(pcall(function()
+ assert(p9.mount(p9.open("/srv/cwfs", "rw"), nil, "/n/test"))
+ assert(p9.openfile("/n/test/lib/glass"))
+end))
+
+
+-- Process control
+-- No idea how to test this properly.
+
+
+
+-- 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