forked from Hyperion/HyperionOS
Potential windows case insensitive filesystem issue fix
This commit is contained in:
223
Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod
Normal file
223
Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod
Normal file
@@ -0,0 +1,223 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
kernel.allowGlobalOverwrites = true
|
||||
|
||||
function string.hasSuffix(str, suffix)
|
||||
return string.sub(str, #suffix + 1) == suffix
|
||||
end
|
||||
|
||||
function string.hasPrefix(str, prefix)
|
||||
return string.sub(str, 1, #prefix) == prefix
|
||||
end
|
||||
|
||||
function string.getSuffix(str, prefix) return string.sub(str, #prefix + 1) end
|
||||
|
||||
function string.getPrefix(str, suffix) return string.sub(str, 1, #suffix) end
|
||||
|
||||
function string.join(str, ...) return table.concat(table.pack(str, ...)) end
|
||||
|
||||
function string.delim(str, ...) return table.concat(table.pack(...), str) end
|
||||
|
||||
function string.split(str, delim, maxResultCountOrNil)
|
||||
assert(#delim == 1, "only delim len 1 supported for now")
|
||||
if not str then return false end
|
||||
maxResultCountOrNil = (maxResultCountOrNil or 0) - 1
|
||||
local rv = {}
|
||||
local buf = ""
|
||||
for i = 1, #str do
|
||||
local c = string.sub(str, i, i)
|
||||
if #rv ~= maxResultCountOrNil and c == delim then
|
||||
table.insert(rv, buf)
|
||||
buf = ""
|
||||
else
|
||||
buf = buf .. c
|
||||
end
|
||||
end
|
||||
table.insert(rv, buf)
|
||||
return rv
|
||||
end
|
||||
|
||||
function table.deepcopy(orig, copies)
|
||||
copies = copies or {}
|
||||
|
||||
if type(orig) ~= 'table' then
|
||||
return orig
|
||||
elseif copies[orig] then
|
||||
return copies[orig]
|
||||
end
|
||||
|
||||
local copy = {}
|
||||
copies[orig] = copy
|
||||
|
||||
for k, v in next, orig, nil do
|
||||
local copied_key = table.deepcopy(k, copies)
|
||||
local copied_val = table.deepcopy(v, copies)
|
||||
copy[copied_key] = copied_val
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
function table.hasKey(tabl, query)
|
||||
for i, v in pairs(tabl) do if i == query then return v end end
|
||||
return false
|
||||
end
|
||||
|
||||
function table.hasVal(tabl, query)
|
||||
for i, v in pairs(tabl) do if v == query then return i end end
|
||||
return false
|
||||
end
|
||||
|
||||
local function serialize(tbl, seen)
|
||||
seen = seen or {}
|
||||
|
||||
if seen[tbl] then return '"[Circular Reference]"' end
|
||||
|
||||
seen[tbl] = true
|
||||
|
||||
local output = "{"
|
||||
local first = true
|
||||
|
||||
for i, v in pairs(tbl) do
|
||||
if not first then output = output .. "," end
|
||||
first = false
|
||||
|
||||
if type(i) == "string" then
|
||||
output = output .. "[\"" .. i .. "\"]="
|
||||
elseif type(i) == "number" then
|
||||
output = output .. "[" .. tostring(i) .. "]="
|
||||
end
|
||||
|
||||
if type(v) == "table" then
|
||||
output = output .. serialize(v, seen)
|
||||
elseif type(v) == "string" then
|
||||
output = output .. "[=[" .. v .. "]=]"
|
||||
elseif type(v) == "number" or type(v) == "boolean" then
|
||||
output = output .. tostring(v)
|
||||
elseif type(v) == "function" then
|
||||
output = output .. "\"" .. tostring(v) .. "\""
|
||||
elseif type(v) == "thread" then
|
||||
output = output .. "\"" .. tostring(v) .. "\""
|
||||
else
|
||||
error("serialization of type \"" .. type(v) .. "\" is not supported")
|
||||
end
|
||||
end
|
||||
|
||||
seen[tbl] = nil
|
||||
|
||||
output = output .. "}"
|
||||
return output
|
||||
end
|
||||
|
||||
local oldtype = type
|
||||
local oldgetmetatable = getmetatable
|
||||
function type(object, trueType)
|
||||
if trueType then return oldtype(object) end
|
||||
if oldtype(object) ~= "table" then
|
||||
return oldtype(object)
|
||||
else
|
||||
if oldtype(oldgetmetatable(object)) == "table" then
|
||||
local metatable = oldgetmetatable(object)
|
||||
---@diagnostic disable-next-line: need-check-nil
|
||||
if metatable.__type then return metatable.__type end
|
||||
else
|
||||
return "table"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function getmetatable(object)
|
||||
if oldtype(object) ~= "table" then return end
|
||||
if oldtype(oldgetmetatable(object)) == "table" then
|
||||
if oldgetmetatable(object).__isuserdata then
|
||||
if oldtype(oldgetmetatable(object).__usermeta) == "function" then
|
||||
return oldgetmetatable(object).__usermeta()
|
||||
else
|
||||
return oldgetmetatable(object).__usermeta
|
||||
end
|
||||
else
|
||||
return oldgetmetatable(object)
|
||||
end
|
||||
else
|
||||
return oldgetmetatable(object)
|
||||
end
|
||||
end
|
||||
|
||||
function isEqualToAny(a, ...)
|
||||
local args = {...}
|
||||
for i = 0, #args do if a == args[i] then return true end end
|
||||
return false
|
||||
end
|
||||
|
||||
function isEqualToAll(a, ...)
|
||||
local args = {...}
|
||||
for i = 0, #args do if a ~= args[i] then return false end end
|
||||
return true
|
||||
end
|
||||
|
||||
function table.keys(t)
|
||||
local a = {}
|
||||
for n in pairs(t) do table.insert(a, n) end
|
||||
return a
|
||||
end
|
||||
|
||||
function table.values(t)
|
||||
local a = {}
|
||||
for _, n in pairs(t) do table.insert(a, n) end
|
||||
return a
|
||||
end
|
||||
|
||||
function table.indexOf(t, value)
|
||||
for i, v in ipairs(t) do if v == value then return i end end
|
||||
return -1
|
||||
end
|
||||
|
||||
function string.replace(s, target, repl)
|
||||
local result = {}
|
||||
local i = 1
|
||||
local n = #s
|
||||
local t_len = #target
|
||||
|
||||
while i <= n do
|
||||
local match = true
|
||||
if i + t_len - 1 <= n then
|
||||
for j = 1, t_len do
|
||||
if s:sub(i + j - 1, i + j - 1) ~= target:sub(j, j) then
|
||||
match = false
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
match = false
|
||||
end
|
||||
|
||||
if match then
|
||||
table.insert(result, repl)
|
||||
i = i + t_len
|
||||
else
|
||||
table.insert(result, s:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(result)
|
||||
end
|
||||
|
||||
function toHex(num)
|
||||
return string.format("%X", num)
|
||||
end
|
||||
|
||||
syscall = setmetatable({}, {
|
||||
__index = function(self, name)
|
||||
return function(...)
|
||||
local res = table.pack(coroutine.yield("syscall", name, ...))
|
||||
if res[1] then
|
||||
return table.unpack(res, 2, res.n)
|
||||
else
|
||||
error(res[2], 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
table.serialize = serialize
|
||||
901
Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod
Normal file
901
Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod
Normal file
@@ -0,0 +1,901 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
local vfs = {}
|
||||
kernel.vfs = vfs
|
||||
vfs.mounts = {["$"] = "/"}
|
||||
vfs.disks = kernel.disks
|
||||
|
||||
-- Metafile format (version 2)
|
||||
-- File header: 1 byte = version (0x02)
|
||||
-- Per-entry:
|
||||
-- 1 byte = name length
|
||||
-- N bytes = name
|
||||
-- 1 byte = entry type (0x00 = regular, 0x01 = symlink)
|
||||
-- 2 bytes = owner uid (little-endian uint16)
|
||||
-- 2 bytes = group gid (little-endian uint16)
|
||||
-- 2 bytes = perms (little-endian uint16)
|
||||
-- bit 0 = world-write bit 1 = world-read
|
||||
-- bit 2 = group-write bit 3 = group-read
|
||||
-- bit 4 = owner-write bit 5 = owner-read
|
||||
-- bit 6 = suid
|
||||
-- bit 7 = world-exec
|
||||
-- bit 8 = group-exec
|
||||
-- bit 9 = owner-exec
|
||||
-- 1 byte = cmeta length
|
||||
-- N bytes = cmeta (for symlinks: the link target path)
|
||||
--
|
||||
-- Version 1:
|
||||
-- 1 byte name len, N bytes name, 1 byte etype, 1 byte owner,
|
||||
-- 1 byte group, 2 bytes perms (little-endian), 1 byte cmeta len, N bytes cmeta
|
||||
--
|
||||
-- Version 0:
|
||||
-- No file header. Per-entry:
|
||||
-- 1 byte name len, N bytes name, 1 byte owner, 1 byte group,
|
||||
-- 1 byte perms (low 7 bits only), 1 byte cmeta len, N bytes cmeta
|
||||
|
||||
local META_VERSION = 0x02
|
||||
|
||||
local function bit_is_set(num, bit)
|
||||
return math.floor(num / (2 ^ bit)) % 2 == 1
|
||||
end
|
||||
|
||||
local function parseMetafile(raw)
|
||||
if not raw or raw == "" then return {} end
|
||||
local ret = {}
|
||||
local p = 1
|
||||
|
||||
local version = 0
|
||||
local firstByte = raw:byte(1)
|
||||
if firstByte == 0x02 or firstByte == 0x01 then
|
||||
version = firstByte
|
||||
p = 2
|
||||
end
|
||||
|
||||
while p <= #raw do
|
||||
if p > #raw then break end
|
||||
local namelen = raw:byte(p); p = p + 1
|
||||
if namelen == 0 or p + namelen - 1 > #raw then break end
|
||||
local name = raw:sub(p, p + namelen - 1); p = p + namelen
|
||||
|
||||
local etype, owner, group, perms, cmeta
|
||||
|
||||
if version == 0x02 then
|
||||
if p + 6 > #raw then break end
|
||||
etype = raw:byte(p); p = p + 1
|
||||
owner = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
|
||||
group = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
|
||||
perms = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
|
||||
elseif version == 0x01 then
|
||||
if p + 4 > #raw then break end
|
||||
etype = raw:byte(p); p = p + 1
|
||||
owner = raw:byte(p); p = p + 1
|
||||
group = raw:byte(p); p = p + 1
|
||||
perms = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
|
||||
else
|
||||
if p + 2 > #raw then break end
|
||||
etype = 0x00
|
||||
owner = raw:byte(p); p = p + 1
|
||||
group = raw:byte(p); p = p + 1
|
||||
perms = raw:byte(p); p = p + 1
|
||||
end
|
||||
|
||||
if p > #raw then break end
|
||||
local cmetalen = raw:byte(p); p = p + 1
|
||||
cmeta = ""
|
||||
if cmetalen > 0 then
|
||||
cmeta = raw:sub(p, p + cmetalen - 1); p = p + cmetalen
|
||||
end
|
||||
|
||||
ret[name] = { etype = etype, owner = owner, group = group,
|
||||
perms = perms, cmeta = cmeta }
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
local function makeMetafile(meta)
|
||||
local out = string.char(META_VERSION)
|
||||
for name, m in pairs(meta) do
|
||||
local plo = m.perms % 256
|
||||
local phi = math.floor(m.perms / 256) % 256
|
||||
local olo = (m.owner or 0) % 256
|
||||
local ohi = math.floor((m.owner or 0) / 256) % 256
|
||||
local glo = (m.group or 0) % 256
|
||||
local ghi = math.floor((m.group or 0) / 256) % 256
|
||||
out = out
|
||||
.. string.char(#name) .. name
|
||||
.. string.char(m.etype or 0x00)
|
||||
.. string.char(olo, ohi, glo, ghi, plo, phi)
|
||||
.. string.char(#m.cmeta) .. m.cmeta
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local SAFE_COMPONENT_PATTERN = "^[A-Za-z0-9_.+%-%@ %(%)%[%] ]+$"
|
||||
|
||||
local function normalizePath(path)
|
||||
local task = kernel.currentTask
|
||||
local cwd = task.cwd or "/"
|
||||
|
||||
if path:sub(1, 1) ~= "/" then
|
||||
path = cwd .. "/" .. path
|
||||
end
|
||||
|
||||
local stack = {}
|
||||
local i = 1
|
||||
local len = #path
|
||||
while i <= len do
|
||||
local j = path:find("/", i, true)
|
||||
local comp
|
||||
if j then
|
||||
comp = path:sub(i, j - 1)
|
||||
i = j + 1
|
||||
else
|
||||
comp = path:sub(i)
|
||||
i = len + 1
|
||||
end
|
||||
|
||||
if comp == "" or comp == "." then
|
||||
elseif comp == ".." then
|
||||
if #stack > 0 then
|
||||
table.remove(stack)
|
||||
end
|
||||
else
|
||||
comp = comp:lower()
|
||||
if not comp:match(SAFE_COMPONENT_PATTERN) then
|
||||
error("EINVAL: illegal characters in path component: " .. comp, 2)
|
||||
end
|
||||
table.insert(stack, comp)
|
||||
end
|
||||
end
|
||||
|
||||
local result = "/" .. table.concat(stack, "/")
|
||||
|
||||
local root = task and task.root
|
||||
if root and root ~= "/" then
|
||||
if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then
|
||||
result = root
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function vfs.splitPath(path)
|
||||
local rv = string.split(path, "/")
|
||||
while table.indexOf(rv, "") ~= -1 do
|
||||
table.remove(rv, table.indexOf(rv, ""))
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
local function resolveMount(normalPath)
|
||||
local mountPoint, mountId = nil, nil
|
||||
for id, mp in pairs(vfs.mounts) do
|
||||
local mpNorm = (mp ~= "/" and mp:sub(-1) == "/") and mp:sub(1,-2) or mp
|
||||
if normalPath == mpNorm
|
||||
or (mpNorm == "/" and normalPath:sub(1,1) == "/")
|
||||
or normalPath:sub(1, #mpNorm + 1) == mpNorm .. "/"
|
||||
then
|
||||
if not mountPoint or #mpNorm > #mountPoint then
|
||||
mountPoint = mpNorm
|
||||
mountId = id
|
||||
end
|
||||
end
|
||||
end
|
||||
if not mountId then error("ENODEV") end
|
||||
local diskPath = normalPath:sub(#mountPoint + 1)
|
||||
if diskPath == "" then diskPath = "/" end
|
||||
return vfs.disks[mountId], diskPath
|
||||
end
|
||||
|
||||
vfs._parseMetafile = parseMetafile
|
||||
|
||||
local function readMetaEntry(disk, parentDiskPath, filename)
|
||||
if filename == ".meta" then error("Cannot open metafile") end
|
||||
local mp
|
||||
if parentDiskPath == "/" then
|
||||
mp = ".meta"
|
||||
else
|
||||
local p = parentDiskPath:gsub("^/+", "")
|
||||
mp = p .. "/.meta"
|
||||
end
|
||||
local ok, f = pcall(function() return disk:open(mp, "r") end)
|
||||
if not ok or not f then return nil end
|
||||
local raw = f.read(65535)
|
||||
if f.close then f.close() end
|
||||
|
||||
if raw and #raw > 0 and raw:byte(1) ~= META_VERSION then
|
||||
local upgraded = makeMetafile(parseMetafile(raw))
|
||||
local wok, wf = pcall(function() return disk:open(mp, "w") end)
|
||||
if wok and wf then wf.write(upgraded); if wf.close then wf.close() end end
|
||||
raw = upgraded
|
||||
end
|
||||
|
||||
local parsed = parseMetafile(raw)
|
||||
return parsed[filename]
|
||||
end
|
||||
|
||||
local MAX_SYMLINK = 16
|
||||
local function resolveSymlinks(path, noFollow, _depth)
|
||||
_depth = _depth or 0
|
||||
if _depth > MAX_SYMLINK then error("ELOOP") end
|
||||
path = normalizePath(path)
|
||||
|
||||
local parts = {}
|
||||
for p in path:gmatch("[^/]+") do table.insert(parts, p) end
|
||||
|
||||
local resolved = ""
|
||||
|
||||
for i, part in ipairs(parts) do
|
||||
local candidate = resolved == "" and ("/" .. part) or (resolved .. "/" .. part)
|
||||
|
||||
if noFollow and i == #parts then
|
||||
resolved = candidate
|
||||
break
|
||||
end
|
||||
|
||||
local disk, parentDisk = resolveMount(resolved == "" and "/" or resolved)
|
||||
local entry = readMetaEntry(disk, parentDisk, part)
|
||||
|
||||
if entry and entry.etype == 0x01 then
|
||||
local target = entry.cmeta
|
||||
if target:sub(1,1) ~= "/" then
|
||||
target = (resolved == "" and "/" or resolved) .. "/" .. target
|
||||
end
|
||||
if i < #parts then
|
||||
target = target .. "/" .. table.concat(parts, "/", i+1, #parts)
|
||||
end
|
||||
return resolveSymlinks(normalizePath(target), noFollow, _depth + 1)
|
||||
end
|
||||
|
||||
resolved = candidate
|
||||
end
|
||||
|
||||
if resolved == "" then resolved = "/" end
|
||||
return resolved
|
||||
end
|
||||
|
||||
local function resolvePath(path, noFollow)
|
||||
local real = resolveSymlinks(path, noFollow)
|
||||
local disk, diskPath = resolveMount(real)
|
||||
if kernel.config.logPathResolution then
|
||||
kernel.log("resolvePath '"..path.."' -> '"..real.."' diskPath '"..diskPath.."'")
|
||||
end
|
||||
return disk, diskPath, real
|
||||
end
|
||||
|
||||
local function getFileMeta(path, noFollow)
|
||||
local real = resolveSymlinks(path, noFollow)
|
||||
|
||||
local parts = {}
|
||||
for p in real:gmatch("[^/]+") do table.insert(parts, p) end
|
||||
|
||||
local default = { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" }
|
||||
if #parts == 0 then return default end
|
||||
|
||||
local parentNorm = "/" .. table.concat(parts, "/", 1, #parts - 1)
|
||||
if parentNorm == "" then parentNorm = "/" end
|
||||
local disk, parentDiskPath = resolveMount(parentNorm)
|
||||
local entry = readMetaEntry(disk, parentDiskPath, parts[#parts])
|
||||
if entry then return entry end
|
||||
return default
|
||||
end
|
||||
|
||||
local function writeMetaEntry(path, name, entry, noFollow)
|
||||
local real = resolveSymlinks(path, noFollow)
|
||||
local disk, diskPath = resolveMount(real)
|
||||
|
||||
local mp
|
||||
if diskPath == "/" then
|
||||
mp = ".meta"
|
||||
else
|
||||
mp = diskPath:gsub("^/+", "") .. "/.meta"
|
||||
end
|
||||
|
||||
local existing = {}
|
||||
local rok, rf = pcall(function() return disk:open(mp, "r") end)
|
||||
if rok and rf then
|
||||
local raw = rf.read(65535)
|
||||
if rf.close then rf.close() end
|
||||
existing = parseMetafile(raw)
|
||||
end
|
||||
|
||||
existing[name] = entry
|
||||
|
||||
local f = disk:open(mp, "w")
|
||||
f.write(makeMetafile(existing))
|
||||
if f.close then f.close() end
|
||||
end
|
||||
|
||||
vfs.P = {
|
||||
OWNER_R = 1 * (2^5), -- 32
|
||||
OWNER_W = 1 * (2^4), -- 16
|
||||
OWNER_X = 1 * (2^9), -- 512
|
||||
GROUP_R = 1 * (2^3), -- 8
|
||||
GROUP_W = 1 * (2^2), -- 4
|
||||
GROUP_X = 1 * (2^8), -- 256
|
||||
WORLD_R = 1 * (2^1), -- 2
|
||||
WORLD_W = 1 * (2^0), -- 1
|
||||
WORLD_X = 1 * (2^7), -- 128
|
||||
SUID = 1 * (2^6), -- 64
|
||||
}
|
||||
|
||||
local P = vfs.P
|
||||
|
||||
vfs.PERM = {
|
||||
RW_R_R = P.OWNER_R + P.OWNER_W + P.GROUP_R + P.WORLD_R, -- 644
|
||||
RWX_RX = P.OWNER_R + P.OWNER_W + P.OWNER_X + P.GROUP_R + P.GROUP_X + P.WORLD_R + P.WORLD_X, -- 755
|
||||
RW_R__ = P.OWNER_R + P.OWNER_W + P.GROUP_R, -- 640
|
||||
RW____ = P.OWNER_R + P.OWNER_W, -- 600
|
||||
RWXR__ = P.OWNER_R + P.OWNER_W + P.OWNER_X + P.GROUP_R + P.WORLD_R, -- 744
|
||||
SUID_755 = P.SUID + P.OWNER_R + P.OWNER_W + P.OWNER_X + P.GROUP_R + P.GROUP_X + P.WORLD_R + P.WORLD_X,
|
||||
RWXRWXRWX = P.OWNER_R+P.OWNER_W+P.OWNER_X+P.GROUP_R+P.GROUP_W+P.GROUP_X+P.WORLD_R+P.WORLD_W+P.WORLD_X,
|
||||
}
|
||||
|
||||
local function checkperms(meta, mode)
|
||||
local task = kernel.currentTask
|
||||
local euid = (task and task.euid) or (task and task.uid) or kernel.uid
|
||||
local groups = (task and task.groups) or kernel.groups or {}
|
||||
|
||||
if euid == 0 then return true end
|
||||
|
||||
local bits = meta.perms
|
||||
|
||||
if mode == "x" then
|
||||
if euid == meta.owner and bit_is_set(bits, 9) then return true end
|
||||
if meta.group then
|
||||
for _, gid in ipairs(groups) do
|
||||
if gid == meta.group and bit_is_set(bits, 8) then return true end
|
||||
end
|
||||
end
|
||||
if bit_is_set(bits, 7) then return true end
|
||||
error("EACCES")
|
||||
end
|
||||
|
||||
local bitmap = {
|
||||
r = {owner = 5, group = 3, everyone = 1},
|
||||
w = {owner = 4, group = 2, everyone = 0},
|
||||
a = {owner = 4, group = 2, everyone = 0},
|
||||
}
|
||||
local m = bitmap[mode]
|
||||
if not m then error("EINVAL") end
|
||||
|
||||
if euid == meta.owner and bit_is_set(bits, m.owner) then return true end
|
||||
if meta.group then
|
||||
for _, gid in ipairs(groups) do
|
||||
if gid == meta.group and bit_is_set(bits, m.group) then return true end
|
||||
end
|
||||
end
|
||||
if bit_is_set(bits, m.everyone) then return true end
|
||||
error("EACCES")
|
||||
end
|
||||
|
||||
local function normalizeMountPoint(path)
|
||||
path = normalizePath(path)
|
||||
if path ~= "/" and path:sub(-1) == "/" then path = path:sub(1,-2) end
|
||||
return path
|
||||
end
|
||||
|
||||
local required = {"open","type","list","attributes","fileExists","makeDirectory","remove"}
|
||||
local function checkDisk(disk)
|
||||
for _, name in ipairs(required) do
|
||||
if type(disk[name]) ~= "function" then
|
||||
error("Invalid disk: missing method '" .. name .. "'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local total = 0
|
||||
local function allocFD(task)
|
||||
local fd = 0
|
||||
while task.fd[fd] do fd = fd + 1 end
|
||||
if fd >= kernel.config.maxFilesPerTask then error("ENFILE") end
|
||||
return fd
|
||||
end
|
||||
local function checkSystemLimit()
|
||||
if total >= kernel.config.maxOpenFiles - 16 then error("ENFILE") end
|
||||
end
|
||||
local function newFileObj(handle, mode, path, meta, ftype)
|
||||
return { handle=handle, mode=mode, path=path, meta=meta, type=ftype, refcount=1 }
|
||||
end
|
||||
|
||||
function vfs.newfd(fdobj)
|
||||
checkSystemLimit(); total = total + 1
|
||||
local fd = allocFD(kernel.currentTask)
|
||||
kernel.currentTask.fd[fd] = fdobj
|
||||
return fd
|
||||
end
|
||||
|
||||
function vfs.mount(target, diskOrId)
|
||||
local _euid = (kernel.currentTask and (kernel.currentTask.euid or kernel.currentTask.uid)) or kernel.uid
|
||||
if _euid ~= 0 then error("EPERM") end
|
||||
if not target then error("EINVAL") end
|
||||
target = normalizeMountPoint(target)
|
||||
if not vfs.exists(target) then vfs.mkdir(target) end
|
||||
if vfs.type(target) ~= "directory" then error("EINVAL") end
|
||||
|
||||
local disk, id
|
||||
if type(diskOrId) == "string" then
|
||||
disk = kernel.disks[diskOrId]
|
||||
if not disk then error("ENODEV") end
|
||||
checkDisk(disk); id = diskOrId
|
||||
elseif type(diskOrId) == "table" then
|
||||
checkDisk(diskOrId); disk = diskOrId
|
||||
id = disk.address; vfs.disks[id] = disk
|
||||
else error("EINVAL") end
|
||||
|
||||
if vfs.mounts[id] then error("EBUSY") end
|
||||
for _, mp in pairs(vfs.mounts) do if mp == target then error("EBUSY") end end
|
||||
vfs.mounts[id] = target
|
||||
return true
|
||||
end
|
||||
|
||||
function vfs.umount(target)
|
||||
local _euid = (kernel.currentTask and (kernel.currentTask.euid or kernel.currentTask.uid)) or kernel.uid
|
||||
if _euid ~= 0 then error("EPERM") end
|
||||
if not target then error("EINVAL") end
|
||||
target = normalizeMountPoint(target)
|
||||
for id, mp in pairs(vfs.mounts) do
|
||||
if mp == target then
|
||||
if id == "$" then error("EBUSY") end
|
||||
vfs.mounts[id] = nil; return true
|
||||
end
|
||||
end
|
||||
error("EINVAL")
|
||||
end
|
||||
|
||||
function vfs.open(path, mode)
|
||||
checkSystemLimit()
|
||||
local task = kernel.currentTask
|
||||
local fd = allocFD(task)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if not disk then error("NODISK") end
|
||||
|
||||
local meta = getFileMeta(path)
|
||||
local isNew = (mode == "w" or mode == "a") and not disk:fileExists(diskPath)
|
||||
|
||||
checkperms(meta, mode == "r" and "r" or "w")
|
||||
|
||||
local handle
|
||||
if disk:type(diskPath) ~= "directory" then
|
||||
handle = disk:open(diskPath, mode)
|
||||
if type(handle) ~= "table" then error("ENFILE") end
|
||||
end
|
||||
|
||||
if isNew then
|
||||
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||
local egid = (task and task.gid) or 0
|
||||
local norm = normalizePath(path)
|
||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||
if parent == "" then parent = "/" end
|
||||
local name = norm:match("[^/]+$")
|
||||
if name then
|
||||
local entry = { etype=0x00, owner=euid, group=egid,
|
||||
perms=vfs.PERM.RW_R_R, cmeta="" }
|
||||
pcall(writeMetaEntry, parent, name, entry, false)
|
||||
meta = entry
|
||||
end
|
||||
end
|
||||
|
||||
local fobj = newFileObj(handle, mode, path, meta, disk:type(diskPath))
|
||||
if mode == "r" and bit_is_set(meta.perms, 6) then
|
||||
fobj.suid_owner = meta.owner
|
||||
end
|
||||
task.fd[fd] = fobj
|
||||
if not disk.isvirt then total = total + 1 end
|
||||
return fd
|
||||
end
|
||||
|
||||
function vfs.read(fd, count)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file or not file.handle or not file.handle.read then error("EBADF") end
|
||||
return file.handle.read(count or 1) or ""
|
||||
end
|
||||
|
||||
function vfs.write(fd, content)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file or not file.handle or not file.handle.write then error("EBADF") end
|
||||
return file.handle.write(content)
|
||||
end
|
||||
|
||||
function vfs.pread(fd, count, offset)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file or not file.handle or not file.handle.read or not file.handle.seek then error("EBADF") end
|
||||
file.handle.seek("set", offset)
|
||||
return file.handle.read(count or 1) or ""
|
||||
end
|
||||
|
||||
function vfs.pwrite(fd, content, offset)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file or not file.handle or not file.handle.write or not file.handle.seek then error("EBADF") end
|
||||
file.handle.seek("set", offset)
|
||||
return file.handle.write(content)
|
||||
end
|
||||
|
||||
function vfs.lseek(fd, offset, whence)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file or not file.handle or not file.handle.seek then error("EBADF") end
|
||||
return file.handle.seek(whence or "set", offset)
|
||||
end
|
||||
|
||||
function vfs.fsync(fd)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file or not file.handle or not file.handle.flush then error("EBADF") end
|
||||
if file.mode ~= "w" and file.mode ~= "a" then error("EBADF") end
|
||||
file.handle.flush()
|
||||
end
|
||||
|
||||
function vfs.close(fd)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
task.fd[fd] = nil
|
||||
total = total - 1
|
||||
file.refcount = file.refcount - 1
|
||||
if file.refcount <= 0 and file.handle and file.handle.close then
|
||||
file.handle.close()
|
||||
end
|
||||
end
|
||||
|
||||
function vfs.sendfile(outfd, infd, count)
|
||||
local inFile = kernel.currentTask.fd[infd]
|
||||
local outFile = kernel.currentTask.fd[outfd]
|
||||
if not inFile or not outFile then error("EBADF") end
|
||||
if not inFile.handle.read or not outFile.handle.write then error("EBADF") end
|
||||
local data = inFile.handle.read(count or 1024)
|
||||
if not data or data == "" then return end
|
||||
return outFile.handle.write(data)
|
||||
end
|
||||
|
||||
function vfs.stat(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
local ok, attrs = pcall(disk.attributes, disk, diskPath)
|
||||
if not ok then attrs = { size=0, modified=0, created=0 } end
|
||||
return {
|
||||
size = attrs.size,
|
||||
modified = attrs.modified,
|
||||
created = attrs.created,
|
||||
owner = meta.owner,
|
||||
group = meta.group,
|
||||
perms = meta.perms,
|
||||
etype = meta.etype,
|
||||
xattr = meta.cmeta,
|
||||
}
|
||||
end
|
||||
|
||||
function vfs.lstat(path)
|
||||
local meta = getFileMeta(path, true)
|
||||
|
||||
local attrs
|
||||
if meta.etype == 0x01 then
|
||||
attrs = { size=0, modified=0, created=0 }
|
||||
else
|
||||
local disk, diskPath = resolvePath(path, true)
|
||||
local ok, a = pcall(disk.attributes, disk, diskPath)
|
||||
attrs = ok and a or { size=0, modified=0, created=0 }
|
||||
end
|
||||
return {
|
||||
size = attrs.size,
|
||||
modified = attrs.modified,
|
||||
created = attrs.created,
|
||||
owner = meta.owner,
|
||||
group = meta.group,
|
||||
perms = meta.perms,
|
||||
etype = meta.etype,
|
||||
xattr = (meta.etype == 0x01) and "" or meta.cmeta,
|
||||
symlink_target = (meta.etype == 0x01) and meta.cmeta or nil,
|
||||
}
|
||||
end
|
||||
|
||||
function vfs.fstat(fd)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
local disk, diskPath = resolvePath(file.path)
|
||||
local attrs = disk:attributes(diskPath)
|
||||
return {
|
||||
size = attrs.size,
|
||||
modified = attrs.modified,
|
||||
created = attrs.created,
|
||||
owner = file.meta.owner,
|
||||
group = file.meta.group,
|
||||
perms = file.meta.perms,
|
||||
etype = file.meta.etype,
|
||||
xattr = file.meta.cmeta,
|
||||
}
|
||||
end
|
||||
|
||||
function vfs.listdir(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
checkperms(meta, "r")
|
||||
if disk:type(diskPath) ~= "directory" then error("ENOTDIR") end
|
||||
|
||||
local list = disk:list(diskPath)
|
||||
local seen = {}
|
||||
local out = {}
|
||||
for _, v in ipairs(list) do
|
||||
if v ~= ".meta" then
|
||||
seen[v] = true
|
||||
table.insert(out, v)
|
||||
end
|
||||
end
|
||||
|
||||
local mp
|
||||
if diskPath == "/" then
|
||||
mp = ".meta"
|
||||
else
|
||||
mp = diskPath:gsub("^/+", "") .. "/.meta"
|
||||
end
|
||||
local lok, lf = pcall(function() return disk:open(mp, "r") end)
|
||||
if lok and lf then
|
||||
local raw = lf.read(65535)
|
||||
if lf.close then lf.close() end
|
||||
local parsed = parseMetafile(raw)
|
||||
for name, entry in pairs(parsed) do
|
||||
if entry.etype == 0x01 and not seen[name] then
|
||||
table.insert(out, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
function vfs.mkdir(path)
|
||||
local norm = normalizePath(path)
|
||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||
if parent == "" then parent = "/" end
|
||||
local parentMeta = getFileMeta(parent)
|
||||
checkperms(parentMeta, "w")
|
||||
local disk, diskPath = resolvePath(path)
|
||||
disk:makeDirectory(diskPath)
|
||||
local task = kernel.currentTask
|
||||
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||
local egid = (task and task.gid) or 0
|
||||
local name = norm:match("[^/]+$")
|
||||
if name then
|
||||
local entry = { etype=0x00, owner=euid, group=egid,
|
||||
perms=vfs.PERM.RWX_RX, cmeta="" }
|
||||
pcall(writeMetaEntry, parent, name, entry, false)
|
||||
end
|
||||
end
|
||||
|
||||
function vfs.remove(path)
|
||||
local norm = resolveSymlinks(path, true)
|
||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||
if parent == "" then parent = "/" end
|
||||
local parentMeta = getFileMeta(parent)
|
||||
checkperms(parentMeta, "w")
|
||||
|
||||
local meta = getFileMeta(path, true)
|
||||
|
||||
if kernel.unixSockets and kernel.unixSockets[path] then
|
||||
kernel.unixSockets[path] = nil
|
||||
end
|
||||
|
||||
if meta.etype == 0x01 then
|
||||
local norm = resolveSymlinks(path, true)
|
||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||
if parent == "" then parent = "/" end
|
||||
local name = norm:match("[^/]+$")
|
||||
local disk, parentDiskPath = resolveMount(parent)
|
||||
local mp
|
||||
if parentDiskPath == "/" then mp = ".meta"
|
||||
else mp = parentDiskPath:gsub("^/+", "") .. "/.meta" end
|
||||
local rok, rf = pcall(function() return disk:open(mp, "r") end)
|
||||
local parsed = {}
|
||||
if rok and rf then
|
||||
local raw = rf.read(65535)
|
||||
if rf.close then rf.close() end
|
||||
parsed = parseMetafile(raw)
|
||||
end
|
||||
parsed[name] = nil
|
||||
local f2 = disk:open(mp, "w")
|
||||
f2.write(makeMetafile(parsed))
|
||||
if f2.close then f2.close() end
|
||||
else
|
||||
local disk, diskPath = resolvePath(path)
|
||||
disk:remove(diskPath)
|
||||
end
|
||||
end
|
||||
|
||||
function vfs.symlink(target, linkPath)
|
||||
if type(target) ~= "string" or type(linkPath) ~= "string" then error("EINVAL") end
|
||||
local norm = normalizePath(linkPath)
|
||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||
if parent == "" then parent = "/" end
|
||||
local name = norm:match("[^/]+$")
|
||||
if not name then error("EINVAL") end
|
||||
|
||||
|
||||
local parentMeta = getFileMeta(parent)
|
||||
checkperms(parentMeta, "w")
|
||||
|
||||
local task = kernel.currentTask
|
||||
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||
local egid = (task and task.gid) or kernel.gid or 0
|
||||
local entry = {
|
||||
etype = 0x01,
|
||||
owner = euid,
|
||||
group = egid,
|
||||
perms = vfs.PERM.RWXRWXRWX,
|
||||
cmeta = target,
|
||||
}
|
||||
local ok, err = pcall(writeMetaEntry, parent, name, entry, false)
|
||||
if not ok then error(err) end
|
||||
end
|
||||
|
||||
function vfs.readlink(path)
|
||||
local meta = getFileMeta(path, true)
|
||||
if meta.etype ~= 0x01 then error("EINVAL") end
|
||||
return meta.cmeta
|
||||
end
|
||||
|
||||
function vfs.access(path, mode)
|
||||
local meta = getFileMeta(path)
|
||||
for i = 1, #mode do
|
||||
checkperms(meta, mode:sub(i,i))
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function updateMeta(path, fn, noFollow)
|
||||
local real = resolveSymlinks(path, noFollow)
|
||||
local norm = real
|
||||
local parent = norm:match("^(.*)/[^/]+$") or "/"
|
||||
if parent == "" then parent = "/" end
|
||||
local name = norm:match("[^/]+$")
|
||||
if not name then error("EINVAL") end
|
||||
|
||||
local disk, parentDisk = resolveMount(parent)
|
||||
local mp
|
||||
if parentDisk == "/" then
|
||||
mp = ".meta"
|
||||
else
|
||||
mp = parentDisk:gsub("^/+", "") .. "/.meta"
|
||||
end
|
||||
|
||||
local existing = {}
|
||||
local uok, uf = pcall(function() return disk:open(mp, "r") end)
|
||||
if uok and uf then
|
||||
local raw = uf.read(65535)
|
||||
if uf.close then uf.close() end
|
||||
existing = parseMetafile(raw)
|
||||
end
|
||||
local entry = existing[name] or { etype=0, owner=0, group=0, perms=63, cmeta="" }
|
||||
fn(entry)
|
||||
existing[name] = entry
|
||||
local f = disk:open(mp, "w"); f.write(makeMetafile(existing)); if f.close then f.close() end
|
||||
end
|
||||
|
||||
function vfs.chmod(path, perms)
|
||||
local meta = getFileMeta(path)
|
||||
local euid = (kernel.currentTask and (kernel.currentTask.euid or kernel.currentTask.uid)) or kernel.uid
|
||||
if euid ~= 0 and euid ~= meta.owner then error("EACCES") end
|
||||
updateMeta(path, function(e) e.perms = perms end)
|
||||
end
|
||||
|
||||
function vfs.fchmod(fd, perms)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
vfs.chmod(file.path, perms)
|
||||
end
|
||||
|
||||
function vfs.chown(path, uid, gid)
|
||||
local _euid = (kernel.currentTask and (kernel.currentTask.euid or kernel.currentTask.uid)) or kernel.uid
|
||||
if _euid ~= 0 then error("EPERM") end
|
||||
updateMeta(path, function(e) e.owner = uid; e.group = gid end)
|
||||
end
|
||||
|
||||
function vfs.fchown(fd, uid, gid)
|
||||
local file = kernel.currentTask.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
vfs.chown(file.path, uid, gid)
|
||||
end
|
||||
|
||||
function vfs.exists(path)
|
||||
local meta = getFileMeta(path, true)
|
||||
if meta.etype == 0x01 then return true end
|
||||
local ok, disk, diskPath = pcall(resolvePath, path)
|
||||
if not ok then return false end
|
||||
return disk:fileExists(diskPath)
|
||||
end
|
||||
|
||||
function vfs.type(path)
|
||||
local meta = getFileMeta(path, true)
|
||||
if meta.etype == 0x01 then return "symlink" end
|
||||
local ok, disk, diskPath = pcall(resolvePath, path)
|
||||
if not ok then return nil end
|
||||
return disk:type(diskPath)
|
||||
end
|
||||
|
||||
function vfs.getcwd() return kernel.currentTask.cwd end
|
||||
function vfs.chdir(path)
|
||||
if vfs.type(path) ~= "directory" then error("ENOTDIR") end
|
||||
kernel.currentTask.cwd = normalizePath(path)
|
||||
end
|
||||
|
||||
function vfs.chroot(path)
|
||||
local euid = (kernel.currentTask and (kernel.currentTask.euid or kernel.currentTask.uid)) or kernel.uid
|
||||
if euid ~= 0 then error("EPERM") end
|
||||
if vfs.type(path) ~= "directory" then error("ENOTDIR") end
|
||||
local norm = normalizePath(path)
|
||||
kernel.currentTask.root = norm
|
||||
kernel.currentTask.cwd = norm
|
||||
end
|
||||
|
||||
function vfs.dup(oldfd)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[oldfd]
|
||||
if not file then error("EBADF") end
|
||||
checkSystemLimit()
|
||||
local newfd = allocFD(task)
|
||||
file.refcount = file.refcount + 1
|
||||
task.fd[newfd] = file
|
||||
total = total + 1
|
||||
return newfd
|
||||
end
|
||||
|
||||
function vfs.dup2(oldfd, newfd)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[oldfd]
|
||||
if not file then error("EBADF") end
|
||||
if newfd < 0 or newfd >= kernel.config.maxFilesPerTask then error("EBADF") end
|
||||
if oldfd == newfd then return newfd end
|
||||
if task.fd[newfd] then vfs.close(newfd) end
|
||||
checkSystemLimit()
|
||||
file.refcount = file.refcount + 1
|
||||
task.fd[newfd] = file
|
||||
total = total + 1
|
||||
return newfd
|
||||
end
|
||||
|
||||
function vfs.devctl(fd, method, ...)
|
||||
if not kernel.currentTask.fd[fd] then error("EBADF") end
|
||||
if not kernel.currentTask.fd[fd].handle[method] then error("EINVAL") end
|
||||
return kernel.currentTask.fd[fd].handle[method](...)
|
||||
end
|
||||
|
||||
vfs.resolveMount = resolveMount
|
||||
|
||||
local sys = kernel.syscalls
|
||||
sys["open"] = vfs.open
|
||||
sys["close"] = vfs.close
|
||||
sys["read"] = vfs.read
|
||||
sys["write"] = vfs.write
|
||||
sys["pread"] = vfs.pread
|
||||
sys["pwrite"] = vfs.pwrite
|
||||
sys["lseek"] = vfs.lseek
|
||||
sys["fsync"] = vfs.fsync
|
||||
sys["sendfile"] = vfs.sendfile
|
||||
sys["stat"] = vfs.stat
|
||||
sys["lstat"] = vfs.lstat
|
||||
sys["fstat"] = vfs.fstat
|
||||
sys["mkdir"] = vfs.mkdir
|
||||
sys["remove"] = vfs.remove
|
||||
sys["listdir"] = vfs.listdir
|
||||
sys["chmod"] = vfs.chmod
|
||||
sys["fchmod"] = vfs.fchmod
|
||||
sys["chown"] = vfs.chown
|
||||
sys["fchown"] = vfs.fchown
|
||||
sys["exists"] = vfs.exists
|
||||
sys["type"] = vfs.type
|
||||
sys["mount"] = vfs.mount
|
||||
sys["umount"] = vfs.umount
|
||||
sys["getcwd"] = vfs.getcwd
|
||||
sys["chdir"] = vfs.chdir
|
||||
sys["chroot"] = vfs.chroot
|
||||
sys["dup"] = vfs.dup
|
||||
sys["dup2"] = vfs.dup2
|
||||
sys["devctl"] = vfs.devctl
|
||||
sys["symlink"] = vfs.symlink
|
||||
sys["readlink"] = vfs.readlink
|
||||
sys["access"] = vfs.access
|
||||
sys["fget_suid"] = function(fd)
|
||||
local fobj = kernel.currentTask and kernel.currentTask.fd[fd]
|
||||
return fobj and fobj.suid_owner or nil
|
||||
end
|
||||
|
||||
kernel.log("VFS module loaded")
|
||||
40
Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod
Normal file
40
Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod
Normal file
@@ -0,0 +1,40 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
local cache = {}
|
||||
kernel.searchpaths = {
|
||||
"/lib/?.lua", "/lib/?", "/usr/lib/?.lua", "/usr/lib/?",
|
||||
"/usr/local/lib/?.lua", "/usr/local/lib/?", "?.lua", "?"
|
||||
}
|
||||
|
||||
function require(module, ...)
|
||||
if cache[module] then return cache[module] end
|
||||
local modpath = module:gsub("%.", "/")
|
||||
local failed = {}
|
||||
for _, path in ipairs(kernel.searchpaths) do
|
||||
local full_path = string.replace(path, "?", modpath)
|
||||
if full_path:sub(1, 1) ~= "/" then
|
||||
full_path = kernel.currentTask.cwd .. full_path
|
||||
end
|
||||
|
||||
if kernel.vfs.exists(full_path) then
|
||||
if kernel.vfs.type(full_path) == "directory" then
|
||||
full_path = full_path .. "/init"
|
||||
end
|
||||
|
||||
if kernel.vfs.exists(full_path) then
|
||||
local handle = kernel.vfs.open(full_path, "r")
|
||||
local file_content = kernel.vfs.read(handle, 1024 * 1024 * 4)
|
||||
kernel.vfs.close(handle)
|
||||
|
||||
return
|
||||
assert(load(file_content, full_path, "t", kernel._U))(...)
|
||||
else
|
||||
table.insert(failed, full_path)
|
||||
end
|
||||
else
|
||||
table.insert(failed, full_path)
|
||||
end
|
||||
end
|
||||
|
||||
error("Module not found: " .. module .. " (searched paths: " .. table.concat(failed, ", ") .. ")")
|
||||
end
|
||||
147
Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod
Normal file
147
Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod
Normal file
@@ -0,0 +1,147 @@
|
||||
--:Minify:--
|
||||
local kernel = ...
|
||||
|
||||
local proxy = {}
|
||||
local data = {}
|
||||
|
||||
proxy.address = "devfs0000"
|
||||
proxy.isvirt = true
|
||||
proxy.isReadOnly = function() return false end
|
||||
proxy.spaceUsed = function() return 0 end
|
||||
proxy.spaceTotal = function() return 0 end
|
||||
proxy.makeDirectory = function() error("EACCES") end
|
||||
proxy.remove = function() error("EACCES") end
|
||||
proxy.setLabel = function() error("EACCES") end
|
||||
proxy.getLabel = function() return "devfs" end
|
||||
proxy.attributes = function(path) return {
|
||||
size = 0,
|
||||
modified = 0,
|
||||
created = 0,
|
||||
} end
|
||||
|
||||
function proxy:open(path, mode)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
for i=1, #steps-1 do
|
||||
local dat = step[steps[i]]
|
||||
if type(dat) ~= "table" then error("ENFILE") end
|
||||
step=dat
|
||||
end
|
||||
if type(step[steps[#steps]]) == "function" then
|
||||
return step[steps[#steps]]("open", mode)
|
||||
end
|
||||
error("ENFILE")
|
||||
end
|
||||
|
||||
function proxy:type(path, mode)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
if #steps == 0 then
|
||||
return "directory"
|
||||
end
|
||||
for i=1, #steps-1 do
|
||||
local dat = step[steps[i]]
|
||||
if type(dat) ~= "table" then error("ENFILE") end
|
||||
step=dat
|
||||
end
|
||||
if type(step[steps[#steps]]) == "function" then
|
||||
return step[steps[#steps]]("type", mode)
|
||||
end
|
||||
if type(step[steps[#steps]]) == "table" then
|
||||
return "directory"
|
||||
end
|
||||
error("ENOENT")
|
||||
end
|
||||
|
||||
function proxy:list(path)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
if #steps == 0 then
|
||||
return table.keys(data)
|
||||
end
|
||||
for i=1, #steps-1 do
|
||||
local dat = step[steps[i]]
|
||||
if type(dat) ~= "table" then error("ENOENT") end
|
||||
step=dat
|
||||
end
|
||||
if type(step[steps[#steps]]) == "table" then
|
||||
return table.keys(step[steps[#steps]])
|
||||
end
|
||||
error("ENOENT")
|
||||
end
|
||||
|
||||
function proxy:fileExists(path)
|
||||
local ok = pcall(function()
|
||||
return self:type(path)
|
||||
end)
|
||||
return ok
|
||||
end
|
||||
|
||||
function data.random(op, mode)
|
||||
if op=="type" then
|
||||
return "character device"
|
||||
elseif op=="open" then
|
||||
if mode=="r" then
|
||||
return {
|
||||
read=function(amount)
|
||||
local str = ""
|
||||
for i=1, amount or 1 do
|
||||
str=str..string.char(math.random(0, 255))
|
||||
end
|
||||
return str
|
||||
end
|
||||
}
|
||||
elseif mode=="w" or mode=="a" then
|
||||
return {
|
||||
write=function() end
|
||||
}
|
||||
else error("EACCES")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function data.null(op, mode)
|
||||
if op=="type" then
|
||||
return "character device"
|
||||
elseif op=="open" then
|
||||
if mode=="r" then
|
||||
return {
|
||||
read=function(amount) end
|
||||
}
|
||||
elseif mode=="w" or mode=="a" then
|
||||
return {
|
||||
write=function() end
|
||||
}
|
||||
else error("EACCES")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function data.zero(op, mode)
|
||||
if op=="type" then
|
||||
return "character device"
|
||||
elseif op=="open" then
|
||||
if mode=="r" then
|
||||
return {
|
||||
read=function(amount)
|
||||
local str = ""
|
||||
for i=1, amount or 1 do
|
||||
str=str..string.char(0)
|
||||
end
|
||||
return str
|
||||
end
|
||||
}
|
||||
elseif mode=="w" or mode=="a" then
|
||||
return {
|
||||
write=function() end
|
||||
}
|
||||
else error("EACCES")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
data["disk"]={}
|
||||
kernel.devfs={}
|
||||
kernel.devfs.data=data
|
||||
kernel.devfs.proxy=proxy
|
||||
kernel.disks["devfs0000"]=proxy
|
||||
133
Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod
Normal file
133
Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod
Normal file
@@ -0,0 +1,133 @@
|
||||
local kernel = ...
|
||||
|
||||
local proxy = {}
|
||||
local data = {}
|
||||
|
||||
proxy.address = "tmpfs0000"
|
||||
proxy.isvirt = true
|
||||
proxy.isReadOnly = function() return false end
|
||||
|
||||
proxy.spaceUsed = function() return 0 end
|
||||
proxy.spaceTotal = function() return 0 end
|
||||
|
||||
proxy.makeDirectory = function(_, path)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
for i=1,#steps do
|
||||
if not step[steps[i]] then
|
||||
step[steps[i]] = {}
|
||||
elseif type(step[steps[i]]) ~= "table" then
|
||||
error("ENOTDIR")
|
||||
end
|
||||
step = step[steps[i]]
|
||||
end
|
||||
end
|
||||
|
||||
proxy.remove = function(_, path)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
for i=1,#steps-1 do
|
||||
step = step[steps[i]]
|
||||
if not step then error("ENOENT") end
|
||||
end
|
||||
step[steps[#steps]] = nil
|
||||
end
|
||||
|
||||
proxy.setLabel = function(_, label) end
|
||||
proxy.getLabel = function() return "tmpfs" end
|
||||
|
||||
proxy.attributes = function(_, path)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
for i=1,#steps do
|
||||
step = step[steps[i]]
|
||||
if not step then error("ENOENT") end
|
||||
end
|
||||
return {
|
||||
size = type(step) == "string" and #step or 0,
|
||||
modified = 0,
|
||||
created = 0,
|
||||
}
|
||||
end
|
||||
|
||||
function proxy:open(path, mode)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
for i=1,#steps-1 do
|
||||
if not step[steps[i]] then
|
||||
if mode == "w" then step[steps[i]] = {} else error("ENOENT") end
|
||||
elseif type(step[steps[i]]) ~= "table" then
|
||||
error("ENOTDIR")
|
||||
end
|
||||
step = step[steps[i]]
|
||||
end
|
||||
local filename = steps[#steps]
|
||||
|
||||
if mode == "r" then
|
||||
if type(step[filename]) ~= "string" then error("ENOENT") end
|
||||
local content = step[filename]
|
||||
local pos = 1
|
||||
return {
|
||||
read = function(amount)
|
||||
amount = amount or #content
|
||||
local chunk = content:sub(pos, pos+amount-1)
|
||||
pos = pos + #chunk
|
||||
return chunk
|
||||
end,
|
||||
close = function() end,
|
||||
}
|
||||
elseif mode == "w" then
|
||||
step[filename] = ""
|
||||
local buf = {}
|
||||
return {
|
||||
write = function(str)
|
||||
buf[#buf + 1] = str
|
||||
end,
|
||||
close = function()
|
||||
step[filename] = table.concat(buf)
|
||||
end,
|
||||
}
|
||||
elseif mode == "a" then
|
||||
if type(step[filename]) ~= "string" then step[filename] = "" end
|
||||
return {
|
||||
write = function(str)
|
||||
step[filename] = step[filename] .. str
|
||||
end,
|
||||
close = function() end,
|
||||
}
|
||||
else
|
||||
error("EACCES")
|
||||
end
|
||||
end
|
||||
|
||||
function proxy:type(path)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
if #steps == 0 then return "directory" end
|
||||
for i=1,#steps do
|
||||
step = step[steps[i]]
|
||||
if not step then return false end
|
||||
end
|
||||
if type(step) == "table" then return "directory" end
|
||||
if type(step) == "string" then return "file" end
|
||||
end
|
||||
|
||||
function proxy:list(path)
|
||||
local steps = kernel.vfs.splitPath(path)
|
||||
local step = data
|
||||
for i=1,#steps do
|
||||
step = step[steps[i]]
|
||||
if not step then error("ENOENT") end
|
||||
end
|
||||
if type(step) ~= "table" then error("ENOTDIR") end
|
||||
local keys = {}
|
||||
for k,_ in pairs(step) do table.insert(keys, k) end
|
||||
return keys
|
||||
end
|
||||
|
||||
function proxy:fileExists(path)
|
||||
local t = self:type(path)
|
||||
return t == "file" or t == "directory"
|
||||
end
|
||||
|
||||
kernel.disks["tmpfs0000"] = proxy
|
||||
540
Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod
Normal file
540
Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod
Normal file
@@ -0,0 +1,540 @@
|
||||
-- :Minify:--
|
||||
-- Loop device driver:
|
||||
--
|
||||
-- BIND (directory) - re-routes VFS calls into a host directory subtree.
|
||||
-- Identical to the original behaviour.
|
||||
--
|
||||
-- IMAGE (*.hfs file) - mounts a Hyperion Filesystem Image. The image is
|
||||
-- loaded entirely into memory; reads and writes operate
|
||||
-- on the in-memory tree, so the image file is only
|
||||
-- touched on attach/detach.
|
||||
--
|
||||
-- BHFS v1 - Binary Hyperion Filesystem Image format:
|
||||
--
|
||||
-- File header (8 bytes):
|
||||
-- [0-3] magic: 0x42 0x48 0x46 0x53 ("BHFS")
|
||||
-- [4] version: 0x01
|
||||
-- [5] flags: bit0 = per-file deflate compression enabled
|
||||
-- [6-7] reserved: 0x00 0x00
|
||||
--
|
||||
-- Records (repeated until END record):
|
||||
-- [0] type: 0x01=file 0x02=dir 0x03=symlink 0xFF=end
|
||||
-- [1-4] path_len (uint32 LE) - byte length of the path string
|
||||
-- [5-8] raw_size (uint32 LE) - original uncompressed data size (0 for dirs)
|
||||
-- [9-12] stored_size (uint32 LE) - bytes that follow in stream
|
||||
-- (< raw_size means deflate-compressed;
|
||||
-- = raw_size means stored as-is)
|
||||
-- [13 .. 13+path_len-1] path bytes (no null terminator)
|
||||
-- [.. +stored_size] data bytes
|
||||
--
|
||||
-- Dirs have raw_size=0, stored_size=0, zero data bytes.
|
||||
-- Symlinks store the target path as data; stored_size == raw_size (no compression).
|
||||
--
|
||||
-- Syscalls:
|
||||
-- id = syscall.losetup(path) attach dir OR .hfs image
|
||||
-- id = syscall.losetup(path, true) force image mode
|
||||
-- syscall.lodetach(id) detach (must be unmounted first)
|
||||
-- tbl = syscall.lolist() {id -> {path,mode}, ...}
|
||||
-- str = syscall.loimgcreate(srcdir) serialise VFS dir -> BHFS binary string
|
||||
-- syscall.loimgwrite(str, dest) write BHFS string to a file (binary)
|
||||
|
||||
local kernel = ...
|
||||
|
||||
local _deflate = nil
|
||||
local function getDeflate()
|
||||
if _deflate == nil then
|
||||
local ok, lib = pcall(require, "store.deflate")
|
||||
_deflate = ok and lib or false
|
||||
end
|
||||
return _deflate or nil
|
||||
end
|
||||
|
||||
local function pack32(n)
|
||||
n = math.floor(n) % 4294967296
|
||||
return string.char(
|
||||
n % 256,
|
||||
math.floor(n / 256) % 256,
|
||||
math.floor(n / 65536) % 256,
|
||||
math.floor(n / 16777216) % 256
|
||||
)
|
||||
end
|
||||
|
||||
local function unpack32(s, i)
|
||||
local a, b, c, d = s:byte(i, i + 3)
|
||||
return (a or 0)
|
||||
+ (b or 0) * 256
|
||||
+ (c or 0) * 65536
|
||||
+ (d or 0) * 16777216
|
||||
end
|
||||
|
||||
local BHFS_MAGIC = "BHFS"
|
||||
local BHFS_VERSION = "\001"
|
||||
local BHFS_FLAG_COMPRESS = 1
|
||||
|
||||
local TYPE_FILE = "\001"
|
||||
local TYPE_DIR = "\002"
|
||||
local TYPE_LINK = "\003"
|
||||
local TYPE_END = "\255"
|
||||
|
||||
local B64D = {}
|
||||
do
|
||||
local a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
for i = 1, #a do B64D[a:sub(i, i)] = i - 1 end
|
||||
end
|
||||
|
||||
local function b64dec(s)
|
||||
s = s:gsub("[^A-Za-z0-9+/=]", "")
|
||||
local t, i = {}, 1
|
||||
while i <= #s do
|
||||
local c1 = B64D[s:sub(i, i )] or 0
|
||||
local c2 = B64D[s:sub(i+1, i+1)] or 0
|
||||
local c3 = B64D[s:sub(i+2, i+2)] or 0
|
||||
local c4 = B64D[s:sub(i+3, i+3)] or 0
|
||||
local n = c1*262144 + c2*4096 + c3*64 + c4
|
||||
t[#t+1] = string.char(math.floor(n/65536) % 256)
|
||||
if s:sub(i+2, i+2) ~= "=" then t[#t+1] = string.char(math.floor(n/256) % 256) end
|
||||
if s:sub(i+3, i+3) ~= "=" then t[#t+1] = string.char(n % 256) end
|
||||
i = i + 4
|
||||
end
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
local loopDevs = {}
|
||||
local nextLoop = 0
|
||||
|
||||
local function makeBindDisk(id, dirPath)
|
||||
local disk = { address = id, isvirt = false }
|
||||
disk.isReadOnly = function() return false end
|
||||
disk.spaceUsed = function() return 0 end
|
||||
disk.spaceTotal = function() return 0 end
|
||||
disk.setLabel = function() end
|
||||
disk.getLabel = function() return id end
|
||||
|
||||
local function resolveBase()
|
||||
local mp, mid = "/", "$"
|
||||
for id2, m in pairs(kernel.vfs.mounts) do
|
||||
if dirPath == m or (m == "/" and dirPath:sub(1,1) == "/")
|
||||
or dirPath:sub(1, #m+1) == m.."/" then
|
||||
if #m > #mp then mp = m; mid = id2 end
|
||||
end
|
||||
end
|
||||
return kernel.vfs.disks[mid], dirPath:sub(#mp+1)
|
||||
end
|
||||
|
||||
local function dp(path)
|
||||
local hd, base = resolveBase()
|
||||
local b = (base == "" or base == "/") and "" or base:gsub("^/+","")
|
||||
local p = path:gsub("^/+","")
|
||||
local c = ((b=="") and "/"..p or "/"..b.."/"..p):gsub("//+","/")
|
||||
local r = c:sub(2); if r == "" then r = "/" end
|
||||
return hd, r
|
||||
end
|
||||
|
||||
function disk:open(path,mode) local h,r=dp(path); return h:open(r,mode) end
|
||||
function disk:type(path) local h,r=dp(path); return h:type(r) end
|
||||
function disk:list(path) local h,r=dp(path); return h:list(r) end
|
||||
function disk:fileExists(path) local h,r=dp(path); return h:fileExists(r) end
|
||||
function disk:attributes(path) local h,r=dp(path); return h:attributes(r) end
|
||||
function disk:makeDirectory(path) local h,r=dp(path); return h:makeDirectory(r) end
|
||||
function disk:remove(path) local h,r=dp(path); return h:remove(r) end
|
||||
return disk
|
||||
end
|
||||
|
||||
local function makeImageDisk(id, imageStr)
|
||||
local root = { kind="dir", children={} }
|
||||
|
||||
local function getNode(path, create)
|
||||
local parts = {}
|
||||
for p in path:gmatch("[^/]+") do parts[#parts+1] = p end
|
||||
local node = root
|
||||
for i = 1, #parts do
|
||||
local name = parts[i]
|
||||
if not node.children then
|
||||
if not create then return nil end
|
||||
node.children = {}
|
||||
end
|
||||
if not node.children[name] then
|
||||
if not create then return nil end
|
||||
node.children[name] = { kind="dir", children={} }
|
||||
end
|
||||
node = node.children[name]
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
local function ensureParent(path)
|
||||
local par = path:match("^(.*)/[^/]+$") or ""
|
||||
if par ~= "" then
|
||||
local n = getNode(par, true)
|
||||
if not n.children then n.children = {} end
|
||||
end
|
||||
end
|
||||
|
||||
if imageStr:sub(1, 4) == BHFS_MAGIC then
|
||||
local pos = 9
|
||||
|
||||
while pos <= #imageStr do
|
||||
local rtype = imageStr:sub(pos, pos)
|
||||
pos = pos + 1
|
||||
|
||||
if rtype == TYPE_END then break end
|
||||
|
||||
local path_len = unpack32(imageStr, pos); pos = pos + 4
|
||||
local raw_size = unpack32(imageStr, pos); pos = pos + 4
|
||||
local stored_size = unpack32(imageStr, pos); pos = pos + 4
|
||||
|
||||
local path = imageStr:sub(pos, pos + path_len - 1)
|
||||
pos = pos + path_len
|
||||
|
||||
local stored_data = imageStr:sub(pos, pos + stored_size - 1)
|
||||
pos = pos + stored_size
|
||||
local data = stored_data
|
||||
if stored_size < raw_size then
|
||||
local deflate = getDeflate()
|
||||
if deflate then
|
||||
data = deflate.decompress(stored_data) or stored_data
|
||||
end
|
||||
end
|
||||
|
||||
if rtype == TYPE_DIR then
|
||||
if path ~= "" and path ~= "/" then
|
||||
ensureParent(path)
|
||||
local n = getNode(path, true)
|
||||
n.kind = "dir"; n.children = n.children or {}
|
||||
end
|
||||
elseif rtype == TYPE_FILE then
|
||||
ensureParent(path)
|
||||
local n = getNode(path, true)
|
||||
n.kind="file"; n.data=data; n.size=#data; n.children=nil
|
||||
elseif rtype == TYPE_LINK then
|
||||
ensureParent(path)
|
||||
local n = getNode(path, true)
|
||||
n.kind="link"; n.target=data; n.children=nil
|
||||
end
|
||||
end
|
||||
else
|
||||
for line in (imageStr.."\n"):gmatch("([^\n]*)\n") do
|
||||
if line == "END" then
|
||||
break
|
||||
elseif line:sub(1,4) == "DIR " then
|
||||
local p = line:sub(5):match("^%s*(.-)%s*$")
|
||||
if p and p ~= "" and p ~= "/" then
|
||||
ensureParent(p)
|
||||
local n = getNode(p, true)
|
||||
n.kind = "dir"; n.children = n.children or {}
|
||||
end
|
||||
elseif line:sub(1,5) == "FILE " then
|
||||
local p, sz, body = line:sub(6):match("^(%S+)%s+(%d+)%s*(.-)%s*$")
|
||||
if p then
|
||||
ensureParent(p)
|
||||
local data = (tonumber(sz) or 0) > 0 and b64dec(body) or ""
|
||||
local n = getNode(p, true)
|
||||
n.kind="file"; n.data=data; n.size=#data; n.children=nil
|
||||
end
|
||||
elseif line:sub(1,5) == "LINK " then
|
||||
local p, tgt = line:sub(6):match("^(%S+)%s+(.+)$")
|
||||
if p then
|
||||
ensureParent(p)
|
||||
local n = getNode(p, true)
|
||||
n.kind="link"; n.target=tgt; n.children=nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local disk = { address=id, isvirt=false }
|
||||
disk.isReadOnly = function() return false end
|
||||
disk.spaceTotal = function() return 1024*1024*64 end
|
||||
disk.spaceUsed = function()
|
||||
local tot = 0
|
||||
local function w(n)
|
||||
if n.kind=="file" then tot = tot + (n.size or 0)
|
||||
elseif n.kind=="dir" then for _,c in pairs(n.children or {}) do w(c) end end
|
||||
end
|
||||
w(root); return tot
|
||||
end
|
||||
disk.setLabel = function() end
|
||||
disk.getLabel = function() return id end
|
||||
|
||||
local function norm(path)
|
||||
return path:gsub("^/+",""):gsub("/+$","")
|
||||
end
|
||||
|
||||
function disk:type(path)
|
||||
local p = norm(path)
|
||||
if p == "" then return "directory" end
|
||||
local n = getNode(p)
|
||||
if not n then return nil end
|
||||
if n.kind == "dir" then return "directory" end
|
||||
return "file"
|
||||
end
|
||||
|
||||
function disk:fileExists(path)
|
||||
local p = norm(path)
|
||||
if p == "" then return true end
|
||||
return getNode(p) ~= nil
|
||||
end
|
||||
|
||||
function disk:list(path)
|
||||
local p = norm(path)
|
||||
local node = (p=="") and root or getNode(p)
|
||||
if not node or node.kind ~= "dir" then return {} end
|
||||
local out = {}
|
||||
for name in pairs(node.children or {}) do out[#out+1] = name end
|
||||
return out
|
||||
end
|
||||
|
||||
function disk:attributes(path)
|
||||
local p = norm(path)
|
||||
local node = (p=="") and root or getNode(p)
|
||||
if not node then return nil end
|
||||
return {
|
||||
size = node.kind=="file" and (node.size or 0) or 0,
|
||||
isDir = node.kind=="dir",
|
||||
isReadOnly = false,
|
||||
created = 0,
|
||||
modified = 0,
|
||||
}
|
||||
end
|
||||
|
||||
function disk:open(path, mode)
|
||||
local p = norm(path)
|
||||
local node = getNode(p)
|
||||
|
||||
if mode == "r" then
|
||||
if not node or node.kind ~= "file" then error("ENOENT: "..path) end
|
||||
local data, pos = node.data or "", 1
|
||||
return {
|
||||
read = function(n)
|
||||
if pos > #data then return nil end
|
||||
local chunk = data:sub(pos, pos + (n or 1) - 1)
|
||||
pos = pos + #chunk; return chunk
|
||||
end,
|
||||
readAll = function()
|
||||
local all = data:sub(pos); pos = #data + 1; return all
|
||||
end,
|
||||
readLine = function()
|
||||
if pos > #data then return nil end
|
||||
local nl = data:find("\n", pos, true)
|
||||
local line
|
||||
if nl then line=data:sub(pos, nl-1); pos=nl+1
|
||||
else line=data:sub(pos); pos=#data+1 end
|
||||
return line
|
||||
end,
|
||||
seek = function(w, o)
|
||||
o = o or 0
|
||||
if w == "set" then pos = o + 1
|
||||
elseif w == "cur" then pos = pos + o
|
||||
elseif w == "end" then pos = #data + 1 + o end
|
||||
return pos - 1
|
||||
end,
|
||||
close = function() end,
|
||||
}
|
||||
|
||||
elseif mode == "w" or mode == "a" then
|
||||
local buf = (mode=="a" and node and node.kind=="file")
|
||||
and {node.data or ""} or {}
|
||||
local done = false
|
||||
local function commit()
|
||||
if done then return end; done = true
|
||||
local data = table.concat(buf)
|
||||
if not node then ensureParent(p); node = getNode(p, true) end
|
||||
node.kind="file"; node.data=data; node.size=#data; node.children=nil
|
||||
end
|
||||
return {
|
||||
write = function(s) buf[#buf+1] = tostring(s) end,
|
||||
writeLine = function(s) buf[#buf+1] = tostring(s).."\n" end,
|
||||
flush = function() end,
|
||||
close = commit,
|
||||
}
|
||||
else
|
||||
error("EINVAL: unknown mode: "..tostring(mode))
|
||||
end
|
||||
end
|
||||
|
||||
function disk:makeDirectory(path)
|
||||
local p = norm(path)
|
||||
if p == "" then return end
|
||||
ensureParent(p)
|
||||
local n = getNode(p, true)
|
||||
n.kind="dir"; n.children=n.children or {}; n.data=nil; n.size=nil
|
||||
end
|
||||
|
||||
function disk:remove(path)
|
||||
local p = norm(path)
|
||||
if p == "" then error("EBUSY: cannot remove root") end
|
||||
local par = p:match("^(.*)/[^/]+$") or ""
|
||||
local name = p:match("([^/]+)$")
|
||||
local pn = (par=="") and root or getNode(par)
|
||||
if pn and pn.children then pn.children[name] = nil end
|
||||
end
|
||||
|
||||
disk._root = root
|
||||
return disk
|
||||
end
|
||||
|
||||
local function serializeDir(srcPath)
|
||||
local deflate = getDeflate()
|
||||
local useCompress = deflate ~= nil
|
||||
|
||||
local flags = useCompress and BHFS_FLAG_COMPRESS or 0
|
||||
local parts = {
|
||||
BHFS_MAGIC,
|
||||
BHFS_VERSION,
|
||||
string.char(flags),
|
||||
"\0\0",
|
||||
}
|
||||
|
||||
srcPath = srcPath:gsub("/$", "")
|
||||
|
||||
local MIN_COMPRESS = 64
|
||||
|
||||
local function walk(vpath)
|
||||
local ftype = kernel.vfs.type(vpath)
|
||||
|
||||
if ftype == "directory" then
|
||||
if vpath ~= srcPath then
|
||||
local relPath = vpath:sub(#srcPath + 1)
|
||||
parts[#parts+1] = TYPE_DIR
|
||||
parts[#parts+1] = pack32(#relPath)
|
||||
parts[#parts+1] = pack32(0)
|
||||
parts[#parts+1] = pack32(0)
|
||||
parts[#parts+1] = relPath
|
||||
end
|
||||
local ok, entries = pcall(kernel.vfs.listdir, vpath)
|
||||
if ok and entries then
|
||||
table.sort(entries)
|
||||
for _, name in ipairs(entries) do
|
||||
walk(vpath:gsub("/$","").."/"..name)
|
||||
end
|
||||
end
|
||||
|
||||
elseif ftype == "file" then
|
||||
local relPath = vpath:sub(#srcPath + 1)
|
||||
local ok, fd = pcall(kernel.vfs.open, vpath, "r")
|
||||
if ok then
|
||||
local rawData = ""
|
||||
local ok2, content = pcall(kernel.vfs.read, fd, 1024*1024)
|
||||
if ok2 then rawData = content or "" end
|
||||
pcall(kernel.vfs.close, fd)
|
||||
|
||||
local storedData = rawData
|
||||
if useCompress and #rawData >= MIN_COMPRESS then
|
||||
local compressed = deflate.compress(rawData)
|
||||
if compressed and #compressed < #rawData then
|
||||
storedData = compressed
|
||||
end
|
||||
end
|
||||
|
||||
parts[#parts+1] = TYPE_FILE
|
||||
parts[#parts+1] = pack32(#relPath)
|
||||
parts[#parts+1] = pack32(#rawData)
|
||||
parts[#parts+1] = pack32(#storedData)
|
||||
parts[#parts+1] = relPath
|
||||
parts[#parts+1] = storedData
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
walk(srcPath)
|
||||
|
||||
parts[#parts+1] = TYPE_END
|
||||
|
||||
return table.concat(parts)
|
||||
end
|
||||
|
||||
kernel.syscalls["losetup"] = function(filePath, forceImage)
|
||||
if not filePath then error("EINVAL") end
|
||||
local task = kernel.currentTask
|
||||
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||
if euid ~= 0 then error("EPERM") end
|
||||
|
||||
filePath = filePath:gsub("/$", "")
|
||||
local id = "loop" .. tostring(nextLoop)
|
||||
nextLoop = nextLoop + 1
|
||||
|
||||
local ftype = kernel.vfs.type(filePath)
|
||||
local disk, mode
|
||||
|
||||
if not forceImage and ftype == "directory" then
|
||||
disk = makeBindDisk(id, filePath)
|
||||
mode = "bind"
|
||||
elseif ftype == "file" or forceImage then
|
||||
if ftype ~= "file" then error("ENOENT: not a file: "..filePath) end
|
||||
|
||||
local img
|
||||
local ok, fd = pcall(kernel.vfs.open, filePath, "rb")
|
||||
if ok then
|
||||
local ok2, data = pcall(kernel.vfs.read, fd, 1024*1024*16)
|
||||
pcall(kernel.vfs.close, fd)
|
||||
if ok2 and data then img = data end
|
||||
end
|
||||
if not img then
|
||||
local ok2, fd2 = pcall(kernel.vfs.open, filePath, "r")
|
||||
if not ok2 then error("EIO: cannot open image: "..filePath) end
|
||||
local ok3, data = pcall(kernel.vfs.read, fd2, 1024*1024*16)
|
||||
pcall(kernel.vfs.close, fd2)
|
||||
if not ok3 or not data then error("EIO: cannot read image: "..filePath) end
|
||||
img = data
|
||||
end
|
||||
|
||||
disk = makeImageDisk(id, img)
|
||||
mode = "image"
|
||||
else
|
||||
error("EINVAL: path must be a directory or .hfs image file")
|
||||
end
|
||||
|
||||
kernel.vfs.disks[id] = disk
|
||||
loopDevs[id] = { path=filePath, disk=disk, mode=mode }
|
||||
kernel.log("losetup: attached "..id.." ("..mode..") -> "..filePath, "INFO")
|
||||
return id
|
||||
end
|
||||
|
||||
kernel.syscalls["lodetach"] = function(id)
|
||||
local task = kernel.currentTask
|
||||
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||
if euid ~= 0 then error("EPERM") end
|
||||
|
||||
if not loopDevs[id] then error("ENXIO") end
|
||||
for mid in pairs(kernel.vfs.mounts) do
|
||||
if mid == id then error("EBUSY: loop device is still mounted") end
|
||||
end
|
||||
kernel.vfs.disks[id] = nil
|
||||
loopDevs[id] = nil
|
||||
kernel.log("lodetach: detached "..id, "INFO")
|
||||
end
|
||||
|
||||
kernel.syscalls["lolist"] = function()
|
||||
local rv = {}
|
||||
for id, info in pairs(loopDevs) do
|
||||
rv[id] = { path=info.path, mode=info.mode }
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
kernel.syscalls["loimgcreate"] = function(srcPath)
|
||||
local task = kernel.currentTask
|
||||
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||
if euid ~= 0 then error("EPERM") end
|
||||
if not srcPath then error("EINVAL") end
|
||||
if kernel.vfs.type(srcPath) ~= "directory" then error("ENOTDIR: "..srcPath) end
|
||||
return serializeDir(srcPath)
|
||||
end
|
||||
|
||||
kernel.syscalls["loimgwrite"] = function(imgStr, destPath)
|
||||
local task = kernel.currentTask
|
||||
local euid = (task and (task.euid or task.uid)) or kernel.uid
|
||||
if euid ~= 0 then error("EPERM") end
|
||||
if not imgStr or not destPath then error("EINVAL") end
|
||||
|
||||
local ok, fd = pcall(kernel.vfs.open, destPath, "wb")
|
||||
if not ok then
|
||||
ok, fd = pcall(kernel.vfs.open, destPath, "w")
|
||||
if not ok then error("EIO: cannot write: "..tostring(destPath)) end
|
||||
end
|
||||
local ok2, werr = pcall(kernel.vfs.write, fd, imgStr)
|
||||
pcall(kernel.vfs.close, fd)
|
||||
if not ok2 then error("EIO: write failed: "..tostring(werr)) end
|
||||
end
|
||||
|
||||
kernel.log("Loop device driver loaded (bind + BHFS binary image + legacy HFS compat)")
|
||||
22
Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod
Normal file
22
Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod
Normal file
@@ -0,0 +1,22 @@
|
||||
---- :Minify:--
|
||||
--local kernel = ...
|
||||
--
|
||||
--local timeout = false
|
||||
--kernel.processes.keventd = function()
|
||||
-- while true do
|
||||
-- local event = {kernel.computer:getMachineEvent()}
|
||||
-- if event[1] then
|
||||
-- if event[1] == "keyTyped" then
|
||||
-- if event[3] == "\x1b^s" then
|
||||
-- kernel.shutdown()
|
||||
-- elseif event[3] == "\x1b^r" then
|
||||
-- kernel.reboot()
|
||||
-- end
|
||||
-- end
|
||||
-- timeout = false
|
||||
-- else
|
||||
-- timeout = true
|
||||
-- end
|
||||
-- if timeout then sleep(.05) end
|
||||
-- end
|
||||
--end
|
||||
34
Src/Hyperion-kernel/lib/modules/hyperion/19_fstab.kmod
Normal file
34
Src/Hyperion-kernel/lib/modules/hyperion/19_fstab.kmod
Normal file
@@ -0,0 +1,34 @@
|
||||
--:Minify:--
|
||||
local kernel = ...
|
||||
|
||||
local function trim(str)
|
||||
local s, e = 1, #str
|
||||
while s <= e and (str:sub(s,s) == " " or str:sub(s,s) == "\t") do s = s + 1 end
|
||||
while e >= s and (str:sub(e,e) == " " or str:sub(e,e) == "\t" or str:sub(e,e) == "\n" or str:sub(e,e) == "\r") do e = e - 1 end
|
||||
if s > e then return "" end
|
||||
return str:sub(s,e)
|
||||
end
|
||||
|
||||
for _, line in ipairs(string.split(kernel.fstab, "\n")) do
|
||||
line = trim(line)
|
||||
if line ~= "" and line:sub(1,1) == "U" then
|
||||
local semicolon_pos
|
||||
for i = 3, #line do
|
||||
if line:sub(i,i) == ";" then
|
||||
semicolon_pos = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not semicolon_pos or semicolon_pos == 3 then
|
||||
kernel.log("Invalid fstab line: "..line.." ... Skipping.", "WARN", 8)
|
||||
else
|
||||
local id = line:sub(3, semicolon_pos - 1)
|
||||
local path = trim(line:sub(semicolon_pos + 1))
|
||||
kernel.log("Mounted "..id.." to "..path)
|
||||
if id ~= "$" then
|
||||
kernel.vfs.mount(path, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
Src/Hyperion-kernel/lib/modules/hyperion/20_signals.kmod
Normal file
27
Src/Hyperion-kernel/lib/modules/hyperion/20_signals.kmod
Normal file
@@ -0,0 +1,27 @@
|
||||
--:Minify:--
|
||||
local kernel = ...
|
||||
local signal = {}
|
||||
kernel.signal=signal
|
||||
|
||||
function signal.sigsend(pid, sig)
|
||||
if sig<0 or sig>256 then error("EINVAL") end
|
||||
local task = kernel.tasks[tostring(pid)]
|
||||
if not task then error("ENOENT") end
|
||||
if not task.sigq then return end
|
||||
task.sigq[#task.sigq+1] = sig
|
||||
end
|
||||
|
||||
function signal.sigcatch(handler)
|
||||
kernel.currentTask.sigh=handler
|
||||
if not kernel.currentTask.sigq then kernel.currentTask.sigq={} end
|
||||
end
|
||||
|
||||
function signal.sigignore()
|
||||
kernel.currentTask.sigh=nil
|
||||
kernel.currentTask.sigq=nil
|
||||
end
|
||||
|
||||
local s=kernel.syscalls
|
||||
s["sigsend"] = signal.sigsend
|
||||
s["sigcatch"] = signal.sigcatch
|
||||
s["sigignore"] = signal.sigignore
|
||||
556
Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod
Normal file
556
Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod
Normal file
@@ -0,0 +1,556 @@
|
||||
-- :Minify:--
|
||||
-- Supports:
|
||||
-- AF_UNIX - local IPC via /var/run/*.sock paths
|
||||
-- AF_INET - network sockets with three backends:
|
||||
-- rednet://0.0.B.C or rednet+PROTO://0.0.B.C -> CC rednet (computer B*256+C)
|
||||
-- modem://0.0.B.C -> raw CC modem frames
|
||||
-- http://host/path or https://... -> HTTP via CC http API
|
||||
-- A.B.C.D (dotted quad, non-zero A) -> HTTP
|
||||
--
|
||||
-- Socket lifecycle:
|
||||
-- fd = syscall.socket(domain, socktype) -- "unix"/"inet", "stream"/"dgram"
|
||||
-- syscall.bind(fd, address) -- server: claim address
|
||||
-- syscall.listen(fd, backlog) -- server: mark as listening
|
||||
-- cfd = syscall.accept(fd) -- server: get connected client fd (blocking poll)
|
||||
-- syscall.connect(fd, address) -- client: connect to server
|
||||
-- syscall.send(fd, data) -- send bytes
|
||||
-- syscall.recv(fd, len) -- receive bytes (blocking poll, returns "" on nothing)
|
||||
-- syscall.sockshutdown(fd) -- half-close send side
|
||||
-- -- normal vfs.close(fd) closes the socket
|
||||
|
||||
local kernel = ...
|
||||
|
||||
local sockets = {}
|
||||
local unixSocks = {}
|
||||
local nextSockId = 1
|
||||
|
||||
local function allocSockId()
|
||||
local id = nextSockId
|
||||
nextSockId = nextSockId + 1
|
||||
return id
|
||||
end
|
||||
|
||||
local function parseAddress(addr)
|
||||
if not addr then error("EINVAL") end
|
||||
|
||||
if addr:sub(1,1) == "/" or addr:sub(1,5) == "unix:" then
|
||||
local path = addr:sub(1,5) == "unix:" and addr:sub(6) or addr
|
||||
return { backend="unix", path=path }
|
||||
end
|
||||
|
||||
local rproto, raddr = addr:match("^rednet%+?([^:/]*)://(.+)$")
|
||||
if raddr then
|
||||
local a,b,c,d = raddr:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
||||
if not a then error("EINVAL: bad rednet address " .. raddr) end
|
||||
local compId = tonumber(c)*256 + tonumber(d)
|
||||
return { backend="rednet", compId=compId,
|
||||
protocol=(rproto ~= "" and rproto or "hyperion") }
|
||||
end
|
||||
|
||||
local maddr = addr:match("^modem://(.+)$")
|
||||
if maddr then
|
||||
local a,b,c,d = maddr:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
||||
if not a then error("EINVAL: bad modem address " .. maddr) end
|
||||
local compId = tonumber(c)*256 + tonumber(d)
|
||||
local port = tonumber(maddr:match(":(%d+)$")) or 0
|
||||
return { backend="modem", compId=compId, port=port }
|
||||
end
|
||||
|
||||
local scheme, rest = addr:match("^(https?)://(.+)$")
|
||||
if scheme then
|
||||
return { backend=scheme, url=addr }
|
||||
end
|
||||
|
||||
local a,b,c,d = addr:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)")
|
||||
if a and tonumber(a) ~= 0 then
|
||||
return { backend="http", url="http://" .. addr }
|
||||
end
|
||||
|
||||
error("EINVAL: unrecognised address format: " .. tostring(addr))
|
||||
end
|
||||
|
||||
local rednetOpen = false
|
||||
local function ensureRednet()
|
||||
if rednetOpen then return end
|
||||
local rn = kernel.apis and kernel.apis.rednet
|
||||
if not rn then error("ENODEV: no rednet API available") end
|
||||
local peripheral = kernel.apis.peripheral
|
||||
if peripheral then
|
||||
for _, name in ipairs(peripheral.getNames and peripheral.getNames() or {}) do
|
||||
if peripheral.getType(name) == "modem" then
|
||||
pcall(rn.open, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
rednetOpen = true
|
||||
end
|
||||
|
||||
local function getModem()
|
||||
local peripheral = kernel.apis and kernel.apis.peripheral
|
||||
if not peripheral then error("ENODEV") end
|
||||
for _, name in ipairs(peripheral.getNames and peripheral.getNames() or {}) do
|
||||
if peripheral.getType(name) == "modem" then
|
||||
local m = peripheral.wrap(name)
|
||||
if m then return m, name end
|
||||
end
|
||||
end
|
||||
error("ENODEV: no modem peripheral found")
|
||||
end
|
||||
|
||||
local function pumpEvents()
|
||||
local ev = kernel.computer:getMachineEvent()
|
||||
while ev do
|
||||
if ev == "rednet_message" then
|
||||
for _, sock in pairs(sockets) do
|
||||
if sock.backend == "rednet" and sock.bound then
|
||||
if sock.address.protocol == tostring(select(4, table.unpack({ev}))) or
|
||||
sock.address.protocol == "hyperion" then
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
ev = kernel.computer:getMachineEvent()
|
||||
end
|
||||
end
|
||||
|
||||
local function pollEvent()
|
||||
local results = table.pack(kernel.computer:getMachineEvent())
|
||||
if results.n == 0 or results[1] == nil then return nil end
|
||||
return results
|
||||
end
|
||||
|
||||
local function dispatchEvent(ev)
|
||||
if not ev then return end
|
||||
local evtype = ev[1]
|
||||
|
||||
if evtype == "rednet_message" then
|
||||
local senderId = ev[2]
|
||||
local message = ev[3]
|
||||
local protocol = ev[4] or "hyperion"
|
||||
for _, sock in pairs(sockets) do
|
||||
if sock.backend == "rednet" and (sock.listening or sock.connected) then
|
||||
if sock.address and sock.address.protocol == protocol then
|
||||
table.insert(sock.rxbuf, { from=senderId, data=message })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif evtype == "modem_message" then
|
||||
local channel = ev[3]
|
||||
local msg = ev[5]
|
||||
local fromCh = ev[4]
|
||||
for _, sock in pairs(sockets) do
|
||||
if sock.backend == "modem" and sock.modemChannel == channel then
|
||||
table.insert(sock.rxbuf, { from=fromCh, data=msg })
|
||||
end
|
||||
end
|
||||
|
||||
elseif evtype == "http_success" then
|
||||
local url = ev[2]
|
||||
local handle = ev[3]
|
||||
for _, sock in pairs(sockets) do
|
||||
if sock.backend == "http" or sock.backend == "https" then
|
||||
if sock.pendingUrl == url then
|
||||
local body = handle.readAll and handle.readAll() or ""
|
||||
handle.close()
|
||||
table.insert(sock.rxbuf, { data=body, done=true })
|
||||
sock.pendingUrl = nil
|
||||
sock.connected = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif evtype == "http_failure" then
|
||||
local url = ev[2]
|
||||
local err = ev[3]
|
||||
for _, sock in pairs(sockets) do
|
||||
if (sock.backend == "http" or sock.backend == "https") and
|
||||
sock.pendingUrl == url then
|
||||
sock.error = err
|
||||
sock.pendingUrl = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function pumpAll()
|
||||
local ev = pollEvent()
|
||||
while ev do
|
||||
dispatchEvent(ev)
|
||||
ev = pollEvent()
|
||||
end
|
||||
end
|
||||
|
||||
local function newSocket(domain, socktype)
|
||||
local sock = {
|
||||
id = allocSockId(),
|
||||
domain = domain, -- "unix" | "inet"
|
||||
socktype = socktype, -- "stream" | "dgram"
|
||||
backend = nil,
|
||||
state = "idle", -- idle | bound | listening | connected | closed
|
||||
rxbuf = {},
|
||||
txbuf = {},
|
||||
backlog = {},
|
||||
address = nil,
|
||||
peer = nil,
|
||||
modemChannel = nil,
|
||||
modem = nil,
|
||||
pendingUrl = nil,
|
||||
bound = false,
|
||||
listening = false,
|
||||
connected = false,
|
||||
error = nil,
|
||||
}
|
||||
sockets[sock.id] = sock
|
||||
return sock
|
||||
end
|
||||
|
||||
local sockSend, sockClose
|
||||
|
||||
local function socketToFd(sock)
|
||||
return {
|
||||
isSocket = true,
|
||||
sockId = sock.id,
|
||||
mode = "rw",
|
||||
meta = { etype=0, owner=0, group=0, perms=0x1FF, cmeta="" },
|
||||
type = "socket",
|
||||
refcount = 1,
|
||||
handle = {
|
||||
read = function(count)
|
||||
pumpAll()
|
||||
if #sock.rxbuf == 0 then return "" end
|
||||
local item = table.remove(sock.rxbuf, 1)
|
||||
local data = type(item) == "table" and (item.data or "") or tostring(item)
|
||||
if count and #data > count then
|
||||
table.insert(sock.rxbuf, 1, { data=data:sub(count+1), from=item.from })
|
||||
data = data:sub(1, count)
|
||||
end
|
||||
return data
|
||||
end,
|
||||
write = function(data)
|
||||
if sock.state == "closed" then error("EBADF") end
|
||||
return sockSend(sock, data)
|
||||
end,
|
||||
close = function()
|
||||
sockClose(sock)
|
||||
end,
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
sockSend = function(sock, data)
|
||||
if sock.backend == "unix" then
|
||||
local peer = sock.peer
|
||||
if not peer then error("ENOTCONN") end
|
||||
table.insert(peer.rxbuf, { data=data })
|
||||
return #data
|
||||
|
||||
elseif sock.backend == "rednet" then
|
||||
ensureRednet()
|
||||
local rn = kernel.apis.rednet
|
||||
rn.send(sock.address.compId, data, sock.address.protocol)
|
||||
return #data
|
||||
|
||||
elseif sock.backend == "modem" then
|
||||
local modem = sock.modem
|
||||
if not modem then error("ENOTCONN") end
|
||||
modem.transmit(sock.address.port, sock.modemChannel or 0, data)
|
||||
return #data
|
||||
|
||||
elseif sock.backend == "http" or sock.backend == "https" then
|
||||
local http = kernel.apis and kernel.apis.http
|
||||
if not http then error("ENODEV: no http API") end
|
||||
local url = sock.address.url
|
||||
local ok, err = pcall(http.request, url, data, {
|
||||
["Content-Type"] = "application/octet-stream"
|
||||
})
|
||||
if not ok then error("ENETDOWN: " .. tostring(err)) end
|
||||
sock.pendingUrl = url
|
||||
return #data
|
||||
end
|
||||
error("EPROTONOSUPPORT")
|
||||
end
|
||||
|
||||
sockClose = function(sock)
|
||||
if sock.state == "closed" then return end
|
||||
sock.state = "closed"
|
||||
|
||||
if sock.backend == "unix" then
|
||||
if sock.peer then
|
||||
sock.peer.peer = nil
|
||||
sock.peer.state = "closed"
|
||||
end
|
||||
if sock.bound and sock.address and sock.address.path then
|
||||
unixSocks[sock.address.path] = nil
|
||||
end
|
||||
|
||||
elseif sock.backend == "modem" and sock.modem and sock.modemChannel then
|
||||
pcall(sock.modem.close, sock.modemChannel)
|
||||
|
||||
elseif sock.backend == "rednet" then
|
||||
end
|
||||
|
||||
sockets[sock.id] = nil
|
||||
end
|
||||
|
||||
kernel.syscalls["socket"] = function(domain, socktype)
|
||||
domain = domain or "inet"
|
||||
socktype = socktype or "stream"
|
||||
if domain ~= "unix" and domain ~= "inet" then error("EAFNOSUPPORT") end
|
||||
if socktype ~= "stream" and socktype ~= "dgram" then error("EPROTOTYPE") end
|
||||
|
||||
local sock = newSocket(domain, socktype)
|
||||
local fdobj = socketToFd(sock)
|
||||
local fd = kernel.vfs.newfd(fdobj)
|
||||
return fd
|
||||
end
|
||||
|
||||
kernel.syscalls["bind"] = function(fd, address)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock then error("EBADF") end
|
||||
if sock.bound then error("EINVAL") end
|
||||
|
||||
local parsed = parseAddress(address)
|
||||
|
||||
if parsed.backend == "unix" then
|
||||
local existing = unixSocks[parsed.path]
|
||||
if existing then
|
||||
if existing.state == "closed" then
|
||||
unixSocks[parsed.path] = nil
|
||||
else
|
||||
error("EADDRINUSE")
|
||||
end
|
||||
end
|
||||
sock.backend = "unix"
|
||||
sock.address = parsed
|
||||
sock.bound = true
|
||||
sock.state = "bound"
|
||||
unixSocks[parsed.path] = sock
|
||||
|
||||
elseif parsed.backend == "rednet" then
|
||||
ensureRednet()
|
||||
sock.backend = "rednet"
|
||||
sock.address = parsed
|
||||
sock.bound = true
|
||||
sock.state = "bound"
|
||||
|
||||
elseif parsed.backend == "modem" then
|
||||
local modem, side = getModem()
|
||||
sock.backend = "modem"
|
||||
sock.address = parsed
|
||||
sock.modem = modem
|
||||
sock.modemChannel = parsed.port
|
||||
sock.bound = true
|
||||
sock.state = "bound"
|
||||
modem.open(parsed.port)
|
||||
|
||||
else
|
||||
error("EOPNOTSUPP: cannot bind to " .. parsed.backend .. " address")
|
||||
end
|
||||
end
|
||||
|
||||
kernel.syscalls["listen"] = function(fd, backlog)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock then error("EBADF") end
|
||||
if not sock.bound then error("EDESTADDRREQ") end
|
||||
sock.listening = true
|
||||
sock.state = "listening"
|
||||
sock.maxBacklog = backlog or 5
|
||||
end
|
||||
|
||||
kernel.syscalls["accept"] = function(fd)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock then error("EBADF") end
|
||||
if not sock.listening then error("EINVAL") end
|
||||
|
||||
local deadline = kernel.computer:time() + 30000
|
||||
while #sock.backlog == 0 do
|
||||
pumpAll()
|
||||
if kernel.computer:time() > deadline then error("ETIMEDOUT") end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
local clientSock = table.remove(sock.backlog, 1)
|
||||
local cfdobj = socketToFd(clientSock)
|
||||
local newfd = kernel.vfs.newfd(cfdobj)
|
||||
return newfd
|
||||
end
|
||||
|
||||
kernel.syscalls["connect"] = function(fd, address)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock then error("EBADF") end
|
||||
if sock.connected then error("EISCONN") end
|
||||
|
||||
local parsed = parseAddress(address)
|
||||
sock.address = parsed
|
||||
sock.backend = parsed.backend
|
||||
|
||||
if parsed.backend == "unix" then
|
||||
local server = unixSocks[parsed.path]
|
||||
if not server then error("ECONNREFUSED") end
|
||||
if not server.listening then error("ECONNREFUSED") end
|
||||
if #server.backlog >= (server.maxBacklog or 5) then error("ECONNREFUSED") end
|
||||
|
||||
local serverPeer = newSocket("unix", sock.socktype)
|
||||
serverPeer.backend = "unix"
|
||||
serverPeer.connected = true
|
||||
serverPeer.state = "connected"
|
||||
serverPeer.peer = sock
|
||||
|
||||
sock.peer = serverPeer
|
||||
sock.connected = true
|
||||
sock.state = "connected"
|
||||
|
||||
table.insert(server.backlog, serverPeer)
|
||||
|
||||
elseif parsed.backend == "rednet" then
|
||||
ensureRednet()
|
||||
sock.connected = true
|
||||
sock.state = "connected"
|
||||
|
||||
elseif parsed.backend == "modem" then
|
||||
local modem, side = getModem()
|
||||
local replyChannel = math.random(1024, 65534)
|
||||
sock.modem = modem
|
||||
sock.modemChannel = replyChannel
|
||||
sock.connected = true
|
||||
sock.state = "connected"
|
||||
modem.open(replyChannel)
|
||||
|
||||
elseif parsed.backend == "http" or parsed.backend == "https" then
|
||||
sock.connected = true
|
||||
sock.state = "connected"
|
||||
|
||||
else
|
||||
error("EAFNOSUPPORT")
|
||||
end
|
||||
end
|
||||
|
||||
kernel.syscalls["send"] = function(fd, data)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock then error("EBADF") end
|
||||
return sockSend(sock, data)
|
||||
end
|
||||
|
||||
kernel.syscalls["recv"] = function(fd, maxlen, timeout_ms)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock then error("EBADF") end
|
||||
|
||||
local deadline = kernel.computer:time() + (timeout_ms or 10000)
|
||||
while #sock.rxbuf == 0 do
|
||||
pumpAll()
|
||||
if #sock.rxbuf > 0 then break end
|
||||
if sock.state == "closed" or sock.error then
|
||||
if sock.error then error("ECONNRESET: " .. tostring(sock.error)) end
|
||||
return ""
|
||||
end
|
||||
if kernel.computer:time() > deadline then return "" end
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
local item = table.remove(sock.rxbuf, 1)
|
||||
local data = type(item) == "table" and (item.data or "") or tostring(item)
|
||||
if maxlen and #data > maxlen then
|
||||
table.insert(sock.rxbuf, 1, { data=data:sub(maxlen+1), from=item and item.from })
|
||||
data = data:sub(1, maxlen)
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
kernel.syscalls["sockshutdown"] = function(fd)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if sock then sockClose(sock) end
|
||||
end
|
||||
|
||||
kernel.syscalls["getpeername"] = function(fd)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock or not sock.connected then error("ENOTCONN") end
|
||||
if sock.address then return sock.address end
|
||||
return nil
|
||||
end
|
||||
|
||||
kernel.syscalls["getsockname"] = function(fd)
|
||||
local task = kernel.currentTask
|
||||
local fdobj = task.fd[fd]
|
||||
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
|
||||
local sock = sockets[fdobj.sockId]
|
||||
if not sock then error("EBADF") end
|
||||
return sock.address
|
||||
end
|
||||
|
||||
kernel.syscalls["httpget"] = function(url, headers)
|
||||
local http = kernel.apis and kernel.apis.http
|
||||
if not http then error("ENODEV: no http API") end
|
||||
|
||||
local ok, err = pcall(http.request, url, nil, headers)
|
||||
if not ok then error("ENETDOWN: " .. tostring(err)) end
|
||||
|
||||
local deadline = kernel.computer:time() + 15000
|
||||
while true do
|
||||
local ev = pollEvent()
|
||||
if ev then
|
||||
if ev[1] == "http_success" and ev[2] == url then
|
||||
local handle = ev[3]
|
||||
local body = handle.readAll and handle.readAll() or ""
|
||||
handle.close()
|
||||
return body
|
||||
elseif ev[1] == "http_failure" and ev[2] == url then
|
||||
error("ECONNREFUSED: " .. tostring(ev[3]))
|
||||
else
|
||||
dispatchEvent(ev)
|
||||
end
|
||||
end
|
||||
if kernel.computer:time() > deadline then error("ETIMEDOUT") end
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
|
||||
kernel.syscalls["resolve"] = function(hostname)
|
||||
if hostname:match("^%d+%.%d+%.%d+%.%d+$") then return hostname end
|
||||
|
||||
local a,b,c,d = hostname:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
||||
if a and tonumber(a) == 0 and tonumber(b) == 0 then
|
||||
return hostname
|
||||
end
|
||||
|
||||
local http = kernel.apis and kernel.apis.http
|
||||
if not http then error("ENODEV: no http API for DNS") end
|
||||
|
||||
local url = "https://cloudflare-dns.com/dns-query?name=" .. hostname .. "&type=A"
|
||||
local body = kernel.syscalls["httpget"](url, {
|
||||
["Accept"] = "application/dns-json"
|
||||
})
|
||||
|
||||
local ip = body:match('"type":1[^}]*"data":"([%d%.]+)"')
|
||||
if not ip then error("ENOENT: could not resolve " .. hostname) end
|
||||
return ip
|
||||
end
|
||||
|
||||
kernel.sockets = sockets
|
||||
kernel.unixSockets = unixSocks
|
||||
|
||||
kernel.log("Loaded socket module")
|
||||
395
Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod
Normal file
395
Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod
Normal file
@@ -0,0 +1,395 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
local apis = kernel.apis
|
||||
local native = apis.peripheral
|
||||
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
||||
local peripheral={}
|
||||
|
||||
function peripheral.getNames()
|
||||
local results = {}
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.isPresent(side) then
|
||||
table.insert(results, side)
|
||||
if native.hasType(side, "peripheral_hub") then
|
||||
local remote = native.call(side, "getNamesRemote")
|
||||
for _, name in ipairs(remote) do
|
||||
table.insert(results, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
function peripheral.isPresent(name)
|
||||
if native.isPresent(name) then
|
||||
return true
|
||||
end
|
||||
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function peripheral.getType(peripheral)
|
||||
if type(peripheral) == "string" then
|
||||
if native.isPresent(peripheral) then
|
||||
return native.getType(peripheral)
|
||||
end
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
|
||||
return native.call(side, "getTypeRemote", peripheral)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
else
|
||||
local mt = getmetatable(peripheral)
|
||||
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
|
||||
error("bad argument #1 (table is not a peripheral)", 2)
|
||||
end
|
||||
return table.unpack(mt.types)
|
||||
end
|
||||
end
|
||||
|
||||
function peripheral.hasType(peripheral, peripheral_type)
|
||||
if type(peripheral) == "string" then
|
||||
if native.isPresent(peripheral) then
|
||||
return native.hasType(peripheral, peripheral_type)
|
||||
end
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
|
||||
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
else
|
||||
local mt = getmetatable(peripheral)
|
||||
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
|
||||
error("bad argument #1 (table is not a peripheral)", 2)
|
||||
end
|
||||
return mt.types[peripheral_type] ~= nil
|
||||
end
|
||||
end
|
||||
|
||||
function peripheral.getMethods(name)
|
||||
if native.isPresent(name) then
|
||||
return native.getMethods(name)
|
||||
end
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
|
||||
return native.call(side, "getMethodsRemote", name)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function peripheral.getName(peripheral)
|
||||
local mt = getmetatable(peripheral)
|
||||
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
|
||||
error("bad argument #1 (table is not a peripheral)", 2)
|
||||
end
|
||||
return mt.name
|
||||
end
|
||||
|
||||
function peripheral.call(name, method, ...)
|
||||
if native.isPresent(name) then
|
||||
return native.call(name, method, ...)
|
||||
end
|
||||
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
|
||||
return native.call(side, "callRemote", name, method, ...)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function peripheral.wrap(name)
|
||||
local methods = peripheral.getMethods(name)
|
||||
if not methods then
|
||||
return nil
|
||||
end
|
||||
|
||||
local types = { peripheral.getType(name) }
|
||||
for i = 1, #types do types[types[i]] = true end
|
||||
local result = setmetatable({}, {
|
||||
__name = "peripheral",
|
||||
name = name,
|
||||
type = types[1],
|
||||
types = types,
|
||||
})
|
||||
for _, method in ipairs(methods) do
|
||||
result[method] = function(...)
|
||||
return peripheral.call(name, method, ...)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function peripheral.find(ty, filter)
|
||||
local results = {}
|
||||
for _, name in ipairs(peripheral.getNames()) do
|
||||
if peripheral.hasType(name, ty) then
|
||||
local wrapped = peripheral.wrap(name)
|
||||
if filter == nil or filter(name, wrapped) then
|
||||
table.insert(results, wrapped)
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.unpack(results)
|
||||
end
|
||||
|
||||
local icolors = {
|
||||
[0x1] = 1, -- #000000
|
||||
[0x2] = 2, -- #FFFFFF
|
||||
[0x4] = 3, -- #FF0000
|
||||
[0x8] = 4, -- #00FF00
|
||||
[0x10] = 5, -- #0000FF
|
||||
[0x20] = 6, -- #00FFFF
|
||||
[0x40] = 7, -- #FF00FF
|
||||
[0x80] = 8, -- #FFFF00
|
||||
[0x100] = 9, -- #FF6D00
|
||||
[0x200] = 10, -- #6DFF55
|
||||
[0x400] = 11, -- #24FFFF
|
||||
[0x800] = 12, -- #924900
|
||||
[0x1000] = 13, -- #6D6D55
|
||||
[0x2000] = 14, -- #DBDBAA
|
||||
[0x4000] = 15, -- #6D00FF
|
||||
[0x8000] = 16 -- #B6FF00
|
||||
}
|
||||
|
||||
local colors = {
|
||||
0x0001, -- #000000
|
||||
0x0002, -- #FFFFFF
|
||||
0x0004, -- #FF0000
|
||||
0x0008, -- #00FF00
|
||||
0x0010, -- #0000FF
|
||||
0x0020, -- #00FFFF
|
||||
0x0040, -- #FF00FF
|
||||
0x0080, -- #FFFF00
|
||||
0x0100, -- #FF6D00
|
||||
0x0200, -- #6DFF55
|
||||
0x0400, -- #24FFFF
|
||||
0x0800, -- #924900
|
||||
0x1000, -- #6D6D55
|
||||
0x2000, -- #DBDBAA
|
||||
0x4000, -- #6D00FF
|
||||
0x8000 -- #B6FF00
|
||||
}
|
||||
|
||||
local function write(text, term)
|
||||
local x, y = term.getCursorPos()
|
||||
local w, h = term.getSize()
|
||||
|
||||
for i = 1, #text do
|
||||
local c = text:sub(i, i)
|
||||
|
||||
if c == "\n" then
|
||||
y = y + 1
|
||||
x = 1
|
||||
elseif c == "\t" then
|
||||
local tabSize = 4
|
||||
local spaces = tabSize - ((x - 1) % tabSize)
|
||||
term.write(string.rep(" ", spaces))
|
||||
x = x + spaces
|
||||
elseif c == "\b" then
|
||||
if x > 1 then
|
||||
x = x - 1
|
||||
term.setCursorPos(x, y)
|
||||
term.write(" ")
|
||||
term.setCursorPos(x, y)
|
||||
end
|
||||
else
|
||||
if x <= w and y <= h then
|
||||
term.setCursorPos(x, y)
|
||||
term.write(c)
|
||||
x = x + 1
|
||||
end
|
||||
end
|
||||
|
||||
if x > w then
|
||||
x = 1
|
||||
y = y + 1
|
||||
end
|
||||
|
||||
if y - 1 >= h then
|
||||
term.scroll(1)
|
||||
y = h
|
||||
term.setCursorPos(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
term.setCursorPos(x, y)
|
||||
end
|
||||
|
||||
kernel.devfs.data.tty={}
|
||||
local ctrl,alt = false, false
|
||||
|
||||
local function serializeBool(bool)
|
||||
if bool then
|
||||
return "T"
|
||||
else
|
||||
return "F"
|
||||
end
|
||||
end
|
||||
|
||||
local function newtty(obj, id, ev)
|
||||
kernel.devfs.data["tty"][id] = function(op, mode)
|
||||
if op=="type" then
|
||||
return "character device"
|
||||
elseif op=="open" then
|
||||
local h = {
|
||||
read=function(amount)
|
||||
local rv=""
|
||||
for i=1, amount or 1 do
|
||||
local event = {ev()}
|
||||
if event[1] then
|
||||
rv=rv..event[1]
|
||||
end
|
||||
end
|
||||
if rv=="" then rv=nil end
|
||||
return rv
|
||||
end,
|
||||
write=function(content)
|
||||
write(content, obj)
|
||||
end,
|
||||
size=function()
|
||||
local s={obj.getSize()}
|
||||
return table.concat(s,";")
|
||||
end,
|
||||
clear=function()
|
||||
obj.clear()
|
||||
obj.setCursorPos(1,1)
|
||||
end,
|
||||
gpos=function()
|
||||
local s={obj.getCursorPos()}
|
||||
return table.concat(s,";")
|
||||
end,
|
||||
spos=function(x,y)
|
||||
return obj.setCursorPos(x,y)
|
||||
end,
|
||||
sfgc=function(c)
|
||||
return obj.setTextColor(colors[c])
|
||||
end,
|
||||
sbgc=function(c)
|
||||
return obj.setBackgroundColor(colors[c])
|
||||
end,
|
||||
gfgc=function()
|
||||
return icolors[obj.getTextColor()]
|
||||
end,
|
||||
gbgc=function()
|
||||
return icolors[obj.getBackgroundColor()]
|
||||
end,
|
||||
gctrl=function()
|
||||
return serializeBool(ctrl)..";"..serializeBool(alt)
|
||||
end
|
||||
}
|
||||
if mode=="rw" then
|
||||
return h
|
||||
elseif mode=="r" then
|
||||
h["write"]=nil
|
||||
return h
|
||||
elseif mode=="w" then
|
||||
h["read"]=nil
|
||||
return h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local fifo = kernel.newFifo()
|
||||
|
||||
local ctrlLetterKeys = nil
|
||||
local specialKeys = nil
|
||||
|
||||
local function buildKeyMaps()
|
||||
if ctrlLetterKeys then return end
|
||||
local k = apis.keys
|
||||
ctrlLetterKeys = {}
|
||||
local letters = {
|
||||
{k.a,1},{k.b,2},{k.c,3},{k.d,4},{k.e,5},{k.f,6},{k.g,7},
|
||||
{k.h,8}, {k.j,10},{k.k,11},{k.l,12},{k.m,13},
|
||||
{k.n,14},{k.o,15},{k.p,16},
|
||||
{k.u,21},{k.v,22},{k.w,23},{k.x,24},{k.y,25},{k.z,26},
|
||||
}
|
||||
for _, pair in ipairs(letters) do
|
||||
ctrlLetterKeys[pair[1]] = string.char(pair[2])
|
||||
end
|
||||
specialKeys = {
|
||||
[k.home] = "\1",
|
||||
[k.delete] = "\4",
|
||||
[k["end"]] = "\5",
|
||||
[k.pageUp] = "\2",
|
||||
[k.pageDown]= "\12",
|
||||
}
|
||||
end
|
||||
|
||||
kernel.processes.cctmond = function()
|
||||
local timeout = false
|
||||
while true do
|
||||
local event = {kernel.computer:getMachineEvent()}
|
||||
|
||||
if event[1] then
|
||||
local eventType = event[1]
|
||||
local charOrKey = event[3]
|
||||
|
||||
buildKeyMaps()
|
||||
|
||||
if eventType == "keyPressed" then
|
||||
if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then
|
||||
ctrl = true
|
||||
elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then
|
||||
alt = true
|
||||
end
|
||||
|
||||
if ctrl and charOrKey == apis.keys.c then
|
||||
for _, task in ipairs(syscall.getTasks()) do
|
||||
syscall.sigsend(task, 1)
|
||||
end
|
||||
end
|
||||
|
||||
if ctrl and ctrlLetterKeys[charOrKey] then
|
||||
fifo.push(ctrlLetterKeys[charOrKey])
|
||||
end
|
||||
|
||||
if specialKeys[charOrKey] then
|
||||
fifo.push(specialKeys[charOrKey])
|
||||
end
|
||||
elseif eventType == "keyReleased" then
|
||||
if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then
|
||||
ctrl = false
|
||||
elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then
|
||||
alt = false
|
||||
end
|
||||
elseif eventType == "keyTyped" then
|
||||
if charOrKey then fifo.push(charOrKey) end
|
||||
end
|
||||
|
||||
timeout = false
|
||||
else
|
||||
timeout = true
|
||||
end
|
||||
|
||||
if timeout then
|
||||
sleep(0.05)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newtty(apis.term, "tty1", fifo.pop)
|
||||
|
||||
|
||||
for i,v in ipairs({peripheral.find("monitor")}) do
|
||||
v.setTextScale(.5)
|
||||
v.write("Initializing...")
|
||||
newtty(v,"tty"..tostring(i+1),function () end)
|
||||
end
|
||||
57
Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod
Normal file
57
Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod
Normal file
@@ -0,0 +1,57 @@
|
||||
-- :Minify:--
|
||||
local args = {...}
|
||||
local kernel = args[1]
|
||||
kernel._G = _G
|
||||
|
||||
|
||||
local function readonly(tbl)
|
||||
return setmetatable({}, {
|
||||
__index = function(_, key)
|
||||
local value = tbl[key]
|
||||
if type(value) == "table" then return readonly(value) end
|
||||
return value
|
||||
end,
|
||||
|
||||
__newindex = function(t, k, v)
|
||||
if kernel.config.allowGlobalOverwrites or
|
||||
kernel.allowGlobalOverwrites then
|
||||
rawset(tbl, k, v)
|
||||
return
|
||||
end
|
||||
error("Attempt to modify global variable '" .. k .. "'", 2)
|
||||
end,
|
||||
|
||||
__pairs = function(self)
|
||||
local function iter(_, key)
|
||||
local nextKey, value = next(tbl, key)
|
||||
if type(value) == "table" then
|
||||
value = readonly(value)
|
||||
end
|
||||
return nextKey, value
|
||||
end
|
||||
return iter, self, nil
|
||||
end,
|
||||
|
||||
__ipairs = function()
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
local value = tbl[i]
|
||||
if value == nil then return end
|
||||
if type(value) == "table" then
|
||||
value = readonly(value)
|
||||
end
|
||||
return i, value
|
||||
end
|
||||
end,
|
||||
|
||||
__len = function() return #tbl end,
|
||||
|
||||
__metatable = false
|
||||
})
|
||||
end
|
||||
local origLoad = load
|
||||
|
||||
kernel._U = readonly(kernel._G)
|
||||
kernel._U._G = kernel._U
|
||||
kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end
|
||||
622
Src/Hyperion-kernel/lib/modules/hyperion/40_auth.kmod
Normal file
622
Src/Hyperion-kernel/lib/modules/hyperion/40_auth.kmod
Normal file
@@ -0,0 +1,622 @@
|
||||
--:Minify:--
|
||||
local kernel = ...
|
||||
local auth = {}
|
||||
kernel.auth = auth
|
||||
|
||||
-- PASSWD FILE FORMAT: uid:gid:username:homedir:shell
|
||||
-- SHADOW FILE FORMAT: uid:salt:hash
|
||||
|
||||
local function getFile(path)
|
||||
local file = kernel.vfs.open(path, "r")
|
||||
if not file then error("Failed to open file: " .. path) end
|
||||
local content = kernel.vfs.read(file, 1024000)
|
||||
kernel.vfs.close(file)
|
||||
return content
|
||||
end
|
||||
|
||||
local function writeFile(path, content)
|
||||
local file = kernel.vfs.open(path, "w")
|
||||
if not file then error("Failed to open file for writing: " .. path) end
|
||||
kernel.vfs.write(file, content)
|
||||
kernel.vfs.close(file)
|
||||
end
|
||||
|
||||
local blake2s
|
||||
do
|
||||
local MOD32 = 2^32
|
||||
local function norm(x) return x % MOD32 end
|
||||
local function tobits(x)
|
||||
x = norm(x)
|
||||
local t = {}
|
||||
for i = 0, 31 do local b = x % 2; t[i] = b; x = (x - b) / 2 end
|
||||
return t
|
||||
end
|
||||
local function frombits(t)
|
||||
local x, p = 0, 1
|
||||
for i = 0, 31 do if t[i] == 1 then x = x + p end; p = p * 2 end
|
||||
return norm(x)
|
||||
end
|
||||
local function bor(...)
|
||||
local args = {...}
|
||||
if #args == 0 then return 0 end
|
||||
local bits = tobits(args[1])
|
||||
for i = 2, #args do
|
||||
local b = tobits(args[i])
|
||||
for j = 0, 31 do bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0 end
|
||||
end
|
||||
return frombits(bits)
|
||||
end
|
||||
local function bxor(...)
|
||||
local args = {...}
|
||||
if #args == 0 then return 0 end
|
||||
local bits = tobits(args[1])
|
||||
for i = 2, #args do
|
||||
local b = tobits(args[i])
|
||||
for j = 0, 31 do bits[j] = (bits[j] ~= b[j]) and 1 or 0 end
|
||||
end
|
||||
return frombits(bits)
|
||||
end
|
||||
local function lshift(x, n) return norm(norm(x) * 2^n) end
|
||||
local function rshift(x, n) return math.floor(norm(x) / 2^n) end
|
||||
local function rotr(x, n) return bor(rshift(x, n), lshift(x, 32 - n)) end
|
||||
local IV = {
|
||||
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
|
||||
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
|
||||
}
|
||||
local SIGMA = {
|
||||
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
|
||||
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
|
||||
{11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
|
||||
{7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
|
||||
{9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
|
||||
{2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
|
||||
{12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
|
||||
{13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
|
||||
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
|
||||
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0}
|
||||
}
|
||||
local function G(v, a, b, c, d, x, y)
|
||||
v[a] = (v[a] + v[b] + x) % MOD32
|
||||
v[d] = rotr(bxor(v[d], v[a]), 16)
|
||||
v[c] = (v[c] + v[d]) % MOD32
|
||||
v[b] = rotr(bxor(v[b], v[c]), 12)
|
||||
v[a] = (v[a] + v[b] + y) % MOD32
|
||||
v[d] = rotr(bxor(v[d], v[a]), 8)
|
||||
v[c] = (v[c] + v[d]) % MOD32
|
||||
v[b] = rotr(bxor(v[b], v[c]), 7)
|
||||
end
|
||||
local function compress(h, block, t, last)
|
||||
local v = {}
|
||||
for i = 1, 8 do v[i] = h[i] end
|
||||
for i = 1, 8 do v[i + 8] = IV[i] end
|
||||
v[13] = bxor(v[13], t)
|
||||
if last then v[15] = bxor(v[15], 0xFFFFFFFF) end
|
||||
local m = {}
|
||||
for i = 0, 15 do
|
||||
local p = i * 4 + 1
|
||||
m[i] = (block:byte(p) or 0)
|
||||
+ ((block:byte(p+1) or 0) * 0x100)
|
||||
+ ((block:byte(p+2) or 0) * 0x10000)
|
||||
+ ((block:byte(p+3) or 0) * 0x1000000)
|
||||
end
|
||||
for r = 1, 10 do
|
||||
local s = SIGMA[r]
|
||||
G(v,1,5,9,13, m[s[1]], m[s[2]])
|
||||
G(v,2,6,10,14, m[s[3]], m[s[4]])
|
||||
G(v,3,7,11,15, m[s[5]], m[s[6]])
|
||||
G(v,4,8,12,16, m[s[7]], m[s[8]])
|
||||
G(v,1,6,11,16, m[s[9]], m[s[10]])
|
||||
G(v,2,7,12,13, m[s[11]], m[s[12]])
|
||||
G(v,3,8,9,14, m[s[13]], m[s[14]])
|
||||
G(v,4,5,10,15, m[s[15]], m[s[16]])
|
||||
end
|
||||
for i = 1, 8 do h[i] = bxor(h[i], v[i], v[i+8]) end
|
||||
end
|
||||
function blake2s(msg, key)
|
||||
key = key or ""
|
||||
local h = {}
|
||||
for i = 1, 8 do h[i] = IV[i] end
|
||||
local outlen = 32
|
||||
h[1] = bxor(h[1], 0x01010000 + lshift(#key, 8) + outlen)
|
||||
local t = 0
|
||||
if #key > 0 then
|
||||
local block = key .. string.rep("\0", 64 - #key)
|
||||
t = #key
|
||||
compress(h, block, t, false)
|
||||
end
|
||||
for i = 1, #msg, 64 do
|
||||
local block = msg:sub(i, i + 63)
|
||||
if #block < 64 then block = block .. string.rep("\0", 64 - #block) end
|
||||
t = t + math.min(64, #msg - i + 1)
|
||||
compress(h, block, t, i + 64 > #msg)
|
||||
end
|
||||
local out = ""
|
||||
for i = 1, 8 do out = out .. string.format("%08x", h[i]) end
|
||||
return out
|
||||
end
|
||||
end
|
||||
|
||||
if not blake2s then error("Failed to load blake2s") end
|
||||
|
||||
if not kernel.vfs.exists("/etc/pam.d/secret") then
|
||||
kernel.log("PAM SECRET REGENERATING PLEASE USE ROOT")
|
||||
local key = ""
|
||||
for i = 1, 256 do key = key .. string.char(math.random(0, 255)) end
|
||||
local handle = kernel.vfs.open("/etc/pam.d/secret", "w")
|
||||
kernel.vfs.write(handle, key)
|
||||
kernel.vfs.close(handle)
|
||||
end
|
||||
|
||||
local pepper = getFile("/etc/pam.d/secret")
|
||||
|
||||
local function genSalt()
|
||||
local chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
|
||||
local s = ""
|
||||
for i = 1, 16 do
|
||||
s = s .. chars:sub(math.random(1, #chars), math.random(1, #chars))
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function hashPassword(password, salt)
|
||||
local key = (pepper .. salt):sub(1, 32)
|
||||
return blake2s(password, key)
|
||||
end
|
||||
|
||||
local passwdFile = getFile("/etc/passwd")
|
||||
local shadowFile = getFile("/etc/shadow")
|
||||
|
||||
local passwdLines = string.split(passwdFile, "\n")
|
||||
local shadowLines = string.split(shadowFile, "\n")
|
||||
|
||||
local passwd, shadow = {}, {}
|
||||
for _, v in ipairs(passwdLines) do
|
||||
local fields = string.split(v, ":")
|
||||
if fields[1] and fields[1] ~= "" then
|
||||
passwd[#passwd + 1] = fields
|
||||
end
|
||||
end
|
||||
for _, v in ipairs(shadowLines) do
|
||||
local fields = string.split(v, ":")
|
||||
if fields[1] and fields[1] ~= "" then
|
||||
shadow[#shadow + 1] = fields
|
||||
end
|
||||
end
|
||||
|
||||
for _, v in ipairs(passwd) do
|
||||
local uid = tonumber(v[1])
|
||||
if uid then kernel.users[uid] = v[3] end
|
||||
end
|
||||
|
||||
kernel.passwd = passwd
|
||||
|
||||
local function flushPasswd()
|
||||
local lines = {}
|
||||
for _, v in ipairs(passwd) do
|
||||
lines[#lines + 1] = table.concat(v, ":")
|
||||
end
|
||||
writeFile("/etc/passwd", table.concat(lines, "\n"))
|
||||
end
|
||||
|
||||
local function flushShadow()
|
||||
local lines = {}
|
||||
for _, v in ipairs(shadow) do
|
||||
lines[#lines + 1] = table.concat(v, ":")
|
||||
end
|
||||
writeFile("/etc/shadow", table.concat(lines, "\n"))
|
||||
end
|
||||
|
||||
local function getPasswdByUID(uid)
|
||||
for _, v in ipairs(passwd) do
|
||||
if tonumber(v[1]) == uid then return v end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function getShadowByUID(uid)
|
||||
for _, v in ipairs(shadow) do
|
||||
if tonumber(v[1]) == uid then return v end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function getPasswdByUsername(username)
|
||||
for _, v in ipairs(passwd) do
|
||||
if v[3] == username then return v end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function nextUID()
|
||||
local max = 999
|
||||
for _, v in ipairs(passwd) do
|
||||
local uid = tonumber(v[1])
|
||||
if uid and uid >= 1000 and uid > max then max = uid end
|
||||
end
|
||||
return max + 1
|
||||
end
|
||||
|
||||
function auth.login(username, password)
|
||||
if type(username) ~= "string" or type(password) ~= "string" then
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
local entry = getPasswdByUsername(username)
|
||||
if not entry then
|
||||
-- timing attack resistance
|
||||
hashPassword(password, "aaaaaaaaaaaaaaaa")
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
local uid = tonumber(entry[1])
|
||||
local sEntry = getShadowByUID(uid)
|
||||
if not sEntry then
|
||||
hashPassword(password, "aaaaaaaaaaaaaaaa")
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
local salt = sEntry[2]
|
||||
local storedHash = sEntry[3]
|
||||
|
||||
local computed = hashPassword(password, salt)
|
||||
if computed ~= storedHash then
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
kernel.currentUID = uid
|
||||
|
||||
local _task = kernel.currentTask
|
||||
if _task then
|
||||
_task.uid = uid
|
||||
_task.euid = uid
|
||||
_task.gid = tonumber(entry[2]) or uid
|
||||
_task.egid = tonumber(entry[2]) or uid
|
||||
end
|
||||
|
||||
kernel.log("AUTH: login uid=" .. tostring(uid) .. " (" .. username .. ")")
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.setPassword(uid, newPassword)
|
||||
uid = tonumber(uid)
|
||||
if not uid then return nil, "Invalid uid" end
|
||||
|
||||
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||
or kernel.currentUID or 0
|
||||
|
||||
if callerUID ~= 0 and callerUID ~= uid then
|
||||
return nil, "Permission denied"
|
||||
end
|
||||
|
||||
if type(newPassword) ~= "string" or #newPassword == 0 then
|
||||
return nil, "Password may not be empty"
|
||||
end
|
||||
|
||||
if #newPassword < 6 then
|
||||
return nil, "Password is too short (minimum 6 characters)"
|
||||
end
|
||||
|
||||
local salt = genSalt()
|
||||
local hash = hashPassword(newPassword, salt)
|
||||
|
||||
local sEntry = getShadowByUID(uid)
|
||||
if sEntry then
|
||||
sEntry[2] = salt
|
||||
sEntry[3] = hash
|
||||
else
|
||||
shadow[#shadow + 1] = { tostring(uid), salt, hash }
|
||||
end
|
||||
|
||||
flushShadow()
|
||||
kernel.log("AUTH: password changed for uid=" .. tostring(uid))
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.setUsername(uid, newUsername)
|
||||
uid = tonumber(uid)
|
||||
if not uid then return nil, "Invalid uid" end
|
||||
|
||||
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||
or kernel.currentUID or 0
|
||||
|
||||
if callerUID ~= 0 then
|
||||
return nil, "Permission denied (root only)"
|
||||
end
|
||||
|
||||
if type(newUsername) ~= "string" or #newUsername == 0 then
|
||||
return nil, "Invalid username"
|
||||
end
|
||||
|
||||
if not newUsername:match("^[a-z_][a-z0-9_%-]*$") or #newUsername > 32 then
|
||||
return nil, "Invalid username format"
|
||||
end
|
||||
|
||||
if getPasswdByUsername(newUsername) then
|
||||
return nil, "Username already taken"
|
||||
end
|
||||
|
||||
local entry = getPasswdByUID(uid)
|
||||
if not entry then return nil, "No such user" end
|
||||
|
||||
local oldName = entry[3]
|
||||
entry[3] = newUsername
|
||||
kernel.users[uid] = newUsername
|
||||
|
||||
flushPasswd()
|
||||
kernel.log("AUTH: uid=" .. tostring(uid) .. " renamed '" .. oldName .. "' → '" .. newUsername .. "'")
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.newUser(username, password, gid, homedir, shell)
|
||||
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||
or kernel.currentUID or 0
|
||||
|
||||
if callerUID ~= 0 then
|
||||
return nil, "Permission denied (root only)"
|
||||
end
|
||||
|
||||
if type(username) ~= "string" or #username == 0 then
|
||||
return nil, "Invalid username"
|
||||
end
|
||||
|
||||
if not username:match("^[a-z_][a-z0-9_%-]*$") or #username > 32 then
|
||||
return nil, "Invalid username format"
|
||||
end
|
||||
|
||||
if getPasswdByUsername(username) then
|
||||
return nil, "Username already exists"
|
||||
end
|
||||
|
||||
if type(password) ~= "string" or #password < 6 then
|
||||
return nil, "Password is too short (minimum 6 characters)"
|
||||
end
|
||||
|
||||
local uid = nextUID()
|
||||
gid = tonumber(gid) or uid
|
||||
homedir = homedir or ("/home/" .. username)
|
||||
shell = shell or "/bin/hysh"
|
||||
|
||||
passwd[#passwd + 1] = {
|
||||
tostring(uid),
|
||||
tostring(gid),
|
||||
username,
|
||||
homedir,
|
||||
shell
|
||||
}
|
||||
kernel.users[uid] = username
|
||||
|
||||
local salt = genSalt()
|
||||
local hash = hashPassword(password, salt)
|
||||
shadow[#shadow + 1] = { tostring(uid), salt, hash }
|
||||
|
||||
flushPasswd()
|
||||
flushShadow()
|
||||
|
||||
if kernel.vfs.mkdir and not kernel.vfs.exists(homedir) then
|
||||
kernel.vfs.mkdir(homedir)
|
||||
-- Homedir must be owned by the new user, not root
|
||||
pcall(kernel.vfs.chown, homedir, uid, uid)
|
||||
end
|
||||
|
||||
kernel.log("AUTH: new user '" .. username .. "' uid=" .. tostring(uid))
|
||||
return uid
|
||||
end
|
||||
|
||||
function auth.whoami()
|
||||
local uid = (kernel.currentProcess and kernel.currentProcess.euid)
|
||||
or kernel.currentUID
|
||||
if not uid then return nil, "Not logged in" end
|
||||
return kernel.users[uid] or ("uid=" .. tostring(uid))
|
||||
end
|
||||
|
||||
function auth.getUID(username)
|
||||
local entry = getPasswdByUsername(username)
|
||||
if entry then return tonumber(entry[1]) end
|
||||
return nil
|
||||
end
|
||||
|
||||
function auth.getPasswd(uid)
|
||||
uid = tonumber(uid)
|
||||
local entry = getPasswdByUID(uid)
|
||||
if not entry then return nil end
|
||||
return {
|
||||
uid = tonumber(entry[1]),
|
||||
gid = tonumber(entry[2]),
|
||||
username = entry[3],
|
||||
homedir = entry[4],
|
||||
shell = entry[5],
|
||||
}
|
||||
end
|
||||
|
||||
function auth.deleteUser(uid)
|
||||
uid = tonumber(uid)
|
||||
if not uid then return nil, "Invalid uid" end
|
||||
|
||||
local callerUID = kernel.uid or 0
|
||||
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||
if uid == 0 then return nil, "Cannot delete root" end
|
||||
|
||||
local entry = getPasswdByUID(uid)
|
||||
if not entry then return nil, "No such user" end
|
||||
local username = entry[3]
|
||||
|
||||
for i, v in ipairs(passwd) do
|
||||
if tonumber(v[1]) == uid then table.remove(passwd, i); break end
|
||||
end
|
||||
for i, v in ipairs(shadow) do
|
||||
if tonumber(v[1]) == uid then table.remove(shadow, i); break end
|
||||
end
|
||||
kernel.users[uid] = nil
|
||||
|
||||
flushPasswd()
|
||||
flushShadow()
|
||||
kernel.log("AUTH: deleted user '" .. username .. "' uid=" .. tostring(uid))
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.lockUser(uid)
|
||||
uid = tonumber(uid)
|
||||
if not uid then return nil, "Invalid uid" end
|
||||
|
||||
local callerUID = kernel.uid or 0
|
||||
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||
if uid == 0 then return nil, "Cannot lock root" end
|
||||
|
||||
local sEntry = getShadowByUID(uid)
|
||||
if not sEntry then return nil, "No shadow entry for uid" end
|
||||
|
||||
if sEntry[3]:sub(1,1) ~= "!" then
|
||||
sEntry[3] = "!" .. sEntry[3]
|
||||
end
|
||||
flushShadow()
|
||||
kernel.log("AUTH: locked uid=" .. tostring(uid))
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.unlockUser(uid)
|
||||
uid = tonumber(uid)
|
||||
if not uid then return nil, "Invalid uid" end
|
||||
|
||||
local callerUID = kernel.uid or 0
|
||||
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||
|
||||
local sEntry = getShadowByUID(uid)
|
||||
if not sEntry then return nil, "No shadow entry for uid" end
|
||||
|
||||
if sEntry[3]:sub(1,1) == "!" then
|
||||
sEntry[3] = sEntry[3]:sub(2)
|
||||
end
|
||||
flushShadow()
|
||||
kernel.log("AUTH: unlocked uid=" .. tostring(uid))
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.listUsers()
|
||||
local result = {}
|
||||
for _, v in ipairs(passwd) do
|
||||
local uid = tonumber(v[1])
|
||||
local sEntry = getShadowByUID(uid)
|
||||
local locked = sEntry and sEntry[3]:sub(1,1) == "!"
|
||||
result[#result+1] = {
|
||||
uid = uid,
|
||||
gid = tonumber(v[2]),
|
||||
username = v[3],
|
||||
homedir = v[4],
|
||||
shell = v[5],
|
||||
locked = locked or false,
|
||||
}
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function auth.setShell(uid, shell)
|
||||
uid = tonumber(uid)
|
||||
if not uid then return nil, "Invalid uid" end
|
||||
|
||||
local callerUID = kernel.uid or 0
|
||||
if callerUID ~= 0 and callerUID ~= uid then
|
||||
return nil, "Permission denied"
|
||||
end
|
||||
|
||||
if type(shell) ~= "string" or #shell == 0 then
|
||||
return nil, "Invalid shell"
|
||||
end
|
||||
|
||||
local entry = getPasswdByUID(uid)
|
||||
if not entry then return nil, "No such user" end
|
||||
entry[5] = shell
|
||||
|
||||
flushPasswd()
|
||||
kernel.log("AUTH: uid=" .. tostring(uid) .. " shell -> " .. shell)
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.setHomedir(uid, homedir)
|
||||
uid = tonumber(uid)
|
||||
if not uid then return nil, "Invalid uid" end
|
||||
|
||||
local callerUID = kernel.uid or 0
|
||||
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||
|
||||
if type(homedir) ~= "string" or #homedir == 0 then
|
||||
return nil, "Invalid homedir"
|
||||
end
|
||||
|
||||
local entry = getPasswdByUID(uid)
|
||||
if not entry then return nil, "No such user" end
|
||||
entry[4] = homedir
|
||||
|
||||
flushPasswd()
|
||||
kernel.log("AUTH: uid=" .. tostring(uid) .. " homedir -> " .. homedir)
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.setGID(uid, gid)
|
||||
uid = tonumber(uid)
|
||||
gid = tonumber(gid)
|
||||
if not uid or not gid then return nil, "Invalid uid or gid" end
|
||||
|
||||
local callerUID = kernel.uid or 0
|
||||
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
|
||||
|
||||
local entry = getPasswdByUID(uid)
|
||||
if not entry then return nil, "No such user" end
|
||||
entry[2] = tostring(gid)
|
||||
|
||||
flushPasswd()
|
||||
kernel.log("AUTH: uid=" .. tostring(uid) .. " gid -> " .. tostring(gid))
|
||||
return true
|
||||
end
|
||||
|
||||
function auth.elevate(targetUsername, password)
|
||||
if type(targetUsername) ~= "string" or type(password) ~= "string" then
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
local entry = getPasswdByUsername(targetUsername)
|
||||
if not entry then
|
||||
hashPassword(password, "aaaaaaaaaaaaaaaa") -- timing resistance
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
local uid = tonumber(entry[1])
|
||||
local sEntry = getShadowByUID(uid)
|
||||
if not sEntry then
|
||||
hashPassword(password, "aaaaaaaaaaaaaaaa")
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
local computed = hashPassword(password, sEntry[2])
|
||||
if computed ~= sEntry[3] then
|
||||
return nil, "Authentication failure"
|
||||
end
|
||||
|
||||
local task = kernel.currentTask
|
||||
local prevUid = task.uid
|
||||
task.uid = 0
|
||||
task.euid = 0
|
||||
task.gid = 0
|
||||
task.egid = 0
|
||||
kernel.uid = 0
|
||||
|
||||
kernel.log("AUTH: elevate uid=" .. tostring(prevUid) .. " -> 0 (via " .. targetUsername .. ")")
|
||||
return true, uid
|
||||
end
|
||||
|
||||
if kernel.syscalls then
|
||||
kernel.syscalls["login"] = auth.login
|
||||
kernel.syscalls["setpassword"] = auth.setPassword
|
||||
kernel.syscalls["setusername"] = auth.setUsername
|
||||
kernel.syscalls["newuser"] = auth.newUser
|
||||
kernel.syscalls["whoami"] = auth.whoami
|
||||
kernel.syscalls["getuidbyname"]= auth.getUID
|
||||
kernel.syscalls["getpasswd"] = auth.getPasswd
|
||||
kernel.syscalls["elevate"] = auth.elevate
|
||||
kernel.syscalls["deleteuser"] = auth.deleteUser
|
||||
kernel.syscalls["lockuser"] = auth.lockUser
|
||||
kernel.syscalls["unlockuser"] = auth.unlockUser
|
||||
kernel.syscalls["listusers"] = auth.listUsers
|
||||
kernel.syscalls["setshell"] = auth.setShell
|
||||
kernel.syscalls["sethomedir"] = auth.setHomedir
|
||||
kernel.syscalls["setgid"] = auth.setGID
|
||||
end
|
||||
478
Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod
Normal file
478
Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod
Normal file
@@ -0,0 +1,478 @@
|
||||
--:Minify:--
|
||||
local kernel = ...
|
||||
local tasks = {}
|
||||
local sys = {}
|
||||
local nextpid = 2
|
||||
kernel.exitMain = false
|
||||
|
||||
local resumeWithTimeout = coroutine.resumeWithTimeout
|
||||
|
||||
local function bit_is_set(num, bit)
|
||||
return math.floor(num / (2 ^ bit)) % 2 == 1
|
||||
end
|
||||
|
||||
local function loadExecutable(path, env)
|
||||
kernel.vfs.access(path, "rx")
|
||||
|
||||
local fd = kernel.vfs.open(path, "r")
|
||||
local data = kernel.vfs.read(fd, 1024 * 1024 * 4)
|
||||
kernel.vfs.close(fd)
|
||||
|
||||
local func, err = load(data, "@" .. path, "t", env or kernel._U)
|
||||
if not func then error("ENOEXEC: " .. tostring(err)) end
|
||||
|
||||
local meta = kernel.vfs.lstat(path)
|
||||
local suid_set = bit_is_set(meta.perms, 6)
|
||||
local caller_uid = kernel.currentTask and kernel.currentTask.uid or kernel.uid
|
||||
local euid = suid_set and meta.owner or caller_uid
|
||||
|
||||
return func, euid, suid_set
|
||||
end
|
||||
|
||||
local function createTask(func, name, envars, args, tgid, real_uid, eff_uid)
|
||||
local id = nextpid
|
||||
nextpid = nextpid + 1
|
||||
|
||||
tasks[tostring(id)] = {
|
||||
coro = coroutine.create(function()
|
||||
local ok, err = xpcall(func, debug.traceback, table.unpack(args or {}))
|
||||
|
||||
if kernel.config.logTaskExit then
|
||||
if not ok then
|
||||
kernel.log("Task " .. tostring(id) .. " exited with err: " .. tostring(err), "ERROR", 2)
|
||||
elseif err then
|
||||
kernel.log("Task " .. tostring(id) .. " exited with code: " .. tostring(err), "INFO")
|
||||
else
|
||||
kernel.log("Task " .. tostring(id) .. " exited without code", "INFO")
|
||||
end
|
||||
end
|
||||
|
||||
if type(err) == "number" then
|
||||
tasks[tostring(id)].exit = err
|
||||
end
|
||||
|
||||
if tasks[tostring(id)].fd then
|
||||
for fd, _ in pairs(tasks[tostring(id)].fd) do
|
||||
pcall(kernel.vfs.close, fd)
|
||||
end
|
||||
end
|
||||
tasks[tostring(id)].status = "Z"
|
||||
end),
|
||||
|
||||
name = name or ("task" .. tostring(id)),
|
||||
envars = envars or (kernel.currentTask and kernel.currentTask.envars or {}),
|
||||
args = args or {},
|
||||
status = "R",
|
||||
pid = id,
|
||||
tgid = tgid or (kernel.currentTask and kernel.currentTask.tgid or id),
|
||||
uid = real_uid,
|
||||
euid = eff_uid,
|
||||
gid = (kernel.currentTask and kernel.currentTask.gid) or 0,
|
||||
groups = (kernel.currentTask and kernel.currentTask.groups) or {},
|
||||
fd = {},
|
||||
sleep = 0,
|
||||
ivs = 0,
|
||||
vs = 0,
|
||||
children = {},
|
||||
parent = kernel.currentTask or kernel.kernelTask,
|
||||
siblings = (kernel.currentTask and kernel.currentTask.children) or kernel.kernelTask.children,
|
||||
syscallReturn = {},
|
||||
cwd = (kernel.currentTask and kernel.currentTask.cwd) or "/",
|
||||
timeSlice = 0,
|
||||
lastTime = 0,
|
||||
totalTime = 0,
|
||||
numRuns = 0,
|
||||
}
|
||||
|
||||
table.insert(
|
||||
(kernel.currentTask and kernel.currentTask.children) or kernel.kernelTask.children,
|
||||
tasks[tostring(id)]
|
||||
)
|
||||
return id
|
||||
end
|
||||
|
||||
function sys.spawn(func, name, envars, args, tgid)
|
||||
local caller = kernel.currentTask
|
||||
local real_uid = caller and caller.uid or kernel.uid
|
||||
local eff_uid = caller and caller.euid or real_uid
|
||||
return createTask(func, name, envars, args, tgid, real_uid, eff_uid)
|
||||
end
|
||||
|
||||
function sys.execspawn(path, name, envars, args, tgid)
|
||||
local func, euid, suid_active = loadExecutable(path, kernel._U)
|
||||
|
||||
local caller = kernel.currentTask
|
||||
local real_uid = caller and caller.uid or kernel.uid
|
||||
|
||||
if suid_active then
|
||||
kernel.log(
|
||||
"execspawn: suid exec '" .. path ..
|
||||
"' caller_uid=" .. tostring(real_uid) ..
|
||||
" -> euid=" .. tostring(euid), "INFO"
|
||||
)
|
||||
end
|
||||
|
||||
return createTask(func, name or path, envars, args, tgid, real_uid, euid)
|
||||
end
|
||||
|
||||
function sys.exec(path, args, envars)
|
||||
local task = kernel.currentTask
|
||||
local func, euid, _ = loadExecutable(path, kernel._U)
|
||||
|
||||
if task.fd then
|
||||
for fd, _ in pairs(task.fd) do
|
||||
if fd > 2 then pcall(kernel.vfs.close, fd) end
|
||||
end
|
||||
end
|
||||
|
||||
task.euid = euid
|
||||
task.args = args or {}
|
||||
task.envars = envars or task.envars
|
||||
task.name = path
|
||||
|
||||
task.coro = coroutine.create(function()
|
||||
local ok, err = xpcall(func, debug.traceback, table.unpack(task.args))
|
||||
if kernel.config.logTaskExit then
|
||||
if not ok then
|
||||
kernel.log("Task " .. tostring(task.pid) .. " exec '" .. path .. "' err: " .. tostring(err), "ERROR", 2)
|
||||
else
|
||||
kernel.log("Task " .. tostring(task.pid) .. " exec '" .. path .. "' exited: " .. tostring(err), "INFO")
|
||||
end
|
||||
end
|
||||
if type(err) == "number" then tasks[tostring(task.pid)].exit = err end
|
||||
if tasks[tostring(task.pid)].fd then
|
||||
for fd, _ in pairs(tasks[tostring(task.pid)].fd) do
|
||||
pcall(kernel.vfs.close, fd)
|
||||
end
|
||||
end
|
||||
tasks[tostring(task.pid)].status = "Z"
|
||||
end)
|
||||
task.syscallReturn = {}
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
function sys.sleep(s)
|
||||
kernel.currentTask.status = "S"
|
||||
kernel.currentTask.sleep = kernel.computer:time() + s * 1000
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
function sys.getTask(pid)
|
||||
local task = tasks[tostring(pid)]
|
||||
if not task then return nil end
|
||||
|
||||
local children, siblings = {}, {}
|
||||
for i, v in ipairs(task.children) do children[i] = v.pid end
|
||||
for i, v in ipairs(task.siblings) do siblings[i] = v.pid end
|
||||
|
||||
return {
|
||||
name = task.name,
|
||||
status = task.status,
|
||||
pid = task.pid,
|
||||
tgid = task.tgid,
|
||||
username = kernel.users[task.uid],
|
||||
uid = task.uid,
|
||||
euid = task.euid,
|
||||
exit = task.exit,
|
||||
sleep = task.sleep,
|
||||
ivs = task.ivs,
|
||||
vs = task.vs,
|
||||
children = children,
|
||||
siblings = siblings,
|
||||
parent = task.parent.pid,
|
||||
cwd = task.cwd,
|
||||
term = task.term,
|
||||
}
|
||||
end
|
||||
|
||||
function sys.collect(pid)
|
||||
local children = {}
|
||||
for _, v in ipairs(kernel.currentTask.children) do children[#children+1] = v.pid end
|
||||
|
||||
local task = tasks[tostring(pid)]
|
||||
if not task then
|
||||
return false, "Task does not exist"
|
||||
elseif not isEqualToAny(task.pid, table.unpack(children)) then
|
||||
return false, "You do not own this task"
|
||||
elseif task.status ~= "Z" then
|
||||
return false, "Task must exit to collect status"
|
||||
else
|
||||
task.reapTime = 0
|
||||
return true, task.exit
|
||||
end
|
||||
end
|
||||
|
||||
function sys.kill(pid)
|
||||
local task = tasks[tostring(pid)]
|
||||
if not task then
|
||||
return false, "Task does not exist"
|
||||
elseif task.status == "Z" then
|
||||
return false, "Task is already dead"
|
||||
end
|
||||
local caller = kernel.currentTask
|
||||
local ceuid = caller and (caller.euid or caller.uid) or kernel.uid
|
||||
if ceuid ~= 0 and task.uid ~= (caller and caller.uid or kernel.uid) then
|
||||
return false, "EPERM"
|
||||
end
|
||||
task.status = "Z"
|
||||
return true
|
||||
end
|
||||
|
||||
function sys.stop(pid)
|
||||
local task = tasks[tostring(pid)]
|
||||
if not task then
|
||||
return false, "Task does not exist"
|
||||
elseif task.status ~= "R" then
|
||||
return false, "Cannot stop non-running task"
|
||||
else
|
||||
task.status = "T"
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function sys.continue(pid)
|
||||
local task = tasks[tostring(pid)]
|
||||
if not task then
|
||||
return false, "Task does not exist"
|
||||
elseif task.status ~= "T" then
|
||||
return false, "Task is not stopped"
|
||||
else
|
||||
task.status = "R"
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function sys.getpid() return kernel.currentTask.pid end
|
||||
function sys.getppid() return kernel.currentTask.parent.pid end
|
||||
|
||||
function sys.getTasks()
|
||||
local ret = {}
|
||||
for _, v in pairs(tasks) do ret[#ret+1] = v.pid end
|
||||
return ret
|
||||
end
|
||||
|
||||
function sys.getEnviron(key) return kernel.currentTask.envars[key] end
|
||||
function sys.setEnviron(key, val) kernel.currentTask.envars[key] = val end
|
||||
|
||||
function sys.exit(code)
|
||||
local task = kernel.currentTask
|
||||
if kernel.config.logTaskExit then
|
||||
if code then
|
||||
kernel.log("Task " .. tostring(task.pid) .. " exited with code: " .. tostring(code), "INFO")
|
||||
else
|
||||
kernel.log("Task " .. tostring(task.pid) .. " exited without code", "INFO")
|
||||
end
|
||||
end
|
||||
tasks[tostring(task.pid)].status = "Z"
|
||||
if type(code) == "number" then
|
||||
tasks[tostring(task.pid)].exit = code
|
||||
end
|
||||
end
|
||||
|
||||
function sys.setuid(uid)
|
||||
local task = kernel.currentTask
|
||||
if task.euid ~= 0 and task.uid ~= uid then
|
||||
error("EPERM")
|
||||
end
|
||||
task.uid = uid
|
||||
task.euid = uid
|
||||
kernel.uid = uid
|
||||
end
|
||||
|
||||
function sys.geteuid()
|
||||
return kernel.currentTask.euid
|
||||
end
|
||||
|
||||
function sys.getuid() return kernel.currentTask.uid end
|
||||
|
||||
local function reapDeadTasks()
|
||||
for pid, task in pairs(tasks) do
|
||||
if task.status == "Z" and not task.reapTime then
|
||||
task.coro = nil
|
||||
task.ivs = nil
|
||||
task.vs = nil
|
||||
task.args = nil
|
||||
task.envars = nil
|
||||
task.cwd = nil
|
||||
task.numRuns = nil
|
||||
task.totalTime = nil
|
||||
task.lastTime = nil
|
||||
task.timeSlice = nil
|
||||
task.syscallReturn = nil
|
||||
task.sleep = nil
|
||||
task.fd = nil
|
||||
task.reapTime = kernel.computer:time() + 30000
|
||||
|
||||
elseif task.reapTime and kernel.computer:time() > task.reapTime
|
||||
and task.status == "Z" then
|
||||
for _, child in ipairs(task.children) do
|
||||
child.parent = tasks["1"]
|
||||
child.siblings = tasks["1"].children
|
||||
table.insert(tasks["1"].children, child)
|
||||
end
|
||||
for i, sibling in ipairs(task.siblings) do
|
||||
if sibling.pid == task.pid then
|
||||
table.remove(task.siblings, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
tasks[pid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local alpha = 0.85
|
||||
local C_target = 0.01
|
||||
local Tmin = 0.0005
|
||||
local Tmax = 0.5
|
||||
local lambda_budget = 0.08
|
||||
local lambda_clamp = 0.03
|
||||
local lambda_var = 0.02
|
||||
local k_min = 0.5
|
||||
local k_max = 0.5
|
||||
local B = 0.01
|
||||
|
||||
function kernel.main()
|
||||
while not kernel.exitMain do
|
||||
local N = 0
|
||||
local Tmin_hit = 0
|
||||
local Tmax_hit = 0
|
||||
local totalTaskTime = 0
|
||||
local taskTimes = {}
|
||||
|
||||
for pid, task in pairs(tasks) do
|
||||
if task.status == "S" and kernel.computer:time() >= task.sleep then
|
||||
task.status = "R"
|
||||
task.sleep = 0
|
||||
end
|
||||
|
||||
if task.status == "R" then
|
||||
kernel.currentTask = task
|
||||
|
||||
kernel.uid = task.euid or task.uid
|
||||
kernel.process = task.name
|
||||
N = N + 1
|
||||
|
||||
task.timeSlice = math.min(Tmax, math.max(Tmin, B / (N ^ alpha)))
|
||||
|
||||
if task.sigq and #task.sigq ~= 0 and task.sigh then
|
||||
local coro = coroutine.create(task.sigh)
|
||||
if kernel.config.preempt then
|
||||
resumeWithTimeout(coro, task.timeSlice, table.remove(task.sigq, 1))
|
||||
else
|
||||
coroutine.resume(coro, table.remove(task.sigq, 1))
|
||||
end
|
||||
end
|
||||
|
||||
if task.status == "R" then
|
||||
local startTime = kernel.computer:time()
|
||||
local ret
|
||||
|
||||
if kernel.config.preempt then
|
||||
ret = { resumeWithTimeout(task.coro, task.timeSlice, table.unpack(task.syscallReturn)) }
|
||||
else
|
||||
ret = { coroutine.resume(task.coro, table.unpack(task.syscallReturn)) }
|
||||
end
|
||||
|
||||
local elapsed = kernel.computer:time() - startTime
|
||||
task.lastTime = elapsed
|
||||
task.totalTime = (task.totalTime or 0) + elapsed
|
||||
task.numRuns = (task.numRuns or 0) + 1
|
||||
|
||||
taskTimes[#taskTimes+1] = elapsed
|
||||
totalTaskTime = totalTaskTime + elapsed
|
||||
|
||||
if elapsed <= Tmin then Tmin_hit = Tmin_hit + 1 end
|
||||
if elapsed >= Tmax then Tmax_hit = Tmax_hit + 1 end
|
||||
|
||||
if ret[1] == "error" or ret[1] == false then
|
||||
kernel.log("processHandlerException: " .. tostring(ret[2]), "ERROR", 2)
|
||||
task.status = "Z"
|
||||
task.exit = "processHandlerException: " .. tostring(ret[2])
|
||||
|
||||
elseif ret[1] == "timeout" then
|
||||
task.ivs = task.ivs + 1
|
||||
task.syscallReturn = {}
|
||||
|
||||
elseif ret[1] == "success" or ret[1] == true then
|
||||
task.vs = task.vs + 1
|
||||
|
||||
if ret[2] == "syscall" then
|
||||
local scname = ret[3]
|
||||
if kernel.syscalls[scname] then
|
||||
if kernel.config.debugSyscalls then
|
||||
kernel.log("Task " .. task.pid .. " syscall: " .. scname, "DBUG", 5)
|
||||
for i = 4, #ret do
|
||||
kernel.log(" inval[" .. (i-3) .. "] = " .. tostring(ret[i]), "DBUG", 5)
|
||||
end
|
||||
end
|
||||
|
||||
local sysret = { xpcall(kernel.syscalls[scname], debug.traceback, table.unpack(ret, 4)) }
|
||||
|
||||
if kernel.config.debugSyscalls then
|
||||
if not sysret[1] then
|
||||
kernel.log("Task " .. task.pid .. " syscall " .. scname .. " failed: " .. tostring(sysret[2]), "ERROR", 2)
|
||||
else
|
||||
kernel.log("Task " .. task.pid .. " syscall " .. scname .. " ok, " .. (#sysret-1) .. " retvals", "DBUG", 5)
|
||||
for i = 2, #sysret do
|
||||
local v = type(sysret[i]) == "table" and table.serialize(sysret[i]) or tostring(sysret[i])
|
||||
kernel.log(" retval[" .. (i-1) .. "] = " .. v, "DBUG", 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not sysret[1] then
|
||||
task.syscallReturn = { false, sysret[2] }
|
||||
else
|
||||
task.syscallReturn = { true, table.unpack(sysret, 2) }
|
||||
end
|
||||
else
|
||||
task.syscallReturn = { false, "Unknown syscall: " .. tostring(scname) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local T_prev_avg = (N > 0) and (totalTaskTime / N) or 0
|
||||
local T_prev_var = 0
|
||||
for _, t in ipairs(taskTimes) do
|
||||
T_prev_var = T_prev_var + (t - T_prev_avg) ^ 2
|
||||
end
|
||||
if N > 0 then T_prev_var = T_prev_var / N end
|
||||
|
||||
if N > 0 then
|
||||
local f_clamp = k_min * (Tmin_hit / N) - k_max * (Tmax_hit / N)
|
||||
local B_budget = (C_target * (N ^ (alpha - 1))) / math.max(T_prev_avg, 1e-8)
|
||||
B = B + lambda_budget * (B_budget - B) + lambda_clamp * f_clamp - lambda_var * T_prev_var
|
||||
end
|
||||
|
||||
reapDeadTasks()
|
||||
end
|
||||
end
|
||||
|
||||
local sysc = kernel.syscalls
|
||||
sysc["spawn"] = sys.spawn
|
||||
sysc["execspawn"] = sys.execspawn
|
||||
sysc["exec"] = sys.exec
|
||||
sysc["sleep"] = sys.sleep
|
||||
sysc["getTask"] = sys.getTask
|
||||
sysc["collect"] = sys.collect
|
||||
sysc["kill"] = sys.kill
|
||||
sysc["stop"] = sys.stop
|
||||
sysc["continue"] = sys.continue
|
||||
sysc["getpid"] = sys.getpid
|
||||
sysc["getppid"] = sys.getppid
|
||||
sysc["getTasks"] = sys.getTasks
|
||||
sysc["setEnviron"] = sys.setEnviron
|
||||
sysc["getEnviron"] = sys.getEnviron
|
||||
sysc["exit"] = sys.exit
|
||||
sysc["setuid"] = sys.setuid
|
||||
sysc["getuid"] = sys.getuid
|
||||
sysc["geteuid"] = sys.geteuid
|
||||
|
||||
kernel._G.sleep = function(...) coroutine.yield("syscall", "sleep", ...) end
|
||||
|
||||
kernel.tasks = tasks
|
||||
kernel.hpv = sys
|
||||
7
Src/Hyperion-kernel/lib/modules/hyperion/47_dbg.kmod
Normal file
7
Src/Hyperion-kernel/lib/modules/hyperion/47_dbg.kmod
Normal file
@@ -0,0 +1,7 @@
|
||||
--:Minify:--
|
||||
local kernel=...
|
||||
local debug=debug
|
||||
kernel._G.debug={
|
||||
getinfo=debug.getinfo,
|
||||
traceback=debug.traceback
|
||||
}
|
||||
15
Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod
Normal file
15
Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod
Normal file
@@ -0,0 +1,15 @@
|
||||
local kernel=...
|
||||
local sysc=kernel.syscalls
|
||||
kernel.gpio={}
|
||||
|
||||
sysc["gpio_write"]=function(pin, data)
|
||||
if kernel.gpio[pin] then
|
||||
return kernel.gpio[pin]("w", data)
|
||||
end
|
||||
end
|
||||
|
||||
sysc["gpio_read"]=function(pin)
|
||||
if kernel.gpio[pin] then
|
||||
return kernel.gpio[pin]("r")
|
||||
end
|
||||
end
|
||||
22
Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod
Normal file
22
Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod
Normal file
@@ -0,0 +1,22 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
function print(...)
|
||||
local args = {...}
|
||||
local output = ""
|
||||
for i = 1, #args do output = output .. tostring(args[i]) .. "\t" end
|
||||
output = output:sub(1, -2)
|
||||
syscall.write(1, output.."\n")
|
||||
end
|
||||
|
||||
function printf(fmt, ...)
|
||||
local output = string.format(fmt, ...)
|
||||
syscall.write(1, output.."\n")
|
||||
end
|
||||
|
||||
function printInline(...)
|
||||
local args = {...}
|
||||
local output = ""
|
||||
for i = 1, #args do output = output .. tostring(args[i]) .. "\t" end
|
||||
output = output:sub(1, -2)
|
||||
syscall.write(1, output)
|
||||
end
|
||||
52
Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod
Normal file
52
Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod
Normal file
@@ -0,0 +1,52 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
kernel.log("Loading init system...")
|
||||
kernel.log("InitPath: " .. kernel.config.initPath)
|
||||
|
||||
local initOk, initErr = pcall(kernel.vfs.access, kernel.config.initPath, "rx")
|
||||
if not initOk then
|
||||
kernel.PANIC("Init binary not executable: " .. kernel.config.initPath .. " (" .. tostring(initErr) .. ")")
|
||||
end
|
||||
|
||||
local handle = kernel.vfs.open(kernel.config.initPath, "r")
|
||||
local data = kernel.vfs.read(handle, 1024 * 1024 * 4)
|
||||
kernel.vfs.close(handle)
|
||||
|
||||
local initFunc, err = load(data, "@sysinit", "t", kernel._U)
|
||||
if not initFunc then error("Failed to load init system: " .. err) end
|
||||
|
||||
kernel.tasks["1"] = {
|
||||
coro = coroutine.create(function()
|
||||
local ok, err = xpcall(initFunc, debug.traceback, kernel)
|
||||
if not ok then
|
||||
kernel.panic("Init system crashed: " .. tostring(err))
|
||||
else
|
||||
kernel.panic("Init system exited: " .. tostring(err))
|
||||
end
|
||||
end),
|
||||
|
||||
name = "sysinit",
|
||||
status = "R",
|
||||
pid = 1,
|
||||
tgid = 1,
|
||||
uid = 0,
|
||||
fd = {},
|
||||
envars = {},
|
||||
args = {},
|
||||
exit = "",
|
||||
sleep = 0,
|
||||
ivs = 0,
|
||||
vs = 0,
|
||||
parent = kernel.kernelTask,
|
||||
siblings = kernel.kernelTask.children,
|
||||
children = {},
|
||||
syscallReturn = {},
|
||||
cwd = "/",
|
||||
timeSlice = 0,
|
||||
lastTime = 0,
|
||||
totalTime = 0,
|
||||
numRuns = 0
|
||||
}
|
||||
|
||||
kernel.log("created init task with PID 1")
|
||||
kernel.log("Initializing init system...")
|
||||
9
Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod
Normal file
9
Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod
Normal file
@@ -0,0 +1,9 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
|
||||
kernel.processes.login = function()
|
||||
local ok, err = pcall(syscall.execspawn, "/bin/login", "login")
|
||||
if not ok then
|
||||
kernel.log("Failed to exec /bin/login: " .. tostring(err), "ERROR", 2)
|
||||
end
|
||||
end
|
||||
233
Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod
Normal file
233
Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod
Normal file
@@ -0,0 +1,233 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
|
||||
local P = kernel.vfs.P
|
||||
local PERM = kernel.vfs.PERM
|
||||
|
||||
local RW_R_R = P.OWNER_R + P.OWNER_W + P.GROUP_R + P.WORLD_R
|
||||
local RWX_RX_RX = P.OWNER_R + P.OWNER_W + P.OWNER_X
|
||||
+ P.GROUP_R + P.GROUP_X
|
||||
+ P.WORLD_R + P.WORLD_X
|
||||
local RW_R__ = P.OWNER_R + P.OWNER_W + P.GROUP_R
|
||||
local RW____ = P.OWNER_R + P.OWNER_W
|
||||
local RWXRWXRWX = PERM.RWXRWXRWX
|
||||
local SUID_755 = PERM.SUID_755
|
||||
|
||||
local META_VERSION = 0x02
|
||||
local rootDisk = kernel.disks["$"]
|
||||
|
||||
local function makeEntry(name, etype, owner, group, perms, cmeta)
|
||||
cmeta = cmeta or ""
|
||||
local plo = perms % 256
|
||||
local phi = math.floor(perms / 256) % 256
|
||||
local olo = (owner or 0) % 256
|
||||
local ohi = math.floor((owner or 0) / 256) % 256
|
||||
local glo = (group or 0) % 256
|
||||
local ghi = math.floor((group or 0) / 256) % 256
|
||||
return string.char(#name) .. name
|
||||
.. string.char(etype, olo, ohi, glo, ghi, plo, phi)
|
||||
.. string.char(#cmeta) .. cmeta
|
||||
end
|
||||
|
||||
local REG = 0x00
|
||||
|
||||
local function mergeMeta(dir, entries)
|
||||
local diskDir = dir
|
||||
if diskDir:sub(1,1) == "/" then diskDir = diskDir:sub(2) end
|
||||
local metaPath = (diskDir == "" and ".meta" or diskDir .. "/.meta")
|
||||
|
||||
local existing = {}
|
||||
local rok, rf = pcall(function() return rootDisk:open(metaPath, "r") end)
|
||||
if rok and rf then
|
||||
local raw = rf.read(65535)
|
||||
if rf.close then rf.close() end
|
||||
existing = (kernel.vfs._parseMetafile and kernel.vfs._parseMetafile(raw)) or {}
|
||||
end
|
||||
|
||||
for _, e in ipairs(entries) do
|
||||
local name = e[1]
|
||||
local etype = e[2] or REG
|
||||
local owner = e[3] or 0
|
||||
local group = e[4] or 0
|
||||
local perms = e[5] or RWX_RX_RX
|
||||
local cmeta = e[6] or ""
|
||||
existing[name] = {
|
||||
etype = etype,
|
||||
owner = owner,
|
||||
group = group,
|
||||
perms = perms,
|
||||
cmeta = cmeta,
|
||||
}
|
||||
end
|
||||
|
||||
local data = string.char(META_VERSION)
|
||||
for name, m in pairs(existing) do
|
||||
data = data .. makeEntry(
|
||||
name,
|
||||
m.etype or REG,
|
||||
m.owner or 0,
|
||||
m.group or 0,
|
||||
m.perms or RWX_RX_RX,
|
||||
m.cmeta or ""
|
||||
)
|
||||
end
|
||||
|
||||
local ok, err = pcall(function()
|
||||
local f = rootDisk:open(metaPath, "w")
|
||||
f.write(data)
|
||||
f.close()
|
||||
end)
|
||||
if not ok then
|
||||
kernel.log("permissions: failed to write " .. metaPath .. ": " .. tostring(err), "WARN", 8)
|
||||
end
|
||||
end
|
||||
|
||||
kernel.log("Seeding filesystem permissions...", "INFO")
|
||||
|
||||
mergeMeta("/", {
|
||||
{"bin", REG, 0, 0, RWX_RX_RX},
|
||||
{"boot", REG, 0, 0, RWX_RX_RX},
|
||||
{"dev", REG, 0, 0, RWX_RX_RX},
|
||||
{"etc", REG, 0, 0, RWX_RX_RX},
|
||||
{"home", REG, 0, 0, RWX_RX_RX},
|
||||
{"lib", REG, 0, 0, RWX_RX_RX},
|
||||
{"root", REG, 0, 0, RW____ },
|
||||
{"sbin", REG, 0, 0, RWX_RX_RX},
|
||||
{"tmp", REG, 0, 0, RWXRWXRWX},
|
||||
{"usr", REG, 0, 0, RWX_RX_RX},
|
||||
{"var", REG, 0, 0, RWX_RX_RX},
|
||||
})
|
||||
|
||||
mergeMeta("/boot", {
|
||||
{"kernel.lua", REG, 0, 0, RW_R_R },
|
||||
{"boot.cfg", REG, 0, 0, RW_R_R },
|
||||
{"safeboot.cfg", REG, 0, 0, RW_R_R },
|
||||
{"fstab", REG, 0, 0, RW_R_R },
|
||||
{"initfs", REG, 0, 0, RW_R_R },
|
||||
{"cct", REG, 0, 0, RWX_RX_RX},
|
||||
{"oc", REG, 0, 0, RWX_RX_RX},
|
||||
})
|
||||
|
||||
mergeMeta("/boot/cct", {
|
||||
{"boot.lua", REG, 0, 0, RW_R_R},
|
||||
{"initdisks", REG, 0, 0, RW_R_R},
|
||||
{"eeprom", REG, 0, 0, RW_R_R},
|
||||
})
|
||||
|
||||
mergeMeta("/boot/oc", {
|
||||
{"boot.lua", REG, 0, 0, RW_R_R},
|
||||
{"initfs.lua",REG, 0, 0, RW_R_R},
|
||||
{"eeprom", REG, 0, 0, RW_R_R},
|
||||
})
|
||||
|
||||
mergeMeta("/sbin", {
|
||||
{"init.lua", REG, 0, 0, RWX_RX_RX},
|
||||
})
|
||||
|
||||
mergeMeta("/bin", {
|
||||
{"cat", REG, 0, 0, RWX_RX_RX},
|
||||
{"chattr", REG, 0, 0, RWX_RX_RX},
|
||||
{"chgrp", REG, 0, 0, RWX_RX_RX},
|
||||
{"chmod", REG, 0, 0, RWX_RX_RX},
|
||||
{"chown", REG, 0, 0, RWX_RX_RX},
|
||||
{"chroot", REG, 0, 0, RWX_RX_RX},
|
||||
{"clear", REG, 0, 0, RWX_RX_RX},
|
||||
{"echo", REG, 0, 0, RWX_RX_RX},
|
||||
{"hfetch", REG, 0, 0, RWX_RX_RX},
|
||||
{"help", REG, 0, 0, RWX_RX_RX},
|
||||
{"hysh", REG, 0, 0, RWX_RX_RX},
|
||||
{"hyshex", REG, 0, 0, RWX_RX_RX},
|
||||
{"id", REG, 0, 0, RWX_RX_RX},
|
||||
{"install", REG, 0, 0, RWX_RX_RX},
|
||||
{"ln", REG, 0, 0, RWX_RX_RX},
|
||||
{"login", REG, 0, 0, SUID_755 },
|
||||
{"loimgcreate", REG, 0, 0, RWX_RX_RX},
|
||||
{"looptest", REG, 0, 0, RWX_RX_RX},
|
||||
{"losetup", REG, 0, 0, RWX_RX_RX},
|
||||
{"ls", REG, 0, 0, RWX_RX_RX},
|
||||
{"lsusers", REG, 0, 0, RWX_RX_RX},
|
||||
{"lua", REG, 0, 0, RWX_RX_RX},
|
||||
{"luaold", REG, 0, 0, RWX_RX_RX},
|
||||
{"micro", REG, 0, 0, RWX_RX_RX},
|
||||
{"mkdir", REG, 0, 0, RWX_RX_RX},
|
||||
{"mount", REG, 0, 0, RWX_RX_RX},
|
||||
{"passwd", REG, 0, 0, RWX_RX_RX},
|
||||
{"ps", REG, 0, 0, RWX_RX_RX},
|
||||
{"pwd", REG, 0, 0, RWX_RX_RX},
|
||||
{"readlink", REG, 0, 0, RWX_RX_RX},
|
||||
{"sed", REG, 0, 0, RWX_RX_RX},
|
||||
{"socktest", REG, 0, 0, RWX_RX_RX},
|
||||
{"spm", REG, 0, 0, RWX_RX_RX},
|
||||
{"startup", REG, 0, 0, RWX_RX_RX},
|
||||
{"su", REG, 0, 0, SUID_755 },
|
||||
{"sudo", REG, 0, 0, SUID_755 },
|
||||
{"sysdump", REG, 0, 0, RWX_RX_RX},
|
||||
{"umount", REG, 0, 0, RWX_RX_RX},
|
||||
{"useradd", REG, 0, 0, RWX_RX_RX},
|
||||
{"userdel", REG, 0, 0, RWX_RX_RX},
|
||||
{"usermod", REG, 0, 0, RWX_RX_RX},
|
||||
{"whoami", REG, 0, 0, RWX_RX_RX},
|
||||
{"yes", REG, 0, 0, RWX_RX_RX},
|
||||
})
|
||||
|
||||
mergeMeta("/bin/startup", {
|
||||
{"test.lua", REG, 0, 0, RWX_RX_RX},
|
||||
})
|
||||
|
||||
mergeMeta("/lib", {
|
||||
{"sys", REG, 0, 0, RWX_RX_RX},
|
||||
{"modules", REG, 0, 0, RWX_RX_RX},
|
||||
{"crypto", REG, 0, 0, RWX_RX_RX},
|
||||
{"store", REG, 0, 0, RWX_RX_RX},
|
||||
{"snip", REG, 0, 0, RW_R_R },
|
||||
{"io", REG, 0, 0, RW_R_R },
|
||||
{"bit32", REG, 0, 0, RW_R_R },
|
||||
})
|
||||
|
||||
mergeMeta("/lib/sys", {
|
||||
{"fs", REG, 0, 0, RW_R_R},
|
||||
{"hpv", REG, 0, 0, RW_R_R},
|
||||
{"ipc", REG, 0, 0, RW_R_R},
|
||||
{"term", REG, 0, 0, RW_R_R},
|
||||
{"init", REG, 0, 0, RW_R_R},
|
||||
})
|
||||
|
||||
mergeMeta("/lib/modules", {
|
||||
{"hyperion", REG, 0, 0, RWX_RX_RX},
|
||||
})
|
||||
|
||||
mergeMeta("/lib/modules/hyperion", {
|
||||
{"01_stdlib.kmod", REG, 0, 0, RW_R_R},
|
||||
{"10_vfs.kmod", REG, 0, 0, RW_R_R},
|
||||
{"11_require.kmod", REG, 0, 0, RW_R_R},
|
||||
{"12_devfs.kmod", REG, 0, 0, RW_R_R},
|
||||
{"12_tmpfs.kmod", REG, 0, 0, RW_R_R},
|
||||
{"13_loopdev.kmod", REG, 0, 0, RW_R_R},
|
||||
{"14_keventd.kmod", REG, 0, 0, RW_R_R},
|
||||
{"19_fstab.kmod", REG, 0, 0, RW_R_R},
|
||||
{"20_signals.kmod", REG, 0, 0, RW_R_R},
|
||||
{"20_socket.kmod", REG, 0, 0, RW_R_R},
|
||||
{"26_tty.kmod", REG, 0, 0, RW_R_R},
|
||||
{"30_userspace.kmod", REG, 0, 0, RW_R_R},
|
||||
{"40_auth.kmod", REG, 0, 0, RW_R_R},
|
||||
{"45_hypervisor.kmod", REG, 0, 0, RW_R_R},
|
||||
{"47_dbg.kmod", REG, 0, 0, RW_R_R},
|
||||
{"50_gpio.kmod", REG, 0, 0, RW_R_R},
|
||||
{"70_stdlibadv.kmod", REG, 0, 0, RW_R_R},
|
||||
{"90_init.kmod", REG, 0, 0, RW_R_R},
|
||||
{"91_login.kmod", REG, 0, 0, RW_R_R},
|
||||
{"92_permissions.kmod", REG, 0, 0, RW_R_R},
|
||||
{"99_final.kmod", REG, 0, 0, RW_R_R},
|
||||
})
|
||||
|
||||
mergeMeta("/etc", {
|
||||
{"passwd", REG, 0, 0, RW_R_R },
|
||||
{"shadow", REG, 0, 0, RW____ },
|
||||
{"pam.d", REG, 0, 0, RWX_RX_RX},
|
||||
})
|
||||
|
||||
mergeMeta("/etc/pam.d", {
|
||||
{"secret", REG, 0, 0, RW____},
|
||||
})
|
||||
|
||||
kernel.log("Filesystem permissions seeded.", "INFO")
|
||||
2
Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod
Normal file
2
Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod
Normal file
@@ -0,0 +1,2 @@
|
||||
local kernel = ...
|
||||
kernel.allowGlobalOverwrites = false
|
||||
Reference in New Issue
Block a user