mirror of
https://github.com/kepler155c/opus
synced 2025-01-16 02:15:42 +00:00
environments, error messages, and stack traces, oh my!
Changed the way processes are launched. multishell.openTab and kernel.run now accept the current environment as a parameter. That new process inherits a copy of the passed environment. Reduces complexity as the calling process is not required to create a suitable env. Stack traces have been greatly improved and now include the stack for coroutines that error.
This commit is contained in:
parent
bd911e80e8
commit
8279c1ae12
@ -255,7 +255,7 @@ function page.packetSlide:eventHandler(event)
|
||||
page:setFocus(page.packetGrid)
|
||||
|
||||
elseif event.type == 'packet_lua' then
|
||||
multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
|
||||
multishell.openTab(_ENV, { path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
|
||||
|
||||
elseif event.type == 'prev_packet' then
|
||||
local c = self.currentPacket
|
||||
|
@ -12,6 +12,7 @@ local page = UI.Page {
|
||||
buttons = {
|
||||
{ text = 'Activate', event = 'activate' },
|
||||
{ text = 'Terminate', event = 'terminate' },
|
||||
{ text = 'Inspect', event = 'inspect' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
@ -49,6 +50,12 @@ local page = UI.Page {
|
||||
multishell.setFocus(t.uid)
|
||||
elseif event.type == 'terminate' then
|
||||
multishell.terminate(t.uid)
|
||||
elseif event.type == 'inspect' then
|
||||
multishell.openTab(_ENV, {
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { t },
|
||||
focused = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
if event.type == 'quit' then
|
||||
|
@ -110,7 +110,7 @@ page = UI.Page {
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
multishell.openTab({
|
||||
multishell.openTab(_ENV, {
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.selected.raw },
|
||||
focused = true,
|
||||
|
@ -1,5 +1,3 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('opus.event')
|
||||
local Util = require('opus.util')
|
||||
|
||||
|
@ -31,21 +31,14 @@ local function snmpConnection(socket)
|
||||
socket:write('pong')
|
||||
|
||||
elseif msg.type == 'script' then
|
||||
local env = kernel.makeEnv()
|
||||
local fn, err = load(msg.args, 'script', nil, env)
|
||||
if fn then
|
||||
kernel.run({
|
||||
fn = fn,
|
||||
env = env,
|
||||
kernel.run(_ENV, {
|
||||
chunk = msg.args,
|
||||
title = 'script',
|
||||
})
|
||||
else
|
||||
_G.printError(err)
|
||||
end
|
||||
|
||||
elseif msg.type == 'scriptEx' then
|
||||
local s, m = pcall(function()
|
||||
local env = kernel.makeEnv()
|
||||
local env = kernel.makeEnv(_ENV)
|
||||
local fn, m = load(msg.args, 'script', nil, env)
|
||||
if not fn then
|
||||
error(m)
|
||||
|
@ -41,12 +41,12 @@ local function telnetHost(socket, mode)
|
||||
end
|
||||
end
|
||||
|
||||
local shellThread = kernel.run({
|
||||
local shellThread = kernel.run(_ENV, {
|
||||
window = win,
|
||||
title = mode .. ' client',
|
||||
hidden = true,
|
||||
fn = function()
|
||||
Util.run(kernel.makeEnv(), Alt.get('shell'), table.unpack(termInfo.program))
|
||||
Util.run(kernel.makeEnv(_ENV), Alt.get('shell'), table.unpack(termInfo.program))
|
||||
if socket.queue then
|
||||
socket:write(socket.queue)
|
||||
end
|
||||
|
@ -6,16 +6,6 @@ local fs = _G.fs
|
||||
local settings = _G.settings
|
||||
local shell = _ENV.shell
|
||||
|
||||
local sandboxEnv = { }
|
||||
for k,v in pairs(_ENV) do
|
||||
sandboxEnv[k] = v
|
||||
end
|
||||
sandboxEnv.package = nil
|
||||
sandboxEnv.require = nil
|
||||
sandboxEnv.arg = nil
|
||||
sandboxEnv._ENV = nil
|
||||
sandboxEnv.shell = shell
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local trace = require('opus.trace')
|
||||
@ -51,7 +41,7 @@ local function run(...)
|
||||
local args = tokenise(...)
|
||||
local command = table.remove(args, 1) or error('No such program')
|
||||
local isUrl = not not command:match("^(https?:)")
|
||||
local env = shell.makeEnv()
|
||||
local env = shell.makeEnv(_ENV)
|
||||
|
||||
local path, loadFn
|
||||
if isUrl then
|
||||
@ -64,7 +54,7 @@ local function run(...)
|
||||
|
||||
local fn, err = loadFn(path, env)
|
||||
if not fn then
|
||||
error(err)
|
||||
error(err, -1)
|
||||
end
|
||||
|
||||
if _ENV.multishell then
|
||||
@ -287,13 +277,9 @@ function shell.getRunningInfo()
|
||||
return tProgramStack[#tProgramStack]
|
||||
end
|
||||
|
||||
function shell.setEnv(name, value)
|
||||
_ENV[name] = value
|
||||
sandboxEnv[name] = value
|
||||
end
|
||||
|
||||
function shell.makeEnv()
|
||||
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
||||
-- convenience function for making a runnable env
|
||||
function shell.makeEnv(env)
|
||||
env = setmetatable(Util.shallowCopy(env), { __index = _G })
|
||||
_G.requireInjector(env)
|
||||
return env
|
||||
end
|
||||
@ -321,7 +307,6 @@ function shell.newTab(tabInfo, ...)
|
||||
|
||||
if path then
|
||||
tabInfo.path = path
|
||||
tabInfo.env = shell.makeEnv()
|
||||
tabInfo.args = args
|
||||
tabInfo.title = fs.getName(path):match('([^%.]+)')
|
||||
|
||||
@ -329,23 +314,19 @@ function shell.newTab(tabInfo, ...)
|
||||
table.insert(tabInfo.args, 1, tabInfo.path)
|
||||
tabInfo.path = 'sys/apps/shell.lua'
|
||||
end
|
||||
return _ENV.multishell.openTab(tabInfo)
|
||||
return _ENV.multishell.openTab(_ENV, tabInfo)
|
||||
end
|
||||
return nil, 'No such program'
|
||||
end
|
||||
|
||||
if not _ENV.multishell then
|
||||
function shell.newTab()
|
||||
error('Multishell is not available')
|
||||
end
|
||||
end
|
||||
|
||||
function shell.openTab(...)
|
||||
-- needs to use multishell.launch .. so we can run with stock multishell
|
||||
local tWords = tokenise( ... )
|
||||
local sCommand = tWords[1]
|
||||
if sCommand then
|
||||
local sPath = shell.resolveProgram(sCommand)
|
||||
if sPath == "sys/apps/shell.lua" then
|
||||
return _ENV.multishell.launch(shell.makeEnv(), sPath, table.unpack(tWords, 2))
|
||||
else
|
||||
return _ENV.multishell.launch(shell.makeEnv(), "sys/apps/shell.lua", sCommand, table.unpack(tWords, 2))
|
||||
end
|
||||
end
|
||||
return shell.newTab({ }, ...)
|
||||
end
|
||||
|
||||
function shell.openForegroundTab( ... )
|
||||
|
@ -10,9 +10,8 @@ if multishell and multishell.getTabs then
|
||||
local tab = kernel.getFocused()
|
||||
if tab and not tab.noTerminate then
|
||||
multishell.terminate(tab.uid)
|
||||
multishell.openTab({
|
||||
multishell.openTab(tab.env, {
|
||||
path = tab.path,
|
||||
env = tab.env,
|
||||
args = tab.args,
|
||||
focused = true,
|
||||
})
|
||||
|
@ -39,7 +39,7 @@ local function systemLog()
|
||||
keyboard.removeHotkey('control-d')
|
||||
end
|
||||
|
||||
kernel.run({
|
||||
kernel.run(_ENV, {
|
||||
title = 'System Log',
|
||||
fn = systemLog,
|
||||
noTerminate = true,
|
||||
|
@ -15,7 +15,7 @@ do
|
||||
end
|
||||
|
||||
local function startNetwork()
|
||||
kernel.run({
|
||||
kernel.run(_ENV, {
|
||||
title = 'Net daemon',
|
||||
path = 'sys/apps/netdaemon.lua',
|
||||
hidden = true,
|
||||
|
@ -8,7 +8,6 @@ local kernel = _G.kernel
|
||||
local keys = _G.keys
|
||||
local os = _G.os
|
||||
local printError = _G.printError
|
||||
local shell = _ENV.shell
|
||||
local window = _G.window
|
||||
|
||||
local parentTerm = _G.device.terminal
|
||||
@ -18,7 +17,7 @@ local tabsDirty = false
|
||||
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
|
||||
local multishell = { }
|
||||
|
||||
shell.setEnv('multishell', multishell)
|
||||
_ENV.multishell = multishell
|
||||
|
||||
kernel.window.reposition(1, 2, w, h - 1)
|
||||
|
||||
@ -93,22 +92,21 @@ function multishell.getTabs()
|
||||
return kernel.routines
|
||||
end
|
||||
|
||||
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
||||
function multishell.launch(env, path, ...)
|
||||
-- backwards compatibility
|
||||
return multishell.openTab({
|
||||
env = tProgramEnv,
|
||||
path = sProgramPath,
|
||||
return multishell.openTab(env, {
|
||||
path = path,
|
||||
args = { ... },
|
||||
})
|
||||
end
|
||||
|
||||
function multishell.openTab(tab)
|
||||
function multishell.openTab(env, tab)
|
||||
if not tab.title and tab.path then
|
||||
tab.title = fs.getName(tab.path):match('([^%.]+)')
|
||||
end
|
||||
tab.title = tab.title or 'untitled'
|
||||
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
|
||||
tab.onExit = function(self, result, err)
|
||||
tab.onExit = tab.onExit or function(self, result, err)
|
||||
if not result and err and err ~= 'Terminated' or (err and err ~= 0) then
|
||||
self.terminal.setBackgroundColor(colors.black)
|
||||
if tonumber(err) then
|
||||
@ -131,14 +129,17 @@ function multishell.openTab(tab)
|
||||
end
|
||||
end
|
||||
|
||||
local routine = kernel.run(tab)
|
||||
local routine, message = kernel.run(env, tab)
|
||||
|
||||
if routine then
|
||||
if tab.focused then
|
||||
multishell.setFocus(routine.uid)
|
||||
else
|
||||
redrawMenu()
|
||||
end
|
||||
return routine.uid
|
||||
end
|
||||
|
||||
return routine and routine.uid, message
|
||||
end
|
||||
|
||||
function multishell.hideTab(tabId)
|
||||
@ -316,15 +317,22 @@ kernel.hook('mouse_scroll', function(_, eventData)
|
||||
end)
|
||||
|
||||
kernel.hook('kernel_ready', function()
|
||||
overviewId = multishell.openTab({
|
||||
path = config.launcher or 'sys/apps/Overview.lua',
|
||||
overviewId = multishell.openTab(_ENV, {
|
||||
path = 'sys/apps/shell.lua',
|
||||
args = { config.launcher or 'sys/apps/Overview.lua' },
|
||||
isOverview = true,
|
||||
noTerminate = true,
|
||||
focused = true,
|
||||
title = '+',
|
||||
onExit = function(_, s, m)
|
||||
if not s then
|
||||
kernel.halt(s, m)
|
||||
end
|
||||
end,
|
||||
})
|
||||
multishell.setTitle(overviewId, '+')
|
||||
|
||||
multishell.openTab({
|
||||
multishell.openTab(_ENV, {
|
||||
path = 'sys/apps/shell.lua',
|
||||
args = { 'sys/apps/autorun.lua' },
|
||||
title = 'Autorun',
|
||||
|
@ -108,6 +108,38 @@ function Routine:resume(event, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Routine:run()
|
||||
self.co = self.co or coroutine.create(function()
|
||||
local result, err, fn
|
||||
|
||||
if self.fn then
|
||||
fn = self.fn
|
||||
_G.setfenv(fn, self.env)
|
||||
elseif self.path then
|
||||
fn, err = loadfile(self.path, self.env)
|
||||
elseif self.chunk then
|
||||
fn, err = load(self.chunk, self.title, nil, self.env)
|
||||
end
|
||||
|
||||
if fn then
|
||||
result, err = trace(fn, table.unpack(self.args or { } ))
|
||||
else
|
||||
err = err or 'kernel: invalid routine'
|
||||
end
|
||||
|
||||
pcall(self.onExit, self, result, err)
|
||||
self:cleanup()
|
||||
|
||||
if not result then
|
||||
error(err)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(kernel.routines, self)
|
||||
|
||||
return self:resume()
|
||||
end
|
||||
|
||||
-- override if any post processing is required
|
||||
function Routine:onExit(status, message) -- self, status, message
|
||||
if not status and message ~= 'Terminated' then
|
||||
@ -134,9 +166,14 @@ function kernel.getShell()
|
||||
return shell
|
||||
end
|
||||
|
||||
kernel.makeEnv = shell.makeEnv
|
||||
-- each routine inherits the parent's env
|
||||
function kernel.makeEnv(env)
|
||||
env = setmetatable(Util.shallowCopy(env or _ENV), { __index = _G })
|
||||
_G.requireInjector(env)
|
||||
return env
|
||||
end
|
||||
|
||||
function kernel.newRoutine(args)
|
||||
function kernel.newRoutine(env, args)
|
||||
kernel.UID = kernel.UID + 1
|
||||
|
||||
local routine = setmetatable({
|
||||
@ -147,52 +184,16 @@ function kernel.newRoutine(args)
|
||||
}, { __index = Routine })
|
||||
|
||||
Util.merge(routine, args)
|
||||
routine.env = args.env or shell.makeEnv()
|
||||
routine.env = args.env or kernel.makeEnv(env)
|
||||
routine.terminal = routine.terminal or routine.window
|
||||
|
||||
return routine
|
||||
end
|
||||
|
||||
local function xprun(env, path, ...)
|
||||
setmetatable(env, { __index = _G })
|
||||
local fn, m = loadfile(path, env)
|
||||
if fn then
|
||||
return trace(fn, ...)
|
||||
end
|
||||
return fn, m
|
||||
end
|
||||
|
||||
function kernel.launch(routine)
|
||||
routine.co = routine.co or coroutine.create(function()
|
||||
local result, err
|
||||
|
||||
if routine.fn then
|
||||
result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))
|
||||
elseif routine.path then
|
||||
result, err = xprun(routine.env, routine.path, table.unpack(routine.args or { } ))
|
||||
else
|
||||
err = 'kernel: invalid routine'
|
||||
end
|
||||
|
||||
pcall(routine.onExit, routine, result, err)
|
||||
routine:cleanup()
|
||||
|
||||
if not result then
|
||||
error(err)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(kernel.routines, routine)
|
||||
|
||||
local s, m = routine:resume()
|
||||
|
||||
return s and routine.uid, m
|
||||
end
|
||||
|
||||
function kernel.run(args)
|
||||
local routine = kernel.newRoutine(args)
|
||||
kernel.launch(routine)
|
||||
return routine
|
||||
function kernel.run(env, args)
|
||||
local routine = kernel.newRoutine(env, args)
|
||||
local s, m = routine:run()
|
||||
return s and routine, m
|
||||
end
|
||||
|
||||
function kernel.raise(uid)
|
||||
@ -314,9 +315,10 @@ local function init(...)
|
||||
for _,file in ipairs(files) do
|
||||
local level = file:match('(%d).%S+.lua') or 99
|
||||
if tonumber(level) <= runLevel then
|
||||
-- All init programs run under the original shell
|
||||
local s, m = shell.run(fs.combine(dir, file))
|
||||
if not s then
|
||||
error(m)
|
||||
error(m, -1)
|
||||
end
|
||||
os.sleep(0)
|
||||
end
|
||||
@ -331,7 +333,7 @@ local function init(...)
|
||||
shell.run('sys/apps/autorun.lua')
|
||||
|
||||
local win = window.create(kernel.terminal, 1, 1, w, h, true)
|
||||
local s, m = kernel.run({
|
||||
local s, m = kernel.run(_ENV, {
|
||||
title = args[1],
|
||||
path = 'sys/apps/shell.lua',
|
||||
args = args,
|
||||
@ -349,7 +351,7 @@ local function init(...)
|
||||
end
|
||||
end
|
||||
|
||||
kernel.run({
|
||||
kernel.run(_ENV, {
|
||||
fn = init,
|
||||
title = 'init',
|
||||
args = { ... },
|
||||
|
@ -100,7 +100,7 @@ local function crypt(data, key, nonce, cntr, round)
|
||||
cntr = tonumber(cntr) or 1
|
||||
round = tonumber(round) or 20
|
||||
|
||||
local throttle = Util.throttle(function() _syslog('throttle') end)
|
||||
local throttle = Util.throttle()
|
||||
local out = {}
|
||||
local state = initState(key, nonce, cntr)
|
||||
local blockAmt = math.floor(#data/64)
|
||||
|
@ -58,6 +58,14 @@ function Routine:resume(event, ...)
|
||||
else
|
||||
s, m = coroutine.resume(self.co, event, ...)
|
||||
end
|
||||
|
||||
if not s and event ~= 'terminate' then
|
||||
if m and debug and debug.traceback then
|
||||
local t = (debug.traceback(self.co, 1)) or ''
|
||||
m = m .. '\n' .. t:match('%d\n(.+)')
|
||||
end
|
||||
end
|
||||
|
||||
if self:isDead() then
|
||||
self.co = nil
|
||||
self.filter = nil
|
||||
|
@ -32,95 +32,63 @@ local function traceback(x)
|
||||
end
|
||||
end
|
||||
|
||||
local function trim_traceback(target, marker)
|
||||
local ttarget, tmarker = {}, {}
|
||||
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
|
||||
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
|
||||
local function trim_traceback(target)
|
||||
local t = { }
|
||||
local filters = {
|
||||
"%[C%]: in function 'xpcall'",
|
||||
"(...tail calls...)",
|
||||
"xpcall: $",
|
||||
"trace.lua:%d+:",
|
||||
}
|
||||
|
||||
--[[
|
||||
TODO : fix this trace
|
||||
Anavrins - if you could take a look, it would be appreciated
|
||||
-- basically i want the stack logged in a more readable
|
||||
-- format and the normal code/error message to be returned
|
||||
|
||||
-- unsure why the traceback method concatenates the stack
|
||||
-- when it can just be returned as a table - would make
|
||||
-- filtering much simpler
|
||||
|
||||
-- the following seems to reduce the stacktrace way too
|
||||
-- much - losing most of the relevant call stack
|
||||
|
||||
-- i have modified this a bit from the original - not sure
|
||||
-- if my changes are causing the issues or not
|
||||
|
||||
-- Trim identical suffixes
|
||||
local t_len, m_len = #ttarget, #tmarker
|
||||
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len, m_len = t_len - 1, m_len - 1
|
||||
local function matchesFilter(line)
|
||||
for _, filter in pairs(filters) do
|
||||
if line:match(filter) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Trim elements from this file and xpcall invocations
|
||||
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
|
||||
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len = t_len - 1
|
||||
for line in target:gmatch("([^\n]*)\n?") do
|
||||
if not matchesFilter(line) then
|
||||
table.insert(t, line)
|
||||
end
|
||||
]]
|
||||
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
|
||||
ttarget[#ttarget] = nil
|
||||
|
||||
return ttarget
|
||||
end
|
||||
|
||||
--- Run a function with
|
||||
return t
|
||||
end
|
||||
|
||||
return function (fn, ...)
|
||||
-- So this is rather grim: we need to get the full traceback and current one and remove
|
||||
-- the common prefix
|
||||
local trace
|
||||
local args = { ... }
|
||||
|
||||
-- xpcall in Lua 5.1 does not accept parameters
|
||||
-- which is not ideal
|
||||
local args = { ... }
|
||||
local res = table.pack(xpcall(function()
|
||||
return fn(table.unpack(args))
|
||||
end, traceback))
|
||||
|
||||
if not res[1] then
|
||||
trace = traceback("trace.lua:1:")
|
||||
end
|
||||
local ok, err = res[1], res[2]
|
||||
|
||||
if not ok and err ~= nil then
|
||||
trace = trim_traceback(err, trace)
|
||||
local trace = trim_traceback(err)
|
||||
|
||||
-- Find the position where the stack traceback actually starts
|
||||
local trace_starts
|
||||
for i = #trace, 1, -1 do
|
||||
if trace[i] == "stack traceback:" then trace_starts = i; break end
|
||||
err = { }
|
||||
while true do
|
||||
local line = table.remove(trace, 1)
|
||||
if not line or line == 'stack traceback:' then
|
||||
break
|
||||
end
|
||||
table.insert(err, line)
|
||||
end
|
||||
err = table.concat(err, '\n')
|
||||
|
||||
_G._syslog('\n' .. err .. '\n' .. 'stack traceback:')
|
||||
for _, v in ipairs(trace) do
|
||||
if v ~= 'stack traceback:' then
|
||||
_G._syslog(v:gsub("in function", "in"))
|
||||
end
|
||||
end
|
||||
|
||||
_G._syslog('')
|
||||
for _, line in pairs(trace) do
|
||||
_G._syslog(line)
|
||||
end
|
||||
|
||||
-- If this traceback is more than 15 elements long, keep the first 9, last 5
|
||||
-- and put an ellipsis between the rest
|
||||
local max = 10
|
||||
if trace_starts and #trace - trace_starts > max then
|
||||
local keep_starts = trace_starts + 7
|
||||
for i = #trace - trace_starts - max, 0, -1 do
|
||||
table.remove(trace, keep_starts + i)
|
||||
end
|
||||
table.insert(trace, keep_starts, " ...")
|
||||
end
|
||||
|
||||
for k, line in pairs(trace) do
|
||||
trace[k] = line:gsub("in function", " in")
|
||||
end
|
||||
|
||||
return false, table.remove(trace, 1), table.concat(trace, "\n")
|
||||
return ok, err
|
||||
end
|
||||
|
||||
return table.unpack(res, 1, res.n)
|
||||
|
@ -117,7 +117,7 @@ function UI:init()
|
||||
|
||||
if ie.code == 'control-shift-mouse_click' then -- hack
|
||||
local event = currentPage:pointToChild(x, y)
|
||||
_ENV.multishell.openTab({
|
||||
_ENV.multishell.openTab(_ENV, {
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.element, self, _ENV },
|
||||
focused = true })
|
||||
|
Loading…
Reference in New Issue
Block a user