Advertisement
osmarks

Polychoron NotOS

Sep 6th, 2018
2,833
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 6.45 KB | None | 0 0
  1. if _G.polychoron ~= nil then
  2.     if fs.exists "autorun" then shell.run "autorun" end
  3.     -- exit now
  4.     -- as we are running inside it already
  5.     return
  6. end
  7.  
  8. local version = "1.4"
  9.  
  10. -- Localize frequently used functions for performance
  11. local osepoch = os.epoch
  12. local osclock = os.clock
  13. local stringformat = string.format
  14. local coroutineresume = coroutine.resume
  15. local coroutineyield = coroutine.yield
  16. local coroutinestatus = coroutine.status
  17. local tostring = tostring
  18. local ccemuxnanoTime
  19. if ccemux then
  20.     ccemuxnanoTime = ccemux.nanoTime
  21. end
  22.  
  23. -- Return a time of some sort. Not used to provide "objective" time measurement, just for duration comparison
  24. local function time()
  25.     if ccemuxnanoTime then
  26.         return ccemuxnanoTime() / 1e9
  27.     elseif osepoch then
  28.         return osepoch "utc" / 1000 else
  29.     return os.clock() end
  30. end
  31.  
  32. local processes = {}
  33. _G.process = {}
  34.  
  35. -- Allow getting processes by name, and nice process views from process.list()
  36. local process_list_mt = {
  37.     __tostring = function(ps)
  38.         local o = ""
  39.         for _, p in pairs(ps) do
  40.             o = o .. tostring(p)
  41.             o = o .. "\n"
  42.         end
  43.         return o:gsub("\n$", "") -- strip trailing newline
  44.     end,
  45.     __index = function(tabl, key)
  46.         for i, p in pairs(tabl) do
  47.             if p.name == key then return p end
  48.         end
  49.     end
  50. }
  51. setmetatable(processes, process_list_mt)
  52.  
  53. -- To make suspend kind of work with sleep, we need to bodge it a bit
  54. -- So this modified sleep *also* checks the time, in case timer events were eaten
  55. function _G.sleep(time)
  56.     time = time or 0
  57.     local t = os.startTimer(time)
  58.     local start = os.clock()
  59.     local ev, arg, tdiff
  60.  
  61.     repeat
  62.         ev, arg = os.pullEvent()
  63.     until (ev == "timer" and arg == t) or (os.clock() - start) > time
  64. end
  65.  
  66. process.statuses = {
  67.     DEAD = "dead",
  68.     ERRORED = "errored",
  69.     OK = "ok",
  70.     STOPPED = "stopped"
  71. }
  72.  
  73. process.signals = {
  74.     START = "start",
  75.     STOP = "stop",
  76.     TERMINATE = "terminate",
  77.     KILL = "kill"
  78. }
  79.  
  80. -- Gets the first key in a table with the given value
  81. local function get_key_with_value(t, v)
  82.     for tk, tv in pairs(t) do
  83.         if v == tv then
  84.             return tk
  85.         end
  86.     end
  87. end
  88.  
  89. -- Contains custom stringification, and an equality thing using IDs
  90. local process_metatable = {
  91.     __tostring = function(p)
  92.         local text = stringformat("[process %d %s: %s", p.ID, p.name or "[unnamed]", get_key_with_value(process.statuses, p.status) or "?")
  93.         if p.parent then
  94.             text = text .. stringformat("; parent %s", p.parent.name or p.parent.ID)
  95.         end
  96.         return text .. "]"
  97.     end,
  98.     __eq = function(p1, p2)
  99.         return p1.ID == p2.ID
  100.     end
  101. }
  102.  
  103. -- Whitelist of events which ignore filters.
  104. local allow_event = {
  105.     terminate = true
  106. }
  107.  
  108. local function process_to_info(p)
  109.     if not p then return nil end
  110.     local out = {}
  111.     for k, v in pairs(p) do
  112.         if k == "parent" and v ~= nil then
  113.             out.parent = process_to_info(v)
  114.         else
  115.             -- PS#85DD8AFC
  116.             -- Through some bizarre environment weirdness even exposing the function causes security risks. So don't.
  117.             if k ~= "coroutine" and k ~= "function" then
  118.                 out[k] = v
  119.             end
  120.         end
  121.     end
  122.     setmetatable(out, process_metatable)
  123.     return out
  124. end
  125.  
  126. -- Fancy BSOD
  127. local function BSOD(e)
  128.     if term.isColor() then term.setBackgroundColor(colors.blue) term.setTextColor(colors.white)
  129.     else term.setBackgroundColor(colors.white) term.setTextColor(colors.black) end
  130.  
  131.     term.clear()
  132.     term.setCursorBlink(false)
  133.     term.setCursorPos(1, 1)
  134.    
  135.     print(e)
  136. end
  137.  
  138. local running
  139. -- Apply "event" to "proc"
  140. -- Where most important stuff happens
  141. local function tick(proc, event)
  142.     if not proc then error "No such process" end
  143.     if process.running and process.running.ID == proc.ID then return end
  144.  
  145.     -- Run any given event preprocessor on the event
  146.     -- Actually don't, due to (hypothetical) PS#D7CD76C0-like exploits
  147.     --[[
  148.     if type(proc.event_preprocessor) == "function" then
  149.         event = proc.event_preprocessor(event)
  150.         if event == nil then return end
  151.     end
  152.     ]]
  153.  
  154.     -- If coroutine is dead, just ignore it but set its status to dead
  155.     if coroutinestatus(proc.coroutine) == "dead" then
  156.         proc.status = process.statuses.DEAD
  157.         if proc.ephemeral then
  158.             processes[proc.ID] = nil
  159.         end
  160.     end
  161.     -- If coroutine ready and filter matches or event is allowed, run it, set the running process in its environment,
  162.     -- get execution time, and run error handler if errors happen.
  163.     if proc.status == process.statuses.OK and (proc.filter == nil or proc.filter == event[1] or allow_event[event[1]]) then
  164.         process.running = process_to_info(proc)
  165.         running = proc
  166.         local start_time = time()
  167.         local ok, res = coroutineresume(proc.coroutine, table.unpack(event))
  168.         local end_time = time()
  169.         proc.execution_time = end_time - start_time
  170.         if not ok then
  171.             if proc.error_handler then
  172.                 proc.error_handler(res)
  173.             else
  174.                 proc.status = process.statuses.ERRORED
  175.                 proc.error = res
  176.                 if res ~= "Terminated" then -- programs terminating is normal, other errors not so much
  177.                     BSOD(stringformat("Process %s has crashed!\nError: %s", tostring(proc.ID) or proc.name, tostring(res)))
  178.                 end
  179.             end
  180.         else
  181.             proc.filter = res
  182.         end
  183.         process.running = nil
  184.     end
  185. end
  186.  
  187. function process.get_running()
  188.     return running
  189. end
  190.  
  191. -- Send/apply the given signal to the given process
  192. local function apply_signal(proc, signal)
  193.     local rID = nil
  194.     if process.running then rID = process.running.ID end
  195.     tick(proc, { "signal", signal, rID })
  196.     -- START - starts stopped process
  197.     if signal == process.signals.START and proc.status == process.statuses.STOPPED then
  198.         proc.status = process.statuses.OK
  199.     -- STOP stops started process
  200.     elseif signal == process.signals.STOP and proc.status == process.statuses.OK then
  201.         proc.status = process.statuses.STOPPED
  202.     elseif signal == process.signals.TERMINATE then
  203.         proc.terminated_time = os.clock()
  204.         tick(proc, { "terminate" })
  205.     elseif signal == process.signals.KILL then
  206.         proc.status = process.statuses.DEAD
  207.     end
  208. end
  209.  
  210. local next_ID = 1
  211. function process.spawn(fn, name, extra)
  212.     local this_ID = next_ID
  213.     local proc = {
  214.         coroutine = coroutine.create(fn),
  215.         name = name,
  216.         status = process.statuses.OK,
  217.         ID = this_ID,
  218.         parent = process.running,
  219.         ["function"] = fn
  220.     }
  221.  
  222.     if extra then for k, v in pairs(extra) do proc[k] = v end end
  223.  
  224.     setmetatable(proc, process_metatable)
  225.     processes[this_ID] = proc
  226.     next_ID = next_ID + 1
  227.     return this_ID
  228. end
  229.  
  230. function process.thread(fn, name)
  231.     local parent = process.running.name or tostring(process.running.ID)
  232.     process.spawn(fn, ("%s_%s_%04x"):format(name or "thread", parent, math.random(0, 0xFFFF)), { ephemeral = true })
  233. end
  234.  
  235. -- Sends a signal to the given process ID
  236. function process.signal(ID, signal)
  237.     if not processes[ID] then error(stringformat("No such process %s.", tostring(ID))) end
  238.     apply_signal(processes[ID], signal)
  239. end
  240.  
  241. -- PS#F7686798
  242. -- Prevent mutation of processes through exposed API to prevent PS#D7CD76C0-like exploits
  243. -- List all processes
  244. function process.list()
  245.     local out = {}
  246.     for k, v in pairs(processes) do
  247.         out[k] = process_to_info(v)
  248.     end
  249.     return setmetatable(out, process_list_mt)
  250. end
  251.  
  252. function process.info(ID)
  253.     return process_to_info(processes[ID])
  254. end
  255.  
  256. -- Run main event loop
  257. local function run_loop()
  258.     while true do
  259.         local ev = {coroutineyield()}
  260.         for ID, proc in pairs(processes) do
  261.             tick(proc, ev)
  262.         end
  263.     end
  264. end
  265.  
  266. local base_processes = {
  267.     ["shell"] = function() os.run({}, "/rom/programs/shell.lua") end,
  268.     ["rednetd"] = function()
  269.         -- bodge, because of the stupid rednet bRunning thing
  270.         local old_error = error
  271.         _G.error = function() _G.error = old_error end
  272.         rednet.run()
  273.     end
  274. }
  275.  
  276. -- hacky magic to run our code and not the BIOS stuff
  277. -- this terminates the shell, which crashes the BIOS, which then causes an error, which is printed with printError
  278. local old_printError = _G.printError
  279. function _G.printError()
  280.     _G.printError = old_printError
  281.     -- Multishell must die.
  282.     term.redirect(term.native())
  283.     multishell = nil
  284.     term.setTextColor(colors.yellow)
  285.     term.setBackgroundColor(colors.black)
  286.     term.setCursorPos(1,1)
  287.     term.clear()
  288.  
  289.     _G.polychoron = {version = version, process = process}
  290.     polychoron.polychoron = polychoron
  291.     polychoron.BSOD = BSOD
  292.  
  293.     for n, p in pairs(base_processes) do
  294.         process.spawn(p, n)
  295.     end
  296.  
  297.     os.queueEvent "event" -- so that processes get one free "tick"
  298.     run_loop()
  299. end
  300.  
  301. os.queueEvent "terminate"
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement