diff options
author | Thomas Touhey <thomas@touhey.fr> | 2021-06-01 13:15:46 +0200 |
---|---|---|
committer | Thomas Touhey <thomas@touhey.fr> | 2021-06-01 13:15:46 +0200 |
commit | 41a947fa67e111c71c8cfbf81da3b4adcf724363 (patch) | |
tree | 36cbea80b6224fef73071b33afcdc1bcd8f59c34 | |
parent | b143d7969fca33f660254ecc84ed4575db660f5b (diff) |
A few things, still working on the base system.
-rw-r--r-- | docs/system.rst | 1 | ||||
-rw-r--r-- | docs/system/pkgd.rst | 34 | ||||
-rw-r--r-- | docs/system/pkgd/pkgbuild.rst | 66 | ||||
-rw-r--r-- | pkg/core/daemon/pkgbuild.lua | 4 | ||||
-rw-r--r-- | pkg/core/startup-cc/pkgbuild.lua (renamed from pkg/core/startup/pkgbuild.lua) | 7 | ||||
-rw-r--r-- | pkg/core/startup-cc/startup (renamed from pkg/core/startup/startup) | 0 | ||||
-rw-r--r-- | pkg/core/startup-cc/startup.lua | 656 | ||||
-rw-r--r-- | pkg/core/startup/startup.lua | 175 | ||||
-rw-r--r-- | pkg/core/thox/pkgbuild.lua | 2 | ||||
-rw-r--r-- | pkg/core/thox/system.lua | 591 |
10 files changed, 1070 insertions, 466 deletions
diff --git a/docs/system.rst b/docs/system.rst index d47b9f4..1042955 100644 --- a/docs/system.rst +++ b/docs/system.rst @@ -20,6 +20,7 @@ components. system/gpsd system/netd system/rand + system/pkgd .. todo:: diff --git a/docs/system/pkgd.rst b/docs/system/pkgd.rst new file mode 100644 index 0000000..516e141 --- /dev/null +++ b/docs/system/pkgd.rst @@ -0,0 +1,34 @@ +.. _thox-pkgd: + +pkgd: the thox package management daemon +======================================== + +thox manages the system through the filesystem using a dedicated package +manager, simply named pkgd for "package (management) daemon". The goal of +such a software, to the end user, is to: + + * Install features on a per-need basis, with dependencies. + * Keep the system up to date, thanks to a client/server protocol using + HTTP and static files. + +The choice of using a daemon instead of a library comes from the fact that +the system and its files are a shared resource between processes, and thus +accesses and modifications need to be regulated to avoid conflicts and +corruptions. + +.. todo:: + + At the beginning, package management should actually not be managed by + a daemon, but the system should be built statically through a little + utility in order to have the repository managed by a non-thox system. + However, that prevents thox-specific tools being used, which is really + a shame since building thox packages on thox is the end objective; + maybe some kind of simulator can be done? + + Maybe a pacstrap equivalent, which needs not to be used on the system + which interacting is made with, but another thox-based system? Yeah, + chicken & egg problem, I'll have to work on that. + +.. toctree:: + + pkgd/pkgbuild diff --git a/docs/system/pkgd/pkgbuild.rst b/docs/system/pkgd/pkgbuild.rst new file mode 100644 index 0000000..f75c3ea --- /dev/null +++ b/docs/system/pkgd/pkgbuild.rst @@ -0,0 +1,66 @@ +pkgbuild format +=============== + +Source packages include a special Lua file that describes the package and +how to build it, entitled ``pkgbuild.lua``. Their role and format are +inspired by Archlinux's PKGBUILD_ files. + +The following properties should be defined: + +``pkgname`` + The technical name of the source package; should also correspond to + the name of the build package. + +``pkgver`` + The upstream package version, expressed as a string only containing + letters, numbers, dots and underscores. + +``pkgrel`` + The package release number as an integer, relative to the upstream + package version. + +``pkgdesc`` + A short and inline description of the source package. + +``url`` + The URL of the official site of the upstream software being packaged. + +``license`` + The identifier of the license of the software being packaged. + For now, only ``MIT`` is allowed. + +``depends`` + A sequence representing the packages on which the current source package + depends at runtime. + +``source`` + A sequence of the source files or archives being required to build the + package. For now these files must be included with the pkgbuild. + +The following functions should be defined: + +``build(pkgdir, srcdir)`` + The build function, that takes the destination package directory + and the source directory in which source files are available. + + .. todo:: + + Should archives be directly unpacked? + + .. todo:: + + What functions are available to this function? What members? + The following functions should at least be available, in addition + to copies of the pkgbuild globals: + + :: + + fs.combine(path, ...) -- combine paths with separators. + fs.makedirs(path) -- make multiple parent directories. + fs.copy(dest_path, src_path) -- copy a given file. + fs.listdir(path) -- list files and directories. + + And what about packages being required for build? Can they be + represented using out of system builds? (kind of cross compiling) + +.. _PKGBUILD: https://wiki.archlinux.org/title/PKGBUILD diff --git a/pkg/core/daemon/pkgbuild.lua b/pkg/core/daemon/pkgbuild.lua index 5829a1f..f43f1fe 100644 --- a/pkg/core/daemon/pkgbuild.lua +++ b/pkg/core/daemon/pkgbuild.lua @@ -1,5 +1,5 @@ pkgname = "daemon" -pkgver = "2021-03-29-01" +pkgver = "2021032901" pkgrel = 0 pkgdesc = "thox daemon library" url = "https://thox.madefor.cc/libs/daemon.html" @@ -10,7 +10,7 @@ source = { "daemon.lua"} function build(pkgdir, srcdir) - fs.makeDirs(fs.combine(pkgdir, "lib")) + fs.makedirs(fs.combine(pkgdir, "lib")) fs.copy(fs.combine(pkgdir, "lib", "daemon.lua"), fs.combine(srcdir, "daemon.lua")) end diff --git a/pkg/core/startup/pkgbuild.lua b/pkg/core/startup-cc/pkgbuild.lua index 65b9655..bb9c093 100644 --- a/pkg/core/startup/pkgbuild.lua +++ b/pkg/core/startup-cc/pkgbuild.lua @@ -1,11 +1,12 @@ -pkgname = "startup" -pkgver = "2021-03-29-01" +pkgname = "startup-cc" +pkgver = "2021060101" pkgrel = 0 -pkgdesc = "thox startup" +pkgdesc = "thox startup for CC:T computers" url = "https://thox.madefor.cc/system/startup.html" license = "MIT" depends = {} +provides = {"startup"} source = { "startup", "startup.lua"} diff --git a/pkg/core/startup/startup b/pkg/core/startup-cc/startup index 1dcab5b..1dcab5b 100644 --- a/pkg/core/startup/startup +++ b/pkg/core/startup-cc/startup diff --git a/pkg/core/startup-cc/startup.lua b/pkg/core/startup-cc/startup.lua new file mode 100644 index 0000000..6c9451d --- /dev/null +++ b/pkg/core/startup-cc/startup.lua @@ -0,0 +1,656 @@ +-- a simple startup script for booting thox in a standardized environment +-- from the startup ComputerCraft: Tweaked environment. +-- +-- Copyright (C) 2020-2021 Thomas Touhey <thomas@touhey.fr> +-- This file is part of the thox project, which is MIT-licensed. +-- +-- This file contains a basic compatibility layer which reinterprets all of +-- the basic APIs into a peripheral / component logic, for allowing more +-- advanced OSes to take place. +-- +-- For more information about this component, consult the documentation +-- available at <https://thox.madefor.cc/system/startup.html>. +-- +-- Basic variables that should be accessible at all times: +-- +-- * `G`: the exported functions to the final system environment, +-- set using the `__index` member of the environment's variable. +-- * `inals`: functions and other utilities defined by the startup script +-- for itself. +-- * `bios`: the isolated functions available from the basic environment +-- from which the startup script is run. These utilities are either used +-- in the rest that this script defines, or disappear for +-- compatibility [1]. +-- +-- [1] We do not let unsupported utilities filter out to the system in order +-- to avoid systems using machine-specific elements, defeating the purpose +-- of such a script. + +local G = {}, bios = {}, inals = {} + +-- Check if we are in the right environment. Otherwise, there might be a +-- need to change startup script from a rescue environment. + +if string.sub(_HOST, 1, 14) ~= "ComputerCraft " then + print("!!! INVALID ENVIRONMENT FOR COMPUTERCRAFT STARTUP:") + print(" " .. _HOST) + print("!!! PLEASE REBOOT IN RESCUE MODE AND FIX THE STARTUP USED.") + + while true do + sleep(60) + end +end + +-- Start to prepare the `inals` (for "internals") table, and prepare +-- the getupvalue function for getting a function upvalue by name. +-- +-- getupvalue might not be defined if the `debug` module is not available, +-- or the `debug.getupvalue` function is not defined in our current +-- environment; this might be the case when ComputerCraft doesn't enable +-- the debug module. + +inals = { + version = _VERSION, + host = _HOST, + + type = type, + pairs = pairs, + ipairs = ipairs, + setmetatable = setmetatable +} + +if type(debug) == "table" and type(debug.getupvalue) == "function" then + local dbg_getupvalue = debug.getupvalue + + inals.getupvalue = function (func, name) + local i = 1 + + while true do + local e_name, e_val = dbg_getupvalue(func, i) + if e_name == name then + return e_val + elseif e_name == nil then + return nil + end + end + end +end + +-- Reverse some things lost in the native bios (but not everything, +-- unfortunately); for CraftOS bioses, see the following: +-- <https://github.com/SquidDev-CC/CC-Tweaked/blob/b97e950d86e4694409c01e507db34a87da85e452/src/main/resources/data/computercraft/lua/bios.lua> +-- +-- TODO: some Lua 5.1 utilities are definitely lost if the +-- option is enabled in ComputerCraft and we have the default BIOS... +-- what to do in that case? + +if (_VERSION == "Lua 5.1" and type(bios.os.version) == "function" + and inals.getupvalue ~= nil) then + local ver = _VERSION, osver = bios.os.version() + + if osver == "CraftOS 1.8" and _CC_DISABLE_LUA51_FEATURES then + _G.load = inals.getupvalue(load, "nativeload") + _G.loadstring = inals.getupvalue(load, "nativeloadstring") + _G.setfenv = inals.getupvalue(load, "nativesetfenv") + + -- TODO: other functions we want to hack. + end +end + +-- Fill the internals table with other useful functions: +-- +-- * version: the _VERSION field, copied for it to be accessible at all time. +-- * host: same, but for the _HOST field. +-- * pairs, setmetatable: standard functions to be available at all time +-- during the setup. +-- * table.insert, table.remove: standard functions to be available at +-- all time during the setup, equivalent to table.insert and table.remove. +-- * getupvalue: get an upvalue using a name. +-- * print: print a message on the terminal or standard output, depending +-- on the environment we're on, for the user to see. +-- * panic: exit the startup script by shutting down the computer. +-- +-- c_print is currently calibrated for ComputerCraft only. It must be +-- undefined if ComputerCraft is not the current os we're on. +-- +-- panic is CCEmuX-aware: if a CCEmuX host is detected, the emulator is +-- closed instead of just shutting down the computer (which would make you +-- obtain a simple black screen). + +do + inals.math = {} + + if math.isinteger ~= nil then + inals.math.isinteger = math.isinteger + else + local floor = math.floor + + function inals.math.isinteger(n) + return type(n) == "number" and floor(n) == n + end + end +end + +do + -- Table function definitions. + -- We define everything we need here in order not to pollute + -- other code too much. + + inals.table = {} + inals.table.insert = table.insert + inals.table.remove = table.remove + + function inals.table.isSequence(t) + if type(t) ~= "table" then + return false + elseif not inals.math.isinteger(t.n) then + return false + end + + return true + end + + -- Important remark: if `t` is a sequence, it defines the `n` field + -- and all other fields are integers (numbers). + + function inals.table.sequenceContains(t, e) + if type(t) ~= "table" then + return false + end + + for _, value in inals.ipairs(t) do + if value == e then + return true + end + end + + return false + end + + -- This function copies a table with all keys, excluding a given set + -- of values represented as a sequence. This function works best with + -- tables without a metatable; only keys and value will be copied, + -- the source's metatable will not. + + do + local function copyKeysAndValues(d, s, excludedKeys) + for key, value in inals.pairs(s) do + if inals.table.sequenceContains(excludedKeys, key) then + -- We do not copy such a key. + elseif type(value) == "table" then + -- We recreate the table and copy recursively. + -- This will cause metatables to be lost. + + d[key] = {} + copyKeysAndValues(d[key], s[key], excludedKeys) + else + d[key] = s[key] + end + end + end + + inals.table.copyKeysAndValues = copyKeysAndValues + end + + -- Interact with sequences. + + function inals.table.addValuesToSequence(t, es) + for _, value in inals.ipairs(es) do + inals.table.insert(t, value) + end + end + + function inals.table.removeValuesFromSequence(t, es) + for t_key in #t - 1, 1, -1 do + local key = t_key + + for _, name in inals.pairs(es) do + if key == name then + inals.table.remove(t, t_key) + break + end + end + end + end +end + +do + local setBackgroundColor = term.setBackgroundColor + local setTextColor = term.setTextColor + local clear = term.clear + local setCursorPos = term.setCursorPos + + local BLACK = 32768, WHITE = 1 + local YELLOW = 16, RED = 16384 + local last_color = 0 + + local function setColors(fore, back) + if setBackgroundColor ~= nil then + setBackgroundColor(back) + end + if setTextColor ~= nil then + setTextColor(fore) + end + end + + inals.print = function (message, color) + if color == "red" then + color = RED + elseif color == "yellow" then + color = YELLOW + else + color = WHITE + end + + if color ~= last_color then + setColors(color, BLACK) + end + + print(tostring(message)) + end + + clear() + setCursorPos(1, 1) +end + +do + local startTimer = os.startTimer + local pullEvent = os.pullEventRaw + local shutdown = os.shutdown + local print = inals.print + + if type(ccemux) == "table" and type(ccemux.closeEmu) == "function" then + shutdown = ccemux.closeEmu + end + + inals.panic = function () + print("The computer will be shut down in 5 seconds.", "red") + + local id = startTimer(5) + while true do + local e_type, e_id = pullEvent() + if e_type == "timer" and e_id == id then + break + end + end + + shutdown() + end +end + +-- Setup the environment by isolating everything that's non +-- standard in a `bios` module, so that we can prepare our own +-- basic APIs with serenity. +-- +-- For this we copy what's in _G, as we've only encountered BIOSes which +-- set up their functions in _G for now (using it as an __index on the +-- _ENV metatable). + +do + G._ENV = _ENV + G._G = G + G._VERSION = _VERSION + + inals.table.copyKeysAndValues(G, _G, {"_G", "_ENV"}) + inals.G = G +end + +-- And now, we set the internals as the __index for the current environment. +-- From now on, any reference to `inals` should be done directly. + +inals.setmetatable(_ENV, {__index = inals}) + +-- Then copy the standard functions of the corresponding version of Lua. +-- See the reference here: +-- <https://thox.madefor.cc/system/startup/startup-api.html#standard-apis> + +local supported_versions = { + "Lua 5.1" = true, + "Lua 5.2" = true, + "Lua 5.3" = true} + +if not supported_versions[_VERSION] then + print("Unsupported Lua version: " .. _VERSION, "red") + panic() + + -- NOTREACHED +end + +do + local std = {} + + -- Start by listing the standard utilities we want to copy from the + -- main environment. This will depend on the Lua version only. + + do + local std = { + _G = {"assert", "error", "getfenv", "getmetatable", "ipairs", "load", + "loadstring", "next", "pairs", "pcall", "print", "rawequal", + "rawget", "rawset", "select", "setfenv", "setmetatable", + "tonumber", "tostring", "type", "unpack", "xpcall"}, + coroutine = {"create", "resume", "running", "status", "wrap", + "yield"}, + string = {"byte", "char", "dump", "find", "format", "gmatch", + "gsub", "len", "lower", "match", "rep", "reverse", "sub", + "upper"}, + table = {"concat", "insert", "maxn", "remove", "sort"}, + math = {"abs", "acos", "asin", "atan", "atan2", "ceil", "cos", + "cosh", "deg", "exp", "floor", "fmod", "frexp", "huge", + "ldexp", "log", "log10", "max", "min", "modf", "pi", "pow", + "rad", "random", "randomseed", "sin", "sinh", "sqrt", "tan", + "tanh"}, + os = {"clock", "date", "difftime", "time"}, + debug = {"debug", "getfenv", "gethook", "getinfo", "getlocal", + "getmetatable", "getregistry", "getupvalue", "setfenv", + "sethook", "setlocal", "setmetatable", "setupvalue", + "traceback"} + } + + if _VERSION ~= "Lua 5.1" then + -- Lua 5.2 and above. + + removeValuesFromSequence(std._G, {"setfenv", "getfenv", "unpack", + "loadstring"}) + addValuesToSequence(std._G, {"rawlen"}) + + removeValuesFromSequence(std.table, {"maxn"}) + addValuesToSequence(std.table, {"pack", "unpack"}) + + removeValuesFromSequence(std.math, {"log10"}) + + removeValuesFromSequence(std.debug, {"getfenv", "setfenv"}) + addValuesToSequence(std.debug, {"getuservalue", "setuservalue", + "upvalueid", "upvaluejoin"}) + + std.bit32 = {"arshift", "band", "bnot", "bor", "btest", + "bxor", "extract", "replace", "lrotate", "lshift", "rrotate", + "rshift"} + + if _VERSION ~= "Lua 5.2" then + -- Lua 5.3 only. + + addValuesToSequence(std.coroutine, {"isyieldable"}) + addValuesToSequence(std.string, {"pack", "packsize", "unpack"}) + addValuesToSequence(std.table, {"move"}) + removeValuesFromSequence(std.math, {"atan2", "cosh", "frexp", + "ldexp", "pow", "sinh"}) + addValuesToSequence(std.math, {"maxinteger", "mininteger", + "tointeger", "type", "ult"}) + + std.utf8 = {"char", "charpattern", "codes", "codepoint", + "len", "offset"} + std.bit32 = nil + end + end + end + + -- Alright, we have decided on what to pick from the default environment, + -- let's take it from the bios and add it to our _G now. + -- Note that this code does not remove the function from the original + -- `bios` module, just in case. + + do + for module, functions in pairs(std) do + if module == "_G" or type(bios[module]) == "table" then + local src = {} + local dest = {} + + if module == "_G" then + src = bios + dest = G + else + src = bios[module] + G[module] = {} + dest = G[module] + end + + for _, func_name in pairs(functions) do + dest[func_name] = src[func_name] + end + end + end + end + + -- From here, if necessary, we can check if functions expected to be here + -- are present, and if it's not the case, try to replace them with + -- equivalents using other functions. +end + +-- Prepare the machine and peripherals system for ComputerCraft. +-- The main properties of each device, available for all, are the following: +-- +-- * `parent`: a reference to the parent bus on which the device is. +-- Is set to `nil` in case any +-- * `address`: the address on the parent bus. +-- * `ids`: the "manufacturer" and "product name" of the device. The first +-- is usually the name of the mod that brings it, while the product name +-- is a given name for these. +-- * `cls`: TODO +-- +-- TODO: use numerical ids for ids? That would mean this startup script +-- will have to manage correspondances... which is the case anyway? I don't +-- really know... +-- +-- TODO: what about devices that are present on both gateways? Should they +-- be treated as different devices? Be listed as having two buses? + +do + local builtin_peripherals = {} + local events_by_types = {} + + -- The function to create a peripheral. + + local Peripheral = class({ + __init = function (self, parent, address, manufacturer, name, + interfaces) + self.parent = parent + self.address = address + self.manufacturer = manufacturer + self.name = name + self.interfaces = interfaces + end + }) + + -- Add the abstract bus. + + local bus = class(Peripheral, { + __init = function (self) + super(self).__init(nil, nil, "thox", "abstract_bus", + {"bus"}) + end, + + listPeripherals = function (self) + local peripherals = {} + + -- We copy the list of builtin peripherals, we do not want + -- to return a reference to an internal list, otherwise + -- the user could tinker with our internals accidentally. + + for address, peripheral in pairs(builtin_peripherals) do + peripherals[address] = peripheral + end + + return peripherals + end, + + getPeripheral = function (self, address) + return builtin_peripherals[address] + end + })() + + _G.bus = bus + + -- The Timer Management Unit (TMU) peripheral, for setting timers, + -- alarms and stuff. + -- + -- We do not actually support alarms here, but remove their support + -- from bios functions; we prefer timers anyway. + + do + local startTimer = bios.startTimer + local cancelTimer = bios.cancelTimer + + local tmu = class(Peripheral, { + __init = function (self) + super(self).__init(bus, "tmu", "thox", "tmu", {"tmu"}) + end, + + add = function (self, duration) + return startTimer(duration) + end, + + cancel = function (self, id) + return cancelTimer(duration) + end + })() + + builtin_peripherals.tmu = tmu + + bios.os.startTimer = nil + bios.os.cancelTimer = nil + bios.os.setAlarm = nil + bios.os.cancelAlarm = nil + + events_by_types.timer = function (event_name, id) + return tmu, id + end + end + + -- The default display peripheral, for input/output operations. + + if bios.term.isColor() then + + else + local display = Peripheral(bus, "display", "thox", "monochrome_display", + {"display"}, { + -- TODO: take the functions from here: + -- <https://tweaked.cc/module/term.html#ty:Redirect> + + -- TODO: other things. + }) + + builtin_peripherals.display = display + end + + bios.term = nil + + -- The default keyboard and mouse peripheral, for input operations. + + do + local getKeyName = bios.keys.getName + + local kbd = class(Peripheral, { + __init = function (self) + super(self).__init(bus, "keyboard", "thox", "keyboard", + {"keyboard"}) + end + })() + + builtin_peripherals.keyboard = kbd + + events_by_types.key = function (event_name, key_id, is_repeat) + if not is_repeat then + return kbd, "key", true, getKeyName(key_id) + end + end + + events_by_types.key_up = function (event_name, key_id) + return kbd, "key", false, getKeyName(key_id) + end + + events_by_types.paste = function (event_name, text) + return kbd, "paste", text + end + + local mouse_buttons = {"left", "middle", "right"} + + events_by_types.mouse_click = function (event_name, button, x, y) + return kbd, "click", mouse_buttons[button], x, y + end + + events_by_types.mouse_drag = function (event_name, button, x, y) + return kbd, "drag", mouse_buttons[button], x, y + end + + events_by_types.mouse_scroll = function (event_name, up_down, x, y) + return kbd, "scroll", up_down, x, y + end + + events_by_types.mouse_up = function (event_name, button, x, y) + return kbd, "up", mouse_buttons[button], x, y + end + + bios.keys = nil + end + + -- TODO: plenty of things to do here: + -- + -- * Manage modems. + -- * Wired buses (requires to get the peripheral list upvalue...). + -- * Disk drives. + -- * Command modules + -- * etc + -- + -- TODO: monitor_touch events should be "click" events on external + -- monitors, no? I don't actually know if that's a good idea or not. + + -- Setup the event system. + -- The goal of this system is to associate events with their related + -- peripheral as listed previously. + + _G.pull = function () + while true do + local event_data = table.pack(os.pullEventRaw()) + local func = event_by_types[event_data[1]] + + if func then + return func(table.unpack(event_data)) + end + end + end + + bios.os.pullEvent = nil + bios.os.pullEventRaw = nil + bios.os.queueEvent = nil +end + +-- Clean the terminal and show that we're booting up. +-- This is calibrated for ComputerCraft. + +do + local bperipherals = "" + + do + local key, value + + for key, value in pairs(_G.bus.peripherals) do + if bperipherals ~= "" then + bperipherals = bperipherals .. ", " + end + + bperipherals = bperipherals .. key + end + end + + print("thox startup taking place...", "yellow") + print("", "yellow") + print(" VERSION = " .. tostring(_VERSION), "yellow") + print(" HOST = " .. tostring(bios._HOST), "yellow") + print(" BIOS = " .. tostring(bios.os.version()), "yellow") + print("", "yellow") + print("Detected peripherals: " .. bperipherals, "yellow") + print("", "white") +end + +-- Run the system. + +setmetatable(_ENV, {__index = G}) + +--do +-- local handle = bios.fs.open("/system.lua") +-- local system = handle:readAll() +-- +-- handle.close() +-- +-- load(program, "", "t")() +--end diff --git a/pkg/core/startup/startup.lua b/pkg/core/startup/startup.lua deleted file mode 100644 index 591281e..0000000 --- a/pkg/core/startup/startup.lua +++ /dev/null @@ -1,175 +0,0 @@ --- a simple startup script for booting thox in a standardized environment. --- --- Copyright (C) 2020-2021 Thomas Touhey <thomas@touhey.fr> --- This file is part of the thox project, which is MIT-licensed. --- --- This file contains the fundamental loading steps of the OS; --- see <https://thox.touhey.pro/system/processes.html#startup-and-main-loop> --- for more information. --- --- Setup the environment by isolating everything that's non --- standard in a `bios` module. - -do - local G = {} - local pairs = _G.pairs - local setmetatable = _G.setmetatable - - -- Start by copying recursively the _G content in the bios module. - - do - local function tcopy(dest, src) - for key, value in pairs(src) do - if key == "_G" or key == "_ENV" then - -- We avoid copying it. - elseif type(value) == "table" then - dest[key] = {} - tcopy(dest[key], value) - else - dest[key] = src[key] - end - end - end - - G._ENV = _ENV - G._G = G - G.bios = {} - tcopy(G.bios, _G) - end - - do - local stdfuncs = { - _G = {"assert", "collectgarbage", "dofile", "error", - "getmetatable", "ipairs", "load", "loadfile", "next", "pairs", - "pcall", "print", "rawequal", "rawget", "rawlen", "rawset", - "select", "setmetatable", "tonumber", "tostring", "type", - "_VERSION", "xpcall", "require"}, - coroutine = {"create", "isyieldable", "resume", "running", - "status", "wrap", "yield"}, - package = {"config", "cpath", "loaded", "loadlib", "path", - "preload", "searchers", "searchpath"}, - string = {"byte", "char", "dump", "find", "format", - "gmatch", "gsub", "len", "lower", "match", "pack", - "packsize", "rep", "reverse", "sub", "unpack", "upper"}, - utf8 = {"char", "charpattern", "codes", "codepoint", "len", - "offset"}, - table = {"concat", "insert", "move", "pack", "remove", "sort", - "unpack"}, - math = {"abs", "acos", "asin", "atan", "ceil", "cos", "deg", - "exp", "floor", "fmod", "huge", "log", "max", "maxinteger", - "min", "mininteger", "modf", "pi", "rad", "random", - "randomseed", "sin", "sqrt", "tan", "tointeger", "type", - "ult"}, - io = {"close", "flush", "input", "lines", "open", "output", - "popen", "read", "tmpfile", "type", "write"}, - os = {"clock", "date", "difftime", "execute", "exit", "getenv", - "remove", "rename", "setlocale", "time", "tmpname"}, - debug = {"debug", "gethook", "getinfo", "getlocal", "getmetatable", - "getmetatable", "getregistry", "getupvalue", "getuservalue", - "sethook", "setlocal", "setmetatable", "setupvalue", - "setuservalue", "traceback", "upvalueid", "upvaluejoin"}, - } - - local function isempty(t) - for key, _ in pairs(t) do - return false - end - - return true - end - - for module, functions in pairs(stdfuncs) do - if module == "_G" or type(G.bios[module]) == "table" then - local src = {} - local dest = {} - - if module == "_G" then - src = G.bios - dest = G - else - src = G.bios[module] - G[module] = {} - dest = G[module] - end - - for _, func_name in pairs(functions) do - dest[func_name] = src[func_name] - src[func_name] = nil - end - - if module ~= "_G" and isempty(G.bios[module]) then - G.bios[module] = nil - end - end - end - - -- Remove a few functions. - - if G.string.gmatch == nil then - G.string.gmatch = G.bios.string.gfind - end - - G.bios.getfenv = nil - G.bios.setfenv = nil - G.bios.loadstring = nil - G.bios.unpack = nil - G.bios.__inext = nil - G.bios.printError = nil - G.bios.write = nil - G.bios.read = nil - G.bios.sleep = nil - - G.bios.io = nil - G.bios.debug = nil - G.bios.coroutine = nil - G.bios.string = nil - G.bios.utf8 = nil - G.bios.math = nil - G.bios.table = nil - G.bios.bit = nil - G.bios.bit32 = nil - end - - setmetatable(_ENV, {__index = G}) -end - --- Clean the terminal and show that we're booting up. - -do - local BLACK = 32768 - local WHITE = 1 - local YELLOW = 16 - - local function setColors(fore, back) - if bios.term.setBackgroundColor ~= nil then - bios.term.setBackgroundColor(back) - end - if bios.term.setTextColor ~= nil then - bios.term.setTextColor(fore) - end - end - - setColors(YELLOW, BLACK) - bios.term.clear() - bios.term.setCursorPos(1, 1) - - print("Booting up thox...") - print() - print(" VERSION = " .. tostring(_VERSION)) - print(" HOST = " .. tostring(bios._HOST)) - print(" BIOS = " .. tostring(bios.os.version())) - print() - - setColors(WHITE, BLACK) -end - --- Run the system. - -do - local handle = bios.fs.open("/system.lua") - local system = handle:readAll() - - handle.close() - - load(program, "", "t")() -end diff --git a/pkg/core/thox/pkgbuild.lua b/pkg/core/thox/pkgbuild.lua index 2bddf72..ab1770c 100644 --- a/pkg/core/thox/pkgbuild.lua +++ b/pkg/core/thox/pkgbuild.lua @@ -1,5 +1,5 @@ pkgname = "thox" -pkgver = "2021-03-29-01" +pkgver = "2021032901" pkgrel = 0 pkgdesc = "thox kernel" url = "https://thox.madefor.cc/system/process.html" diff --git a/pkg/core/thox/system.lua b/pkg/core/thox/system.lua index 1b99323..276d174 100644 --- a/pkg/core/thox/system.lua +++ b/pkg/core/thox/system.lua @@ -63,13 +63,15 @@ do -- An answer to an RPC procedure call has been emitted. ANSWER = 3, - -- An RPC procedure binding has been requested for - -- the current process. - BIND = 4, + -- A call to pull events has been emitted, with + -- filters. + PULL = 4, - -- An RPC procedure unbinding has been requested for - -- the current process. - UNBIND = 5, + -- A call to spawn a process has been emitted. + SPAWN = 5, + + -- A call to exit self has been emitted. + EXIT = 6, } -- Process definition. @@ -149,6 +151,7 @@ do spawn = function (self, program, options) local pid, env + local globals -- First of all, get the next identifier available. -- TODO: find a less dumb way to find a free pid? @@ -174,340 +177,376 @@ do -- -- * https://github.com/SquidDev-CC/CC-Tweaked/blob/mc-1.15.x/src/main/resources/data/computercraft/lua/bios.lua#L525 - do - local globals = {} - env = {} + globals = {} + env = {} - setmetatable(env, { - __index = globals}) - env._ENV = env - env._G = globals + setmetatable(env, { + __index = globals}) + env._ENV = env + env._G = globals - -- Here, we create the basic environment. This chunk actually - -- depends on the process type: - -- - -- * kernel processes start with all of the available APIs, - -- including the native Java APIs. - -- * user processes only start with functions defined as - -- safe for sandboxing, as they do not influence other; - -- processes nor the process manager we're currently - -- defining. - -- - -- References: - -- - -- * http://lua-users.org/wiki/SandBoxes - -- * https://www.lua.org/manual/5.3/manual.html#6 - -- * https://github.com/SquidDev-CC/CC-Tweaked/blob/mc-1.15.x/src/main/resources/data/computercraft/lua/bios.lua - - if isKernel then - local function rcopy(dest, src) - for key, value in pairs(src) do - if key == "_G" or key == "_ENV" then - -- We avoid copying it. - elseif type(value) == "table" then - dest[key] = {} - rcopy(dest[key], value) - else - dest[key] = src[key] - end - end - end - - for _, key in pairs({"assert", "error", "ipairs", - "next", "pairs", "pcall", "tonumber", "tostring", - "type", "_VERSION", "xpcall"}) do - globals[key] = _G[key] - end + -- Here, we create the basic environment. This chunk actually + -- depends on the process type: + -- + -- * kernel processes start with all of the available APIs, + -- including the native Java APIs. + -- * user processes only start with functions defined as + -- safe for sandboxing, as they do not influence other; + -- processes nor the process manager we're currently + -- defining. + -- + -- References: + -- + -- * http://lua-users.org/wiki/SandBoxes + -- * https://www.lua.org/manual/5.3/manual.html#6 + -- * https://github.com/SquidDev-CC/CC-Tweaked/blob/mc-1.15.x/src/main/resources/data/computercraft/lua/bios.lua + -- + -- TODO: review what should be provided here. - for _, module in pairs({"os", "string", "table", - "math", "utf8", "coroutine"}) do - globals[key] = {} + do + local function rcopy(dest, src) + for key, value in pairs(src) do + if key == "_G" or key == "_ENV" then + -- We avoid copying it. + elseif type(value) == "table" then + dest[key] = {} + rcopy(dest[key], value) + else + dest[key] = src[key] + end end + end - for _, key in pairs({"clock", "difftime", "time"}) do - globals.os[key] = _G.os[key] - end + for _, key in pairs({"assert", "error", "ipairs", + "next", "pairs", "pcall", "tonumber", "tostring", + "type", "_VERSION", "xpcall"}) do + globals[key] = _G[key] + end - for _, key in pairs({"byte", "char", "find", "format", - "gmatch", "gsub", "len", "lower", "match", - "rep", "reverse", "sub", "upper"}) do - globals.string[key] = _G.string[key] - end + for _, module in pairs({"os", "string", "table", + "math", "utf8", "coroutine"}) do + globals[key] = {} + end - for _, key in pairs({"insert", "maxn", "remove", - "sort"}) do - globals.table[key] = _G.table[key] - end + for _, key in pairs({"clock", "difftime", "time"}) do + globals.os[key] = _G.os[key] + end - for _, key in pairs({"abs", "acos", "asin", "atan", - "atan2", "ceil", "cos", "cosh", "deg", "exp", - "floor", "fmod", "frexp", "huge", "ldexp", "log", - "log10", "max", "min", "modf", "pi", "pow", "rad"}) do - globals.math[key] = _G.math[key] - end + for _, key in pairs({"byte", "char", "find", "format", + "gmatch", "gsub", "len", "lower", "match", + "rep", "reverse", "sub", "upper"}) do + globals.string[key] = _G.string[key] + end - for _, key in pairs({"char", "charpattern", "codes", - "codepoint", "len", "offset"}) do - globals.utf8[key] = _G.utf8[key] - end + for _, key in pairs({"insert", "maxn", "remove", + "sort"}) do + globals.table[key] = _G.table[key] + end - for name, func in pairs(_G.coroutine) do - globals.coroutine[name] = func - end + for _, key in pairs({"abs", "acos", "asin", "atan", + "atan2", "ceil", "cos", "cosh", "deg", "exp", + "floor", "fmod", "frexp", "huge", "ldexp", "log", + "log10", "max", "min", "modf", "pi", "pow", "rad"}) do + globals.math[key] = _G.math[key] + end - -- TODO: we do not always want to copy the bios - -- functions to the process, permissions would be - -- welcome I think. + for _, key in pairs({"char", "charpattern", "codes", + "codepoint", "len", "offset"}) do + globals.utf8[key] = _G.utf8[key] + end - globals.bios = {} - rcopy(globals.bios, _G.bios) + for name, func in pairs(_G.coroutine) do + globals.coroutine[name] = func end - -- We actually need to hijack the normal work of the - -- coroutine module to inject a few things. - -- Indeed, when coroutine yields, with thox, it can be - -- one of two things: - -- - -- * A system call, as thox makes use of coroutines for - -- its threads. - -- * A normal yield, for internal coroutines uses. - -- - -- For hijacking, we actually yield a first argument that - -- is hidden to the user, which is 1 in the first case - -- and 0 in the second; unless the function yields when - -- returning, in which case there is no special argument - -- because it can not be a system call. - -- - -- References: - -- - -- * http://www.lua.org/manual/5.3/manual.html#2.6 - -- * http://www.lua.org/manual/5.3/manual.html#6.2 + -- TODO: we do not always want to copy the bios + -- functions to the process, permissions would be + -- welcome I think. - do - local cocreate = coroutine.create - local coresume = coroutine.resume - local coyield = coroutine.yield - local costatus = coroutine.status - local prevent_from_hanging = function (func) - return self:prevent_from_hanging(func) - end + globals.bios = {} + rcopy(globals.bios, _G.bios) + end - globals.coroutine.create = function (func) - return prevent_from_hanging(cocreate(func)) - end + -- We actually need to hijack the normal work of the + -- coroutine module to inject a few things. + -- Indeed, when coroutine yields, with thox, it can be + -- one of two things: + -- + -- * A system call, as thox makes use of coroutines for + -- its threads. + -- * A normal yield, for internal coroutines uses. + -- + -- For hijacking, we actually yield a first argument that + -- is hidden to the user, which is 1 in the first case + -- and 0 in the second; unless the function yields when + -- returning, in which case there is no special argument + -- because it can not be a system call. + -- + -- References: + -- + -- * http://www.lua.org/manual/5.3/manual.html#2.6 + -- * http://www.lua.org/manual/5.3/manual.html#6.2 - globals.coroutine.yield = function (...) - local args = table.pack(...) - table.insert(args, 1, YIELD.BASIC) + do + local cocreate = coroutine.create + local coresume = coroutine.resume + local coyield = coroutine.yield + local costatus = coroutine.status + local prevent_from_hanging = function (func) + return self:prevent_from_hanging(func) + end - return coyield(table.unpack(args)) - end + globals.coroutine.create = function (func) + return prevent_from_hanging(cocreate(func)) + end - globals.coroutine.resume = function (co, ...) - local args = table.pack(...) + globals.coroutine.yield = function (...) + local args = table.pack(...) + table.insert(args, 1, YIELD.BASIC) - while true do - local retpack = table.pack(coresume(co, - table.unpack(args))) + return coyield(table.unpack(args)) + end - if costatus(co) == "dead" then - -- The function returned its final yield, - -- which means we just return what it has - -- returned. + globals.coroutine.resume = function (co, ...) + local args = table.pack(...) - return table.unpack(retpack) - elseif retpack[1] == true then - -- The function has just yielded, which - -- means the special argument is there, - -- let's treat it! + while true do + local retpack = table.pack(coresume(co, + table.unpack(args))) - local ret = table.remove(retpack, 1) - local typ = table.remove(retpack, 1) + if costatus(co) == "dead" then + -- The function returned its final yield, + -- which means we just return what it has + -- returned. - if typ == YIELD.BASIC then - -- A normal value. + return table.unpack(retpack) + elseif retpack[1] == true then + -- The function has just yielded, which + -- means the special argument is there, + -- let's treat it! - table.insert(retpack, 1, ret) - return table.unpack(retpack) - else - -- A system yield, we just yield it for - -- the parent coroutine to catch it to - -- transmit it to the main loop, then - -- come back here. - - table.insert(retpack, 1, typ) - args = table.pack(coyield( - table.unpack(retpack))) - end - else - -- An error has occurred, so we just - -- return it as is. + local ret = table.remove(retpack, 1) + local typ = table.remove(retpack, 1) + if typ == YIELD.BASIC then + -- A normal value. + + table.insert(retpack, 1, ret) return table.unpack(retpack) + else + -- A system yield, we just yield it for + -- the parent coroutine to catch it to + -- transmit it to the main loop, then + -- come back here. + + table.insert(retpack, 1, typ) + args = table.pack(coyield( + table.unpack(retpack))) end + else + -- An error has occurred, so we just + -- return it as is. + + return table.unpack(retpack) end end + end - -- System calls. + -- System calls. - local toCanonicalName, toValidName, toValidArgs + local toCanonicalName, toValidName, toValidArgs - do - local vc = "[a-z][a-z0-9_]*" - local vn = ("^" .. vc .. "(.(" .. vc .. ".)*" - .. vc .. ")?$") - local reservedNames = {"", "and", "break", "do", - "else", "elseif", "end", "false", "for", - "function", "goto", "if", "in", "local", "nil", - "not", "or", "repeat", "return", "then", "true", - "until", "while"} + do + local vc = "[a-z][a-z0-9_]*" + local vn = ("^" .. vc .. "(.(" .. vc .. ".)*" + .. vc .. ")?$") + local reservedNames = {"", "and", "break", "do", + "else", "elseif", "end", "false", "for", + "function", "goto", "if", "in", "local", "nil", + "not", "or", "repeat", "return", "then", "true", + "until", "while"} + + toCanonicalName = function (name) + name = string.lower(tostring(name)) + if name == "" then + return nil + end + + return name + end + + toValidName = function (name) + name = string.lower(tostring(name)) - toCanonicalName = function (name) - name = string.lower(tostring(name)) - if name == "" then + if string.match(name, vn) ~= name then + return nil + end + + for _, reserved in pairs(reservedNames) do + if string.find("." .. name .. ".", + "." .. reserved .. ".") ~= nil then return nil end - - return name end - toValidName = function (name) - name = string.lower(tostring(name)) + return name + end + + toValidArgs = function (args) + local sanitizedArgs = {} + + -- What we're trying to do here is to eliminate + -- complicated types to only keep simple types + -- as arguments, for now. + -- + -- For now, we do not allow tables because they + -- can have custom metatables. This is not what + -- we want for now. + + for i, value in ipairs(args) do + local typ = type(value) - if string.match(name, vn) ~= name then + if typ == "number" + or typ == "string" + or typ == "boolean" then + table.insert(sanitizedArgs, value) + else return nil end + end - for _, reserved in pairs(reservedNames) do - if string.find("." .. name .. ".", - "." .. reserved .. ".") ~= nil then - return nil - end - end + return sanitizedArgs + end + end - return name - end + -- Process-related calls. - toValidArgs = function (args) - local sanitizedArgs = {} + local run_func(func, options) + -- TODO: validate that ``func`` is a function, + -- has no upvalues (or upvalues are replaced + -- with nil), and that options is a table and + -- copy the values instead of transmitting them + -- by reference. - -- What we're trying to do here is to eliminate - -- complicated types to only keep simple types - -- as arguments, for now. - -- - -- For now, we do not allow tables because they - -- can have custom metatables. This is not what - -- we want for now. + return coyield(YIELD.SPAWN, func, {}) + end - for i, value in ipairs(args) do - local typ = type(value) + globals.os.run = run_func - if typ == "number" - or typ == "string" - or typ == "boolean" then - table.insert(sanitizedArgs, value) - else - return nil - end - end + globals.os.run_file = function (path, options) + local path = ("\"" .. + string.gsub(string.gsub(path, "\\", "\\\\"), + "\"", "\\\"") .. "\"") + local code = ("local w = load(io.open(" .. path .. ")" .. + ":read(\"a\"), " .. path .. ", \"t\")" .. + "\r\nif w == nil then os.exit(false, true) end" .. + "\r\nreturn w()") - return sanitizedArgs - end - end + return run_func(load(code, "t")) + end - globals.os.bind = function (name) - local name = toValidName(name) - if name == nil then - return false - end + globals.os.run_code = function (code, options) + return run_func(load(code, "t")) + end - return coyield(YIELD.BIND, name) - end + -- Context-related calls. - globals.os.unbind = function (name) - local name = toCanonicalName(name) - if name == nil then - return false - end + globals.os.context = function () + -- TODO: create a context, add a reference to it + -- in the current process, and return its number. - return coyield(YIELD.UNBIND, name) - end + error("not implemented") + end - globals.os.call = function (name, ...) - local name = toCanonicalName(name) - local args = toValidArgs(table.unpack(...)) + globals.os.close = function (ctx) + -- TODO: look for a context with that number, and + -- close it. - if name == nil or args == nil then - return false - end + error("not implemented") + end - return coyield(YIELD.CALL, name, table.unpack(args)) - end + -- Event-related calls. - globals.os.answer = function (id, ...) - local id = math.tointeger(id) - local args = toValidArgs(table.unpack(...)) + globals.os.pull = function (...) + -- TODO: validate the filters. - if name == nil or args == nil then - return false - end + return coyield(YIELD.PULL) + end - return coyield(YIELD.ANSWER, name, table.unpack(args)) + -- RPC interface. + + globals.os.call = function (ctx, name, ...) + local name = toCanonicalName(name) + local args = toValidArgs(table.unpack(...)) + + if name == nil or args == nil then + return false end - globals.os.pull = function () - return coyield(YIELD.CALL, "pull") + -- TODO: check if the given context exists. + + return coyield(YIELD.CALL, name, table.unpack(args)) + end + + globals.os.answer = function (cid, ...) + local cid = math.tointeger(cid) + local args = toValidArgs(table.unpack(...)) + + if name == nil or args == nil then + return false end - globals.os.capture = function (co, ...) - local args = table.pack(...) + return coyield(YIELD.ANSWER, name, table.unpack(args)) + end - while true do - local retpack = table.pack(coresume(co, - table.unpack(args))) + globals.os.capture = function (co, ...) + local args = table.pack(...) - if costatus(co) == "dead" then - -- The function returned its final yield, - -- which means we just return what it has - -- returned. + while true do + local retpack = table.pack(coresume(co, + table.unpack(args))) - return "yield", table.unpack(retpack) - elseif retpack[1] == true then - -- The function has just yielded, which - -- means the special argument is there, - -- let's treat it! + if costatus(co) == "dead" then + -- The function returned its final yield, + -- which means we just return what it has + -- returned. - local ret = table.remove(retpack, 1) - local typ = table.remove(retpack, 1) + return "yield", table.unpack(retpack) + elseif retpack[1] == true then + -- The function has just yielded, which + -- means the special argument is there, + -- let's treat it! - if typ == YIELD.BASIC then - -- A normal value. + local ret = table.remove(retpack, 1) + local typ = table.remove(retpack, 1) - table.insert(retpack, 1, ret) - return "yield", table.unpack(retpack) - elseif typ == YIELD.PREEMPT then - -- Pre-empt, let's just yield for this one. + if typ == YIELD.BASIC then + -- A normal value. - coyield(YIELD.PREEMPT) - elseif typ == YIELD.CALL then - -- A call, we want to return it! + table.insert(retpack, 1, ret) + return "yield", table.unpack(retpack) + elseif typ == YIELD.PREEMPT then + -- Pre-empt, let's just yield for this one. - return "call", table.unpack(retpack) - elseif typ == YIELD.ANSWER then - -- An answer, we want to return it as well. + coyield(YIELD.PREEMPT) + elseif typ == YIELD.CALL then + -- A call, we want to return it! - return "answer", table.unpack(retpack) - elseif typ == YIELD.BIND then - -- A bind, we want to return it as well. + return "call", table.unpack(retpack) + elseif typ == YIELD.ANSWER then + -- An answer, we want to return it as well. - return "bind", retpack[1] - end - else - -- An error has occurred, so we just - -- return it as is. + return "answer", table.unpack(retpack) + elseif typ == YIELD.BIND then + -- A bind, we want to return it as well. - return table.unpack(retpack) + return "bind", retpack[1] end + else + -- An error has occurred, so we just + -- return it as is. + + return table.unpack(retpack) end end end @@ -615,30 +654,12 @@ do -- TODO: check for debug functions, maybe? end - -- The environment is ready, now we can load the program - -- with the environment and check if it has loaded correctly. - -- - -- TODO: name the coroutine! - -- TODO: actually load the file from the coroutine? - -- Not sure about that one yet, but it could be a good idea. - - local chunk - - do - local err - - chunk, err = load(program, "", "t", env) - if chunk == nil then - error("could not load the program: " .. err) - end - end - -- The program has been loaded with the correct environment, -- we can now create the process with the given function. local p = Process() - p.thread = self:prevent_from_hanging(coroutine.create(chunk)) + p.thread = self:prevent_from_hanging(coroutine.create(func)) p.state = Process.RUNNING self.p[pid] = p |