123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- -------------
- -- lua sandboxed environment
- -- function to cross out functions and userdata.
- -- modified from dump()
- function atlatc.remove_invalid_data(o, nested)
- if o==nil then return nil end
- local valid_dt={["nil"]=true, boolean=true, number=true, string=true}
- if type(o) ~= "table" then
- --check valid data type
- if not valid_dt[type(o)] then
- return nil
- end
- return o
- end
- -- Contains table -> true/nil of currently nested tables
- nested = nested or {}
- if nested[o] then
- return nil
- end
- nested[o] = true
- for k, v in pairs(o) do
- v = atlatc.remove_invalid_data(v, nested)
- end
- nested[o] = nil
- return o
- end
- local env_proto={
- load = function(self, envname, data)
- self.name=envname
- self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {}
- self.fdata={}
- self.init_code=data.init_code or ""
- self.subscribers=data.subscribers or {}
- end,
- save = function(self)
- -- throw any function values out of the sdata table
- self.sdata = atlatc.remove_invalid_data(self.sdata)
- return {sdata = self.sdata, init_code=self.init_code, subscribers=self.subscribers}
- end,
- }
- --Environment
- --Code modified from mesecons_luacontroller (credit goes to Jeija and mesecons contributors)
- local safe_globals = {
- "assert", "error", "ipairs", "next", "pairs", "select",
- "tonumber", "tostring", "type", "unpack", "_VERSION"
- }
- local function safe_date(f, t)
- if not f then
- -- fall back to old behavior
- return(os.date("*t",os.time()))
- else
- --pass parameters
- return os.date(f,t)
- end
- end
- -- string.rep(str, n) with a high value for n can be used to DoS
- -- the server. Therefore, limit max. length of generated string.
- local function safe_string_rep(str, n)
- if #str * n > 2000 then
- debug.sethook() -- Clear hook
- error("string.rep: string length overflow", 2)
- end
- return string.rep(str, n)
- end
- -- string.find with a pattern can be used to DoS the server.
- -- Therefore, limit string.find to patternless matching.
- -- Note: Disabled security since there are enough security leaks and this would be unneccessary anyway to DoS the server
- local function safe_string_find(...)
- --if (select(4, ...)) ~= true then
- -- debug.sethook() -- Clear hook
- -- error("string.find: 'plain' (fourth parameter) must always be true for security reasons.")
- --end
- return string.find(...)
- end
- local mp=minetest.get_modpath("advtrains_luaautomation")
- local static_env = {
- --core LUA functions
- string = {
- byte = string.byte,
- char = string.char,
- format = string.format,
- len = string.len,
- lower = string.lower,
- upper = string.upper,
- rep = safe_string_rep,
- reverse = string.reverse,
- sub = string.sub,
- find = safe_string_find,
- },
- math = {
- abs = math.abs,
- acos = math.acos,
- asin = math.asin,
- atan = math.atan,
- atan2 = math.atan2,
- ceil = math.ceil,
- cos = math.cos,
- cosh = math.cosh,
- deg = math.deg,
- exp = math.exp,
- floor = math.floor,
- fmod = math.fmod,
- frexp = math.frexp,
- huge = math.huge,
- ldexp = math.ldexp,
- log = math.log,
- log10 = math.log10,
- max = math.max,
- min = math.min,
- modf = math.modf,
- pi = math.pi,
- pow = math.pow,
- rad = math.rad,
- random = math.random,
- sin = math.sin,
- sinh = math.sinh,
- sqrt = math.sqrt,
- tan = math.tan,
- tanh = math.tanh,
- },
- table = {
- concat = table.concat,
- insert = table.insert,
- maxn = table.maxn,
- remove = table.remove,
- sort = table.sort,
- },
- os = {
- clock = os.clock,
- difftime = os.difftime,
- time = os.time,
- date = safe_date,
- },
- POS = function(x,y,z) return {x=x, y=y, z=z} end,
- getstate = advtrains.getstate,
- setstate = advtrains.setstate,
- is_passive = advtrains.is_passive,
- --interrupts are handled per node, position unknown. (same goes for digilines)
- --however external interrupts can be set here.
- interrupt_pos = function(parpos, imesg)
- local pos=atlatc.pcnaming.resolve_pos(parpos, "interrupt_pos")
- atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg})
- end,
- train_parts = function(train_id)
- if not train_id then return false end
- local train = advtrains.trains[train_id]
- if not train then return false end
- return table.copy(train.trainparts or {})
- end,
- -- sends an atc command to train regardless of where it is in the world
- atc_send_to_train = function(train_id, command)
- assertt(command, "string")
- local train = advtrains.trains[train_id]
- if train then
- advtrains.atc.train_set_command(train, command, true)
- return true
- else
- return false
- end
- end,
- get_slowdown = function()
- return advtrains.global_slowdown
- end
- }
- -- If interlocking is present, enable route setting functions
- if advtrains.interlocking then
- local function gen_checks(signal, route_name, noroutesearch)
- assertt(route_name, "string")
- local pos = atlatc.pcnaming.resolve_pos(signal)
- local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
- if not sigd then
- error("There's no signal at "..minetest.pos_to_string(pos))
- end
- local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
- if not tcbs then
- error("Inconsistent configuration, no tcbs for signal at "..minetest.pos_to_string(pos))
- end
-
- local routeid, route
- if not noroutesearch then
- for routeidt, routet in ipairs(tcbs.routes) do
- if routet.name == route_name then
- routeid = routeidt
- route = routet
- break
- end
- end
- if not route then
- error("No route called "..route_name.." at "..minetest.pos_to_string(pos))
- end
- end
- return pos, sigd, tcbs, routeid, route
- end
- static_env.can_set_route = function(signal, route_name)
- local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name)
- -- if route is already set on signal, return whether it's committed
- if tcbs.routeset == routeid then
- return tcbs.route_committed
- end
- -- actually try setting route (parameter 'true' designates try-run
- local ok = advtrains.interlocking.route.set_route(sigd, route, true)
- return ok
- end
- static_env.set_route = function(signal, route_name)
- local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name)
- return advtrains.interlocking.route.update_route(sigd, tcbs, routeid)
- end
- static_env.cancel_route = function(signal)
- local pos, sigd, tcbs, routeid, route = gen_checks(signal, "", true)
- return advtrains.interlocking.route.update_route(sigd, tcbs, nil, true)
- end
- static_env.get_aspect = function(signal)
- local pos = atlatc.pcnaming.resolve_pos(signal)
- return advtrains.interlocking.signal_get_aspect(pos)
- end
- static_env.set_aspect = function(signal, asp)
- local pos = atlatc.pcnaming.resolve_pos(signal)
- return advtrains.interlocking.signal_set_aspect(pos,asp)
- end
-
- --section_occupancy()
- static_env.section_occupancy = function(ts_id)
- if not ts_id then return nil end
- ts_id = tostring(ts_id)
- local response = advtrains.interlocking.db.get_ts(ts_id)
- if not response then return false end
- return (response.trains and table.copy(response.trains)) or {}
- end
- end
- -- Lines-specific:
- if advtrains.lines then
- local atlrwt = advtrains.lines.rwt
- static_env.rwt = {
- now = atlrwt.now,
- new = atlrwt.new,
- copy = atlrwt.copy,
- to_table = atlrwt.to_table,
- to_secs = atlrwt.to_secs,
- to_string = atlrwt.to_string,
- add = atlrwt.add,
- diff = atlrwt.diff,
- sub = atlrwt.sub,
- adj_diff = atlrwt.adj_diff,
- adjust_cycle = atlrwt.adjust_cycle,
- adjust = atlrwt.adjust,
- to_string = atlrwt.to_string,
- get_time_until = atlrwt.get_time_until,
- next_rpt = atlrwt.next_rpt,
- last_rpt = atlrwt.last_rpt,
- time_from_last_rpt = atlrwt.time_from_last_rpt,
- time_to_next_rpt = atlrwt.time_to_next_rpt,
- }
- end
- atlatc.register_function = function (name, f)
- static_env[name] = f
- end
- for _, name in pairs(safe_globals) do
- static_env[name] = _G[name]
- end
- --The environment all code calls get is a table that has set static_env as metatable.
- --In general, every variable is local to a single code chunk, but kept persistent over code re-runs. Data is also saved, but functions and userdata and circular references are removed
- --Init code and step code's environments are not saved
- -- S - Table that can contain any save data global to the environment. Will be saved statically. Can't contain functions or userdata or circular references.
- -- F - Table global to the environment, can contain volatile data that is deleted when server quits.
- -- The init code should populate this table with functions and other definitions.
- local proxy_env={}
- --proxy_env gets a new metatable in every run, but is the shared environment of all functions ever defined.
- -- returns: true, fenv if successful; nil, error if error
- function env_proto:execute_code(localenv, code, evtdata, customfct)
- -- create us a print function specific for this environment
- if not self.safe_print_func then
- local myenv = self
- self.safe_print_func = function(...)
- myenv:log("info", ...)
- end
- end
-
- local metatbl ={
- __index = function(t, i)
- if i=="S" then
- return self.sdata
- elseif i=="F" then
- return self.fdata
- elseif i=="event" then
- return evtdata
- elseif customfct and customfct[i] then
- return customfct[i]
- elseif localenv and localenv[i] then
- return localenv[i]
- elseif i=="print" then
- return self.safe_print_func
- end
- return static_env[i]
- end,
- __newindex = function(t, i, v)
- if i=="S" or i=="F" or i=="event" or (customfct and customfct[i]) or static_env[i] then
- debug.sethook()
- error("Trying to overwrite environment contents")
- end
- localenv[i]=v
- end,
- }
- setmetatable(proxy_env, metatbl)
- local fun, err=loadstring(code)
- if not fun then
- return false, err
- end
-
- setfenv(fun, proxy_env)
- local succ, data = pcall(fun)
- if succ then
- data=localenv
- end
- return succ, data
- end
- function env_proto:run_initcode()
- if self.init_code and self.init_code~="" then
- local old_fdata=self.fdata
- self.fdata = {}
- --atprint("[atlatc]Running initialization code for environment '"..self.name.."'")
- local succ, err = self:execute_code({}, self.init_code, {type="init", init=true})
- if not succ then
- self:log("error", "Executing InitCode for '"..self.name.."' failed:"..err)
- self.init_err=err
- if old_fdata then
- self.fdata=old_fdata
- self:log("warning", "The 'F' table has been restored to the previous state.")
- end
- end
- end
- end
- -- log to environment subscribers. severity can be "error", "warning" or "info" (used by internal print)
- function env_proto:log(severity, ...)
- local text=advtrains.print_concat_table({"[atlatc "..self.name.." "..severity.."]", ...})
- minetest.log("action", text)
- for _, pname in ipairs(self.subscribers) do
- minetest.chat_send_player(pname, text)
- end
- end
- -- env.subscribers table may be directly altered by callers.
- --- class interface
- function atlatc.env_new(name)
- local newenv={
- name=name,
- init_code="",
- sdata={},
- subscribers={},
- }
- setmetatable(newenv, {__index=env_proto})
- return newenv
- end
- function atlatc.env_load(name, data)
- local newenv={}
- setmetatable(newenv, {__index=env_proto})
- newenv:load(name, data)
- return newenv
- end
- function atlatc.run_initcode()
- for envname, env in pairs(atlatc.envs) do
- env:run_initcode()
- end
- end
|