OpenOS 1.6.3 update (#2414)

Why a version change?
> This minor version change should not introduce breaking changes, but older code doing non standard things with process environments, `require` and the package library, shebang redirections, or direct calls to /bin/lua, may see some breakages. Also, `shell.resolve` has been fully reworked. This is a crucial and heavily used api. Thus, in case of any mistakes or bugs a version change will help pinpoint regressions. I've heavily tested the resolve code, but it is new code and deserves some bake time in the wild.

Changelog:
/bin/lua removes shebang line and calls load directly. Improves workflows that define custom environments, and loadfile no longer removes shebang lines

load: loaded code chunks now inherit the parent _ENV naturally, having a real-lua behavior

require: heavily optimized and made errors more natural and helpful, exposing more information about failure to require a library

/lib/pipes: commented out pipes.create which is a future feature to create event-boxed threads

process: more process crash text is dumped to the shell to help identify "out of memory" issues

shell.resolve: reworked and optimized! possibly breaking change: specifying an extension (ext) to `shell.resolve(name, ext)` will no longer return results to directories, but only files (if they exist). However, it was never the intent of this method to return results to directories when specifying an extension

various memory optimizations throughout. openos now allocates ~153k to boot to shell.
This commit is contained in:
payonel 2017-06-02 13:33:45 -07:00 committed by GitHub
parent f297eefd7d
commit 06a0861597
15 changed files with 119 additions and 190 deletions

View File

@ -5,13 +5,23 @@ if #args == 0 then
args = {"/opt/core/lua_shell.lua"} args = {"/opt/core/lua_shell.lua"}
end end
local script, reason = loadfile(args[1], nil, setmetatable({},{__index=_ENV})) local filename = args[1]
local buffer, script, reason
buffer = io.lines(filename, "*a")()
if buffer then
buffer = buffer:gsub("^#![^\n]+", "") -- remove shebang if any
script, reason = load(buffer, "="..filename)
else
reason = string.format("could not open %s for reading", filename)
end
if not script then if not script then
io.stderr:write(tostring(reason) .. "\n") io.stderr:write(tostring(reason) .. "\n")
os.exit(false) os.exit(false)
end end
local result, reason = pcall(script, table.unpack(args, 2))
if not result then buffer, reason = pcall(script, table.unpack(args, 2))
if not buffer then
io.stderr:write(reason, "\n") io.stderr:write(reason, "\n")
os.exit(false) os.exit(false)
end end

View File

@ -1,4 +1,4 @@
function loadfile(filename, mode, env) function loadfile(filename, ...)
if filename:sub(1,1) ~= "/" then if filename:sub(1,1) ~= "/" then
filename = (os.getenv("PWD") or "/") .. "/" .. filename filename = (os.getenv("PWD") or "/") .. "/" .. filename
end end
@ -16,11 +16,9 @@ function loadfile(filename, mode, env)
end end
break break
end end
table.insert(buffer, data) buffer[#buffer + 1] = data
end end
buffer[1] = (buffer[1] or ""):gsub("^#![^\n]+", "") -- remove shebang if any return load(table.concat(buffer), "=" .. filename, ...)
buffer = table.concat(buffer)
return load(buffer, "=" .. filename, mode, env)
end end
function dofile(filename) function dofile(filename)
@ -35,12 +33,10 @@ function print(...)
local args = table.pack(...) local args = table.pack(...)
local stdout = io.stdout local stdout = io.stdout
stdout:setvbuf("line") stdout:setvbuf("line")
local pre = ""
for i = 1, args.n do for i = 1, args.n do
local arg = tostring(args[i]) stdout:write(pre, tostring(args[i]))
if i > 1 then pre = "\t"
arg = "\t" .. arg
end
stdout:write(arg)
end end
stdout:write("\n") stdout:write("\n")
stdout:setvbuf("no") stdout:setvbuf("no")

View File

@ -27,10 +27,18 @@ local kernel_load = _G.load
local intercept_load local intercept_load
intercept_load = function(source, label, mode, env) intercept_load = function(source, label, mode, env)
if env then if env then
env.load = kernel_load([[ local loader = setmetatable(
local source, label, mode, env = ... {
return load(source, label, mode, env or fenv) env = env,
]], "=load", "t", {fenv=env, load=intercept_load}) load = intercept_load,
},
{__call = function(tbl, _source, _label, _mode, _env)
return tbl.load(_source, _label, _mode, _env or tbl.env)
end})
if env.load and (type(env.load) ~= "table" or env.load.load ~= intercept_load) then
loader.load = env.load
end
env.load = loader
end end
return kernel_load(source, label, mode, env or process.info().env) return kernel_load(source, label, mode, env or process.info().env)
end end

View File

@ -2,11 +2,7 @@ local computer = require("computer")
local event = require("event") local event = require("event")
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell") local shell = require("shell")
local process = require("process") local info = require("process").info
local function env()
return process.info().data.vars
end
os.execute = function(command) os.execute = function(command)
if not command then if not command then
@ -20,7 +16,7 @@ function os.exit(code)
end end
function os.getenv(varname) function os.getenv(varname)
local env = env() local env = info().data.vars
if not varname then if not varname then
return env return env
elseif varname == '#' then elseif varname == '#' then
@ -34,7 +30,7 @@ function os.setenv(varname, value)
if value ~= nil then if value ~= nil then
value = tostring(value) value = tostring(value)
end end
env()[varname] = value info().data.vars[varname] = value
return value return value
end end

View File

@ -1,11 +1,6 @@
local buffer = require("buffer") local buffer = require("buffer")
local tty = require("tty") local tty = require("tty")
local io_open = io.open
function io.open(path, mode)
return io_open(require("shell").resolve(path), mode)
end
local stdinStream = {handle="stdin"} local stdinStream = {handle="stdin"}
local stdoutStream = {handle="stdout"} local stdoutStream = {handle="stdout"}
local stderrStream = {handle="stderr"} local stderrStream = {handle="stderr"}
@ -15,13 +10,13 @@ local function badFileDescriptor()
return nil, "bad file descriptor" return nil, "bad file descriptor"
end end
function stdinStream:close() function stdinStream.close()
return nil, "cannot close standard file" return nil, "cannot close standard file"
end end
stdoutStream.close = stdinStream.close stdoutStream.close = stdinStream.close
stderrStream.close = stdinStream.close stderrStream.close = stdinStream.close
function stdinStream:read() function stdinStream.read()
return tty.read(stdinHistory) return tty.read(stdinHistory)
end end

View File

@ -1,5 +1,3 @@
#!/bin/lua
local component = require("component") local component = require("component")
local computer = require("computer") local computer = require("computer")
local text = require("text") local text = require("text")

View File

@ -36,7 +36,8 @@ end
function io.open(path, mode) function io.open(path, mode)
-- These requires are not on top because this is a bootstrapped file. -- These requires are not on top because this is a bootstrapped file.
local stream, result = require("filesystem").open(path, mode) local resolved_path = require("shell").resolve(path)
local stream, result = require("filesystem").open(resolved_path, mode)
if stream then if stream then
return require("buffer").new(mode, stream) return require("buffer").new(mode, stream)
else else

View File

@ -1,5 +1,3 @@
local component = require("component")
local keyboard = {pressedChars = {}, pressedCodes = {}} local keyboard = {pressedChars = {}, pressedCodes = {}}
-- these key definitions are only a subset of all the defined keys -- these key definitions are only a subset of all the defined keys
@ -32,16 +30,6 @@ keyboard.keys = {
numpadenter = 0x9C, numpadenter = 0x9C,
} }
-- Create inverse mapping for name lookup.
setmetatable(keyboard.keys,
{
__index = function(tbl, k)
getmetatable(keyboard.keys).__index = nil -- to be safe
loadfile(package.searchpath("tools/keyboard_full", package.path), "t", setmetatable({keyboard=keyboard},{__index=_G}))()
return tbl[k]
end
})
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local function getKeyboardAddress(address) local function getKeyboardAddress(address)
@ -94,4 +82,13 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
setmetatable(keyboard.keys,
{
__index = function(tbl, key)
setmetatable(tbl, nil)
dofile("/opt/core/full_keyboard.lua")
return tbl[key] -- some keyboard keys are handled by __index by design
end
})
return keyboard return keyboard

View File

@ -16,11 +16,6 @@ local loaded = {
} }
package.loaded = loaded package.loaded = loaded
local preload = {}
package.preload = preload
package.searchers = {}
function package.searchpath(name, path, sep, rep) function package.searchpath(name, path, sep, rep)
checkArg(1, name, "string") checkArg(1, name, "string")
checkArg(2, path, "string") checkArg(2, path, "string")
@ -47,67 +42,28 @@ function package.searchpath(name, path, sep, rep)
return nil, table.concat(errorFiles, "\n") return nil, table.concat(errorFiles, "\n")
end end
local function preloadSearcher(module)
if preload[module] ~= nil then
return preload[module]
else
return "\tno field package.preload['" .. module .. "']"
end
end
local function pathSearcher(module)
local filepath, reason = package.searchpath(module, package.path)
if filepath then
local loader, reason = loadfile(filepath, "bt", _G)
if loader then
return loader, filepath
else
return reason
end
else
return reason
end
end
table.insert(package.searchers, preloadSearcher)
table.insert(package.searchers, pathSearcher)
function require(module) function require(module)
checkArg(1, module, "string") checkArg(1, module, "string")
if loaded[module] ~= nil then if loaded[module] ~= nil then
return loaded[module] return loaded[module]
elseif not loading[module] then elseif not loading[module] then
loading[module] = true local library, status, step
local loader, value, errorMsg = nil, nil, {"module '" .. module .. "' not found:"}
for i = 1, #package.searchers do step, library, status = "not found", package.searchpath(module, package.path)
-- the pcall is mostly for out of memory errors
local ok, f, extra = pcall(package.searchers[i], module) if library then
if not ok then step, library, status = "loadfile failed", loadfile(library)
table.insert(errorMsg, "\t" .. (f or "nil"))
elseif f and type(f) ~= "string" then
loader = f
value = extra
break
elseif f then
table.insert(errorMsg, f)
end
end end
if loader then
local success, result = pcall(loader, module, value) if library then
loading[module] = true
step, library, status = "load failed", pcall(library, module)
loading[module] = false loading[module] = false
if not success then
error(result, 2)
end
if result then
loaded[module] = result
elseif not loaded[module] then
loaded[module] = true
end
return loaded[module]
else
loading[module] = false
error(table.concat(errorMsg, "\n"), 2)
end end
assert(library, string.format("module '%s' %s:\n%s", module, step, status))
loaded[module] = status
return status
else else
error("already loading: " .. module .. "\n" .. debug.traceback(), 2) error("already loading: " .. module .. "\n" .. debug.traceback(), 2)
end end

View File

@ -371,11 +371,14 @@ function plib.popen(prog, mode, env)
return pfd return pfd
end end
function plib.create(fp, name) -- function plib.create(fp, name)
checkArg(1, fp, "function") -- checkArg(1, fp, "function")
checkArg(2, name, "string", "nil") -- checkArg(2, name, "string", "nil")
local pco = plib.internal.create(fp, nil, name)
return pco.stack[1] -- local pco = plib.internal.create(fp, function()end, name)
end -- -- process.info(pco.stack[1]).data.event = nil
-- return pco.stack[1]
-- end
return plib return plib

View File

@ -32,10 +32,10 @@ function process.load(path, env, init, name)
local p = process.findProcess() local p = process.findProcess()
env = env or p.env env = env or p.env
local code local code
if type(path) == 'string' then if type(path) == "string" then
code = function(...) code = function(...)
local fs, shell = require("filesystem"), require("shell") local fs, shell = require("filesystem"), require("shell")
local program, reason = shell.resolve(path, 'lua') local program, reason = shell.resolve(path, "lua")
if not program then if not program then
if fs.isDirectory(shell.resolve(path)) then if fs.isDirectory(shell.resolve(path)) then
io.stderr:write(path .. ": is a directory\n") io.stderr:write(path .. ": is a directory\n")
@ -48,7 +48,7 @@ function process.load(path, env, init, name)
os.setenv("_", program) os.setenv("_", program)
local f = fs.open(program) local f = fs.open(program)
if f then if f then
local shebang = f:read(1024):match("^#!([^\n]+)") local shebang = (f:read(1024) or ""):match("^#!([^\n]+)")
f:close() f:close()
if shebang then if shebang then
local result = table.pack(shell.execute(shebang:gsub("%s",""), env, program, ...)) local result = table.pack(shell.execute(shebang:gsub("%s",""), env, program, ...))
@ -59,7 +59,7 @@ function process.load(path, env, init, name)
local command local command
command, reason = loadfile(program, "bt", env) command, reason = loadfile(program, "bt", env)
if not command then if not command then
io.stderr:write(program..(reason or ""):gsub("^[^:]*", "").."\n") io.stderr:write(program, " ", reason or "", "\n")
return 128 return 128
end end
return command(...) return command(...)
@ -80,14 +80,14 @@ function process.load(path, env, init, name)
end, end,
function(msg) function(msg)
-- msg can be a custom error object -- msg can be a custom error object
if type(msg) == 'table' then if type(msg) == "table" then
if msg.reason ~= "terminated" then if msg.reason ~= "terminated" then
io.stderr:write(msg.reason.."\n") io.stderr:write(msg.reason.."\n")
end end
return msg.code or 0 return msg.code or 0
end end
local stack = debug.traceback():gsub('^([^\n]*\n)[^\n]*\n[^\n]*\n','%1') local stack = debug.traceback():gsub("^([^\n]*\n)[^\n]*\n[^\n]*\n","%1")
io.stderr:write(string.format('%s:\n%s', msg or '', stack)) io.stderr:write(string.format("%s:\n%s", msg or "", stack))
return 128 -- syserr return 128 -- syserr
end, ...) end, ...)
} }
@ -107,13 +107,11 @@ function process.load(path, env, init, name)
{ {
handles = {}, handles = {},
io = {}, io = {},
coroutine_handler = {}
}, },
parent = p, parent = p,
instances = setmetatable({}, {__mode="v"}), instances = setmetatable({}, {__mode="v"}),
} }
setmetatable(new_proc.data.io, {__index=p.data.io}) setmetatable(new_proc.data.io, {__index=p.data.io})
setmetatable(new_proc.data.coroutine_handler, {__index=p.data.coroutine_handler})
setmetatable(new_proc.data, {__index=p.data}) setmetatable(new_proc.data, {__index=p.data})
process.list[thread] = new_proc process.list[thread] = new_proc

View File

@ -246,12 +246,12 @@ function sh.internal.createThreads(commands, env, start_args)
local thread_env = type(program) == "string" and env or nil local thread_env = type(program) == "string" and env or nil
local thread, reason = process.load(program, thread_env, function(...) local thread, reason = process.load(program, thread_env, function(...)
local cdata = process.info().data.command local cdata = process.info().data.command
local args, has_tokens, start_args = cdata.args, cdata.has_tokens, cdata.start_args local args, has_tokens, c_start_args = cdata.args, cdata.has_tokens, cdata.start_args
if has_tokens then if has_tokens then
args = sh.internal.buildCommandRedirects(args) args = sh.internal.buildCommandRedirects(args)
end end
sh.internal.concatn(args, start_args) sh.internal.concatn(args, c_start_args)
sh.internal.concatn(args, {...}, select('#', ...)) sh.internal.concatn(args, {...}, select('#', ...))
-- popen expects each process to first write an empty string -- popen expects each process to first write an empty string

View File

@ -18,60 +18,13 @@ function shell.getShell()
if shells[shellName] then if shells[shellName] then
return shells[shellName] return shells[shellName]
end end
local sh, reason = loadfile(shellName, "t") local sh, reason = loadfile(shellName)
if sh then if sh then
shells[shellName] = sh shells[shellName] = sh
end end
return sh, reason return sh, reason
end end
local function findFile(name, ext)
checkArg(1, name, "string")
local function findIn(dir)
if dir:sub(1, 1) ~= "/" then
dir = shell.resolve(dir)
end
dir = fs.concat(fs.concat(dir, name), "..")
local name = fs.name(name)
local list = fs.list(dir)
if list and name then
local files = {}
for file in list do
files[file] = true
end
if ext and name:sub(-(1 + ext:len())) == "." .. ext then
-- Name already contains extension, prioritize.
if files[name] then
return true, fs.concat(dir, name)
end
elseif files[name] then
-- Check exact name.
return true, fs.concat(dir, name)
elseif ext then
-- Check name with automatially added extension.
local name = name .. "." .. ext
if files[name] then
return true, fs.concat(dir, name)
end
end
end
return false
end
if name:sub(1, 1) == "/" then
local found, where = findIn("/")
if found then return where end
elseif name:sub(1, 2) == "./" then
local found, where = findIn(shell.getWorkingDirectory())
if found then return where end
else
for path in string.gmatch(shell.getPath(), "[^:]+") do
local found, where = findIn(path)
if found then return where end
end
end
return false
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function shell.prime() function shell.prime()
@ -133,21 +86,37 @@ function shell.setPath(value)
end end
function shell.resolve(path, ext) function shell.resolve(path, ext)
if ext then checkArg(1, path, "string")
local dir = path
if dir:find("/") ~= 1 then
dir = fs.concat(shell.getWorkingDirectory(), dir)
end
local name = fs.name(path)
dir = fs[name and "path" or "canonical"](dir)
local fullname = fs.concat(dir, name or "")
if not ext then
return fullname
elseif name then
checkArg(2, ext, "string") checkArg(2, ext, "string")
local where = findFile(path, ext) -- search for name in PATH if no dir was given
if where then -- no dir was given if path has no /
return where local search_in = path:find("/") and dir or shell.getPath()
else for search_path in string.gmatch(search_in, "[^:]+") do
return nil, "file not found" -- resolve search_path because they may be relative
end local search_name = fs.concat(shell.resolve(search_path), name)
else if not fs.exists(search_name) then
if path:sub(1, 1) == "/" then search_name = search_name .. "." .. ext
return fs.canonical(path) end
else -- extensions are provided when the caller is looking for a file
return fs.concat(shell.getWorkingDirectory(), path) if fs.exists(search_name) and not fs.isDirectory(search_name) then
return search_name
end
end end
end end
return nil, "file not found"
end end
function shell.execute(command, env, ...) function shell.execute(command, env, ...)
@ -179,11 +148,11 @@ function shell.parse(...)
if param == "--" then if param == "--" then
doneWithOptions = true -- stop processing options at `--` doneWithOptions = true -- stop processing options at `--`
elseif param:sub(1, 2) == "--" then elseif param:sub(1, 2) == "--" then
if param:match("%-%-(.-)=") ~= nil then local key, value = param:match("%-%-(.-)=(.*)")
options[param:match("%-%-(.-)=")] = param:match("=(.*)") if not key then
else key, value = param:sub(3), true
options[param:sub(3)] = true
end end
options[key] = value
elseif param:sub(1, 1) == "-" and param ~= "-" then elseif param:sub(1, 1) == "-" and param ~= "-" then
for j = 2, unicode.len(param) do for j = 2, unicode.len(param) do
options[unicode.sub(param, j, j)] = true options[unicode.sub(param, j, j)] = true

View File

@ -1,7 +1,7 @@
-- called from /init.lua -- called from /init.lua
local raw_loadfile = ... local raw_loadfile = ...
_G._OSVERSION = "OpenOS 1.6.2" _G._OSVERSION = "OpenOS 1.6.3"
local component = component local component = component
local computer = computer local computer = computer
@ -96,11 +96,11 @@ do
package.loaded.component = component package.loaded.component = component
package.loaded.computer = computer package.loaded.computer = computer
package.loaded.unicode = unicode package.loaded.unicode = unicode
package.preload["buffer"] = loadfile("/lib/buffer.lua") package.loaded.buffer = assert(loadfile("/lib/buffer.lua"))()
package.preload["filesystem"] = loadfile("/lib/filesystem.lua") package.loaded.filesystem = assert(loadfile("/lib/filesystem.lua"))()
-- Inject the io modules -- Inject the io modules
_G.io = loadfile("/lib/io.lua")() _G.io = assert(loadfile("/lib/io.lua"))()
end end
status("Initializing file system...") status("Initializing file system...")

View File

@ -1,3 +1,5 @@
local keyboard = require("keyboard")
keyboard.keys["1"] = 0x02 keyboard.keys["1"] = 0x02
keyboard.keys["2"] = 0x03 keyboard.keys["2"] = 0x03
keyboard.keys["3"] = 0x04 keyboard.keys["3"] = 0x04
@ -132,7 +134,7 @@ setmetatable(keyboard.keys,
{ {
__index = function(tbl, k) __index = function(tbl, k)
if type(k) ~= "number" then return end if type(k) ~= "number" then return end
for name,value in pairs(keyboard.keys) do for name,value in pairs(tbl.keys) do
if value == k then if value == k then
return name return name
end end