environment.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. -------------
  2. -- lua sandboxed environment
  3. -- function to cross out functions and userdata.
  4. -- modified from dump()
  5. function atlatc.remove_invalid_data(o, nested)
  6. if o==nil then return nil end
  7. local valid_dt={["nil"]=true, boolean=true, number=true, string=true}
  8. if type(o) ~= "table" then
  9. --check valid data type
  10. if not valid_dt[type(o)] then
  11. return nil
  12. end
  13. return o
  14. end
  15. -- Contains table -> true/nil of currently nested tables
  16. nested = nested or {}
  17. if nested[o] then
  18. return nil
  19. end
  20. nested[o] = true
  21. for k, v in pairs(o) do
  22. v = atlatc.remove_invalid_data(v, nested)
  23. end
  24. nested[o] = nil
  25. return o
  26. end
  27. local env_proto={
  28. load = function(self, envname, data)
  29. self.name=envname
  30. self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {}
  31. self.fdata={}
  32. self.init_code=data.init_code or ""
  33. self.subscribers=data.subscribers or {}
  34. end,
  35. save = function(self)
  36. -- throw any function values out of the sdata table
  37. self.sdata = atlatc.remove_invalid_data(self.sdata)
  38. return {sdata = self.sdata, init_code=self.init_code, subscribers=self.subscribers}
  39. end,
  40. }
  41. --Environment
  42. --Code modified from mesecons_luacontroller (credit goes to Jeija and mesecons contributors)
  43. local safe_globals = {
  44. "assert", "error", "ipairs", "next", "pairs", "select",
  45. "tonumber", "tostring", "type", "unpack", "_VERSION"
  46. }
  47. local function safe_date(f, t)
  48. if not f then
  49. -- fall back to old behavior
  50. return(os.date("*t",os.time()))
  51. else
  52. --pass parameters
  53. return os.date(f,t)
  54. end
  55. end
  56. -- string.rep(str, n) with a high value for n can be used to DoS
  57. -- the server. Therefore, limit max. length of generated string.
  58. local function safe_string_rep(str, n)
  59. if #str * n > 2000 then
  60. debug.sethook() -- Clear hook
  61. error("string.rep: string length overflow", 2)
  62. end
  63. return string.rep(str, n)
  64. end
  65. -- string.find with a pattern can be used to DoS the server.
  66. -- Therefore, limit string.find to patternless matching.
  67. -- Note: Disabled security since there are enough security leaks and this would be unneccessary anyway to DoS the server
  68. local function safe_string_find(...)
  69. --if (select(4, ...)) ~= true then
  70. -- debug.sethook() -- Clear hook
  71. -- error("string.find: 'plain' (fourth parameter) must always be true for security reasons.")
  72. --end
  73. return string.find(...)
  74. end
  75. local mp=minetest.get_modpath("advtrains_luaautomation")
  76. local static_env = {
  77. --core LUA functions
  78. string = {
  79. byte = string.byte,
  80. char = string.char,
  81. format = string.format,
  82. len = string.len,
  83. lower = string.lower,
  84. upper = string.upper,
  85. rep = safe_string_rep,
  86. reverse = string.reverse,
  87. sub = string.sub,
  88. find = safe_string_find,
  89. },
  90. math = {
  91. abs = math.abs,
  92. acos = math.acos,
  93. asin = math.asin,
  94. atan = math.atan,
  95. atan2 = math.atan2,
  96. ceil = math.ceil,
  97. cos = math.cos,
  98. cosh = math.cosh,
  99. deg = math.deg,
  100. exp = math.exp,
  101. floor = math.floor,
  102. fmod = math.fmod,
  103. frexp = math.frexp,
  104. huge = math.huge,
  105. ldexp = math.ldexp,
  106. log = math.log,
  107. log10 = math.log10,
  108. max = math.max,
  109. min = math.min,
  110. modf = math.modf,
  111. pi = math.pi,
  112. pow = math.pow,
  113. rad = math.rad,
  114. random = math.random,
  115. sin = math.sin,
  116. sinh = math.sinh,
  117. sqrt = math.sqrt,
  118. tan = math.tan,
  119. tanh = math.tanh,
  120. },
  121. table = {
  122. concat = table.concat,
  123. insert = table.insert,
  124. maxn = table.maxn,
  125. remove = table.remove,
  126. sort = table.sort,
  127. },
  128. os = {
  129. clock = os.clock,
  130. difftime = os.difftime,
  131. time = os.time,
  132. date = safe_date,
  133. },
  134. POS = function(x,y,z) return {x=x, y=y, z=z} end,
  135. getstate = advtrains.getstate,
  136. setstate = advtrains.setstate,
  137. is_passive = advtrains.is_passive,
  138. --interrupts are handled per node, position unknown. (same goes for digilines)
  139. --however external interrupts can be set here.
  140. interrupt_pos = function(parpos, imesg)
  141. local pos=atlatc.pcnaming.resolve_pos(parpos, "interrupt_pos")
  142. atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg})
  143. end,
  144. train_parts = function(train_id)
  145. if not train_id then return false end
  146. local train = advtrains.trains[train_id]
  147. if not train then return false end
  148. return table.copy(train.trainparts or {})
  149. end,
  150. -- sends an atc command to train regardless of where it is in the world
  151. atc_send_to_train = function(train_id, command)
  152. assertt(command, "string")
  153. local train = advtrains.trains[train_id]
  154. if train then
  155. advtrains.atc.train_set_command(train, command, true)
  156. return true
  157. else
  158. return false
  159. end
  160. end,
  161. get_slowdown = function()
  162. return advtrains.global_slowdown
  163. end
  164. }
  165. -- If interlocking is present, enable route setting functions
  166. if advtrains.interlocking then
  167. local function gen_checks(signal, route_name, noroutesearch)
  168. assertt(route_name, "string")
  169. local pos = atlatc.pcnaming.resolve_pos(signal)
  170. local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
  171. if not sigd then
  172. error("There's no signal at "..minetest.pos_to_string(pos))
  173. end
  174. local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
  175. if not tcbs then
  176. error("Inconsistent configuration, no tcbs for signal at "..minetest.pos_to_string(pos))
  177. end
  178. local routeid, route
  179. if not noroutesearch then
  180. for routeidt, routet in ipairs(tcbs.routes) do
  181. if routet.name == route_name then
  182. routeid = routeidt
  183. route = routet
  184. break
  185. end
  186. end
  187. if not route then
  188. error("No route called "..route_name.." at "..minetest.pos_to_string(pos))
  189. end
  190. end
  191. return pos, sigd, tcbs, routeid, route
  192. end
  193. static_env.can_set_route = function(signal, route_name)
  194. local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name)
  195. -- if route is already set on signal, return whether it's committed
  196. if tcbs.routeset == routeid then
  197. return tcbs.route_committed
  198. end
  199. -- actually try setting route (parameter 'true' designates try-run
  200. local ok = advtrains.interlocking.route.set_route(sigd, route, true)
  201. return ok
  202. end
  203. static_env.set_route = function(signal, route_name)
  204. local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name)
  205. return advtrains.interlocking.route.update_route(sigd, tcbs, routeid)
  206. end
  207. static_env.cancel_route = function(signal)
  208. local pos, sigd, tcbs, routeid, route = gen_checks(signal, "", true)
  209. return advtrains.interlocking.route.update_route(sigd, tcbs, nil, true)
  210. end
  211. static_env.get_aspect = function(signal)
  212. local pos = atlatc.pcnaming.resolve_pos(signal)
  213. return advtrains.interlocking.signal_get_aspect(pos)
  214. end
  215. static_env.set_aspect = function(signal, asp)
  216. local pos = atlatc.pcnaming.resolve_pos(signal)
  217. return advtrains.interlocking.signal_set_aspect(pos,asp)
  218. end
  219. --section_occupancy()
  220. static_env.section_occupancy = function(ts_id)
  221. if not ts_id then return nil end
  222. ts_id = tostring(ts_id)
  223. local response = advtrains.interlocking.db.get_ts(ts_id)
  224. if not response then return false end
  225. return (response.trains and table.copy(response.trains)) or {}
  226. end
  227. end
  228. -- Lines-specific:
  229. if advtrains.lines then
  230. local atlrwt = advtrains.lines.rwt
  231. static_env.rwt = {
  232. now = atlrwt.now,
  233. new = atlrwt.new,
  234. copy = atlrwt.copy,
  235. to_table = atlrwt.to_table,
  236. to_secs = atlrwt.to_secs,
  237. to_string = atlrwt.to_string,
  238. add = atlrwt.add,
  239. diff = atlrwt.diff,
  240. sub = atlrwt.sub,
  241. adj_diff = atlrwt.adj_diff,
  242. adjust_cycle = atlrwt.adjust_cycle,
  243. adjust = atlrwt.adjust,
  244. to_string = atlrwt.to_string,
  245. get_time_until = atlrwt.get_time_until,
  246. next_rpt = atlrwt.next_rpt,
  247. last_rpt = atlrwt.last_rpt,
  248. time_from_last_rpt = atlrwt.time_from_last_rpt,
  249. time_to_next_rpt = atlrwt.time_to_next_rpt,
  250. }
  251. end
  252. atlatc.register_function = function (name, f)
  253. static_env[name] = f
  254. end
  255. for _, name in pairs(safe_globals) do
  256. static_env[name] = _G[name]
  257. end
  258. --The environment all code calls get is a table that has set static_env as metatable.
  259. --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
  260. --Init code and step code's environments are not saved
  261. -- 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.
  262. -- F - Table global to the environment, can contain volatile data that is deleted when server quits.
  263. -- The init code should populate this table with functions and other definitions.
  264. local proxy_env={}
  265. --proxy_env gets a new metatable in every run, but is the shared environment of all functions ever defined.
  266. -- returns: true, fenv if successful; nil, error if error
  267. function env_proto:execute_code(localenv, code, evtdata, customfct)
  268. -- create us a print function specific for this environment
  269. if not self.safe_print_func then
  270. local myenv = self
  271. self.safe_print_func = function(...)
  272. myenv:log("info", ...)
  273. end
  274. end
  275. local metatbl ={
  276. __index = function(t, i)
  277. if i=="S" then
  278. return self.sdata
  279. elseif i=="F" then
  280. return self.fdata
  281. elseif i=="event" then
  282. return evtdata
  283. elseif customfct and customfct[i] then
  284. return customfct[i]
  285. elseif localenv and localenv[i] then
  286. return localenv[i]
  287. elseif i=="print" then
  288. return self.safe_print_func
  289. end
  290. return static_env[i]
  291. end,
  292. __newindex = function(t, i, v)
  293. if i=="S" or i=="F" or i=="event" or (customfct and customfct[i]) or static_env[i] then
  294. debug.sethook()
  295. error("Trying to overwrite environment contents")
  296. end
  297. localenv[i]=v
  298. end,
  299. }
  300. setmetatable(proxy_env, metatbl)
  301. local fun, err=loadstring(code)
  302. if not fun then
  303. return false, err
  304. end
  305. setfenv(fun, proxy_env)
  306. local succ, data = pcall(fun)
  307. if succ then
  308. data=localenv
  309. end
  310. return succ, data
  311. end
  312. function env_proto:run_initcode()
  313. if self.init_code and self.init_code~="" then
  314. local old_fdata=self.fdata
  315. self.fdata = {}
  316. --atprint("[atlatc]Running initialization code for environment '"..self.name.."'")
  317. local succ, err = self:execute_code({}, self.init_code, {type="init", init=true})
  318. if not succ then
  319. self:log("error", "Executing InitCode for '"..self.name.."' failed:"..err)
  320. self.init_err=err
  321. if old_fdata then
  322. self.fdata=old_fdata
  323. self:log("warning", "The 'F' table has been restored to the previous state.")
  324. end
  325. end
  326. end
  327. end
  328. -- log to environment subscribers. severity can be "error", "warning" or "info" (used by internal print)
  329. function env_proto:log(severity, ...)
  330. local text=advtrains.print_concat_table({"[atlatc "..self.name.." "..severity.."]", ...})
  331. minetest.log("action", text)
  332. for _, pname in ipairs(self.subscribers) do
  333. minetest.chat_send_player(pname, text)
  334. end
  335. end
  336. -- env.subscribers table may be directly altered by callers.
  337. --- class interface
  338. function atlatc.env_new(name)
  339. local newenv={
  340. name=name,
  341. init_code="",
  342. sdata={},
  343. subscribers={},
  344. }
  345. setmetatable(newenv, {__index=env_proto})
  346. return newenv
  347. end
  348. function atlatc.env_load(name, data)
  349. local newenv={}
  350. setmetatable(newenv, {__index=env_proto})
  351. newenv:load(name, data)
  352. return newenv
  353. end
  354. function atlatc.run_initcode()
  355. for envname, env in pairs(atlatc.envs) do
  356. env:run_initcode()
  357. end
  358. end