Isolated shell environments

This commit is contained in:
Alessandro Proto 2023-08-15 18:04:17 +02:00
parent 2bf4b17577
commit f5d6bbfaae
6 changed files with 197 additions and 51 deletions

View file

@ -1,4 +1,4 @@
local version = "0.1.0"
local version = "0.1.1"
local systemDirectory = "/sys"
print("Starting CapyOS")

View file

@ -4,6 +4,7 @@ local fs = require("fs")
local machine = require("machine")
local argparser = require("argparser")
local scheduler = require("scheduler")
local createPackageEnvironment = require("shell.package")
local exit = false
local parentShell = shell
@ -16,15 +17,19 @@ shell.aliases = parentShell and parentShell.aliases or {}
local currentDir = parentShell and parentShell.getDir() or shell.homePath
local function buildEnvironment(path, args, argf)
local function buildEnvironment(command, filepath, args, argf)
local arg = { table.unpack(args, 2) }
arg[0] = path
arg[0] = command
arg.string = argf
local envPackage = createPackageEnvironment(filepath)
envPackage.loaded.scheduler = scheduler
return setmetatable({
shell = shell,
arg = arg,
}, { __index = _G })
scheduler = scheduler,
}, { __index = envPackage.loaded._G })
end
function shell.getDir()
@ -78,7 +83,7 @@ function shell.run(...)
end
end
local env = buildEnvironment(command, args, argf)
local env = buildEnvironment(command, path, args, argf)
local func, err = loadfile(path, "t", env)
@ -90,11 +95,13 @@ function shell.run(...)
local ok, err
local function run()
ok, err = pcall(func, table.unpack(args, 2))
end
local programTask = scheduler.spawn(run)
coroutine.yield("scheduler_task_end")
local programTask, yielded = scheduler.spawn(run)
if yielded then
coroutine.yield("scheduler_task_end")
end
if not ok then
io.stderr.print(err)

View file

@ -55,39 +55,6 @@ local function findParent()
return nil
end
function scheduler.spawn(func, options)
expect(1, func, "function")
expect(2, options, "nil", "table")
options = options or {}
options.args = options.args or {}
local source = debug.getinfo(2)
local task = newTask()
local pid = #tasks + 1
task.pid = pid
task.options = options
task.source = source.source
task.uuid = tostring(func)
task.thread = coroutine.create(func)
task.started = false
local parent = findParent()
if parent then
task.parent = parent.pid
table.insert(parent.children, pid)
end
task.filters = {}
task.children = {}
task.eventQueue = {}
tasks[pid] = task
processes = processes + 1
return task
end
local function cascadeKill(pid, err)
local task = tasks[pid]
if not task then
@ -116,6 +83,50 @@ local function cascadeKill(pid, err)
end
end
local function resumeTask(task, yieldPars)
local pars = table.pack(coroutine.resume(task.thread, table.unpack(yieldPars)))
if pars[1] then
task.filters = table.pack(table.unpack(pars, 2))
return coroutine.status(task.thread) ~= "dead"
else
cascadeKill(task.pid, pars[2])
return false
end
end
function scheduler.spawn(func, options)
expect(1, func, "function")
expect(2, options, "nil", "table")
options = options or {}
options.args = options.args or {}
local source = debug.getinfo(2)
local task = newTask()
local pid = #tasks + 1
task.pid = pid
task.options = options
task.source = source.source
task.uuid = tostring(func)
task.thread = coroutine.create(func)
local parent = findParent()
if parent then
task.parent = parent.pid
table.insert(parent.children, pid)
end
task.filters = {}
task.children = {}
task.eventQueue = {}
task.skip = true
tasks[pid] = task
processes = processes + 1
return task, resumeTask(task, task.options.args)
end
function scheduler.kill(pid)
expect(1, pid, "number")
cascadeKill(pid)
@ -146,15 +157,10 @@ function scheduler.init()
yieldPars = table.pack(table.unpack(ev, 3))
end
if yieldPars[1] ~= "scheduler" and not task.filters or #task.filters == 0 or contains(task.filters, yieldPars[1]) or yieldPars[1] == "interrupt" then
if not task.started then
yieldPars = task.options.args
task.started = true
end
local pars = table.pack(coroutine.resume(task.thread, table.unpack(yieldPars)))
if pars[1] then
task.filters = table.pack(table.unpack(pars, 2))
if task.skip then
task.skip = false
else
cascadeKill(pid, pars[2])
resumeTask(task, yieldPars)
end
end

View file

@ -0,0 +1,112 @@
local expect = require("expect").expect
local fs = require("fs")
local nativePackage = package
local function copyTable(source, target)
target = target or {}
for k, v in pairs(source) do
target[k] = v
end
return target
end
local hostPackages = copyTable(nativePackage._host)
local function createPreloadSearcher(envPackage)
return function(name)
if not envPackage.preload[name] then
return string.format("no field package.preload['%s']", name)
end
return envPackage.preload[name], ":preload:"
end
end
local function createLoaderSearcher(envPackage)
return function(name)
local path, err = envPackage.searchpath(name, envPackage.path)
if not path then
return err
end
local func, err = loadfile(path)
if not func then
return string.format("error loading module '%s' from file '%s':\t%s", name, path, err)
end
return func, path
end
end
local function createEnvironment(filePath)
local envPackage = {
cpath = nativePackage.cpath,
searchpath = nativePackage.searchpath,
config = nativePackage.config,
searchers = {},
loaded = {},
preload = {},
}
local dirName = fs.getDir(filePath)
--envPackage.path = string.format("%s/?.lua;%s/?/init.lua;", dirName, dirName) .. nativePackage.path
envPackage.path = nativePackage.path
envPackage.searchers[1] = createPreloadSearcher(envPackage)
envPackage.searchers[2] = createLoaderSearcher(envPackage)
local function envRequire(modname)
expect(1, modname, "string", "number")
modname = tostring(modname)
if envPackage.loaded[modname] then
return envPackage.loaded[modname]
end
local errorOutput = ""
local libFunction, libPath
for i = 1, #envPackage.searchers do
local par, path = envPackage.searchers[i](modname)
if type(par) == "function" then
libFunction, libPath = par, path
break
else
errorOutput = errorOutput .. "\n\t" .. par
end
end
if not libFunction then
error(string.format("module '%s' not found:%s", modname, errorOutput), 2)
end
local ok, par = pcall(libFunction)
if not ok then
error(par, 0)
end
if par == nil then
envPackage.loaded[modname] = true
return true
end
envPackage.loaded[modname] = par
return par, libPath
end
copyTable(hostPackages, envPackage.loaded)
envPackage.loaded.package = envPackage
local env_G = copyTable(envPackage.loaded._G or _G)
envPackage.loaded._G = env_G
env_G._G = env_G
envPackage.loaded._G.package = envPackage
envPackage.loaded._G.require = envRequire
return envPackage, envRequire
end
return createEnvironment

View file

@ -177,7 +177,7 @@ local function bootScreen()
term.setPos(1,2)
writeCenter("Capy64")
term.setPos(1,4)
writeCenter("Powered by Capybaras")
writeCenter("(c) 2023 AlexDevs")
term.setPos(1, h - 1)
writeCenter("Press F2 to open setup")

View file

@ -136,6 +136,7 @@ internal class RuntimeManager : IComponent
luaState = new LuaState();
_game.LuaRuntime = luaState;
luaState.Init();
CopyHostLibraries();
emitter = new(_game.EventEmitter, luaState);
@ -162,11 +163,31 @@ internal class RuntimeManager : IComponent
}
}
private void CopyHostLibraries()
{
// table that will contain host libraries
luaState.Thread.NewTable();
luaState.Thread.GetGlobal("package");
luaState.Thread.GetField(-1, "loaded");
luaState.Thread.PushNil();
while (luaState.Thread.Next(-2))
{
var libname = luaState.Thread.ToString(-2);
luaState.Thread.SetField(1, libname);
}
luaState.Thread.Rotate(1, -1);
luaState.Thread.SetField(1, "_host");
luaState.Thread.SetTop(0);
}
private void LoadFirmware()
{
var firmwareContent = File.ReadAllText(Path.Combine(Capy64.AssetsPath, "Lua/firmware.lua"));
var errored = luaState.Thread.DoString(firmwareContent);
if(errored)
if (errored)
{
throw new LuaException(luaState.Thread.ToString(-1));
}