mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-13 09:18:05 -04:00
memory, sandbox, devfs, signals, log, rc, terminal, oppm
A big update final push before 1.6 release. Here is the detailed rundown of the changes /lib/term.lua 1. delay calling gpu.getViewport() when creating a terminal window. this fixes scenarios where the gpu could be a proxy object that doesn't have a viewport defined yet. 2. big blink cleanup to minimize the potential number of gpu calls during blink, and simplify the code /lib/sh.lua, /lib/process.lua 1. moving shell sandboxing code to process library. This actually simplifies creating the sandbox for processes and handling process crash (process lib and sh lib shared common crash code) /bin/rc.lua 1. found a bug in restart, fixed /lib/pipes.lua 1. required update to be compatible with internal(private) methods to the sh library /lib/package.lua 1. just aesthetic cleanup of error reporting /bin/mktmp.lua 1. use existing os.tmpname() helper method /init.lua and (added) /lib/tools/boot.lua 1. moving all the code I can to tools file to allow the memory to unload after boot completes. /bin/ls.lua 1. fixing symbolic links to directory display (had extra /) /lib/event.lua 1. refactor listeners and timers into common registers (fully backwards compatible). Provides a mechanism for drivers to know about ALL events without stealing them from the main process. Will document it later when the api is hardened (new api, event.register) 2. memory savings due to refactor 3. protecting computer.pullSignal! this is critical, user scripts that were previously pulling diirection from the computer api were able to put the kernel in a bad state. computer.pullSignal is still available, but it calls the event api to correctly dispatch events as drivers expect. devfs 1. eeprom and eeprom-data are now dynamically available. This is significant, now when a user removes the eeprom, it will not be listed in /dev 2. devfs upgrade, support built for future dynamic folders (such as /dev/filesystems/) autorun 1. code cleanup oppm 1. fixing cwd expected by oppm.lua during initial install (if using .install via `install oppm`)
This commit is contained in:
parent
9dcc6070e0
commit
917befcd0e
@ -1,12 +1,11 @@
|
||||
local fs = require("filesystem")
|
||||
local uuid = require("uuid")
|
||||
local shell = require("shell")
|
||||
local sh = require("sh")
|
||||
|
||||
local touch = loadfile(shell.resolve("touch", "lua"))
|
||||
local mkdir = loadfile(shell.resolve("mkdir", "lua"))
|
||||
|
||||
if not uuid or not touch then
|
||||
if not touch then
|
||||
local errorMessage = "missing tools for mktmp"
|
||||
io.stderr:write(errorMessage .. '\n')
|
||||
return false, errorMessage
|
||||
@ -57,24 +56,14 @@ if not fs.exists(prefix) then
|
||||
return 1
|
||||
end
|
||||
|
||||
while true do
|
||||
local tmp = prefix .. uuid.next()
|
||||
if not fs.exists(tmp) then
|
||||
local tmp = os.tmpname()
|
||||
local ok, reason = (directory and mkdir or touch)(tmp)
|
||||
|
||||
local ok, reason
|
||||
if directory then
|
||||
ok, reason = mkdir(tmp)
|
||||
else
|
||||
ok, reason = touch(tmp)
|
||||
end
|
||||
|
||||
if sh.internal.command_passed(ok) then
|
||||
if verbose then
|
||||
print(tmp)
|
||||
end
|
||||
return tmp
|
||||
else
|
||||
return ok, reason
|
||||
end
|
||||
if sh.internal.command_passed(ok) then
|
||||
if verbose then
|
||||
print(tmp)
|
||||
end
|
||||
return tmp
|
||||
end
|
||||
|
||||
return ok, reason
|
||||
|
@ -65,9 +65,10 @@ local function rawRunCommand(conf, name, cmd, args, ...)
|
||||
return true
|
||||
end
|
||||
elseif cmd == "restart" and type(result["stop"]) == "function" and type(result["start"]) == "function" then
|
||||
result, what = xpcall(result["stop"], debug.traceback, ...)
|
||||
local daemon = result
|
||||
result, what = xpcall(daemon["stop"], debug.traceback, ...)
|
||||
if result then
|
||||
result, what = xpcall(result["start"], debug.traceback, ...)
|
||||
result, what = xpcall(daemon["start"], debug.traceback, ...)
|
||||
if result then
|
||||
return true
|
||||
end
|
||||
|
@ -13,7 +13,7 @@ local function onInit()
|
||||
local path = fs.concat(os.getenv("TMPDIR") or "/tmp", "event.log")
|
||||
local log = io.open(path, "a")
|
||||
if log then
|
||||
log:write(reason .. "\n")
|
||||
log:write(tostring(result) .. ":" .. tostring(reason) .. "\n")
|
||||
log:close()
|
||||
end
|
||||
end
|
||||
@ -34,20 +34,17 @@ local function onComponentAdded(_, address, componentType)
|
||||
name = fs.concat("/mnt", name)
|
||||
fs.mount(proxy, name)
|
||||
if fs.isAutorunEnabled() then
|
||||
local function run()
|
||||
local file = shell.resolve(fs.concat(name, "autorun"), "lua") or
|
||||
shell.resolve(fs.concat(name, ".autorun"), "lua")
|
||||
if file then
|
||||
local result, reason = shell.execute(file, _ENV, proxy)
|
||||
if not result then
|
||||
error(reason, 0)
|
||||
end
|
||||
local file = shell.resolve(fs.concat(name, "autorun"), "lua") or
|
||||
shell.resolve(fs.concat(name, ".autorun"), "lua")
|
||||
if file then
|
||||
local run = function()
|
||||
assert(shell.execute(file, _ENV, proxy))
|
||||
end
|
||||
if isInitialized then
|
||||
run()
|
||||
else
|
||||
table.insert(pendingAutoruns, run)
|
||||
end
|
||||
end
|
||||
if isInitialized then
|
||||
run()
|
||||
else
|
||||
table.insert(pendingAutoruns, run)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,167 +1,21 @@
|
||||
do
|
||||
_G._OSVERSION = "OpenOS 1.6"
|
||||
|
||||
local component = component
|
||||
local computer = computer
|
||||
local unicode = unicode
|
||||
|
||||
-- Runlevel information.
|
||||
local runlevel, shutdown = "S", computer.shutdown
|
||||
computer.runlevel = function() return runlevel end
|
||||
computer.shutdown = function(reboot)
|
||||
runlevel = reboot and 6 or 0
|
||||
if os.sleep then
|
||||
computer.pushSignal("shutdown")
|
||||
os.sleep(0.1) -- Allow shutdown processing.
|
||||
end
|
||||
shutdown(reboot)
|
||||
end
|
||||
|
||||
-- Low level dofile implementation to read filesystem libraries.
|
||||
local rom = {}
|
||||
function rom.invoke(method, ...)
|
||||
return component.invoke(computer.getBootAddress(), method, ...)
|
||||
end
|
||||
function rom.open(file) return rom.invoke("open", file) end
|
||||
function rom.read(handle) return rom.invoke("read", handle, math.huge) end
|
||||
function rom.close(handle) return rom.invoke("close", handle) end
|
||||
function rom.inits() return ipairs(rom.invoke("list", "boot")) end
|
||||
function rom.isDirectory(path) return rom.invoke("isDirectory", path) end
|
||||
|
||||
local screen = component.list('screen', true)()
|
||||
for address in component.list('screen', true) do
|
||||
if #component.invoke(address, 'getKeyboards') > 0 then
|
||||
screen = address
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
_G.boot_screen = screen
|
||||
|
||||
-- Report boot progress if possible.
|
||||
local gpu = component.list("gpu", true)()
|
||||
local w, h
|
||||
if gpu and screen then
|
||||
component.invoke(gpu, "bind", screen)
|
||||
w, h = component.invoke(gpu, "maxResolution")
|
||||
component.invoke(gpu, "setResolution", w, h)
|
||||
component.invoke(gpu, "setBackground", 0x000000)
|
||||
component.invoke(gpu, "setForeground", 0xFFFFFF)
|
||||
component.invoke(gpu, "fill", 1, 1, w, h, " ")
|
||||
end
|
||||
local y = 1
|
||||
local function status(msg)
|
||||
if gpu and screen then
|
||||
component.invoke(gpu, "set", 1, y, msg)
|
||||
if y == h then
|
||||
component.invoke(gpu, "copy", 1, 2, w, h - 1, 0, -1)
|
||||
component.invoke(gpu, "fill", 1, h, w, 1, " ")
|
||||
else
|
||||
y = y + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
status("Booting " .. _OSVERSION .. "...")
|
||||
|
||||
-- Custom low-level loadfile/dofile implementation reading from our ROM.
|
||||
local function loadfile(file)
|
||||
status("> " .. file)
|
||||
local handle, reason = rom.open(file)
|
||||
local loadfile = load([[return function(file)
|
||||
local handle, reason = invoke(addr, "open", file)
|
||||
if not handle then
|
||||
error(reason)
|
||||
end
|
||||
local buffer = ""
|
||||
repeat
|
||||
local data, reason = rom.read(handle)
|
||||
local data, reason = invoke(addr, "read", handle, math.huge)
|
||||
if not data and reason then
|
||||
error(reason)
|
||||
end
|
||||
buffer = buffer .. (data or "")
|
||||
until not data
|
||||
rom.close(handle)
|
||||
return load(buffer, "=" .. file)
|
||||
end
|
||||
|
||||
local function dofile(file)
|
||||
local program, reason = loadfile(file)
|
||||
if program then
|
||||
local result = table.pack(pcall(program))
|
||||
if result[1] then
|
||||
return table.unpack(result, 2, result.n)
|
||||
else
|
||||
error(result[2])
|
||||
end
|
||||
else
|
||||
error(reason)
|
||||
end
|
||||
end
|
||||
|
||||
status("Initializing package management...")
|
||||
|
||||
-- Load file system related libraries we need to load other stuff moree
|
||||
-- comfortably. This is basically wrapper stuff for the file streams
|
||||
-- provided by the filesystem components.
|
||||
local package = dofile("/lib/package.lua")
|
||||
|
||||
do
|
||||
-- Unclutter global namespace now that we have the package module.
|
||||
_G.component = nil
|
||||
_G.computer = nil
|
||||
_G.process = nil
|
||||
_G.unicode = nil
|
||||
|
||||
-- Initialize the package module with some of our own APIs.
|
||||
package.loaded.component = component
|
||||
package.loaded.computer = computer
|
||||
package.loaded.unicode = unicode
|
||||
package.preload["buffer"] = loadfile("/lib/buffer.lua")
|
||||
package.preload["filesystem"] = loadfile("/lib/filesystem.lua")
|
||||
|
||||
-- Inject the package and io modules into the global namespace, as in Lua.
|
||||
_G.package = package
|
||||
_G.io = loadfile("/lib/io.lua")()
|
||||
|
||||
--mark modules for delay loaded api
|
||||
package.delayed["text"] = true
|
||||
package.delayed["sh"] = true
|
||||
package.delayed["transforms"] = true
|
||||
package.delayed["term"] = true
|
||||
end
|
||||
|
||||
status("Initializing file system...")
|
||||
|
||||
-- Mount the ROM and temporary file systems to allow working on the file
|
||||
-- system module from this point on.
|
||||
require("filesystem").mount(computer.getBootAddress(), "/")
|
||||
package.preload={}
|
||||
|
||||
status("Running boot scripts...")
|
||||
|
||||
-- Run library startup scripts. These mostly initialize event handlers.
|
||||
local scripts = {}
|
||||
for _, file in rom.inits() do
|
||||
local path = "boot/" .. file
|
||||
if not rom.isDirectory(path) then
|
||||
table.insert(scripts, path)
|
||||
end
|
||||
end
|
||||
table.sort(scripts)
|
||||
for i = 1, #scripts do
|
||||
dofile(scripts[i])
|
||||
end
|
||||
|
||||
status("Initializing components...")
|
||||
|
||||
for c, t in component.list() do
|
||||
computer.pushSignal("component_added", c, t)
|
||||
end
|
||||
os.sleep(0.5) -- Allow signal processing by libraries.
|
||||
status("Initializing system...")
|
||||
|
||||
computer.pushSignal("init") -- so libs know components are initialized.
|
||||
require("event").pull(1, "init") -- Allow init processing.
|
||||
runlevel = 1
|
||||
invoke(addr, "close", handle)
|
||||
return load(buffer, "=" .. file, "bt", _G)
|
||||
end]], "=loadfile", "bt", {load=load,math=math,addr=computer.getBootAddress(), invoke=component.invoke})()
|
||||
loadfile("/lib/tools/boot.lua")(loadfile)
|
||||
end
|
||||
|
||||
while true do
|
||||
|
@ -1,5 +1,8 @@
|
||||
local fs = require("filesystem")
|
||||
local comp = require("component")
|
||||
local text = require("text")
|
||||
|
||||
local sys = {} -- base class
|
||||
|
||||
local function new_node(parent, name, is_dir, proxy)
|
||||
local node = {parent=parent, name=name, is_dir=is_dir, proxy=proxy}
|
||||
@ -9,189 +12,231 @@ local function new_node(parent, name, is_dir, proxy)
|
||||
return node
|
||||
end
|
||||
|
||||
local function new_devfs_dir(name)
|
||||
local sys = {}
|
||||
sys.mtab = new_node(nil, name or "/", true)
|
||||
-- node may support isAvailable, and may choose to not be available
|
||||
local function isAvailable(node)
|
||||
return node and not (node.proxy and node.proxy.isAvailable and not node.proxy.isAvailable())
|
||||
end
|
||||
|
||||
-- returns: dir, point or path
|
||||
-- node (table): the handler responsible for the path
|
||||
-- this is essentially the device filesystem that is registered for the given path
|
||||
-- point (string): the point name (like a file name)
|
||||
function sys.findNode(path, create)
|
||||
checkArg(1, path, "string")
|
||||
local segments = fs.segments(path)
|
||||
local node = sys.mtab
|
||||
while #segments > 0 do
|
||||
local name = table.remove(segments, 1)
|
||||
local prev_path = path
|
||||
path = table.concat(segments, "/")
|
||||
|
||||
if not node.children[name] then
|
||||
if not create then
|
||||
path = prev_path
|
||||
break
|
||||
end
|
||||
node.children[name] = new_node(node, name, true, false)
|
||||
end
|
||||
|
||||
node = node.children[name]
|
||||
|
||||
if node.proxy then -- if there is a proxy handler we stop searching here
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- only dirs can have trailing path
|
||||
-- trailing path on a dev point (file) not allowed
|
||||
if path == "" or node.is_dir and node.proxy then
|
||||
return node, path
|
||||
end
|
||||
end
|
||||
|
||||
function sys.invoke(method, path, ...)
|
||||
local node, rest = sys.findNode(path)
|
||||
if not node or -- not found
|
||||
rest == "" and node.is_dir or -- path is dir
|
||||
not node.proxy[method] then -- optional method
|
||||
return 0
|
||||
end
|
||||
-- proxy could be a file, which doesn't take an argument, but it can be ignored if passed
|
||||
return node.proxy[method](rest)
|
||||
end
|
||||
|
||||
function sys.size(path)
|
||||
return sys.invoke("size", path)
|
||||
end
|
||||
|
||||
function sys.lastModified(path)
|
||||
return sys.invoke("lastModified", path)
|
||||
end
|
||||
|
||||
function sys.isDirectory(path)
|
||||
local node, rest = sys.findNode(path)
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
|
||||
if rest == "" then
|
||||
return node.is_dir
|
||||
elseif node.proxy then
|
||||
return node.proxy.isDirectory(rest)
|
||||
end
|
||||
end
|
||||
|
||||
function sys.open(path, mode)
|
||||
checkArg(1, path, "string")
|
||||
checkArg(2, mode, "string", "nil")
|
||||
|
||||
if not sys.exists(path) then
|
||||
return nil, path.." file not found"
|
||||
elseif sys.isDirectory(path) then
|
||||
return nil, path.." is a directory"
|
||||
end
|
||||
|
||||
mode = mode or "r"
|
||||
-- everything at this level should be a binary open
|
||||
mode = mode:gsub("b", "")
|
||||
|
||||
if not ({a=true,w=true,r=true})[mode] then
|
||||
return nil, "invalid mode"
|
||||
end
|
||||
|
||||
local node, rest = sys.findNode(path)
|
||||
-- there must be a node, else exists would have failed
|
||||
|
||||
local args = {}
|
||||
if rest ~= "" then
|
||||
-- having more rest means we expect the proxy fs to open the point
|
||||
args[1] = rest
|
||||
end
|
||||
args[#args+1] = mode
|
||||
|
||||
return node.proxy.open(table.unpack(args))
|
||||
end
|
||||
|
||||
function sys.list(path)
|
||||
local node, rest = sys.findNode(path)
|
||||
if not node or (rest ~= "" and not node.is_dir) then-- not found
|
||||
return {}
|
||||
elseif rest == "" and not node.is_dir then -- path is file
|
||||
return {path}
|
||||
elseif node.proxy then
|
||||
-- proxy could be a file, which doesn't take an argument, but it can be ignored if passed
|
||||
return node.proxy.list(rest)
|
||||
end
|
||||
|
||||
-- rest == "" and node.is_dir
|
||||
local keys = {}
|
||||
for k in pairs(node.children) do
|
||||
table.insert(keys, k)
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
function sys.remove(path)
|
||||
return nil, "cannot remove devfs files or directories"
|
||||
--checkArg(1, path, "string")
|
||||
|
||||
--if path == "" then
|
||||
-- return nil, "no such file or directory"
|
||||
--end
|
||||
|
||||
--if not sys.exists(path) then
|
||||
-- return nil, path.." file not found"
|
||||
--end
|
||||
|
||||
--local node, rest = sys.findNode(path)
|
||||
|
||||
--if rest ~= "" then -- if rest is not resolved, this isn't our path
|
||||
-- return node.proxy.remove(rest)
|
||||
--end
|
||||
|
||||
--node.parent.children[node.name] = nil
|
||||
end
|
||||
|
||||
function sys.exists(path)
|
||||
checkArg(1, path, "string")
|
||||
local node, rest = sys.findNode(path)
|
||||
|
||||
if not node then
|
||||
return false
|
||||
elseif rest == "" then
|
||||
return true
|
||||
else
|
||||
return node.proxy.exists(rest)
|
||||
end
|
||||
end
|
||||
|
||||
function sys.create(path, handler)
|
||||
if sys.exists(path) then
|
||||
return nil, "path already exists"
|
||||
end
|
||||
|
||||
local segments = fs.segments(path)
|
||||
local target = table.remove(segments)
|
||||
-- returns: dir, point or path
|
||||
-- node (table): the handler responsible for the path
|
||||
-- this is essentially the device filesystem that is registered for the given path
|
||||
-- point (string): the point name (like a file name)
|
||||
function sys:findNode(path, create)
|
||||
checkArg(1, path, "string")
|
||||
local segments = fs.segments(path)
|
||||
local node = self.mtab
|
||||
while #segments > 0 do
|
||||
local name = table.remove(segments, 1)
|
||||
local prev_path = path
|
||||
path = table.concat(segments, "/")
|
||||
|
||||
if not target or target == "" then
|
||||
return nil, "missing argument"
|
||||
local next_node = node.children[name]
|
||||
if not isAvailable(next_node) then
|
||||
next_node = nil
|
||||
end
|
||||
|
||||
local node, rest = sys.findNode(path, true)
|
||||
if rest ~= "" then
|
||||
return node.proxy.create(rest, handler)
|
||||
if not next_node then
|
||||
if not create then
|
||||
path = prev_path
|
||||
break
|
||||
end
|
||||
node.children[name] = new_node(node, name, true, false)
|
||||
end
|
||||
|
||||
node = node.children[name]
|
||||
|
||||
if node.proxy then -- if there is a proxy handler we stop searching here
|
||||
break
|
||||
end
|
||||
node.children[target] = new_node(node, target, not not handler.list, handler)
|
||||
return true
|
||||
end
|
||||
|
||||
return sys
|
||||
-- only dirs can have trailing path
|
||||
-- trailing path on a dev point (file) not allowed
|
||||
if path == "" or node.is_dir and node.proxy then
|
||||
return node, path
|
||||
end
|
||||
end
|
||||
|
||||
function sys:invoke(method, path, ...)
|
||||
local node, rest = self.findNode(path)
|
||||
if not node or -- not found
|
||||
rest == "" and node.is_dir or -- path is dir
|
||||
not node.proxy[method] then -- optional method
|
||||
return 0
|
||||
end
|
||||
-- proxy could be a file, which doesn't take an argument, but it can be ignored if passed
|
||||
return node.proxy[method](rest)
|
||||
end
|
||||
|
||||
function sys:size(path)
|
||||
return self.invoke("size", path)
|
||||
end
|
||||
|
||||
function sys:lastModified(path)
|
||||
return self.invoke("lastModified", path)
|
||||
end
|
||||
|
||||
function sys:isDirectory(path)
|
||||
local node, rest = self.findNode(path)
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
|
||||
if rest == "" then
|
||||
return node.is_dir
|
||||
elseif node.proxy then
|
||||
return node.proxy.isDirectory(rest)
|
||||
end
|
||||
end
|
||||
|
||||
function sys:open(path, mode)
|
||||
checkArg(1, path, "string")
|
||||
checkArg(2, mode, "string", "nil")
|
||||
|
||||
if not self.exists(path) then
|
||||
return nil, path.." file not found"
|
||||
elseif self.isDirectory(path) then
|
||||
return nil, path.." is a directory"
|
||||
end
|
||||
|
||||
mode = mode or "r"
|
||||
-- everything at this level should be a binary open
|
||||
mode = mode:gsub("b", "")
|
||||
|
||||
if not ({a=true,w=true,r=true})[mode] then
|
||||
return nil, "invalid mode"
|
||||
end
|
||||
|
||||
local node, rest = self.findNode(path)
|
||||
-- there must be a node, else exists would have failed
|
||||
|
||||
local args = {}
|
||||
if rest ~= "" then
|
||||
-- having more rest means we expect the proxy fs to open the point
|
||||
args[1] = rest
|
||||
end
|
||||
args[#args+1] = mode
|
||||
|
||||
return node.proxy.open(table.unpack(args))
|
||||
end
|
||||
|
||||
function sys:list(path)
|
||||
local node, rest = self.findNode(path)
|
||||
if not node or (rest ~= "" and not node.is_dir) then-- not found
|
||||
return {}
|
||||
elseif rest == "" and not node.is_dir then -- path is file
|
||||
return {path}
|
||||
elseif node.proxy then
|
||||
-- proxy could be a file, which doesn't take an argument, but it can be ignored if passed
|
||||
return node.proxy.list(rest)
|
||||
end
|
||||
|
||||
-- rest == "" and node.is_dir
|
||||
local keys = {}
|
||||
for k,node in pairs(node.children) do
|
||||
if isAvailable(node) then
|
||||
table.insert(keys, k)
|
||||
end
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
function sys:remove(path)
|
||||
return nil, "cannot remove devfs files or directories"
|
||||
--checkArg(1, path, "string")
|
||||
|
||||
--if path == "" then
|
||||
-- return nil, "no such file or directory"
|
||||
--end
|
||||
|
||||
--if not self.exists(path) then
|
||||
-- return nil, path.." file not found"
|
||||
--end
|
||||
|
||||
--local node, rest = self.findNode(path)
|
||||
|
||||
--if rest ~= "" then -- if rest is not resolved, this isn't our path
|
||||
-- return node.proxy.remove(rest)
|
||||
--end
|
||||
|
||||
--node.parent.children[node.name] = nil
|
||||
end
|
||||
|
||||
function sys:exists(path)
|
||||
checkArg(1, path, "string")
|
||||
local node, rest = self.findNode(path)
|
||||
|
||||
if not node then
|
||||
return false
|
||||
elseif rest == "" then
|
||||
return true
|
||||
else
|
||||
return node.proxy.exists(rest)
|
||||
end
|
||||
end
|
||||
|
||||
function sys:create(path, handler)
|
||||
if self.exists(path) then
|
||||
return nil, "path already exists"
|
||||
end
|
||||
|
||||
local segments = fs.segments(path)
|
||||
local target = table.remove(segments)
|
||||
path = table.concat(segments, "/")
|
||||
|
||||
if not target or target == "" then
|
||||
return nil, "missing argument"
|
||||
end
|
||||
|
||||
local node, rest = self.findNode(path, true)
|
||||
if rest ~= "" then
|
||||
return node.proxy.create(rest, handler)
|
||||
end
|
||||
node.children[target] = new_node(node, target, not not handler.list, handler)
|
||||
return true
|
||||
end
|
||||
|
||||
local function new_devfs_dir(name)
|
||||
local sys_child = setmetatable({}, {__index=function(tbl,key)
|
||||
if sys[key] then
|
||||
return function(...)
|
||||
return sys[key](tbl, ...)
|
||||
end
|
||||
end
|
||||
end})
|
||||
sys_child.mtab = new_node(nil, name or "/", true)
|
||||
|
||||
return sys_child
|
||||
end
|
||||
|
||||
local devfs = new_devfs_dir()
|
||||
|
||||
local bfd = "bad file descriptor"
|
||||
|
||||
-- to allow sub dirs to act like sub devfs
|
||||
devfs.new_dir = new_devfs_dir
|
||||
devfs.new_node = new_node
|
||||
function devfs.new_callback_proxy(read_callback, write_callback)
|
||||
return
|
||||
{
|
||||
open = function(mode)
|
||||
if ({r=true, rb=true})[mode] then
|
||||
if not read_callback then
|
||||
return nil, "file cannot be opened for read"
|
||||
end
|
||||
return text.internal.reader(read_callback())
|
||||
end
|
||||
if not write_callback then
|
||||
return nil, "file cannot be opened for write"
|
||||
end
|
||||
return text.internal.writer(write_callback, ({a=true,ab=true})[mode] and read_callback())
|
||||
end,
|
||||
size = function()
|
||||
return read_callback and string.len(read_callback()) or 0
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function devfs.setLabel(value)
|
||||
error("drive does not support labeling")
|
||||
end
|
||||
@ -268,12 +313,15 @@ end
|
||||
-- /dev is a special handler
|
||||
|
||||
local function devfs_load(key)
|
||||
return require("tools/devfs/" .. key)
|
||||
-- using loadfile to allow us to pass args
|
||||
-- load order complication: some dev points are dirs that want to use devfs api, but can't require devfs
|
||||
devfs.create(key, loadfile("/lib/tools/devfs/" .. key .. ".lua", "bt", _G)(devfs))
|
||||
end
|
||||
|
||||
devfs.create("null", devfs_load("null"))
|
||||
devfs.create("random", devfs_load("random"))
|
||||
devfs.create("eeprom", devfs_load("eeprom"))
|
||||
devfs.create("eeprom-data", devfs_load("eeprom-data"))
|
||||
devfs_load("random")
|
||||
devfs_load("null")
|
||||
devfs_load("eeprom")
|
||||
devfs_load("eeprom-data")
|
||||
--devfs_load("filesystems")
|
||||
|
||||
return devfs
|
||||
|
@ -1,9 +1,12 @@
|
||||
local computer = require("computer")
|
||||
local keyboard = require("keyboard")
|
||||
|
||||
local event, listeners, timers = {}, {}, {}
|
||||
local event = {}
|
||||
local handlers = {}
|
||||
local lastInterrupt = -math.huge
|
||||
|
||||
event.handlers = handlers
|
||||
|
||||
local function call(callback, ...)
|
||||
local result, message = pcall(callback, ...)
|
||||
if not result and type(event.onError) == "function" then
|
||||
@ -13,42 +16,78 @@ local function call(callback, ...)
|
||||
return message
|
||||
end
|
||||
|
||||
local function dispatch(signal, ...)
|
||||
if listeners[signal] then
|
||||
local function callbacks()
|
||||
local list = {}
|
||||
for index, listener in ipairs(listeners[signal]) do
|
||||
list[index] = listener
|
||||
end
|
||||
return list
|
||||
function event.register(key, callback, interval, times)
|
||||
local handler =
|
||||
{
|
||||
key = key,
|
||||
times = times or 0,
|
||||
callback = callback,
|
||||
interval = interval or math.huge,
|
||||
}
|
||||
|
||||
handler.timeout = computer.uptime() + handler.interval
|
||||
|
||||
if not interval then
|
||||
handler.times = math.huge
|
||||
end
|
||||
|
||||
local id = 0
|
||||
repeat
|
||||
id = id + 1
|
||||
until not handlers[id]
|
||||
|
||||
handlers[id] = handler
|
||||
return id
|
||||
end
|
||||
|
||||
local function time_to_nearest()
|
||||
local timeout = math.huge
|
||||
for _,handler in pairs(handlers) do
|
||||
if timeout > handler.timeout then
|
||||
timeout = handler.timeout
|
||||
end
|
||||
for _, callback in ipairs(callbacks()) do
|
||||
if call(callback, signal, ...) == false then
|
||||
event.ignore(signal, callback) -- alternative method of removing a listener
|
||||
end
|
||||
return timeout
|
||||
end
|
||||
|
||||
local function dispatch(...)
|
||||
local signal = (...)
|
||||
local eligable = {}
|
||||
local ids_to_remove = {}
|
||||
local time = computer.uptime()
|
||||
for id, handler in pairs(handlers) do
|
||||
-- timers have false keys
|
||||
-- nil keys match anything
|
||||
local key = handler.key
|
||||
key = (key == nil and signal) or key
|
||||
if key == signal or time >= handler.timeout then
|
||||
|
||||
-- push ticks to end of list (might be slightly faster to fire them last)
|
||||
table.insert(eligable, select(handler.key and 1 or 2, 1, {handler.callback, id}))
|
||||
|
||||
handler.times = handler.times - 1
|
||||
handler.timeout = computer.uptime() + handler.interval
|
||||
if handler.times <= 0 then
|
||||
table.insert(ids_to_remove, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,pack in ipairs(eligable) do
|
||||
if call(pack[1], ...) == false then
|
||||
table.insert(ids_to_remove, pack[2])
|
||||
end
|
||||
end
|
||||
for _,id in ipairs(ids_to_remove) do
|
||||
handlers[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function tick()
|
||||
local function elapsed()
|
||||
local list = {}
|
||||
for id, timer in pairs(timers) do
|
||||
if timer.after <= computer.uptime() then
|
||||
table.insert(list, timer.callback)
|
||||
timer.times = timer.times - 1
|
||||
if timer.times <= 0 then
|
||||
timers[id] = nil
|
||||
else
|
||||
timer.after = computer.uptime() + timer.interval
|
||||
end
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
for _, callback in ipairs(elapsed()) do
|
||||
call(callback)
|
||||
end
|
||||
local _pullSignal = computer.pullSignal
|
||||
computer.pullSignal = function(...)
|
||||
return (function(...)
|
||||
dispatch(...)
|
||||
return ...
|
||||
end)(_pullSignal(...))
|
||||
end
|
||||
|
||||
local function createPlainFilter(name, ...)
|
||||
@ -94,8 +133,8 @@ end
|
||||
|
||||
function event.cancel(timerId)
|
||||
checkArg(1, timerId, "number")
|
||||
if timers[timerId] then
|
||||
timers[timerId] = nil
|
||||
if handlers[timerId] then
|
||||
handlers[timerId] = nil
|
||||
return true
|
||||
end
|
||||
return false
|
||||
@ -104,15 +143,10 @@ end
|
||||
function event.ignore(name, callback)
|
||||
checkArg(1, name, "string")
|
||||
checkArg(2, callback, "function")
|
||||
if listeners[name] then
|
||||
for i = 1, #listeners[name] do
|
||||
if listeners[name][i] == callback then
|
||||
table.remove(listeners[name], i)
|
||||
if #listeners[name] == 0 then
|
||||
listeners[name] = nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
for id, handler in pairs(handlers) do
|
||||
if handler.key == name and handler.callback == callback then
|
||||
handlers[id] = nil
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
@ -121,17 +155,12 @@ end
|
||||
function event.listen(name, callback)
|
||||
checkArg(1, name, "string")
|
||||
checkArg(2, callback, "function")
|
||||
if listeners[name] then
|
||||
for i = 1, #listeners[name] do
|
||||
if listeners[name][i] == callback then
|
||||
return false
|
||||
end
|
||||
for id, handler in pairs(handlers) do
|
||||
if handler.key == name and handler.callback == callback then
|
||||
return false
|
||||
end
|
||||
else
|
||||
listeners[name] = {}
|
||||
end
|
||||
table.insert(listeners[name], callback)
|
||||
return true
|
||||
return event.register(name, callback, nil, nil)
|
||||
end
|
||||
|
||||
function event.onError(message)
|
||||
@ -169,7 +198,6 @@ function event.pullMultiple(...)
|
||||
end
|
||||
end
|
||||
return event.pullFiltered(seconds, createMultipleFilter(table.unpack(args, 1, args.n)))
|
||||
|
||||
end
|
||||
|
||||
function event.pullFiltered(...)
|
||||
@ -187,15 +215,8 @@ function event.pullFiltered(...)
|
||||
|
||||
local deadline = seconds and (computer.uptime() + seconds) or math.huge
|
||||
repeat
|
||||
local closest = deadline
|
||||
for _, timer in pairs(timers) do
|
||||
closest = math.min(closest, timer.after)
|
||||
end
|
||||
local closest = math.min(deadline, time_to_nearest())
|
||||
local signal = table.pack(computer.pullSignal(closest - computer.uptime()))
|
||||
if signal.n > 0 then
|
||||
dispatch(table.unpack(signal, 1, signal.n))
|
||||
end
|
||||
tick()
|
||||
if event.shouldInterrupt() then
|
||||
lastInterrupt = computer.uptime()
|
||||
error("interrupted", 0)
|
||||
@ -230,20 +251,12 @@ function event.timer(interval, callback, times)
|
||||
checkArg(1, interval, "number")
|
||||
checkArg(2, callback, "function")
|
||||
checkArg(3, times, "number", "nil")
|
||||
local id
|
||||
repeat
|
||||
id = math.floor(math.random(1, 0x7FFFFFFF))
|
||||
until not timers[id]
|
||||
timers[id] = {
|
||||
interval = interval,
|
||||
after = computer.uptime() + interval,
|
||||
callback = callback,
|
||||
times = times or 1
|
||||
}
|
||||
return id
|
||||
return event.register(false, callback, interval, times)
|
||||
end
|
||||
|
||||
-- users may expect to find event.push to exist
|
||||
event.push = computer.pushSignal
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
return event
|
||||
|
@ -137,7 +137,7 @@ function require(module)
|
||||
error(table.concat(errorMsg, "\n"), 2)
|
||||
end
|
||||
else
|
||||
error("already loading: " .. module .. debug.traceback(), 2)
|
||||
error("already loading: " .. module .. "\n" .. debug.traceback(), 2)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -126,13 +126,13 @@ function plib.internal.redirectRead(pm)
|
||||
return reader
|
||||
end
|
||||
|
||||
function plib.internal.create(fp)
|
||||
function plib.internal.create(fp, init, name)
|
||||
local _co = process.info().data.coroutine_handler
|
||||
|
||||
local pco = setmetatable(
|
||||
{
|
||||
stack = {},
|
||||
next = nil,
|
||||
next = false,
|
||||
create = _co.create,
|
||||
wrap = _co.wrap,
|
||||
previous_handler = _co
|
||||
@ -161,19 +161,19 @@ function plib.internal.create(fp)
|
||||
return _co.yield(...)
|
||||
end
|
||||
function pco.set_unwind(from)
|
||||
pco.next = nil
|
||||
pco.next = false
|
||||
if from then
|
||||
local index = pco.index_of(from)
|
||||
if index then
|
||||
pco.stack = tx.sub(pco.stack, 1, index-1)
|
||||
pco.next = pco.stack[index-1]
|
||||
pco.next = pco.stack[index-1] or false
|
||||
end
|
||||
end
|
||||
end
|
||||
function pco.resume_all(...)
|
||||
local base = pco.stack[1]
|
||||
local top = pco.top()
|
||||
if type(base) ~= "thread" or _co.status(base) ~= "suspended" or
|
||||
if type(base) ~= "thread" or _co.status(base) ~= "suspended" or
|
||||
type(top) ~= "thread" or _co.status(top) ~= "suspended" then
|
||||
return false
|
||||
end
|
||||
@ -193,7 +193,7 @@ function plib.internal.create(fp)
|
||||
checkArg(1, thread, "thread")
|
||||
local status = pco.status(thread)
|
||||
if status ~= "suspended" then
|
||||
local msg = string.format("cannot resume %s coroutine",
|
||||
local msg = string.format("cannot resume %s coroutine",
|
||||
status == "dead" and "dead" or "non-suspended")
|
||||
return false, msg
|
||||
end
|
||||
@ -212,7 +212,7 @@ function plib.internal.create(fp)
|
||||
return true, _co.yield(...) -- pass args to resume next
|
||||
else
|
||||
-- the stack is not running
|
||||
pco.next = nil
|
||||
pco.next = false
|
||||
local yield_args = table.pack(_co.resume(thread, ...))
|
||||
if #pco.stack > 0 then
|
||||
-- thread may have crashed (crash unwinds as well)
|
||||
@ -226,7 +226,7 @@ function plib.internal.create(fp)
|
||||
-- in such a case, yield out first, then resume where we left off
|
||||
if pco.next and pco.next ~= thread then
|
||||
local next = pco.next
|
||||
pco.next = nil
|
||||
pco.next = false
|
||||
return pco.resume(next, table.unpack(yield_args,2,yield_args.n))
|
||||
end
|
||||
end
|
||||
@ -251,7 +251,7 @@ function plib.internal.create(fp)
|
||||
end
|
||||
|
||||
if fp then
|
||||
pco.stack = {process.load(fp,nil,nil--[[init]],"pco root")}
|
||||
pco.stack = {process.load(fp,nil,init,name or "pco root")}
|
||||
process.info(pco.stack[1]).data.coroutine_handler = pco
|
||||
end
|
||||
|
||||
@ -320,7 +320,7 @@ function pipeManager.new(prog, mode, env)
|
||||
)
|
||||
pm.prog_id = pm.mode == "r" and 1 or 2
|
||||
pm.self_id = pm.mode == "r" and 2 or 1
|
||||
pm.handler = pm.mode == "r" and
|
||||
pm.handler = pm.mode == "r" and
|
||||
function()return pipeManager.reader(pm)end or
|
||||
function()pm.dead=true end
|
||||
|
||||
@ -329,10 +329,8 @@ function pipeManager.new(prog, mode, env)
|
||||
pm.commands[pm.self_id] = {pm.handler, {}}
|
||||
|
||||
pm.root = function()
|
||||
local startup_args = {}
|
||||
|
||||
local reason
|
||||
pm.threads, reason = sh.internal.createThreads(pm.commands, {}, pm.env)
|
||||
pm.threads, reason = sh.internal.createThreads(pm.commands, pm.env, {[pm.prog_id]=table.pack(pm.env,pm.prog)})
|
||||
|
||||
if not pm.threads then
|
||||
pm.dead = true
|
||||
@ -340,8 +338,7 @@ function pipeManager.new(prog, mode, env)
|
||||
end
|
||||
|
||||
pm.pipe = process.info(pm.threads[1]).data.io[1]
|
||||
process.info(pm.threads[pm.prog_id]).data.args = {pm.env,pm.prog}
|
||||
|
||||
|
||||
-- if we are the writer, we need args to resume prog
|
||||
if pm.mode == "w" then
|
||||
pm.pipe.stream.redirect[0] = plib.internal.redirectRead(pm)
|
||||
@ -365,7 +362,7 @@ function plib.popen(prog, mode, env)
|
||||
end
|
||||
|
||||
pm.pco=plib.internal.create(pm.root)
|
||||
|
||||
|
||||
local pfd = require("buffer").new(mode, pipeStream.new(pm))
|
||||
pfd:setvbuf("no", 0) -- 2nd are to read chunk size
|
||||
|
||||
|
@ -20,6 +20,12 @@ function process.findProcess(co)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
local function parent_data(pre, tbl, k, ...)
|
||||
if tbl and k then
|
||||
return parent_data(pre, tbl[k], ...)
|
||||
end
|
||||
return setmetatable(pre, {__index=tbl})
|
||||
end
|
||||
|
||||
function process.load(path, env, init, name)
|
||||
checkArg(1, path, "string", "function")
|
||||
@ -28,6 +34,7 @@ function process.load(path, env, init, name)
|
||||
checkArg(4, name, "string", "nil")
|
||||
|
||||
assert(type(path) == "string" or env == nil, "process cannot load function environemnts")
|
||||
name = name or ""
|
||||
|
||||
local p = process.findProcess()
|
||||
if p then
|
||||
@ -37,31 +44,42 @@ function process.load(path, env, init, name)
|
||||
|
||||
local code = nil
|
||||
if type(path) == 'string' then
|
||||
local f, reason = io.open(path)
|
||||
if not f then
|
||||
return nil, reason
|
||||
end
|
||||
local reason
|
||||
if f:read(2) == "#!" then
|
||||
local command = f:read()
|
||||
if require("text").trim(command) == "" then
|
||||
reason = "no exec command"
|
||||
else
|
||||
code = function()
|
||||
local result = table.pack(require("shell").execute(command, env, path))
|
||||
if not result[1] then
|
||||
error(result[2], 0)
|
||||
else
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
code = function(...)
|
||||
local fs, shell = require("filesystem"), require("shell")
|
||||
local program, reason = shell.resolve(path, 'lua')
|
||||
if not program then
|
||||
if fs.isDirectory(shell.resolve(path)) then
|
||||
io.stderr:write(path .. ": is a directory\n")
|
||||
return 126
|
||||
end
|
||||
io.stderr:write(path .. ": " .. reason .. "\n")
|
||||
return 127
|
||||
end
|
||||
else
|
||||
code, reason = loadfile(path, "t", env)
|
||||
end
|
||||
f:close()
|
||||
if not code then
|
||||
return nil, reason
|
||||
os.setenv("_", program)
|
||||
local f, reason = io.open(program)
|
||||
if not f then
|
||||
io.stderr:write("could not read '" .. program .. "': " .. tostring(reason) .. "\n")
|
||||
return 1
|
||||
end
|
||||
local shabang = f:read(2)
|
||||
local command = f:read()
|
||||
f:close()
|
||||
if shabang == "#!" then
|
||||
if require("text").trim(command or "") == "" then
|
||||
return -- nothing to do
|
||||
end
|
||||
local result = table.pack(require("shell").execute(command, env, program, ...))
|
||||
if not result[1] then
|
||||
error(result[2])
|
||||
end
|
||||
return table.unpack(result)
|
||||
end
|
||||
command, reason = loadfile(program, "bt", env)
|
||||
if not command then
|
||||
io.stderr:write(program..(reason or ""):gsub("^[^:]*", "").."\n")
|
||||
return 128
|
||||
end
|
||||
return command(...)
|
||||
end
|
||||
else -- path is code
|
||||
code = path
|
||||
@ -69,43 +87,46 @@ function process.load(path, env, init, name)
|
||||
|
||||
local thread = nil
|
||||
thread = coroutine.create(function(...)
|
||||
if init then
|
||||
init()
|
||||
end
|
||||
-- pcall code so that we can remove it from the process list on exit
|
||||
local result =
|
||||
local result =
|
||||
{
|
||||
xpcall(code, function(msg)
|
||||
if type(msg) == 'table' then return msg end
|
||||
local stack = debug.traceback():gsub('^([^\n]*\n)[^\n]*\n[^\n]*\n','%1')
|
||||
return string.format('%s:\n%s', msg or '', stack)
|
||||
end, ...)
|
||||
xpcall(function(...)
|
||||
os.setenv("_", name)
|
||||
init = init or function(...) return ... end
|
||||
return code(init(...))
|
||||
end,
|
||||
function(msg)
|
||||
-- msg can be a custom error object
|
||||
if type(msg) == 'table' then
|
||||
if msg.reason ~= "terminated" then
|
||||
io.stderr:write(msg.reason.."\n")
|
||||
end
|
||||
return msg.code
|
||||
end
|
||||
local stack = debug.traceback():gsub('^([^\n]*\n)[^\n]*\n[^\n]*\n','%1')
|
||||
io.stderr:write(string.format('%s:\n%s', msg or '', stack))
|
||||
return 128 -- syserr
|
||||
end, ...)
|
||||
}
|
||||
process.internal.close(thread)
|
||||
if not result[1] then
|
||||
-- msg can be a custom error object
|
||||
local msg = result[2]
|
||||
if type(msg) == 'table' then
|
||||
if msg.reason~="terminated" then error(msg.reason,2) end
|
||||
result={0,msg.code}
|
||||
else
|
||||
error(msg,2)
|
||||
end
|
||||
--result[1] is false if the exception handler also crashed
|
||||
if not result[1] and type(result[2]) ~= "number" then
|
||||
require("event").onError(string.format("process library exception handler crashed: %s", tostring(result[2])))
|
||||
end
|
||||
return select(2,table.unpack(result))
|
||||
end,true)
|
||||
return select(2, table.unpack(result))
|
||||
end, true)
|
||||
process.list[thread] = {
|
||||
path = path,
|
||||
command = name,
|
||||
env = env,
|
||||
data = setmetatable(
|
||||
data = parent_data(
|
||||
{
|
||||
handles = {},
|
||||
io = setmetatable({}, {__index=p and p.data and p.data.io or nil}),
|
||||
coroutine_handler = setmetatable({}, {__index=p and p.data and p.data.coroutine_handler or nil}),
|
||||
}, {__index=p and p.data or nil}),
|
||||
io = parent_data({}, p, "data", "io"),
|
||||
coroutine_handler = parent_data({}, p, "data", "coroutine_handler"),
|
||||
}, p, "data"),
|
||||
parent = p,
|
||||
instances = setmetatable({}, {__mode="v"})
|
||||
instances = setmetatable({}, {__mode="v"}),
|
||||
}
|
||||
return thread
|
||||
end
|
||||
|
@ -23,7 +23,6 @@ local local_env = {event=event,fs=fs,process=process,shell=shell,term=term,text=
|
||||
sh.internal.globbers = {{"*",".*"},{"?","."}}
|
||||
sh.internal.ec = {}
|
||||
sh.internal.ec.parseCommand = 127
|
||||
sh.internal.ec.sysError = 128
|
||||
sh.internal.ec.last = 0
|
||||
|
||||
function sh.getLastExitCode()
|
||||
@ -211,26 +210,20 @@ function sh.internal.parseCommand(words)
|
||||
if #words == 0 then
|
||||
return nil
|
||||
end
|
||||
local evaluated_words = {}
|
||||
-- evaluated words
|
||||
local ewords = {}
|
||||
-- the arguments have < or > which require parsing for redirection
|
||||
local has_tokens
|
||||
for i=1,#words do
|
||||
for _, arg in ipairs(sh.internal.evaluate(words[i])) do
|
||||
table.insert(evaluated_words, arg)
|
||||
table.insert(ewords, arg)
|
||||
has_tokens = has_tokens or arg:find("[<>]")
|
||||
end
|
||||
end
|
||||
local eword = evaluated_words[1]
|
||||
local possible_dir_path = shell.resolve(eword)
|
||||
if possible_dir_path and fs.isDirectory(possible_dir_path) then
|
||||
return nil, string.format("%s: is a directory", eword)
|
||||
end
|
||||
local program, reason = shell.resolve(eword, "lua")
|
||||
if not program then
|
||||
return nil, eword .. ": " .. reason
|
||||
end
|
||||
evaluated_words = tx.sub(evaluated_words, 2)
|
||||
return program, evaluated_words
|
||||
return table.remove(ewords, 1), ewords, has_tokens
|
||||
end
|
||||
|
||||
function sh.internal.createThreads(commands, eargs, env)
|
||||
function sh.internal.createThreads(commands, env, start_args)
|
||||
-- Piping data between programs works like so:
|
||||
-- program1 gets its output replaced with our custom stream.
|
||||
-- program2 gets its input replaced with our custom stream.
|
||||
@ -240,14 +233,23 @@ function sh.internal.createThreads(commands, eargs, env)
|
||||
-- custom stream may have "redirect" entries for fallback/duplication.
|
||||
local threads = {}
|
||||
for i = 1, #commands do
|
||||
local program, args = table.unpack(commands[i])
|
||||
local program, c_args, c_has_tokens = table.unpack(commands[i])
|
||||
local name, thread = tostring(program)
|
||||
local thread_env = type(program) == "string" and env or nil
|
||||
local thread, reason = process.load(program, thread_env, function()
|
||||
os.setenv("_", name)
|
||||
local thread, reason = process.load(program, thread_env, function(...)
|
||||
local cdata = process.info().data.command
|
||||
local args, has_tokens, start_args = cdata.args, cdata.has_tokens, cdata.start_args
|
||||
if has_tokens then
|
||||
args = sh.internal.buildCommandRedirects(args)
|
||||
end
|
||||
|
||||
sh.internal.concatn(args, start_args)
|
||||
sh.internal.concatn(args, {...}, select('#', ...))
|
||||
|
||||
-- popen expects each process to first write an empty string
|
||||
-- this is required for proper thread order
|
||||
io.write('')
|
||||
io.write("")
|
||||
return table.unpack(args, 1, args.n or #args)
|
||||
end, name)
|
||||
|
||||
threads[i] = thread
|
||||
@ -259,25 +261,20 @@ function sh.internal.createThreads(commands, eargs, env)
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
process.info(thread).data.args = args
|
||||
local pdata = process.info(thread).data
|
||||
pdata.command =
|
||||
{
|
||||
args = c_args,
|
||||
has_tokens = c_has_tokens,
|
||||
start_args = start_args and start_args[i] or {}
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
if #threads > 1 then
|
||||
sh.internal.buildPipeChain(threads)
|
||||
end
|
||||
|
||||
for i = 1, #threads do
|
||||
local thread = threads[i]
|
||||
local args = process.info(thread).data.args
|
||||
|
||||
-- smart check if ios should be loaded
|
||||
if tx.first(args, function(token) return token == "<" or token:find(">") end) then
|
||||
args, reason = sh.internal.buildCommandRedirects(thread, args)
|
||||
end
|
||||
|
||||
process.info(thread).data.args = tx.concat(args, eargs or {})
|
||||
end
|
||||
|
||||
return threads
|
||||
end
|
||||
|
||||
@ -285,25 +282,19 @@ function sh.internal.runThreads(threads)
|
||||
local result = {}
|
||||
for i = 1, #threads do
|
||||
-- Emulate CC behavior by making yields a filtered event.pull()
|
||||
local thread, args = threads[i]
|
||||
local thread, args = threads[i], {}
|
||||
while coroutine.status(thread) ~= "dead" do
|
||||
args = args or process.info(thread).data.args
|
||||
result = table.pack(coroutine.resume(thread, table.unpack(args)))
|
||||
if coroutine.status(thread) ~= "dead" then
|
||||
args = sh.internal.handleThreadYield(result)
|
||||
-- in case this was the end of the line, args is returned
|
||||
result = args
|
||||
if table.remove(args, 1) then
|
||||
break
|
||||
-- in case this was the end of the line, args is returned
|
||||
return args[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
if not result[1] then
|
||||
sh.internal.handleThreadCrash(thread, result)
|
||||
break
|
||||
end
|
||||
end
|
||||
return table.unpack(result)
|
||||
return result[2]
|
||||
end
|
||||
|
||||
function sh.internal.executePipes(pipe_parts, eargs, env)
|
||||
@ -318,23 +309,12 @@ function sh.internal.executePipes(pipe_parts, eargs, env)
|
||||
return sh.internal.ec.parseCommand
|
||||
end
|
||||
end
|
||||
local threads, reason = sh.internal.createThreads(commands, eargs, env)
|
||||
local threads, reason = sh.internal.createThreads(commands, env, {[#commands]=eargs})
|
||||
if not threads then
|
||||
io.stderr:write(reason,"\n")
|
||||
return false
|
||||
end
|
||||
local result, cmd_result = sh.internal.runThreads(threads)
|
||||
|
||||
if not result then
|
||||
if cmd_result then
|
||||
if type(cmd_result) == "string" then
|
||||
cmd_result = cmd_result:gsub("^/lib/process%.lua:%d+: /", '/')
|
||||
end
|
||||
io.stderr:write(tostring(cmd_result),"\n")
|
||||
end
|
||||
return sh.internal.ec.sysError
|
||||
end
|
||||
return cmd_result
|
||||
return sh.internal.runThreads(threads)
|
||||
end
|
||||
|
||||
function sh.execute(env, command, ...)
|
||||
@ -347,7 +327,8 @@ function sh.execute(env, command, ...)
|
||||
return true, 0
|
||||
end
|
||||
|
||||
local eargs = {...}
|
||||
-- MUST be table.pack for non contiguous ...
|
||||
local eargs = table.pack(...)
|
||||
|
||||
-- simple
|
||||
if reason then
|
||||
@ -358,6 +339,15 @@ function sh.execute(env, command, ...)
|
||||
return sh.internal.execute_complex(statements, eargs, env)
|
||||
end
|
||||
|
||||
function sh.internal.concatn(apack, bpack, bn)
|
||||
local an = (apack.n or #apack)
|
||||
bn = bn or bpack.n or #bpack
|
||||
for i=1,bn do
|
||||
apack[an + i] = bpack[i]
|
||||
end
|
||||
apack.n = an + bn
|
||||
end
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.handleThreadYield(result)
|
||||
local action = result[2]
|
||||
if action == nil or type(action) == "number" then
|
||||
@ -367,20 +357,7 @@ function --[[@delayloaded-start@]] sh.internal.handleThreadYield(result)
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.handleThreadCrash(thread, result)
|
||||
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||
if result[2].code then
|
||||
result[1] = true
|
||||
result.n = 1
|
||||
else
|
||||
result[2] = "terminated"
|
||||
end
|
||||
elseif type(result[2]) == "string" then
|
||||
result[2] = debug.traceback(thread, result[2])
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.buildCommandRedirects(thread, args)
|
||||
function --[[@delayloaded-start@]] sh.internal.buildCommandRedirects(args, thread)
|
||||
local data = process.info(thread).data
|
||||
local tokens, ios, handles = args, data.io, data.handles
|
||||
args = {}
|
||||
@ -406,7 +383,7 @@ function --[[@delayloaded-start@]] sh.internal.buildCommandRedirects(thread, arg
|
||||
else
|
||||
local file, reason = io.open(shell.resolve(token), mode)
|
||||
if not file then
|
||||
return nil, "could not open '" .. token .. "': " .. reason
|
||||
error("could not open '" .. token .. "': " .. reason)
|
||||
end
|
||||
table.insert(handles, file)
|
||||
ios[from_io] = file
|
||||
@ -447,7 +424,6 @@ function --[[@delayloaded-start@]] sh.internal.buildPipeChain(threads)
|
||||
|
||||
prev_pipe = pipe
|
||||
end
|
||||
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern)
|
||||
@ -825,7 +801,7 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
-- if that is the case, it is important to leave stream.buffer alone
|
||||
return self.redirect[0]:read(n)
|
||||
elseif self.buffer == "" then
|
||||
process.info(self.next).data.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
||||
coroutine.yield()
|
||||
end
|
||||
local result = string.sub(self.buffer, 1, n)
|
||||
self.buffer = string.sub(self.buffer, n + 1)
|
||||
@ -842,15 +818,13 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
return self.redirect[1]:write(value)
|
||||
elseif not self.closed then
|
||||
self.buffer = self.buffer .. value
|
||||
local args = process.info(self.next).data.args
|
||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(args)))
|
||||
local result = table.pack(coroutine.resume(self.next))
|
||||
if coroutine.status(self.next) == "dead" then
|
||||
self:close()
|
||||
end
|
||||
if not self.result[1] then
|
||||
error(self.result[2], 0)
|
||||
if not result[1] then
|
||||
error(result[2], 0)
|
||||
end
|
||||
table.remove(self.result, 1)
|
||||
return self
|
||||
end
|
||||
os.exit(0) -- abort the current process: SIGPIPE
|
||||
|
@ -3,6 +3,7 @@ local event = require("event")
|
||||
local process = require("process")
|
||||
local kb = require("keyboard")
|
||||
local component = require("component")
|
||||
local computer = require("computer")
|
||||
local keys = kb.keys
|
||||
|
||||
local term = {}
|
||||
@ -35,8 +36,11 @@ end
|
||||
function term.setViewport(w,h,dx,dy,x,y,window)
|
||||
window = window or W()
|
||||
|
||||
local gw,gh = window.gpu.getViewport()
|
||||
w,h,dx,dy,x,y = w or gw,h or gh,dx or 0,dy or 0,x or 1,y or 1
|
||||
dx,dy,x,y = dx or 0,dy or 0,x or 1,y or 1
|
||||
if not w or not h then
|
||||
local gw,gh = window.gpu.getViewport()
|
||||
w,h = w or gw, h or gh
|
||||
end
|
||||
|
||||
window.w,window.h,window.dx,window.dy,window.x,window.y,window.gw,window.gh=
|
||||
w,h,dx,dy,x,y, gw, gh
|
||||
@ -60,57 +64,79 @@ function term.isAvailable(w)
|
||||
return w and not not (w.gpu and w.gpu.getScreen())
|
||||
end
|
||||
|
||||
function term.internal.pull(input, c, off, t, ...)
|
||||
t=t or math.huge
|
||||
if t < 0 then return end
|
||||
local w,unpack=W(),table.unpack
|
||||
local d,h,dx,dy,x,y=term.getViewport(w)
|
||||
function term.internal.pull(input, timeout, ...)
|
||||
timeout = timeout or math.huge
|
||||
|
||||
local w = W()
|
||||
local d, h, dx, dy, x, y = term.getViewport(w)
|
||||
local out = (x<1 or x>d or y<1 or y>h)
|
||||
|
||||
if input and out then
|
||||
input:move(0)
|
||||
y=w.y
|
||||
y = w.y
|
||||
input:scroll()
|
||||
end
|
||||
x,y=w.x+dx,w.y+dy
|
||||
local gpu
|
||||
|
||||
if input or not out then
|
||||
gpu=w.gpu
|
||||
local sf,sb=gpu.setForeground,gpu.setBackground
|
||||
c=c or {{gpu.getBackground()},{gpu.getForeground()},gpu.get(x,y)}
|
||||
local c11,c12 = unpack(c[1])
|
||||
local c21,c22 = unpack(c[2])
|
||||
-- c can fail if gpu does not have a screen
|
||||
-- if can happen during a type of race condition when a screen is removed
|
||||
if not c11 then
|
||||
x, y = w.x + dx, w.y + dy
|
||||
local gpu = (input or not out) and w.gpu
|
||||
|
||||
local bgColor, bgIsPalette
|
||||
local fgColor, fgIsPalette
|
||||
local char_at_cursor
|
||||
local blinking
|
||||
if gpu then
|
||||
bgColor, bgIsPalette = gpu.getBackground()
|
||||
-- it can happen during a type of race condition when a screen is removed
|
||||
if not bgColor then
|
||||
return nil, "interrupted"
|
||||
end
|
||||
if not off then
|
||||
sf(c11,c12)
|
||||
sb(c21,c22)
|
||||
|
||||
fgColor, fgIsPalette = gpu.getForeground()
|
||||
char_at_cursor = gpu.get(x, y)
|
||||
|
||||
blinking = w.blink
|
||||
if input then
|
||||
blinking = input.blink
|
||||
end
|
||||
gpu.set(x,y,c[3])
|
||||
sb(c11,c12)
|
||||
sf(c21,c22)
|
||||
end
|
||||
|
||||
local a={event.pull(math.min(t,0.5),...)}
|
||||
|
||||
if #a>1 or t<.5 then
|
||||
-- get the next event
|
||||
local blinked = false
|
||||
local done = false
|
||||
local signal
|
||||
while true do
|
||||
if gpu then
|
||||
gpu.set(x,y,c[3])
|
||||
if not blinked and not done then
|
||||
gpu.setForeground(bgColor, bgIsPalette)
|
||||
gpu.setBackground(fgColor, fgIsPalette)
|
||||
gpu.set(x, y, char_at_cursor)
|
||||
gpu.setForeground(fgColor, fgIsPalette)
|
||||
gpu.setBackground(bgColor, bgIsPalette)
|
||||
blinked = true
|
||||
elseif blinked then
|
||||
gpu.set(x, y, char_at_cursor)
|
||||
blinked = false
|
||||
end
|
||||
end
|
||||
return unpack(a)
|
||||
|
||||
if done then
|
||||
return table.unpack(signal, 1, signal.n)
|
||||
end
|
||||
|
||||
signal = table.pack(event.pull(math.min(.5, timeout), ...))
|
||||
timeout = timeout - .5
|
||||
done = signal.n > 1 or timeout < .5
|
||||
end
|
||||
local blinking = w.blink
|
||||
if input then blinking = input.blink end
|
||||
return term.internal.pull(input,c,blinking and not off,t-0.5,...)
|
||||
end
|
||||
|
||||
function term.pull(p,...)
|
||||
local a,t = {p,...}
|
||||
if type(p) == "number" then t = table.remove(a,1) end
|
||||
return term.internal.pull(nil,nil,nil,t,table.unpack(a))
|
||||
function term.pull(...)
|
||||
local args = table.pack(...)
|
||||
local timeout = nil
|
||||
if type(args[1]) == "number" then
|
||||
timeout = table.remove(args, 1)
|
||||
args.n = args.n - 1
|
||||
end
|
||||
return term.internal.pull(nil, timeout, table.unpack(args, 1, args.n))
|
||||
end
|
||||
|
||||
function term.read(history,dobreak,hintHandler,pwchar,filter)
|
||||
@ -582,7 +608,7 @@ function --[[@delayloaded-start@]] term.internal.build_horizontal_reader(input)
|
||||
_.index,_.data,win.x=0,"",px
|
||||
gpu.fill(px+dx,y+dy,w-px+1-dx,1," ")
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] term.clearLine(window)
|
||||
window = window or W()
|
||||
@ -627,7 +653,7 @@ function --[[@delayloaded-start@]] term.internal.tab(input,hints)
|
||||
input:update(next)
|
||||
input:move(-tail)
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] term.getGlobalArea(window)
|
||||
local w,h,dx,dy = term.getViewport(window)
|
||||
@ -645,6 +671,6 @@ function --[[@delayloaded-start@]] term.internal.clipboard(char)
|
||||
char = char:sub(1, first_line - 1)
|
||||
end
|
||||
return char
|
||||
end --[[@delayloaded-end@]]
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
return term, local_env
|
||||
|
@ -0,0 +1,146 @@
|
||||
-- called from /init.lua
|
||||
local raw_loadfile = ...
|
||||
|
||||
_G._OSVERSION = "OpenOS 1.6"
|
||||
|
||||
local component = component
|
||||
local computer = computer
|
||||
local unicode = unicode
|
||||
|
||||
-- Runlevel information.
|
||||
local runlevel, shutdown = "S", computer.shutdown
|
||||
computer.runlevel = function() return runlevel end
|
||||
computer.shutdown = function(reboot)
|
||||
runlevel = reboot and 6 or 0
|
||||
if os.sleep then
|
||||
computer.pushSignal("shutdown")
|
||||
os.sleep(0.1) -- Allow shutdown processing.
|
||||
end
|
||||
shutdown(reboot)
|
||||
end
|
||||
|
||||
local screen = component.list('screen', true)()
|
||||
for address in component.list('screen', true) do
|
||||
if #component.invoke(address, 'getKeyboards') > 0 then
|
||||
screen = address
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
_G.boot_screen = screen
|
||||
|
||||
-- Report boot progress if possible.
|
||||
local gpu = component.list("gpu", true)()
|
||||
local w, h
|
||||
if gpu and screen then
|
||||
component.invoke(gpu, "bind", screen)
|
||||
w, h = component.invoke(gpu, "maxResolution")
|
||||
component.invoke(gpu, "setResolution", w, h)
|
||||
component.invoke(gpu, "setBackground", 0x000000)
|
||||
component.invoke(gpu, "setForeground", 0xFFFFFF)
|
||||
component.invoke(gpu, "fill", 1, 1, w, h, " ")
|
||||
end
|
||||
local y = 1
|
||||
local function status(msg)
|
||||
if gpu and screen then
|
||||
component.invoke(gpu, "set", 1, y, msg)
|
||||
if y == h then
|
||||
component.invoke(gpu, "copy", 1, 2, w, h - 1, 0, -1)
|
||||
component.invoke(gpu, "fill", 1, h, w, 1, " ")
|
||||
else
|
||||
y = y + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
status("Booting " .. _OSVERSION .. "...")
|
||||
|
||||
-- Custom low-level dofile implementation reading from our ROM.
|
||||
local loadfile = function(file)
|
||||
status("> " .. file)
|
||||
return raw_loadfile(file)
|
||||
end
|
||||
|
||||
local function dofile(file)
|
||||
local program, reason = loadfile(file)
|
||||
if program then
|
||||
local result = table.pack(pcall(program))
|
||||
if result[1] then
|
||||
return table.unpack(result, 2, result.n)
|
||||
else
|
||||
error(result[2])
|
||||
end
|
||||
else
|
||||
error(reason)
|
||||
end
|
||||
end
|
||||
|
||||
status("Initializing package management...")
|
||||
|
||||
-- Load file system related libraries we need to load other stuff moree
|
||||
-- comfortably. This is basically wrapper stuff for the file streams
|
||||
-- provided by the filesystem components.
|
||||
local package = dofile("/lib/package.lua")
|
||||
|
||||
do
|
||||
-- Unclutter global namespace now that we have the package module.
|
||||
_G.component = nil
|
||||
_G.computer = nil
|
||||
_G.process = nil
|
||||
_G.unicode = nil
|
||||
|
||||
-- Initialize the package module with some of our own APIs.
|
||||
package.loaded.component = component
|
||||
package.loaded.computer = computer
|
||||
package.loaded.unicode = unicode
|
||||
package.preload["buffer"] = loadfile("/lib/buffer.lua")
|
||||
package.preload["filesystem"] = loadfile("/lib/filesystem.lua")
|
||||
|
||||
-- Inject the package and io modules into the global namespace, as in Lua.
|
||||
_G.package = package
|
||||
_G.io = loadfile("/lib/io.lua")()
|
||||
|
||||
--mark modules for delay loaded api
|
||||
package.delayed["text"] = true
|
||||
package.delayed["sh"] = true
|
||||
package.delayed["transforms"] = true
|
||||
package.delayed["term"] = true
|
||||
end
|
||||
|
||||
status("Initializing file system...")
|
||||
|
||||
-- Mount the ROM and temporary file systems to allow working on the file
|
||||
-- system module from this point on.
|
||||
require("filesystem").mount(computer.getBootAddress(), "/")
|
||||
package.preload={}
|
||||
|
||||
status("Running boot scripts...")
|
||||
|
||||
-- Run library startup scripts. These mostly initialize event handlers.
|
||||
local function rom_invoke(method, ...)
|
||||
return component.invoke(computer.getBootAddress(), method, ...)
|
||||
end
|
||||
|
||||
local scripts = {}
|
||||
for _, file in ipairs(rom_invoke("list", "boot")) do
|
||||
local path = "boot/" .. file
|
||||
if not rom_invoke("isDirectory", path) then
|
||||
table.insert(scripts, path)
|
||||
end
|
||||
end
|
||||
table.sort(scripts)
|
||||
for i = 1, #scripts do
|
||||
dofile(scripts[i])
|
||||
end
|
||||
|
||||
status("Initializing components...")
|
||||
|
||||
for c, t in component.list() do
|
||||
computer.pushSignal("component_added", c, t)
|
||||
end
|
||||
|
||||
status("Initializing system...")
|
||||
|
||||
computer.pushSignal("init") -- so libs know components are initialized.
|
||||
require("event").pull(1, "init") -- Allow init processing.
|
||||
_G.runlevel = 1
|
@ -1,15 +1,10 @@
|
||||
local comp = require("component")
|
||||
local text = require("text")
|
||||
local devfs = ...
|
||||
|
||||
return
|
||||
{
|
||||
open = function(mode)
|
||||
if ({r=true, rb=true})[mode] then
|
||||
return text.internal.reader(comp.eeprom.getData())
|
||||
end
|
||||
return text.internal.writer(comp.eeprom.setData, ({a=true,ab=true})[mode] and comp.eeprom.getData())
|
||||
end,
|
||||
size = function()
|
||||
return string.len(comp.eeprom.getData())
|
||||
end
|
||||
}
|
||||
-- eeprom get/set has to delayed because comp.eeprom may not be available
|
||||
local node = devfs.new_callback_proxy(function() return comp.eeprom.getData() end, function(...) comp.eeprom.setData(...) end)
|
||||
function node.isAvailable()
|
||||
return comp.list("eeprom", true)()
|
||||
end
|
||||
|
||||
return node
|
||||
|
@ -1,15 +1,10 @@
|
||||
local comp = require("component")
|
||||
local text = require("text")
|
||||
local devfs = ...
|
||||
|
||||
return
|
||||
{
|
||||
open = function(mode)
|
||||
if ({r=true, rb=true})[mode] then
|
||||
return text.internal.reader(comp.eeprom.get())
|
||||
end
|
||||
return text.internal.writer(comp.eeprom.set, ({a=true,ab=true})[mode] and comp.eeprom.get())
|
||||
end,
|
||||
size = function()
|
||||
return string.len(comp.eeprom.get())
|
||||
end
|
||||
}
|
||||
-- eeprom get/set has to delayed because comp.eeprom may not be available
|
||||
local node = devfs.new_callback_proxy(function() return comp.eeprom.get() end, function(...) comp.eeprom.set(...) end)
|
||||
function node.isAvailable()
|
||||
return comp.list("eeprom", true)()
|
||||
end
|
||||
|
||||
return node
|
||||
|
@ -162,7 +162,7 @@ end
|
||||
local function wide(n,i)
|
||||
local t = _isLink(n,i) and 'l' or _isDir(n,i) and 'd' or 'f'
|
||||
local link_target = _isLink(n,i) and
|
||||
string.format(" -> %s",_linkPath(n,i)..(_isDir(n,i)and"/"or""))or""
|
||||
string.format(" -> %s", _linkPath(n, i):gsub("/+$", "") .. (_isDir(n, i) and "/" or "")) or ""
|
||||
local w = fs.get(_fullPath(n,i)).isReadOnly() and '-' or 'w'
|
||||
local size = formatSize(_size(n,i))
|
||||
local modDate = formatDate(_time(n,i))
|
||||
|
@ -1 +1,3 @@
|
||||
os.execute(install.from.."/oppm.lua")
|
||||
require("shell").setWorkingDirectory(install.from)
|
||||
os.execute("oppm.lua")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user