mirror of
https://github.com/NeoFlock/neonucleus.git
synced 2025-09-24 17:13:31 +02:00
466 lines
13 KiB
Lua
466 lines
13 KiB
Lua
-- sandbox stuff
|
|
|
|
local function copy(v, p)
|
|
if type(v) == "table" then
|
|
local t = {}
|
|
for key, val in pairs(v) do
|
|
t[key] = copy(val)
|
|
end
|
|
if p then
|
|
for key, val in pairs(p) do
|
|
t[key] = copy(val)
|
|
end
|
|
end
|
|
return t
|
|
else
|
|
return v
|
|
end
|
|
end
|
|
|
|
local function nextDeadline()
|
|
return computer.uptime() + 5
|
|
end
|
|
|
|
local bubbleYield = false
|
|
local timeout = nextDeadline()
|
|
|
|
local function tooLong()
|
|
return computer.uptime() >= timeout
|
|
end
|
|
|
|
local tooLongWithoutYielding = "too long without yielding"
|
|
|
|
local function nextHeatUp()
|
|
return computer.uptime() + math.random() * 2 + 0.1
|
|
end
|
|
|
|
local heatInc = nextHeatUp()
|
|
|
|
debug.sethook(function()
|
|
if computer.uptime() >= heatInc then
|
|
heatInc = nextHeatUp()
|
|
computer.addHeat(math.random() * 3)
|
|
end
|
|
|
|
if tooLong() and not bubbleYield then
|
|
bubbleYield = true
|
|
error(tooLongWithoutYielding) -- here it is an actual string
|
|
end
|
|
end, "c", 100000) -- no bogo mips, the check is cheap anyways
|
|
|
|
local function resume(co, val1, ...)
|
|
while true do
|
|
local t = {coroutine.resume(co, val1, ...)}
|
|
if bubbleYield then -- yield was meaningless
|
|
coroutine.yield() -- carry through
|
|
else
|
|
return table.unpack(t) -- yield the user cares about
|
|
end
|
|
end
|
|
end
|
|
|
|
local function yield()
|
|
bubbleYield = true
|
|
coroutine.yield()
|
|
end
|
|
|
|
local function ensureYields()
|
|
if bubbleYield then
|
|
coroutine.yield()
|
|
end
|
|
end
|
|
|
|
local function checkArg(idx, v, ...)
|
|
local bad = true
|
|
local n = select("#", ...)
|
|
for i=1,n do
|
|
local t = select(i, ...)
|
|
if type(v) == t then bad = false break end
|
|
end
|
|
if not bad then return end
|
|
local msg = string.format("bad argument #%d (%s expected, got %s)", idx, table.concat({...}, " or "), type(v))
|
|
error(msg, 3)
|
|
end
|
|
|
|
local libcomponent
|
|
|
|
local componentProxy = {
|
|
__pairs = function(self)
|
|
local method
|
|
return function()
|
|
method = next(self, method)
|
|
if method then
|
|
return method, self[method]
|
|
end
|
|
end
|
|
end,
|
|
}
|
|
|
|
local componentCallback = {
|
|
__call = function(self, ...)
|
|
return libcomponent.invoke(self.address, self.name, ...)
|
|
end,
|
|
__tostring = function(self)
|
|
return libcomponent.doc(self.address, self.name) or "undocumented"
|
|
end
|
|
}
|
|
|
|
libcomponent = {
|
|
invoke = function(addr, method, ...)
|
|
checkArg(1, addr, "string")
|
|
checkArg(2, method, "string")
|
|
|
|
while true do
|
|
local r = {pcall(component.invoke, addr, method, ...)}
|
|
computer.clearError()
|
|
|
|
-- in this situation, either the temperature is above 100 C and we throttle
|
|
-- or the call budget has been filled and we dont care
|
|
if computer.isOverheating() or computer.isOverworked() then
|
|
local ok = pcall(yield)
|
|
assert(ok, "component explicitly requested to be suspended")
|
|
end
|
|
|
|
if computer.getState() == states.blackout then
|
|
-- oops, powerout
|
|
local ok = pcall(yield)
|
|
assert(ok, "blackout")
|
|
end
|
|
|
|
if computer.getState() == states.busy then
|
|
-- busy gets to try again
|
|
computer.setState(states.running)
|
|
else
|
|
if r[1] then
|
|
return table.unpack(r, 2)
|
|
end
|
|
return nil, r[2]
|
|
end
|
|
end
|
|
end,
|
|
list = function(filter, exact)
|
|
checkArg(1, filter, "string", "nil")
|
|
local t = component.list()
|
|
local list = t
|
|
if filter then
|
|
list = {}
|
|
for addr, kind in pairs(t) do
|
|
if type(exact) == "boolean" and exact then
|
|
if kind == filter then
|
|
list[addr] = kind
|
|
end
|
|
elseif rawequal(exact, "pattern") then
|
|
if string.match(kind, filter) then
|
|
list[addr] = kind
|
|
end
|
|
else
|
|
if string.find(kind, filter, nil, true) then
|
|
list[addr] = kind
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local key = nil
|
|
return setmetatable(list, {
|
|
__call = function()
|
|
key = next(list, key)
|
|
if key then
|
|
return key, list[key]
|
|
end
|
|
end,
|
|
})
|
|
end,
|
|
methods = component.methods,
|
|
fields = component.fields,
|
|
doc = component.doc,
|
|
slot = component.slot,
|
|
type = component.type,
|
|
proxy = function(addr)
|
|
checkArg(1, addr, "string")
|
|
if not component.type(addr) then return nil, "no such component" end
|
|
local proxy = setmetatable({
|
|
address = addr,
|
|
type = component.type(addr),
|
|
slot = component.slot(addr),
|
|
fields = {},
|
|
}, componentProxy)
|
|
local methods = component.methods(addr)
|
|
for method in pairs(methods) do
|
|
proxy[method] = setmetatable({address = addr, name = method}, componentCallback)
|
|
end
|
|
return proxy
|
|
end,
|
|
}
|
|
|
|
local libcomputer = {
|
|
isRobot = function()
|
|
return libcomponent.list("robot", true) ~= nil
|
|
end,
|
|
address = computer.address,
|
|
tmpAddress = computer.tmpAddress,
|
|
usedMemory = computer.usedMemory,
|
|
freeMemory = computer.freeMemory,
|
|
totalMemory = computer.totalMemory,
|
|
uptime = computer.uptime,
|
|
energy = computer.energy,
|
|
maxEnergy = computer.maxEnergy,
|
|
users = computer.users,
|
|
-- these 2 are not actually implemented
|
|
-- TODO: implement them
|
|
addUser = computer.addUser,
|
|
removeUser = computer.removeUser,
|
|
|
|
shutdown = function(reboot)
|
|
computer.setState(reboot and states.REPEAT or states.closing)
|
|
yield()
|
|
end,
|
|
pushSignal = computer.pushSignal,
|
|
pullSignal = function(timeout)
|
|
local deadline = computer.uptime() + (type(timeout) == "number" and timeout or math.huge)
|
|
|
|
repeat
|
|
yield() -- give executor a chance to give us stuff
|
|
local s = table.pack(computer.popSignal())
|
|
if s.n > 0 then
|
|
return table.unpack(s)
|
|
end
|
|
until computer.uptime() >= deadline
|
|
end,
|
|
beep = computer.beep,
|
|
getDeviceInfo = function()
|
|
return {} -- yup
|
|
end,
|
|
getProgramLocations = function()
|
|
return {} -- yup
|
|
end,
|
|
|
|
getArchitectures = computer.getArchitectures,
|
|
getArchitecture = computer.getArchitecture,
|
|
setArchitecture = function(...)
|
|
computer.setArchitecture(...) -- also sets state to SWITCH
|
|
yield()
|
|
end,
|
|
getTemperature = computer.getTemperature,
|
|
}
|
|
|
|
local sandbox
|
|
sandbox = {
|
|
assert = assert,
|
|
error = error,
|
|
getmetatable = function(t)
|
|
if type(t) == "string" then -- HUGE security problem
|
|
return nil -- fixed
|
|
end
|
|
return getmetatable(t)
|
|
end,
|
|
ipairs = ipairs,
|
|
load = function(ld, source, _, env) -- mode is ignored as bytecode is just fully illegal for now
|
|
return load(ld, source, "t", env or sandbox)
|
|
end,
|
|
next = next,
|
|
pairs = pairs,
|
|
pcall = function(...)
|
|
if tooLong() then
|
|
yield()
|
|
return false, tooLongWithoutYielding
|
|
end
|
|
local t = {pcall(...)}
|
|
ensureYields() -- if it took too long, this will make it yield
|
|
return table.unpack(t)
|
|
end,
|
|
rawequal = rawequal,
|
|
rawget = rawget,
|
|
rawlen = rawlen,
|
|
rawset = rawset,
|
|
select = select,
|
|
setmetatable = function(t, mt)
|
|
if type(mt) ~= "table" then
|
|
return setmetatable(t, mt)
|
|
end
|
|
-- we do mutate the metatable but this field shouldn't exist anyways
|
|
mt.__gc = nil
|
|
return setmetatable(t, mt)
|
|
end,
|
|
tonumber = tonumber,
|
|
tostring = tostring,
|
|
type = type,
|
|
_VERSION = _VERSION,
|
|
xpcall = function(f, msgh, ...)
|
|
checkArg(1, f, "function")
|
|
checkArg(2, msgh, "function")
|
|
|
|
-- to prevent infinite loops we simply terminate the error handler if it took too long.
|
|
local function errorCapture(ff, ...)
|
|
--ensureYields() -- you can't yield in xpcall...
|
|
-- Immediately dont care
|
|
if tooLong() then
|
|
return nil, tooLongWithoutYielding
|
|
end
|
|
-- This would mean you shutdown in the errorCapture.
|
|
-- In vanilla OC, that does nothing.
|
|
-- In here, it returns a suspended error and then yields eventually.
|
|
if bubbleYield then
|
|
return nil, "suspended"
|
|
end
|
|
|
|
return xpcall(ff, function(...)
|
|
if tooLong() then
|
|
return tooLongWithoutYielding
|
|
else
|
|
return select(2, errorCapture(msgh, ...))
|
|
end
|
|
end, ...)
|
|
end
|
|
|
|
local t = {errorCapture(f, ...)}
|
|
pcall(ensureYields) -- it can fail if we are doing xpcall in xpcall.
|
|
return table.unpack(t)
|
|
end,
|
|
|
|
coroutine = {
|
|
create = coroutine.create,
|
|
resume = resume,
|
|
running = coroutine.running,
|
|
status = coroutine.status,
|
|
yield = coroutine.yield,
|
|
wrap = function(f)
|
|
-- uses the correct resume
|
|
local co = coroutine.create(f)
|
|
return function(...)
|
|
local result = {resume(co, ...)}
|
|
if result[1] then
|
|
return table.unpack(result, 2)
|
|
else
|
|
error(result[2], 0)
|
|
end
|
|
end
|
|
end,
|
|
isyieldable = coroutine.isyieldable,
|
|
},
|
|
|
|
string = copy(string),
|
|
table = copy(table),
|
|
math = copy(math, {
|
|
-- patch table
|
|
atan2 = math.atan,
|
|
ldexp = function(a, e) return a * (2.0 ^ e) end,
|
|
pow = function(a, b) return a ^ b end,
|
|
}),
|
|
|
|
os = {
|
|
clock = os.clock,
|
|
date = os.date,
|
|
difftime = function(t2, t1) return t2 - t1 end, -- thanks UNIX
|
|
time = function(t)
|
|
checkArg(1, t, "table", "nil")
|
|
return os.time(t)
|
|
end,
|
|
},
|
|
|
|
debug = {
|
|
getinfo = function(...)
|
|
local result = debug.getinfo(...)
|
|
if result then
|
|
return {
|
|
source = result.source,
|
|
short_src = result.short_src,
|
|
linedefined = result.linedefined,
|
|
lastlinedefined = result.lastlinedefined,
|
|
what = result.what,
|
|
currentline = result.currentline,
|
|
nups = result.nups,
|
|
nparams = result.nparams,
|
|
isvararg = result.isvararg,
|
|
name = result.name,
|
|
namewhat = result.namewhat,
|
|
istailcall = result.istailcall,
|
|
-- believe it or not, this IS NOT safe.
|
|
-- They may use this to re-call machine.lua which would reset the hook and timeout.
|
|
-- TODO: make this safe.
|
|
--func = result.func,
|
|
}
|
|
end
|
|
end,
|
|
traceback = debug.traceback,
|
|
-- we only allow the first return, aka not the value.
|
|
-- Otherwise, some dumb shmuck could do nasty stuff.
|
|
-- TODO: make them not need this.
|
|
getlocal = function(...) return (debug.getlocal(...)) end,
|
|
getupvalue = function(...) return (debug.getupvalue(...)) end,
|
|
},
|
|
|
|
utf8 = copy(utf8),
|
|
unicode = copy(unicode, {
|
|
wtrunc = function (str,space)
|
|
space = space - 1
|
|
return str:sub(1,(space >= utf8.len(str)) and (#str) or (utf8.offset(str,space+1)-1))
|
|
end,
|
|
isWide = function(s) return unicode.wlen(s) > unicode.len(s) end,
|
|
upper = string.upper,
|
|
lower = string.lower,
|
|
--[[
|
|
sub = function (str,a,b)
|
|
if not b then b = utf8.len(str) end
|
|
if not a then a = 1 end
|
|
-- a = math.max(a,1)
|
|
|
|
if a < 0 then
|
|
-- negative
|
|
|
|
a = utf8.len(str) + a + 1
|
|
end
|
|
|
|
if b < 0 then
|
|
b = utf8.len(str) + b + 1
|
|
end
|
|
|
|
if a > b then return "" end
|
|
|
|
if b >= utf8.len(str) then b = #str else b = utf8.offset(str,b+1)-1 end
|
|
|
|
if a > utf8.len(str) then return "" end
|
|
a = utf8.offset(str,a)
|
|
|
|
return str:sub(a,b)
|
|
-- return str:sub(a, b)
|
|
end,
|
|
]]
|
|
}),
|
|
checkArg = checkArg,
|
|
component = libcomponent,
|
|
computer = libcomputer,
|
|
debugprint = print,
|
|
}
|
|
sandbox._G = sandbox
|
|
|
|
local function bootstrap()
|
|
local eeprom = libcomponent.list("eeprom")()
|
|
assert(eeprom, "no eeprom")
|
|
|
|
local code = libcomponent.invoke(eeprom, "get")
|
|
assert(code and #code > 0, "empty eeprom")
|
|
|
|
return assert(load(code, "=bios", "t", sandbox))
|
|
end
|
|
|
|
coroutine.yield() -- startup delay
|
|
|
|
local f = bootstrap()
|
|
local co = coroutine.create(f)
|
|
|
|
while true do
|
|
timeout = nextDeadline()
|
|
bubbleYield = false
|
|
|
|
local ok, err = coroutine.resume(co)
|
|
|
|
if not ok then
|
|
error(debug.traceback(co, err), 0)
|
|
elseif coroutine.status(co) == "dead" then
|
|
error("computer halted", 0)
|
|
else
|
|
coroutine.yield()
|
|
end
|
|
end
|