From 06a0861597d403c64b51e614e1e041fddeb7023b Mon Sep 17 00:00:00 2001 From: payonel Date: Fri, 2 Jun 2017 13:33:45 -0700 Subject: [PATCH] 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. --- .../opencomputers/loot/openos/bin/lua.lua | 16 ++- .../loot/openos/boot/00_base.lua | 16 ++- .../loot/openos/boot/01_process.lua | 16 ++- .../opencomputers/loot/openos/boot/02_os.lua | 10 +- .../opencomputers/loot/openos/boot/03_io.lua | 9 +- .../assets/opencomputers/loot/openos/etc/motd | 2 - .../opencomputers/loot/openos/lib/io.lua | 3 +- .../loot/openos/lib/keyboard.lua | 21 ++-- .../opencomputers/loot/openos/lib/package.lua | 72 +++----------- .../opencomputers/loot/openos/lib/pipes.lua | 15 +-- .../opencomputers/loot/openos/lib/process.lua | 16 ++- .../opencomputers/loot/openos/lib/sh.lua | 4 +- .../opencomputers/loot/openos/lib/shell.lua | 97 +++++++------------ .../loot/openos/opt/core/boot.lua | 8 +- .../core/full_keyboard.lua} | 4 +- 15 files changed, 119 insertions(+), 190 deletions(-) rename src/main/resources/assets/opencomputers/loot/openos/{lib/tools/keyboard_full.lua => opt/core/full_keyboard.lua} (98%) diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua index c6eba31d4..d95547020 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua @@ -5,13 +5,23 @@ if #args == 0 then args = {"/opt/core/lua_shell.lua"} 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 io.stderr:write(tostring(reason) .. "\n") os.exit(false) 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") os.exit(false) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua index ccf951e45..50fe336b7 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua @@ -1,4 +1,4 @@ -function loadfile(filename, mode, env) +function loadfile(filename, ...) if filename:sub(1,1) ~= "/" then filename = (os.getenv("PWD") or "/") .. "/" .. filename end @@ -16,11 +16,9 @@ function loadfile(filename, mode, env) end break end - table.insert(buffer, data) + buffer[#buffer + 1] = data end - buffer[1] = (buffer[1] or ""):gsub("^#![^\n]+", "") -- remove shebang if any - buffer = table.concat(buffer) - return load(buffer, "=" .. filename, mode, env) + return load(table.concat(buffer), "=" .. filename, ...) end function dofile(filename) @@ -35,12 +33,10 @@ function print(...) local args = table.pack(...) local stdout = io.stdout stdout:setvbuf("line") + local pre = "" for i = 1, args.n do - local arg = tostring(args[i]) - if i > 1 then - arg = "\t" .. arg - end - stdout:write(arg) + stdout:write(pre, tostring(args[i])) + pre = "\t" end stdout:write("\n") stdout:setvbuf("no") diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua index 4656a4bca..b514508bd 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua @@ -27,10 +27,18 @@ local kernel_load = _G.load local intercept_load intercept_load = function(source, label, mode, env) if env then - env.load = kernel_load([[ - local source, label, mode, env = ... - return load(source, label, mode, env or fenv) - ]], "=load", "t", {fenv=env, load=intercept_load}) + local loader = setmetatable( + { + env = env, + 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 return kernel_load(source, label, mode, env or process.info().env) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua index f9c6acf00..015a225ca 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua @@ -2,11 +2,7 @@ local computer = require("computer") local event = require("event") local fs = require("filesystem") local shell = require("shell") -local process = require("process") - -local function env() - return process.info().data.vars -end +local info = require("process").info os.execute = function(command) if not command then @@ -20,7 +16,7 @@ function os.exit(code) end function os.getenv(varname) - local env = env() + local env = info().data.vars if not varname then return env elseif varname == '#' then @@ -34,7 +30,7 @@ function os.setenv(varname, value) if value ~= nil then value = tostring(value) end - env()[varname] = value + info().data.vars[varname] = value return value end diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua index bf637e580..e6c46dd45 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua @@ -1,11 +1,6 @@ local buffer = require("buffer") 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 stdoutStream = {handle="stdout"} local stderrStream = {handle="stderr"} @@ -15,13 +10,13 @@ local function badFileDescriptor() return nil, "bad file descriptor" end -function stdinStream:close() +function stdinStream.close() return nil, "cannot close standard file" end stdoutStream.close = stdinStream.close stderrStream.close = stdinStream.close -function stdinStream:read() +function stdinStream.read() return tty.read(stdinHistory) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/etc/motd b/src/main/resources/assets/opencomputers/loot/openos/etc/motd index 4a839de7e..85ec4759f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/etc/motd +++ b/src/main/resources/assets/opencomputers/loot/openos/etc/motd @@ -1,5 +1,3 @@ -#!/bin/lua - local component = require("component") local computer = require("computer") local text = require("text") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua index 525f40af9..306ce1874 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua @@ -36,7 +36,8 @@ end function io.open(path, mode) -- 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 return require("buffer").new(mode, stream) else diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua index b7112f1dd..f77d5c3d8 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua @@ -1,5 +1,3 @@ -local component = require("component") - local keyboard = {pressedChars = {}, pressedCodes = {}} -- these key definitions are only a subset of all the defined keys @@ -32,16 +30,6 @@ keyboard.keys = { 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) @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua index 10670c257..732213568 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua @@ -16,11 +16,6 @@ local loaded = { } package.loaded = loaded -local preload = {} -package.preload = preload - -package.searchers = {} - function package.searchpath(name, path, sep, rep) checkArg(1, name, "string") checkArg(2, path, "string") @@ -47,67 +42,28 @@ function package.searchpath(name, path, sep, rep) return nil, table.concat(errorFiles, "\n") 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) checkArg(1, module, "string") if loaded[module] ~= nil then return loaded[module] elseif not loading[module] then - loading[module] = true - local loader, value, errorMsg = nil, nil, {"module '" .. module .. "' not found:"} - for i = 1, #package.searchers do - -- the pcall is mostly for out of memory errors - local ok, f, extra = pcall(package.searchers[i], module) - if not ok then - 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 + local library, status, step + + step, library, status = "not found", package.searchpath(module, package.path) + + if library then + step, library, status = "loadfile failed", loadfile(library) 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 - 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 + + assert(library, string.format("module '%s' %s:\n%s", module, step, status)) + loaded[module] = status + return status else error("already loading: " .. module .. "\n" .. debug.traceback(), 2) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/pipes.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/pipes.lua index 0fe5c919f..cffda718e 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/pipes.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/pipes.lua @@ -371,11 +371,14 @@ function plib.popen(prog, mode, env) return pfd end -function plib.create(fp, name) - checkArg(1, fp, "function") - checkArg(2, name, "string", "nil") - local pco = plib.internal.create(fp, nil, name) - return pco.stack[1] -end +-- function plib.create(fp, name) +-- checkArg(1, fp, "function") +-- checkArg(2, name, "string", "nil") + +-- local pco = plib.internal.create(fp, function()end, name) +-- -- process.info(pco.stack[1]).data.event = nil + +-- return pco.stack[1] +-- end return plib diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua index 5a51ada9d..1f19a0b4a 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua @@ -32,10 +32,10 @@ function process.load(path, env, init, name) local p = process.findProcess() env = env or p.env local code - if type(path) == 'string' then + if type(path) == "string" then code = function(...) 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 fs.isDirectory(shell.resolve(path)) then io.stderr:write(path .. ": is a directory\n") @@ -48,7 +48,7 @@ function process.load(path, env, init, name) os.setenv("_", program) local f = fs.open(program) if f then - local shebang = f:read(1024):match("^#!([^\n]+)") + local shebang = (f:read(1024) or ""):match("^#!([^\n]+)") f:close() if shebang then local result = table.pack(shell.execute(shebang:gsub("%s",""), env, program, ...)) @@ -59,7 +59,7 @@ function process.load(path, env, init, name) local command command, reason = loadfile(program, "bt", env) if not command then - io.stderr:write(program..(reason or ""):gsub("^[^:]*", "").."\n") + io.stderr:write(program, " ", reason or "", "\n") return 128 end return command(...) @@ -80,14 +80,14 @@ function process.load(path, env, init, name) end, function(msg) -- msg can be a custom error object - if type(msg) == 'table' then + if type(msg) == "table" then if msg.reason ~= "terminated" then io.stderr:write(msg.reason.."\n") end return msg.code or 0 end - local stack = debug.traceback():gsub('^([^\n]*\n)[^\n]*\n[^\n]*\n','%1') - io.stderr:write(string.format('%s:\n%s', msg or '', stack)) + 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, ...) } @@ -107,13 +107,11 @@ function process.load(path, env, init, name) { handles = {}, io = {}, - coroutine_handler = {} }, parent = p, instances = setmetatable({}, {__mode="v"}), } 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}) process.list[thread] = new_proc diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua index f18b68d45..9f6e6257e 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua @@ -246,12 +246,12 @@ function sh.internal.createThreads(commands, env, start_args) local thread_env = type(program) == "string" and env or nil 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 + local args, has_tokens, c_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, c_start_args) sh.internal.concatn(args, {...}, select('#', ...)) -- popen expects each process to first write an empty string diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua index e38d651cc..feac9acff 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua @@ -18,60 +18,13 @@ function shell.getShell() if shells[shellName] then return shells[shellName] end - local sh, reason = loadfile(shellName, "t") + local sh, reason = loadfile(shellName) if sh then shells[shellName] = sh end return sh, reason 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() @@ -133,21 +86,37 @@ function shell.setPath(value) end 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") - local where = findFile(path, ext) - if where then - return where - else - return nil, "file not found" - end - else - if path:sub(1, 1) == "/" then - return fs.canonical(path) - else - return fs.concat(shell.getWorkingDirectory(), path) + -- search for name in PATH if no dir was given + -- no dir was given if path has no / + local search_in = path:find("/") and dir or shell.getPath() + for search_path in string.gmatch(search_in, "[^:]+") do + -- resolve search_path because they may be relative + local search_name = fs.concat(shell.resolve(search_path), name) + if not fs.exists(search_name) then + search_name = search_name .. "." .. ext + end + -- extensions are provided when the caller is looking for a file + if fs.exists(search_name) and not fs.isDirectory(search_name) then + return search_name + end end end + + return nil, "file not found" end function shell.execute(command, env, ...) @@ -179,11 +148,11 @@ function shell.parse(...) if param == "--" then doneWithOptions = true -- stop processing options at `--` elseif param:sub(1, 2) == "--" then - if param:match("%-%-(.-)=") ~= nil then - options[param:match("%-%-(.-)=")] = param:match("=(.*)") - else - options[param:sub(3)] = true + local key, value = param:match("%-%-(.-)=(.*)") + if not key then + key, value = param:sub(3), true end + options[key] = value elseif param:sub(1, 1) == "-" and param ~= "-" then for j = 2, unicode.len(param) do options[unicode.sub(param, j, j)] = true diff --git a/src/main/resources/assets/opencomputers/loot/openos/opt/core/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/boot.lua index 7165207e1..7403ad248 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/opt/core/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/boot.lua @@ -1,7 +1,7 @@ -- called from /init.lua local raw_loadfile = ... -_G._OSVERSION = "OpenOS 1.6.2" +_G._OSVERSION = "OpenOS 1.6.3" local component = component local computer = computer @@ -96,11 +96,11 @@ do 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") + package.loaded.buffer = assert(loadfile("/lib/buffer.lua"))() + package.loaded.filesystem = assert(loadfile("/lib/filesystem.lua"))() -- Inject the io modules - _G.io = loadfile("/lib/io.lua")() + _G.io = assert(loadfile("/lib/io.lua"))() end status("Initializing file system...") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/keyboard_full.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_keyboard.lua similarity index 98% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/keyboard_full.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/full_keyboard.lua index 2270420b3..f458c3a4d 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/keyboard_full.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_keyboard.lua @@ -1,3 +1,5 @@ +local keyboard = require("keyboard") + keyboard.keys["1"] = 0x02 keyboard.keys["2"] = 0x03 keyboard.keys["3"] = 0x04 @@ -132,7 +134,7 @@ setmetatable(keyboard.keys, { __index = function(tbl, k) 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 return name end