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:
kepler155c@gmail.com 2020-05-11 17:25:58 -06:00
parent bd911e80e8
commit 8279c1ae12
16 changed files with 151 additions and 187 deletions

View File

@ -255,7 +255,7 @@ function page.packetSlide:eventHandler(event)
page:setFocus(page.packetGrid) page:setFocus(page.packetGrid)
elseif event.type == 'packet_lua' then 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 elseif event.type == 'prev_packet' then
local c = self.currentPacket local c = self.currentPacket

View File

@ -12,6 +12,7 @@ local page = UI.Page {
buttons = { buttons = {
{ text = 'Activate', event = 'activate' }, { text = 'Activate', event = 'activate' },
{ text = 'Terminate', event = 'terminate' }, { text = 'Terminate', event = 'terminate' },
{ text = 'Inspect', event = 'inspect' },
}, },
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
@ -49,6 +50,12 @@ local page = UI.Page {
multishell.setFocus(t.uid) multishell.setFocus(t.uid)
elseif event.type == 'terminate' then elseif event.type == 'terminate' then
multishell.terminate(t.uid) multishell.terminate(t.uid)
elseif event.type == 'inspect' then
multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { t },
focused = true,
})
end end
end end
if event.type == 'quit' then if event.type == 'quit' then

View File

@ -110,7 +110,7 @@ page = UI.Page {
self.grid:draw() self.grid:draw()
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
multishell.openTab({ multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua', path = 'sys/apps/Lua.lua',
args = { event.selected.raw }, args = { event.selected.raw },
focused = true, focused = true,

View File

@ -1,5 +1,3 @@
_G.requireInjector(_ENV)
local Event = require('opus.event') local Event = require('opus.event')
local Util = require('opus.util') local Util = require('opus.util')

View File

@ -31,21 +31,14 @@ local function snmpConnection(socket)
socket:write('pong') socket:write('pong')
elseif msg.type == 'script' then elseif msg.type == 'script' then
local env = kernel.makeEnv() kernel.run(_ENV, {
local fn, err = load(msg.args, 'script', nil, env) chunk = msg.args,
if fn then title = 'script',
kernel.run({ })
fn = fn,
env = env,
title = 'script',
})
else
_G.printError(err)
end
elseif msg.type == 'scriptEx' then elseif msg.type == 'scriptEx' then
local s, m = pcall(function() local s, m = pcall(function()
local env = kernel.makeEnv() local env = kernel.makeEnv(_ENV)
local fn, m = load(msg.args, 'script', nil, env) local fn, m = load(msg.args, 'script', nil, env)
if not fn then if not fn then
error(m) error(m)

View File

@ -41,12 +41,12 @@ local function telnetHost(socket, mode)
end end
end end
local shellThread = kernel.run({ local shellThread = kernel.run(_ENV, {
window = win, window = win,
title = mode .. ' client', title = mode .. ' client',
hidden = true, hidden = true,
fn = function() 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 if socket.queue then
socket:write(socket.queue) socket:write(socket.queue)
end end

View File

@ -6,16 +6,6 @@ local fs = _G.fs
local settings = _G.settings local settings = _G.settings
local shell = _ENV.shell 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) _G.requireInjector(_ENV)
local trace = require('opus.trace') local trace = require('opus.trace')
@ -51,7 +41,7 @@ local function run(...)
local args = tokenise(...) local args = tokenise(...)
local command = table.remove(args, 1) or error('No such program') local command = table.remove(args, 1) or error('No such program')
local isUrl = not not command:match("^(https?:)") local isUrl = not not command:match("^(https?:)")
local env = shell.makeEnv() local env = shell.makeEnv(_ENV)
local path, loadFn local path, loadFn
if isUrl then if isUrl then
@ -64,7 +54,7 @@ local function run(...)
local fn, err = loadFn(path, env) local fn, err = loadFn(path, env)
if not fn then if not fn then
error(err) error(err, -1)
end end
if _ENV.multishell then if _ENV.multishell then
@ -287,13 +277,9 @@ function shell.getRunningInfo()
return tProgramStack[#tProgramStack] return tProgramStack[#tProgramStack]
end end
function shell.setEnv(name, value) -- convenience function for making a runnable env
_ENV[name] = value function shell.makeEnv(env)
sandboxEnv[name] = value env = setmetatable(Util.shallowCopy(env), { __index = _G })
end
function shell.makeEnv()
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
_G.requireInjector(env) _G.requireInjector(env)
return env return env
end end
@ -321,7 +307,6 @@ function shell.newTab(tabInfo, ...)
if path then if path then
tabInfo.path = path tabInfo.path = path
tabInfo.env = shell.makeEnv()
tabInfo.args = args tabInfo.args = args
tabInfo.title = fs.getName(path):match('([^%.]+)') tabInfo.title = fs.getName(path):match('([^%.]+)')
@ -329,25 +314,21 @@ function shell.newTab(tabInfo, ...)
table.insert(tabInfo.args, 1, tabInfo.path) table.insert(tabInfo.args, 1, tabInfo.path)
tabInfo.path = 'sys/apps/shell.lua' tabInfo.path = 'sys/apps/shell.lua'
end end
return _ENV.multishell.openTab(tabInfo) return _ENV.multishell.openTab(_ENV, tabInfo)
end end
return nil, 'No such program' return nil, 'No such program'
end end
function shell.openTab(...) if not _ENV.multishell then
-- needs to use multishell.launch .. so we can run with stock multishell function shell.newTab()
local tWords = tokenise( ... ) error('Multishell is not available')
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 end
end end
function shell.openTab(...)
return shell.newTab({ }, ...)
end
function shell.openForegroundTab( ... ) function shell.openForegroundTab( ... )
return shell.newTab({ focused = true }, ...) return shell.newTab({ focused = true }, ...)
end end

View File

@ -10,9 +10,8 @@ if multishell and multishell.getTabs then
local tab = kernel.getFocused() local tab = kernel.getFocused()
if tab and not tab.noTerminate then if tab and not tab.noTerminate then
multishell.terminate(tab.uid) multishell.terminate(tab.uid)
multishell.openTab({ multishell.openTab(tab.env, {
path = tab.path, path = tab.path,
env = tab.env,
args = tab.args, args = tab.args,
focused = true, focused = true,
}) })

View File

@ -39,7 +39,7 @@ local function systemLog()
keyboard.removeHotkey('control-d') keyboard.removeHotkey('control-d')
end end
kernel.run({ kernel.run(_ENV, {
title = 'System Log', title = 'System Log',
fn = systemLog, fn = systemLog,
noTerminate = true, noTerminate = true,

View File

@ -15,7 +15,7 @@ do
end end
local function startNetwork() local function startNetwork()
kernel.run({ kernel.run(_ENV, {
title = 'Net daemon', title = 'Net daemon',
path = 'sys/apps/netdaemon.lua', path = 'sys/apps/netdaemon.lua',
hidden = true, hidden = true,

View File

@ -8,7 +8,6 @@ local kernel = _G.kernel
local keys = _G.keys local keys = _G.keys
local os = _G.os local os = _G.os
local printError = _G.printError local printError = _G.printError
local shell = _ENV.shell
local window = _G.window local window = _G.window
local parentTerm = _G.device.terminal local parentTerm = _G.device.terminal
@ -18,7 +17,7 @@ local tabsDirty = false
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*' local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
local multishell = { } local multishell = { }
shell.setEnv('multishell', multishell) _ENV.multishell = multishell
kernel.window.reposition(1, 2, w, h - 1) kernel.window.reposition(1, 2, w, h - 1)
@ -93,22 +92,21 @@ function multishell.getTabs()
return kernel.routines return kernel.routines
end end
function multishell.launch( tProgramEnv, sProgramPath, ... ) function multishell.launch(env, path, ...)
-- backwards compatibility -- backwards compatibility
return multishell.openTab({ return multishell.openTab(env, {
env = tProgramEnv, path = path,
path = sProgramPath,
args = { ... }, args = { ... },
}) })
end end
function multishell.openTab(tab) function multishell.openTab(env, tab)
if not tab.title and tab.path then if not tab.title and tab.path then
tab.title = fs.getName(tab.path):match('([^%.]+)') tab.title = fs.getName(tab.path):match('([^%.]+)')
end end
tab.title = tab.title or 'untitled' tab.title = tab.title or 'untitled'
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false) 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 if not result and err and err ~= 'Terminated' or (err and err ~= 0) then
self.terminal.setBackgroundColor(colors.black) self.terminal.setBackgroundColor(colors.black)
if tonumber(err) then if tonumber(err) then
@ -131,14 +129,17 @@ function multishell.openTab(tab)
end end
end end
local routine = kernel.run(tab) local routine, message = kernel.run(env, tab)
if tab.focused then if routine then
multishell.setFocus(routine.uid) if tab.focused then
else multishell.setFocus(routine.uid)
redrawMenu() else
redrawMenu()
end
end end
return routine.uid
return routine and routine.uid, message
end end
function multishell.hideTab(tabId) function multishell.hideTab(tabId)
@ -316,15 +317,22 @@ kernel.hook('mouse_scroll', function(_, eventData)
end) end)
kernel.hook('kernel_ready', function() kernel.hook('kernel_ready', function()
overviewId = multishell.openTab({ overviewId = multishell.openTab(_ENV, {
path = config.launcher or 'sys/apps/Overview.lua', path = 'sys/apps/shell.lua',
args = { config.launcher or 'sys/apps/Overview.lua' },
isOverview = true, isOverview = true,
noTerminate = true, noTerminate = true,
focused = true, focused = true,
title = '+', 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', path = 'sys/apps/shell.lua',
args = { 'sys/apps/autorun.lua' }, args = { 'sys/apps/autorun.lua' },
title = 'Autorun', title = 'Autorun',

View File

@ -108,6 +108,38 @@ function Routine:resume(event, ...)
end end
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 -- override if any post processing is required
function Routine:onExit(status, message) -- self, status, message function Routine:onExit(status, message) -- self, status, message
if not status and message ~= 'Terminated' then if not status and message ~= 'Terminated' then
@ -134,9 +166,14 @@ function kernel.getShell()
return shell return shell
end 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 kernel.UID = kernel.UID + 1
local routine = setmetatable({ local routine = setmetatable({
@ -147,52 +184,16 @@ function kernel.newRoutine(args)
}, { __index = Routine }) }, { __index = Routine })
Util.merge(routine, args) 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 routine.terminal = routine.terminal or routine.window
return routine return routine
end end
local function xprun(env, path, ...) function kernel.run(env, args)
setmetatable(env, { __index = _G }) local routine = kernel.newRoutine(env, args)
local fn, m = loadfile(path, env) local s, m = routine:run()
if fn then return s and routine, m
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
end end
function kernel.raise(uid) function kernel.raise(uid)
@ -314,9 +315,10 @@ local function init(...)
for _,file in ipairs(files) do for _,file in ipairs(files) do
local level = file:match('(%d).%S+.lua') or 99 local level = file:match('(%d).%S+.lua') or 99
if tonumber(level) <= runLevel then if tonumber(level) <= runLevel then
-- All init programs run under the original shell
local s, m = shell.run(fs.combine(dir, file)) local s, m = shell.run(fs.combine(dir, file))
if not s then if not s then
error(m) error(m, -1)
end end
os.sleep(0) os.sleep(0)
end end
@ -331,7 +333,7 @@ local function init(...)
shell.run('sys/apps/autorun.lua') shell.run('sys/apps/autorun.lua')
local win = window.create(kernel.terminal, 1, 1, w, h, true) 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], title = args[1],
path = 'sys/apps/shell.lua', path = 'sys/apps/shell.lua',
args = args, args = args,
@ -349,7 +351,7 @@ local function init(...)
end end
end end
kernel.run({ kernel.run(_ENV, {
fn = init, fn = init,
title = 'init', title = 'init',
args = { ... }, args = { ... },

View File

@ -100,7 +100,7 @@ local function crypt(data, key, nonce, cntr, round)
cntr = tonumber(cntr) or 1 cntr = tonumber(cntr) or 1
round = tonumber(round) or 20 round = tonumber(round) or 20
local throttle = Util.throttle(function() _syslog('throttle') end) local throttle = Util.throttle()
local out = {} local out = {}
local state = initState(key, nonce, cntr) local state = initState(key, nonce, cntr)
local blockAmt = math.floor(#data/64) local blockAmt = math.floor(#data/64)

View File

@ -58,6 +58,14 @@ function Routine:resume(event, ...)
else else
s, m = coroutine.resume(self.co, event, ...) s, m = coroutine.resume(self.co, event, ...)
end 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 if self:isDead() then
self.co = nil self.co = nil
self.filter = nil self.filter = nil

View File

@ -32,95 +32,63 @@ local function traceback(x)
end end
end end
local function trim_traceback(target, marker) local function trim_traceback(target)
local ttarget, tmarker = {}, {} local t = { }
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end local filters = {
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end "%[C%]: in function 'xpcall'",
"(...tail calls...)",
"xpcall: $",
"trace.lua:%d+:",
}
--[[ local function matchesFilter(line)
TODO : fix this trace for _, filter in pairs(filters) do
Anavrins - if you could take a look, it would be appreciated if line:match(filter) then
-- basically i want the stack logged in a more readable return true
-- format and the normal code/error message to be returned end
end
-- 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
end end
-- Trim elements from this file and xpcall invocations for line in target:gmatch("([^\n]*)\n?") do
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or if not matchesFilter(line) then
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do table.insert(t, line)
table.remove(ttarget, t_len) end
t_len = t_len - 1
end end
]]
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
ttarget[#ttarget] = nil
return ttarget return t
end end
--- Run a function with
return function (fn, ...) 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 -- xpcall in Lua 5.1 does not accept parameters
-- which is not ideal -- which is not ideal
local args = { ... }
local res = table.pack(xpcall(function() local res = table.pack(xpcall(function()
return fn(table.unpack(args)) return fn(table.unpack(args))
end, traceback)) end, traceback))
if not res[1] then
trace = traceback("trace.lua:1:")
end
local ok, err = res[1], res[2] local ok, err = res[1], res[2]
if not ok and err ~= nil then 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 err = { }
local trace_starts while true do
for i = #trace, 1, -1 do local line = table.remove(trace, 1)
if trace[i] == "stack traceback:" then trace_starts = i; break end if not line or line == 'stack traceback:' then
end break
end
_G._syslog('') table.insert(err, line)
for _, line in pairs(trace) do end
_G._syslog(line) err = table.concat(err, '\n')
end
_G._syslog('\n' .. err .. '\n' .. 'stack traceback:')
-- If this traceback is more than 15 elements long, keep the first 9, last 5 for _, v in ipairs(trace) do
-- and put an ellipsis between the rest if v ~= 'stack traceback:' then
local max = 10 _G._syslog(v:gsub("in function", "in"))
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 end
table.insert(trace, keep_starts, " ...")
end end
for k, line in pairs(trace) do return ok, err
trace[k] = line:gsub("in function", " in")
end
return false, table.remove(trace, 1), table.concat(trace, "\n")
end end
return table.unpack(res, 1, res.n) return table.unpack(res, 1, res.n)

View File

@ -117,7 +117,7 @@ function UI:init()
if ie.code == 'control-shift-mouse_click' then -- hack if ie.code == 'control-shift-mouse_click' then -- hack
local event = currentPage:pointToChild(x, y) local event = currentPage:pointToChild(x, y)
_ENV.multishell.openTab({ _ENV.multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua', path = 'sys/apps/Lua.lua',
args = { event.element, self, _ENV }, args = { event.element, self, _ENV },
focused = true }) focused = true })