forgot to commit openos

This commit is contained in:
2026-04-01 11:04:22 +02:00
parent 9291d81d41
commit b0afd0529e
179 changed files with 13952 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
function loadfile(filename, ...)
if filename:sub(1,1) ~= "/" then
filename = (os.getenv("PWD") or "/") .. "/" .. filename
end
local handle, open_reason = require("filesystem").open(filename)
if not handle then
return nil, open_reason
end
local buffer = {}
while true do
local data, reason = handle:read(1024)
if not data then
handle:close()
if reason then
return nil, reason
end
break
end
buffer[#buffer + 1] = data
end
return load(table.concat(buffer), "=" .. filename, ...)
end
function dofile(filename)
local program, reason = loadfile(filename)
if not program then
return error(reason .. ':' .. filename, 0)
end
return program()
end
function print(...)
local args = table.pack(...)
local stdout = io.stdout
local pre = ""
for i = 1, args.n do
stdout:write(pre, (assert(tostring(args[i]), "'tostring' must return a string to 'print'")))
pre = "\t"
end
stdout:write("\n")
stdout:flush()
end

View File

@@ -0,0 +1,84 @@
local process = require("process")
local fs = require("filesystem")
--Initialize coroutine library--
local _coroutine = coroutine -- real coroutine backend
_G.coroutine = setmetatable(
{
resume = function(co, ...)
local proc = process.info(co)
-- proc is nil if the process closed, natural resume will likely complain the coroutine is dead
-- but if proc is dead and an orphan coroutine is alive, it doesn't have any proc data like stack info
-- if the user really wants to resume it, let them
return (proc and proc.data.coroutine_handler.resume or _coroutine.resume)(co, ...)
end
},
{
__index = function(_, key)
local proc = process.info(_coroutine.running())
return (proc and proc.data.coroutine_handler or _coroutine)[key]
end
}
)
package.loaded.coroutine = _G.coroutine
local kernel_load = _G.load
local intercept_load
intercept_load = function(source, label, mode, env)
local prev_load = env and env.load or _G.load
local e = env and setmetatable({
load = function(_source, _label, _mode, _env)
return prev_load(_source, _label, _mode, _env or env)
end}, {
__index = env,
__pairs = function(...) return pairs(env, ...) end,
__newindex = function(_, key, value) env[key] = value end,
})
return kernel_load(source, label, mode, e or process.info().env)
end
_G.load = intercept_load
local kernel_create = _coroutine.create
_coroutine.create = function(f,standAlone)
local co = kernel_create(f)
if not standAlone then
table.insert(process.findProcess().instances, co)
end
return co
end
_coroutine.wrap = function(f)
local thread = coroutine.create(f)
return function(...)
return select(2, coroutine.resume(thread, ...))
end
end
local init_thread = _coroutine.running()
process.list[init_thread] = {
path = "/init.lua",
command = "init",
env = _ENV,
data =
{
vars={},
handles={},
io={}, --init will populate this
coroutine_handler = _coroutine,
signal = error
},
instances = setmetatable({}, {__mode="v"})
}
-- intercept fs open
local fs_open = fs.open
fs.open = function(...)
local fs_open_result = table.pack(fs_open(...))
if fs_open_result[1] then
process.addHandle(fs_open_result[1])
end
return table.unpack(fs_open_result, 1, fs_open_result.n)
end

View File

@@ -0,0 +1,41 @@
local computer = require("computer")
local fs = require("filesystem")
local info = require("process").info
local event = require("event")
function os.getenv(varname)
local env = info().data.vars
if not varname then
return env
elseif varname == '#' then
return #env
end
return env[varname]
end
function os.setenv(varname, value)
checkArg(1, varname, "string", "number")
if value ~= nil then
value = tostring(value)
end
info().data.vars[varname] = value
return value
end
function os.sleep(timeout)
checkArg(1, timeout, "number", "nil")
local deadline = computer.uptime() + (timeout or 0)
repeat
event.pull(deadline - computer.uptime())
until computer.uptime() >= deadline
end
os.setenv("PATH", "/bin:/usr/bin:/home/bin:.")
os.setenv("TMP", "/tmp") -- Deprecated
os.setenv("TMPDIR", "/tmp")
if computer.tmpAddress() then
fs.mount(computer.tmpAddress(), "/tmp")
end
require("package").delay(os, "/lib/core/full_filesystem.lua")

View File

@@ -0,0 +1,36 @@
local buffer = require("buffer")
local tty_stream = require("tty").stream
local core_stdin = buffer.new("r", tty_stream)
local core_stdout = buffer.new("w", tty_stream)
local core_stderr = buffer.new("w", setmetatable(
{
write = function(_, str)
return tty_stream:write("\27[31m"..str.."\27[37m")
end
}, {__index=tty_stream}))
core_stdout:setvbuf("no")
core_stderr:setvbuf("no")
core_stdin.tty = true
core_stdout.tty = true
core_stderr.tty = true
core_stdin.close = tty_stream.close
core_stdout.close = tty_stream.close
core_stderr.close = tty_stream.close
local io_mt = getmetatable(io) or {}
io_mt.__index = function(_, k)
return
k == 'stdin' and io.input() or
k == 'stdout' and io.output() or
k == 'stderr' and io.error() or
nil
end
setmetatable(io, io_mt)
io.input(core_stdin)
io.output(core_stdout)
io.error(core_stderr)

View File

@@ -0,0 +1,203 @@
local component = require("component")
local computer = require("computer")
local event = require("event")
local adding = {}
local primaries = {}
-------------------------------------------------------------------------------
-- This allows writing component.modem.open(123) instead of writing
-- component.getPrimary("modem").open(123), which may be nicer to read.
setmetatable(component, {
__index = function(_, key)
return component.getPrimary(key)
end,
__pairs = function(self)
local parent = false
return function(_, key)
if parent then
return next(primaries, key)
else
local k, v = next(self, key)
if not k then
parent = true
return next(primaries)
else
return k, v
end
end
end
end
})
function component.get(address, componentType)
checkArg(1, address, "string")
checkArg(2, componentType, "string", "nil")
for c in component.list(componentType, true) do
if c:sub(1, address:len()) == address then
return c
end
end
return nil, "no such component"
end
function component.isAvailable(componentType)
checkArg(1, componentType, "string")
if not primaries[componentType] and not adding[componentType] then
-- This is mostly to avoid out of memory errors preventing proxy
-- creation cause confusion by trying to create the proxy again,
-- causing the oom error to be thrown again.
component.setPrimary(componentType, component.list(componentType, true)())
end
return primaries[componentType] ~= nil
end
function component.isPrimary(address)
local componentType = component.type(address)
if componentType then
if component.isAvailable(componentType) then
return primaries[componentType].address == address
end
end
return false
end
function component.getPrimary(componentType)
checkArg(1, componentType, "string")
assert(component.isAvailable(componentType),
"no primary '" .. componentType .. "' available")
return primaries[componentType]
end
function component.setPrimary(componentType, address)
checkArg(1, componentType, "string")
checkArg(2, address, "string", "nil")
if address ~= nil then
address = component.get(address, componentType)
assert(address, "no such component")
end
local wasAvailable = primaries[componentType]
if wasAvailable and address == wasAvailable.address then
return
end
local wasAdding = adding[componentType]
if wasAdding and address == wasAdding.address then
return
end
if wasAdding then
event.cancel(wasAdding.timer)
end
primaries[componentType] = nil
adding[componentType] = nil
local primary = address and component.proxy(address) or nil
if wasAvailable then
computer.pushSignal("component_unavailable", componentType)
end
if primary then
if wasAvailable or wasAdding then
adding[componentType] = {
address=address,
proxy = primary,
timer=event.timer(0.1, function()
adding[componentType] = nil
primaries[componentType] = primary
computer.pushSignal("component_available", componentType)
end)
}
else
primaries[componentType] = primary
computer.pushSignal("component_available", componentType)
end
end
end
-------------------------------------------------------------------------------
local function onComponentAdded(_, address, componentType)
local prev = primaries[componentType] or (adding[componentType] and adding[componentType].proxy)
if prev then
-- special handlers -- some components are just better at being primary
if componentType == "screen" then
--the primary has no keyboards but we do
if #prev.getKeyboards() == 0 then
local first_kb = component.invoke(address, 'getKeyboards')[1]
if first_kb then
-- just in case our kb failed to achieve primary
-- possible if existing primary keyboard became primary first without a screen
-- then prev (a screen) was added without a keyboard
-- and then we attached this screen+kb pair, and our kb fired first - failing to achieve primary
-- also, our kb may fire right after this, which is fine
component.setPrimary("keyboard", first_kb)
prev = nil -- nil meaning we should take this new one over the previous
end
end
elseif componentType == "keyboard" then
-- to reduce signal noise, if this kb is also the prev, we do not need to reset primary
if address ~= prev.address then
--keyboards never replace primary keyboards unless the are the only keyboard on the primary screen
local current_screen = primaries.screen or (adding.screen and adding.screen.proxy)
--if there is not yet a screen, do not use this keyboard, it's not any better
if current_screen then
-- the next phase is complicated
-- there is already a screen and there is already a keyboard
-- this keyboard is only better if this is a keyboard of the primary screen AND the current keyboard is not
-- i don't think we can trust kb order (1st vs 2nd), 2nd could fire first
-- but if there are two kbs on a screen, we can give preferred treatment to the first
-- thus, assume 2nd is not attached for the purposes of primary kb
-- and THUS, whichever (if either) is the 1st kb of the current screen
-- this is only possible if
-- 1. the only kb on the system (current) has no screen
-- 2. a screen is added without a kb
-- 3. this kb is added later manually
-- prev is true when addr is not equal to the primary keyboard of the current screen -- meaning
-- when addr is different, and thus it is not the primary keyboard, then we ignore this
-- keyboard, and keep the previous
-- prev is false means we should take this new keyboard
prev = address ~= current_screen.getKeyboards()[1]
end
end
end
end
if not prev then
component.setPrimary(componentType, address)
end
end
local function onComponentRemoved(_, address, componentType)
if primaries[componentType] and primaries[componentType].address == address or
adding[componentType] and adding[componentType].address == address
then
local next = component.list(componentType, true)()
component.setPrimary(componentType, next)
if componentType == "screen" and next then
-- setPrimary already set the proxy (if successful)
local proxy = (primaries.screen or (adding.screen and adding.screen.proxy))
if proxy then
-- if a screen is removed, and the primary keyboard is actually attached to another, non-primary, screen
-- then the `next` screen, if it has a keyboard, should TAKE priority
local next_kb = proxy.getKeyboards()[1] -- costly, don't call this method often
local old_kb = primaries.keyboard or adding.keyboard
-- if the next screen doesn't have a kb, this operation is without purpose, leave things as they are
-- if there was no previous kb, use the new one
if next_kb and (not old_kb or old_kb.address ~= next_kb) then
component.setPrimary("keyboard", next_kb)
end
end
end
end
end
event.listen("component_added", onComponentAdded)
event.listen("component_removed", onComponentRemoved)
if _G.boot_screen then
component.setPrimary("screen", _G.boot_screen)
end
_G.boot_screen = nil

View File

@@ -0,0 +1,23 @@
require("filesystem").mount(
setmetatable({
address = "f5501a9b-9c23-1e7a-4afe-4b65eed9b88a"
},
{
__index=function(tbl,key)
local result =
({
getLabel = "devfs",
spaceTotal = 0,
spaceUsed = 0,
isReadOnly = false,
})[key]
if result ~= nil then
return function() return result end
end
local lib = require("devfs")
lib.register(tbl)
return lib.proxy[key]
end
}), "/dev")

View File

@@ -0,0 +1,9 @@
-- Run all enabled rc scripts.
-- /boot/*rc was moved before /boot/*filesystem because
-- 1. rc now loads in via the init signal
-- 2. rc used to load directly
-- Thus, for rc to load prior, it needs to register prior
require("event").listen("init", function()
dofile(require("shell").resolve("rc", "lua"))
return false
end)

View File

@@ -0,0 +1,56 @@
local event = require("event")
local fs = require("filesystem")
local shell = require("shell")
local tmp = require("computer").tmpAddress()
local pendingAutoruns = {}
local function onComponentAdded(_, address, componentType)
if componentType == "filesystem" and tmp ~= address then
local proxy = fs.proxy(address)
if proxy then
local name = address:sub(1, 3)
while fs.exists(fs.concat("/mnt", name)) and
name:len() < address:len() -- just to be on the safe side
do
name = address:sub(1, name:len() + 1)
end
name = fs.concat("/mnt", name)
fs.mount(proxy, name)
if not fs.exists("/etc/filesystem.cfg") or fs.isAutorunEnabled() then
local file = shell.resolve(fs.concat(name, "autorun"), "lua") or
shell.resolve(fs.concat(name, ".autorun"), "lua")
if file then
local run = {file, _ENV, proxy}
if pendingAutoruns then
table.insert(pendingAutoruns, run)
else
xpcall(shell.execute, event.onError, table.unpack(run))
end
end
end
end
end
end
local function onComponentRemoved(_, address, componentType)
if componentType == "filesystem" then
if fs.get(shell.getWorkingDirectory()).address == address then
shell.setWorkingDirectory("/")
end
fs.umount(address)
end
end
event.listen("init", function()
for _, run in ipairs(pendingAutoruns) do
xpcall(shell.execute, event.onError, table.unpack(run))
end
pendingAutoruns = nil
return false
end)
event.listen("component_added", onComponentAdded)
event.listen("component_removed", onComponentRemoved)
require("package").delay(fs, "/lib/core/full_filesystem.lua")

View File

@@ -0,0 +1,24 @@
local event = require("event")
local function onComponentAvailable(_, componentType)
local component = require("component")
local tty = require("tty")
if (componentType == "screen" and component.isAvailable("gpu")) or
(componentType == "gpu" and component.isAvailable("screen"))
then
local gpu, screen = component.gpu, component.screen
local screen_address = screen.address
if gpu.getScreen() ~= screen_address then
gpu.bind(screen_address)
end
local depth = math.floor(2^(gpu.getDepth()))
os.setenv("TERM", "term-"..depth.."color")
event.push("gpu_bound", gpu.address, screen_address)
if tty.gpu() ~= gpu then
tty.bind(gpu)
event.push("term_available")
end
end
end
event.listen("component_available", onComponentAvailable)

View File

@@ -0,0 +1,12 @@
local event = require("event")
local keyboard = require("keyboard")
local function onKeyChange(ev, _, char, code)
-- nil might be slightly more mem friendly during runtime
-- and `or nil` appears to only cost 30 bytes
keyboard.pressedChars[char] = ev == "key_down" or nil
keyboard.pressedCodes[code] = ev == "key_down" or nil
end
event.listen("key_down", onKeyChange)
event.listen("key_up", onKeyChange)

View File

@@ -0,0 +1,46 @@
local event = require("event")
local function components_changed(ename, address, type)
local tty = require("tty")
local window = tty.window
if not window then
return
end
if ename == "component_available" or ename == "component_unavailable" then
type = address
end
if ename == "component_removed" or ename == "component_unavailable" then
-- address can be type, when ename is *_unavailable, but *_removed works here and that's all we need
if type == "gpu" and window.gpu.address == address then
window.gpu = nil
window.keyboard = nil
elseif type == "keyboard" then
-- we could check if this was our keyboard
-- i.e. if address == window.keyboard
-- but it is also simple for the terminal to
-- recheck what kb to use
window.keyboard = nil
end
if (type == "screen" or type == "gpu") and not tty.isAvailable() then
event.push("term_unavailable")
end
elseif (ename == "component_added" or ename == "component_available") and type == "keyboard" then
-- we need to clear the current terminals cached keyboard (if any) when
-- a new keyboard becomes available. This is in case the new keyboard was
-- attached to the terminal's window. The terminal library has the code to
-- determine what the best keyboard to use is, but here we'll just set the
-- cache to nil to force term library to reload it. An alternative to this
-- method would be to make sure the terminal library doesn't cache the
-- wrong keybaord to begin with but, users may actually expect that any
-- primary keyboard is a valid keyboard (weird, in my opinion)
window.keyboard = nil
end
end
event.listen("component_removed", components_changed)
event.listen("component_added", components_changed)
event.listen("component_available", components_changed)
event.listen("component_unavailable", components_changed)

View File

@@ -0,0 +1,6 @@
-- there doesn't seem to be a reason to update $HOSTNAME after the init signal
-- as user space /etc/profile comes after this point anyways
if require("filesystem").exists("/etc/hostname") then
loadfile("/bin/hostname.lua")("--update")
end
os.setenv("SHELL","/bin/sh.lua")