From f5d6bbfaaeb893daabc8fcd99149adfac09486e0 Mon Sep 17 00:00:00 2001 From: Alessandro Proto Date: Tue, 15 Aug 2023 18:04:17 +0200 Subject: [PATCH] Isolated shell environments --- Capy64/Assets/Lua/CapyOS/init.lua | 2 +- Capy64/Assets/Lua/CapyOS/sys/bin/shell.lua | 21 ++-- .../Assets/Lua/CapyOS/sys/lib/scheduler.lua | 88 +++++++------- .../Lua/CapyOS/sys/lib/shell/package.lua | 112 ++++++++++++++++++ Capy64/Assets/Lua/bios.lua | 2 +- Capy64/Runtime/RuntimeManager.cs | 23 +++- 6 files changed, 197 insertions(+), 51 deletions(-) create mode 100644 Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua diff --git a/Capy64/Assets/Lua/CapyOS/init.lua b/Capy64/Assets/Lua/CapyOS/init.lua index bae0138..de18f84 100644 --- a/Capy64/Assets/Lua/CapyOS/init.lua +++ b/Capy64/Assets/Lua/CapyOS/init.lua @@ -1,4 +1,4 @@ -local version = "0.1.0" +local version = "0.1.1" local systemDirectory = "/sys" print("Starting CapyOS") diff --git a/Capy64/Assets/Lua/CapyOS/sys/bin/shell.lua b/Capy64/Assets/Lua/CapyOS/sys/bin/shell.lua index d87c69d..946643a 100644 --- a/Capy64/Assets/Lua/CapyOS/sys/bin/shell.lua +++ b/Capy64/Assets/Lua/CapyOS/sys/bin/shell.lua @@ -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) diff --git a/Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua b/Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua index 198704e..00b9445 100644 --- a/Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua +++ b/Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua @@ -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 diff --git a/Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua b/Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua new file mode 100644 index 0000000..469d826 --- /dev/null +++ b/Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua @@ -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 diff --git a/Capy64/Assets/Lua/bios.lua b/Capy64/Assets/Lua/bios.lua index 9a75b84..8f9442c 100644 --- a/Capy64/Assets/Lua/bios.lua +++ b/Capy64/Assets/Lua/bios.lua @@ -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") diff --git a/Capy64/Runtime/RuntimeManager.cs b/Capy64/Runtime/RuntimeManager.cs index dae9e34..e077818 100644 --- a/Capy64/Runtime/RuntimeManager.cs +++ b/Capy64/Runtime/RuntimeManager.cs @@ -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)); }