cleaner VFS abstraction
This commit is contained in:
parent
4cbe8f81d3
commit
288ef5f03a
@ -13,6 +13,11 @@ end
|
||||
|
||||
function sandboxlib.dispatch_if_restricted(rkey, original, restricted)
|
||||
local out = {}
|
||||
for k, v in pairs(restricted) do
|
||||
if not original[k] then
|
||||
out[k] = v
|
||||
end
|
||||
end
|
||||
for k, v in pairs(original) do
|
||||
out[k] = function(...)
|
||||
if processrestriction(rkey) then
|
||||
|
@ -77,31 +77,10 @@ local function add_to_table(t1, t2)
|
||||
end
|
||||
end
|
||||
|
||||
local fscombine, fsgetname, fsgetdir = fs.combine, fs.getName, fs.getDir
|
||||
-- Convert path to canonical form
|
||||
local function canonicalize(path)
|
||||
return fs.combine(path, "")
|
||||
end
|
||||
|
||||
-- Checks whether a path is in a directory
|
||||
local function path_in(p, dir)
|
||||
return starts_with(canonicalize(p), canonicalize(dir))
|
||||
end
|
||||
|
||||
local function make_mappings(root)
|
||||
return {
|
||||
["/disk"] = "/disk",
|
||||
["/rom"] = "/rom",
|
||||
default = root
|
||||
}
|
||||
end
|
||||
|
||||
local function get_root(path, mappings)
|
||||
for mapfrom, mapto in pairs(mappings) do
|
||||
if path_in(path, mapfrom) then
|
||||
return mapto, mapfrom
|
||||
end
|
||||
end
|
||||
return mappings.default, "/"
|
||||
return fscombine(path, "")
|
||||
end
|
||||
|
||||
-- Escapes lua patterns in a string. Should not be needed, but lua is stupid so the only string.replace thing is gsub
|
||||
@ -114,19 +93,12 @@ local function strip(p, root)
|
||||
return p:gsub("^" .. escape(canonicalize(root)), "")
|
||||
end
|
||||
|
||||
local function resolve_path(path, mappings)
|
||||
local root, to_strip = get_root(path, mappings)
|
||||
local newpath = strip(fs.combine(root, path), to_strip)
|
||||
if path_in(newpath, root) then return newpath end
|
||||
return resolve_path(newpath, mappings)
|
||||
end
|
||||
|
||||
local function segments(path)
|
||||
local segs, rest = {}, canonicalize(path)
|
||||
if rest == "" then return {} end -- otherwise we'd get "root" and ".." for some broken reason
|
||||
repeat
|
||||
table.insert(segs, 1, fs.getName(rest))
|
||||
rest = fs.getDir(rest)
|
||||
table.insert(segs, 1, fsgetname(rest))
|
||||
rest = fsgetdir(rest)
|
||||
until rest == ""
|
||||
return segs
|
||||
end
|
||||
@ -134,7 +106,7 @@ end
|
||||
local function combine(segs)
|
||||
local out = ""
|
||||
for _, p in pairs(segs) do
|
||||
out = fs.combine(out, p)
|
||||
out = fscombine(out, p)
|
||||
end
|
||||
return out
|
||||
end
|
||||
@ -179,98 +151,154 @@ end
|
||||
|
||||
local this_level_env = _G
|
||||
|
||||
-- Create a modified FS table which confines you to root and has some extra read-only pseudofiles.
|
||||
local function create_FS(root, overlay)
|
||||
local fs = fs
|
||||
local mappings = make_mappings(root)
|
||||
|
||||
local vfstree = {
|
||||
mount = "potatOS",
|
||||
children = {
|
||||
["disk"] = { mount = "disk" },
|
||||
["rom"] = { mount = "rom" },
|
||||
--["virtual_test"] = { virtual = "bees" }
|
||||
}
|
||||
-- make virtual filesystem from files (no nested directories for simplicity)
|
||||
local function vfs_from_files(files)
|
||||
return {
|
||||
list = function(path)
|
||||
if path ~= "" then return {} end
|
||||
local out = {}
|
||||
for k, v in pairs(files) do
|
||||
table.insert(out, k)
|
||||
end
|
||||
return out
|
||||
end,
|
||||
open = function(path, mode)
|
||||
return make_handle(files[path])
|
||||
end,
|
||||
exists = function(path)
|
||||
return files[path] ~= nil or path == ""
|
||||
end,
|
||||
isReadOnly = function(path)
|
||||
return true
|
||||
end,
|
||||
isDir = function(path)
|
||||
if path == "" then return true end
|
||||
return false
|
||||
end,
|
||||
getDrive = function(_) return "memory" end,
|
||||
getSize = function(path)
|
||||
return #files[path]
|
||||
end,
|
||||
getFreeSpace = function() return 0 end,
|
||||
makeDir = function() end,
|
||||
delete = function() end,
|
||||
move = function() end,
|
||||
copy = function() end,
|
||||
attributes = function(path)
|
||||
return {
|
||||
size = #files[path],
|
||||
modification = os.epoch "utc"
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
local function create_FS(vfstree)
|
||||
local fs = fs
|
||||
|
||||
local function is_usable_node(node)
|
||||
return node.mount or node.vfs
|
||||
end
|
||||
|
||||
local function resolve(sandbox_path)
|
||||
local segs = segments(sandbox_path)
|
||||
local current_tree = vfstree
|
||||
|
||||
local last_usable_node, last_segs = nil, nil
|
||||
|
||||
while true do
|
||||
if is_usable_node(current_tree) then
|
||||
last_usable_node = current_tree
|
||||
last_segs = copy(segs)
|
||||
end
|
||||
local seg = segs[1]
|
||||
if current_tree.children and current_tree.children[seg] then
|
||||
if seg and current_tree.children and current_tree.children[seg] then
|
||||
table.remove(segs, 1)
|
||||
current_tree = current_tree.children[seg]
|
||||
else break end
|
||||
end
|
||||
return last_usable_node, last_segs
|
||||
end
|
||||
|
||||
local new_overlay = {}
|
||||
for k, v in pairs(overlay) do
|
||||
new_overlay[canonicalize(k)] = v
|
||||
local function resolve_path(sandbox_path)
|
||||
local node, segs = resolve(sandbox_path)
|
||||
if node.mount then return fs, fscombine(node.mount, combine(segs)) end
|
||||
return node.vfs, combine(segs)
|
||||
end
|
||||
|
||||
local function lift_to_sandbox(f, n)
|
||||
return function(...)
|
||||
local args = map(function(x) return resolve_path(x, mappings) end, {...})
|
||||
return f(table.unpack(args))
|
||||
return function(path)
|
||||
local vfs, path = resolve_path(path)
|
||||
return vfs[n](path)
|
||||
end
|
||||
end
|
||||
|
||||
local new = copy_some_keys {"getDir", "getName", "combine", "complete"} (fs)
|
||||
|
||||
function new.isReadOnly(path)
|
||||
return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom")
|
||||
end
|
||||
|
||||
function new.open(path, mode)
|
||||
if (contains(mode, "w") or contains(mode, "a")) and new.isReadOnly(path) then
|
||||
error "Access denied"
|
||||
else
|
||||
local overlay_data = path_in_overlay(new_overlay, path)
|
||||
if overlay_data then
|
||||
if type(overlay_data) == "function" then overlay_data = overlay_data(this_level_env) end
|
||||
return make_handle(overlay_data), "YAFSS overlay"
|
||||
end
|
||||
return fs.open(resolve_path(path, mappings), mode)
|
||||
local vfs, path = resolve_path(path)
|
||||
return vfs.open(path, mode)
|
||||
end
|
||||
end
|
||||
|
||||
function new.exists(path)
|
||||
if path_in_overlay(new_overlay, path) ~= nil then return true end
|
||||
return fs.exists(resolve_path(path, mappings))
|
||||
end
|
||||
|
||||
function new.overlay()
|
||||
return map(function(x)
|
||||
if type(x) == "function" then return x(this_level_env)
|
||||
else return x end
|
||||
end, new_overlay)
|
||||
end
|
||||
|
||||
function new.list(dir)
|
||||
local sdir = canonicalize(resolve_path(dir, mappings))
|
||||
local ocontents = {}
|
||||
for opath in pairs(new_overlay) do
|
||||
if fs.getDir(opath) == sdir then
|
||||
table.insert(ocontents, fs.getName(opath))
|
||||
end
|
||||
end
|
||||
local ok, contents = pcall(fs.list, sdir)
|
||||
-- in case of error (nonexistent dir, probably) return overlay contents
|
||||
-- very awful temporary hack until I can get a nicer treeized VFS done
|
||||
if not ok then
|
||||
if #ocontents > 0 then return ocontents end
|
||||
error(contents)
|
||||
function new.move(src, dest)
|
||||
local src_vfs, src_path = resolve_path(src)
|
||||
local dest_vfs, dest_path = resolve_path(dest)
|
||||
if src_vfs == dest_vfs then
|
||||
return src_vfs.move(src_path, dest_path)
|
||||
else
|
||||
for _, v in pairs(ocontents) do
|
||||
table.insert(contents, v)
|
||||
end
|
||||
return contents
|
||||
if src_vfs.isReadOnly(src_path) then error "Access denied" end
|
||||
new.copy(src, dest)
|
||||
src_vfs.delete(src_path)
|
||||
end
|
||||
end
|
||||
|
||||
add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "move", "copy", "delete", "isDriveRoot"} (fs)))
|
||||
function new.copy(src, dest)
|
||||
local src_vfs, src_path = resolve_path(src)
|
||||
local dest_vfs, dest_path = resolve_path(dest)
|
||||
if src_vfs == dest_vfs then
|
||||
return src_vfs.copy(src_path, dest_path)
|
||||
else
|
||||
if src_vfs.isDir(src_path) then
|
||||
dest_vfs.makeDir(dest_path)
|
||||
for _, f in pairs(src_vfs.list(src_path)) do
|
||||
new.copy(fscombine(src, f), fscombine(dest, f))
|
||||
end
|
||||
else
|
||||
local dest_fh = dest_vfs.open(dest_path, "wb")
|
||||
local src_fh = src_vfs.open(src_path, "rb")
|
||||
dest_fh.write(src_fh.readAll())
|
||||
src_fh.close()
|
||||
dest_fh.close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function new.mountVFS(path, vfs)
|
||||
local path = canonicalize(path)
|
||||
local node, relpath = resolve(path)
|
||||
while #relpath > 0 do
|
||||
local seg = table.remove(relpath, 1)
|
||||
if not node.children then node.children = {} end
|
||||
if not node.children[seg] then node.children[seg] = {} end
|
||||
node = node.children[seg]
|
||||
end
|
||||
node.vfs = vfs
|
||||
end
|
||||
|
||||
function new.unmountVFS(path)
|
||||
local node, relpath = resolve(path)
|
||||
if #relpath == 0 then
|
||||
node.vfs = nil
|
||||
else
|
||||
error "Not a mountpoint"
|
||||
end
|
||||
end
|
||||
|
||||
add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "delete", "isDriveRoot", "exists", "isReadOnly", "list", "attributes"} (fs)))
|
||||
|
||||
function new.find(wildcard)
|
||||
local function recurse_spec(results, path, spec) -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua
|
||||
@ -310,7 +338,7 @@ local function create_FS(root, overlay)
|
||||
to_add.c = new.dump(path)
|
||||
to_add.t = "d"
|
||||
else
|
||||
local fh = new.open(path, "r")
|
||||
local fh = new.open(path, "rb")
|
||||
to_add.c = fh.readAll()
|
||||
fh.close()
|
||||
end
|
||||
@ -327,7 +355,7 @@ local function create_FS(root, overlay)
|
||||
new.makeDir(path)
|
||||
new.load(f.c, path)
|
||||
else
|
||||
local fh = new.open(path, "w")
|
||||
local fh = new.open(path, "wb")
|
||||
fh.write(f.c)
|
||||
fh.close()
|
||||
end
|
||||
@ -465,4 +493,4 @@ local function run(API_overrides, init, logger)
|
||||
end
|
||||
end
|
||||
|
||||
return { run = run, create_FS = create_FS }
|
||||
return { run = run, create_FS = create_FS, vfs_from_files = vfs_from_files }
|
||||
|
49
src/main.lua
49
src/main.lua
@ -1297,26 +1297,35 @@ local function run_with_sandbox()
|
||||
end or v
|
||||
end
|
||||
|
||||
-- Provide many, many useful or not useful programs to the potatOS shell.
|
||||
local FS_overlay = {
|
||||
["secret/.pkey"] = fproxy "signing-key.tbl",
|
||||
["secret/log"] = function() return potatOS_proxy.get_log() end,
|
||||
-- The API backing this no longer exists due to excessive server load.
|
||||
-----["/rom/programs/dwarf.lua"] = "print(potatOS.dwarf())",
|
||||
["/secret/processes"] = function()
|
||||
return tostring(process.list())
|
||||
end,
|
||||
["/rom/heavlisp_lib/stdlib.hvl"] = fproxy "stdlib.hvl"
|
||||
local yafss = require "yafss"
|
||||
|
||||
local vfstree = {
|
||||
mount = "potatOS",
|
||||
children = {
|
||||
["rom"] = {
|
||||
mount = "rom",
|
||||
children = {
|
||||
["potatOS_xlib"] = { mount = "/xlib" },
|
||||
programs = {
|
||||
children = {
|
||||
["potatOS"] = { mount = "/bin" }
|
||||
}
|
||||
},
|
||||
["autorun"] = {
|
||||
vfs = yafss.vfs_from_files {
|
||||
["fix_path.lua"] = [[shell.setPath("/rom/programs/potatOS:"..shell.path())]],
|
||||
}
|
||||
},
|
||||
["heavlisp_lib"] = {
|
||||
vfs = yafss.vfs_from_files {
|
||||
["stdlib.hvl"] = fproxy "stdlib.hvl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, file in pairs(fs.list "bin") do
|
||||
FS_overlay[fs.combine("rom/programs", file)] = fproxy(fs.combine("bin", file))
|
||||
end
|
||||
|
||||
for _, file in pairs(fs.list "xlib") do
|
||||
FS_overlay[fs.combine("rom/potato_xlib", file)] = fproxy(fs.combine("xlib", file))
|
||||
end
|
||||
|
||||
local API_overrides = {
|
||||
process = process,
|
||||
json = json,
|
||||
@ -1400,11 +1409,9 @@ local function run_with_sandbox()
|
||||
|
||||
require "metatable_improvements"(potatOS_proxy.add_log, potatOS_proxy.report_incident)
|
||||
|
||||
local yafss = require "yafss"
|
||||
|
||||
local fss_sentinel = sandboxlib.create_sentinel "fs-sandbox"
|
||||
local debug_sentinel = sandboxlib.create_sentinel "constrained-debug"
|
||||
local sandbox_filesystem = yafss.create_FS("potatOS", FS_overlay)
|
||||
local sandbox_filesystem = yafss.create_FS(vfstree)
|
||||
_G.fs = sandboxlib.dispatch_if_restricted(fss_sentinel, _G.fs, sandbox_filesystem)
|
||||
_G.debug = sandboxlib.allow_whitelisted(debug_sentinel, _G.debug, {
|
||||
"traceback",
|
||||
|
@ -573,7 +573,7 @@ local function boot_require(package)
|
||||
return pkg
|
||||
end
|
||||
local npackage = package:gsub("%.", "/")
|
||||
for _, search_path in next, {"/", "lib", "rom/modules/main", "rom/modules/turtle", "rom/modules/command", "rom/potato_xlib"} do
|
||||
for _, search_path in next, {"/", "lib", "rom/modules/main", "rom/modules/turtle", "rom/modules/command", "rom/potatOS_xlib"} do
|
||||
local path = try_paths(search_path, {npackage, npackage .. ".lua"})
|
||||
if path then
|
||||
local ok, res = pcall(dofile, path)
|
||||
@ -589,7 +589,7 @@ _G.require = boot_require
|
||||
_ENV.require = boot_require
|
||||
|
||||
local libs = {}
|
||||
for _, f in pairs(fs.list "rom/potato_xlib") do
|
||||
for _, f in pairs(fs.list "rom/potatOS_xlib") do
|
||||
table.insert(libs, f)
|
||||
end
|
||||
table.sort(libs)
|
||||
@ -1759,7 +1759,7 @@ if potatOS.registry.get "potatOS.immutable_global_scope" then
|
||||
end
|
||||
|
||||
process.spawn(keyboard_shortcuts, "kbsd")
|
||||
if http.websocket then process.spawn(skynet.listen, "skynetd") process.spawn(potatoNET, "systemd-potatod") end
|
||||
if skynet and http.websocket then process.spawn(skynet.listen, "skynetd") process.spawn(potatoNET, "systemd-potatod") end
|
||||
local autorun = potatOS.registry.get "potatOS.autorun"
|
||||
if type(autorun) == "string" then
|
||||
autorun = load(autorun)
|
||||
@ -1781,7 +1781,7 @@ local function run_shell()
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
potatOS.add_log "starting user shell"
|
||||
os.run( {}, sShell )
|
||||
os.run({}, sShell )
|
||||
end
|
||||
|
||||
if potatOS.registry.get "potatOS.extended_monitoring" then process.spawn(excessive_monitoring, "extended_monitoring") end
|
||||
|
Loading…
Reference in New Issue
Block a user