Hyperion v1.2.0

This commit is contained in:
2026-02-22 21:53:02 -06:00
parent dd2437d4af
commit 40c97ca000
37 changed files with 6736 additions and 1329 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,9 @@ proxy.address = "tmpfs0000"
proxy.isvirt = true
proxy.isReadOnly = function() return false end
-- Space functions (just placeholders)
proxy.spaceUsed = function() return 0 end
proxy.spaceTotal = function() return 0 end
-- Writable operations
proxy.makeDirectory = function(_, path)
local steps = kernel.vfs.splitPath(path)
local step = data
@@ -52,7 +50,6 @@ proxy.attributes = function(_, path)
}
end
-- Open files
function proxy:open(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
@@ -71,26 +68,32 @@ function proxy:open(path, mode)
local content = step[filename]
local pos = 1
return {
read = function(amount)
read = function(amount)
amount = amount or #content
local chunk = content:sub(pos, pos+amount-1)
pos = pos + #chunk
return chunk
end
end,
close = function() end,
}
elseif mode == "w" then
step[filename] = ""
local buf = {}
return {
write = function(str)
step[filename] = str
end
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
end,
close = function() end,
}
else
error("EACCES")
@@ -123,7 +126,8 @@ function proxy:list(path)
end
function proxy:fileExists(path)
return pcall(function() return self:type(path) end)
local t = self:type(path)
return t == "file" or t == "directory"
end
kernel.disks["tmpfs0000"] = proxy

View 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)")

View File

@@ -1,14 +1,556 @@
--:Minify:--
-- :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 socket = {}
function socket.socket()
local sockets = {}
local unixSocks = {}
local nextSockId = 1
local function allocSockId()
local id = nextSockId
nextSockId = nextSockId + 1
return id
end
function socket.bind()
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
kernel.socket=socket
kernel.log("Loaded socket module")
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")

View File

@@ -1,6 +1,395 @@
--:Minify:--
local kernel=...
kernel.vfs.open("/dev/null", "r")
kernel.vfs.open("/dev/tty/TTY1", "w")
kernel.vfs.open("/dev/null", "w")
kernel.status="term"
-- :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

View File

@@ -264,11 +264,13 @@ function auth.login(username, password)
end
kernel.currentUID = uid
if kernel.currentProcess then
kernel.currentProcess.uid = uid
kernel.currentProcess.euid = uid
kernel.currentProcess.gid = tonumber(entry[2]) or uid
kernel.currentProcess.egid = tonumber(entry[2]) or 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 .. ")")
@@ -372,7 +374,7 @@ function auth.newUser(username, password, gid, homedir, shell)
local uid = nextUID()
gid = tonumber(gid) or uid
homedir = homedir or ("/home/" .. username)
shell = shell or "/bin/sh"
shell = shell or "/bin/hysh"
passwd[#passwd + 1] = {
tostring(uid),
@@ -436,11 +438,9 @@ function auth.deleteUser(uid)
if not entry then return nil, "No such user" end
local username = entry[3]
-- Remove from passwd
for i, v in ipairs(passwd) do
if tonumber(v[1]) == uid then table.remove(passwd, i); break end
end
-- Remove from shadow
for i, v in ipairs(shadow) do
if tonumber(v[1]) == uid then table.remove(shadow, i); break end
end
@@ -463,7 +463,6 @@ function auth.lockUser(uid)
local sEntry = getShadowByUID(uid)
if not sEntry then return nil, "No shadow entry for uid" end
-- Prefix hash with ! to lock (standard Linux convention)
if sEntry[3]:sub(1,1) ~= "!" then
sEntry[3] = "!" .. sEntry[3]
end
@@ -567,9 +566,6 @@ function auth.setGID(uid, gid)
return true
end
-- Elevate the calling task to targetUid after verifying targetUsername's password.
-- This is the kernel-side primitive for su/sudo — it bypasses the kernel.uid==0
-- check in sys.setuid because the auth module itself is trusted kernel code.
function auth.elevate(targetUsername, password)
if type(targetUsername) ~= "string" or type(password) ~= "string" then
return nil, "Authentication failure"
@@ -593,7 +589,6 @@ function auth.elevate(targetUsername, password)
return nil, "Authentication failure"
end
-- Directly set the calling task's uid — trusted kernel path
local task = kernel.currentTask
local prevUid = task.uid
task.uid = uid
@@ -612,7 +607,7 @@ if kernel.syscalls then
kernel.syscalls["setusername"] = auth.setUsername
kernel.syscalls["newuser"] = auth.newUser
kernel.syscalls["whoami"] = auth.whoami
kernel.syscalls["getuid"] = auth.getUID
kernel.syscalls["getuidbyname"]= auth.getUID
kernel.syscalls["getpasswd"] = auth.getPasswd
kernel.syscalls["elevate"] = auth.elevate
kernel.syscalls["deleteuser"] = auth.deleteUser

View File

@@ -1,305 +1,355 @@
-- :Minify:--
--:Minify:--
local kernel = ...
local tasks = {}
local sys = {}
local tasks = {}
local sys = {}
local nextpid = 2
kernel.exitMain = false
function sys.spawn(func, name, envars, args, tgid)
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
nextpid = nextpid + 1
tasks[tostring(id)] = {
coro = coroutine.create(function()
local ok, err = xpcall(func, debug.traceback, table.unpack(args or {}))
if not ok then
if kernel.config.logTaskExit then
kernel.log(
"Task " .. tostring(id) .. " exited with err: " ..
tostring(err), "ERROR", 2)
end
if type(err) == "number" then
tasks[tostring(id)].exit = err
end
else
if kernel.config.logTaskExit then
if 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
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
for v, _ in ipairs(tasks[tostring(id)].fd) do pcall(kernel.vfs.close,v) end
tasks[tostring(id)].status = "Z"
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.envars,
args = args or {},
status = "R",
pid = id,
tgid = tgid or kernel.currentTask.tgid,
uid = kernel.uid,
fd = {},
sleep = 0,
ivs = 0,
vs = 0,
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,
siblings = kernel.currentTask.children,
parent = kernel.currentTask or kernel.kernelTask,
siblings = (kernel.currentTask and kernel.currentTask.children) or kernel.kernelTask.children,
syscallReturn = {},
cwd = kernel.currentTask.cwd,
cwd = (kernel.currentTask and kernel.currentTask.cwd) or "/",
timeSlice = 0,
lastTime = 0,
lastTime = 0,
totalTime = 0,
numRuns = 0
numRuns = 0,
}
table.insert(kernel.currentTask.children, tasks[tostring(id)])
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
kernel.currentTask.sleep = kernel.computer:time() + s * 1000
coroutine.yield()
end
function sys.getTask(pid)
if tasks[tostring(pid)] then
local task = tasks[tostring(pid)]
local children = {}
local siblings = {}
local task = tasks[tostring(pid)]
if not task then return nil end
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
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,
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
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 i, v in ipairs(kernel.currentTask.children) do children[i] = v.pid end
for _, v in ipairs(kernel.currentTask.children) do children[#children+1] = v.pid end
if not tasks[tostring(pid)] then
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif not isEqualToAny(tasks[tostring(pid)].pid, table.unpack(children)) then
elseif not isEqualToAny(task.pid, table.unpack(children)) then
return false, "You do not own this task"
elseif tasks[tostring(pid)].status ~= "Z" then
elseif task.status ~= "Z" then
return false, "Task must exit to collect status"
else
tasks[tostring(pid)].reapTime = 0
return true, tasks[tostring(pid)].exit
task.reapTime = 0
return true, task.exit
end
end
function sys.kill(pid)
local children = {}
for i, v in ipairs(kernel.currentTask.children) do children[i] = v.pid end
if not tasks[tostring(pid)] then
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif tasks[tostring(pid)].status == "Z" then
elseif task.status == "Z" then
return false, "Task is already dead"
else
tasks[tostring(pid)].status = "Z"
task.status = "Z"
return true
end
end
function sys.stop(pid)
local children = {}
for i, v in ipairs(kernel.currentTask.children) do children[i] = v.pid end
if not tasks[tostring(pid)] then
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif tasks[tostring(pid)].status ~= "R" then
return false, "Cannot stop non running task"
elseif task.status ~= "R" then
return false, "Cannot stop non-running task"
else
tasks[tostring(pid)].status = "T"
task.status = "T"
return true
end
end
function sys.continue(pid)
local children = {}
for i, v in ipairs(kernel.currentTask.children) do children[i] = v.pid end
if not tasks[tostring(pid)] then
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif tasks[tostring(pid)].status ~= "T" then
elseif task.status ~= "T" then
return false, "Task is not stopped"
else
tasks[tostring(pid)].status = "R"
task.status = "R"
return true
end
end
function sys.getpid() return kernel.currentTask.pid end
function sys.getpid() return kernel.currentTask.pid end
function sys.getppid() return kernel.currentTask.parent.pid end
function sys.getTasks()
local ret = {}
for i, v in pairs(tasks) do ret[#ret + 1] = v.pid end
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, value) kernel.currentTask.envars[key] = value 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(kernel.currentTask.pid) .. " exited with code: " .. tostring(code), "INFO")
kernel.log("Task " .. tostring(task.pid) .. " exited with code: " .. tostring(code), "INFO")
else
kernel.log("Task " .. tostring(kernel.currentTask.pid) .. " exited without code", "INFO")
kernel.log("Task " .. tostring(task.pid) .. " exited without code", "INFO")
end
end
tasks[tostring(kernel.currentTask.pid)].status = "Z"
tasks[tostring(task.pid)].status = "Z"
if type(code) == "number" then
tasks[tostring(kernel.currentTask.pid)].exit = code
tasks[tostring(task.pid)].exit = code
end
end
function sys.setuid(uid)
if kernel.uid ~= 0 then error("EACCES") end
kernel.currentTask.uid = 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 sysc = kernel.syscalls
sysc["spawn"] = sys.spawn
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
kernel._G.sleep = function(...) coroutine.yield("syscall", "sleep", ...) end
local function reapDeadTasks()
for pid, task in pairs(tasks) do
if task.status == "Z" and not task.reapTime then
kernel.currentTask = task
kernel.uid = task.uid
kernel.process = task.name
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.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
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
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.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 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
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 N = 0
local Tmin_hit = 0
local Tmax_hit = 0
local totalTaskTime = 0
local taskTimes = {}
local taskTimes = {}
for pid, task in pairs(tasks) do
if task.status == "S" then
if kernel.computer:time() >= task.sleep then
task.status = "R"
task.sleep = 0
end
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.uid
kernel.uid = task.euid or task.uid
kernel.process = task.name
N = N + 1
-- assign adaptive time slice
task.timeSlice = math.min(Tmax, math.max(Tmin, B / (N ^ alpha)))
if task.sigq and #task.sigq~=0 and task.sigh then
if task.sigq and #task.sigq ~= 0 and task.sigh then
local coro = coroutine.create(task.sigh)
if kernel.config.preempt then
coroutine.resumeWithTimeout(coro, task.timeSlice, table.remove(task.sigq, 1))
@@ -308,44 +358,31 @@ function kernel.main()
end
end
-- check for exit/stop
if task.status=="R" then
-- measure execution time
if task.status == "R" then
local startTime = kernel.computer:time()
local ret
if kernel.config.preempt then
ret = {
coroutine.resumeWithTimeout(
task.coro,
task.timeSlice,
table.unpack(task.syscallReturn)
)
}
ret = { coroutine.resumeWithTimeout(task.coro, task.timeSlice, table.unpack(task.syscallReturn)) }
else
ret = {
coroutine.resume(
task.coro,
table.unpack(task.syscallReturn)
)
}
ret = { coroutine.resume(task.coro, table.unpack(task.syscallReturn)) }
end
local elapsed = kernel.computer:time() - startTime
task.lastTime = elapsed
task.lastTime = elapsed
task.totalTime = (task.totalTime or 0) + elapsed
task.numRuns = (task.numRuns or 0) + 1
task.numRuns = (task.numRuns or 0) + 1
taskTimes[#taskTimes + 1] = elapsed
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
-- handle task results
if ret[1] == "error" or ret[1] == false then
kernel.log("processHandlerException: " .. ret[2], "ERROR", 2)
kernel.log("processHandlerException: " .. tostring(ret[2]), "ERROR", 2)
task.status = "Z"
task.exit = "processHandlerException: " .. ret[2]
task.exit = "processHandlerException: " .. tostring(ret[2])
elseif ret[1] == "timeout" then
task.ivs = task.ivs + 1
@@ -355,57 +392,36 @@ function kernel.main()
task.vs = task.vs + 1
if ret[2] == "syscall" then
if kernel.syscalls[ret[3]] then
local scname = ret[3]
if kernel.syscalls[scname] then
if kernel.config.debugSyscalls then
kernel.log("Task " .. task.pid .. " invoking syscall: " .. ret[3], "DBUG", 5)
kernel.log("Task " .. task.pid .. " syscall: " .. scname, "DBUG", 5)
for i = 4, #ret do
kernel.log(" inval[" .. tostring(i - 3) .. "] = " .. tostring(ret[i]), "DBUG", 5)
kernel.log(" inval[" .. (i-3) .. "] = " .. tostring(ret[i]), "DBUG", 5)
end
end
local sysret = {
xpcall(kernel.syscalls[ret[3]], debug.traceback, table.unpack(ret, 4))
}
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 " .. ret[3] .. " failed: " .. tostring(sysret[2]), "ERROR", 2
)
kernel.log("Task " .. task.pid .. " syscall " .. scname .. " failed: " .. tostring(sysret[2]), "ERROR", 2)
else
kernel.log(
"Task " .. task.pid .. " syscall " .. ret[3] .. " completed returning " .. tostring(#sysret - 1) .. " values", "DBUG", 5
)
kernel.log("Task " .. task.pid .. " syscall " .. scname .. " ok, " .. (#sysret-1) .. " retvals", "DBUG", 5)
for i = 2, #sysret do
if type(sysret[i]) == "table" then
kernel.log(
" retval[" .. tostring(i - 1) .. "] = " .. table.serialize(sysret[i]),"DBUG", 5
)
else
kernel.log(
" retval[" .. tostring(i - 1) .. "] = " .. tostring(sysret[i]), "DBUG", 5
)
end
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]}
task.syscallReturn = { false, sysret[2] }
else
task.syscallReturn = {
true, table.unpack(sysret, 2)
}
task.syscallReturn = { true, table.unpack(sysret, 2) }
end
else
task.syscallReturn = {
false, "Unknown syscall: " .. tostring(ret[3])
}
task.syscallReturn = { false, "Unknown syscall: " .. tostring(scname) }
end
end
end
@@ -415,24 +431,42 @@ function kernel.main()
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
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
-- clean up dead tasks
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
kernel.hpv = sys

View File

@@ -3,8 +3,13 @@ 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)
local data = kernel.vfs.read(handle, 1024 * 1024 * 4)
kernel.vfs.close(handle)
local initFunc, err = load(data, "@sysinit", "t", kernel._U)
@@ -20,27 +25,27 @@ kernel.tasks["1"] = {
end
end),
name = "sysinit",
name = "sysinit",
status = "R",
pid = 1,
tgid = 1,
uid = 0,
fd = {},
pid = 1,
tgid = 1,
uid = 0,
fd = {},
envars = {},
args = {},
exit = "",
sleep = 0,
ivs = 0,
vs = 0,
args = {},
exit = "",
sleep = 0,
ivs = 0,
vs = 0,
parent = kernel.kernelTask,
siblings = kernel.kernelTask.children,
children = {},
syscallReturn = {},
cwd = "/",
cwd = "/",
timeSlice = 0,
lastTime = 0,
lastTime = 0,
totalTime = 0,
numRuns = 0
numRuns = 0
}
kernel.log("created init task with PID 1")

View File

@@ -1,16 +1,9 @@
--:Minify:--
-- :Minify:--
local kernel = ...
-- It runs at uid 0 so it can call setuid() to drop privileges to the logged in user
kernel.processes.login = function()
local handle = kernel.vfs.open("/bin/login", "r")
local text = kernel.vfs.read(handle, 1024 * 1024)
kernel.vfs.close(handle)
local fn, err = load(text, "@/bin/login", "t", kernel._U)
if not fn then
kernel.log("Failed to load /bin/login: " .. tostring(err), "ERROR", 2)
return
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
fn()
end

View File

@@ -1,165 +1,142 @@
--:Minify:--
-- :Minify:--
local kernel = ...
local bit32 = require("bit32")
local bor = bit32.bor
local lshift = bit32.lshift
local P = kernel.vfs.P
local PERM = kernel.vfs.PERM
-- bit 0 = everyone-write, bit 1 = everyone-read
-- bit 2 = group-write, bit 3 = group-read
-- bit 4 = owner-write, bit 5 = owner-read
-- bit 6 = suid
local P_OWNER_R = lshift(1, 5)
local P_OWNER_W = lshift(1, 4)
local P_GROUP_R = lshift(1, 3)
local P_GROUP_W = lshift(1, 2)
local P_WORLD_R = lshift(1, 1)
local P_WORLD_W = lshift(1, 0)
local P_SUID = lshift(1, 6)
local RW_R_R = bor(P_OWNER_R, P_OWNER_W, P_GROUP_R, P_WORLD_R) -- 644 / rw-r--r--
local RWX_R_R = bor(P_OWNER_R, P_OWNER_W, P_GROUP_R, P_WORLD_R) -- 755 / rwxr--r--
local RW_R__ = bor(P_OWNER_R, P_OWNER_W, P_GROUP_R) -- 640 / rw-r-----
local RW____ = bor(P_OWNER_R, P_OWNER_W) -- 600 / rw-------
local SUID_755 = bor(P_SUID, P_OWNER_R, P_OWNER_W, P_GROUP_R, P_WORLD_R) -- 4755
local function metaEntry(name, owner, group, perms)
return string.char(#name) .. name
.. string.char(owner, group, perms)
.. string.char(0)
end
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 = 0x01
local rootDisk = kernel.disks["$"]
local function writeMeta(dir, entries)
local diskDir = dir == "/" and "/" or dir
local path = (diskDir:sub(-1) == "/" and diskDir or diskDir .. "/") .. ".meta"
if path:sub(1,1) == "/" then path = path:sub(2) end
if path == "" then path = ".meta" end
local function makeEntry(name, etype, owner, group, perms, cmeta)
cmeta = cmeta or ""
local plo = perms % 256
local phi = math.floor(perms / 256) % 256
return string.char(#name) .. name
.. string.char(etype, owner, group, plo, phi)
.. string.char(#cmeta) .. cmeta
end
local data = ""
local function writeMeta(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 data = string.char(META_VERSION)
for _, e in ipairs(entries) do
data = data .. metaEntry(e[1], e[2], e[3], e[4])
data = data .. makeEntry(e[1], e[2] or 0x00, e[3], e[4], e[5], e[6])
end
local ok, err = pcall(function()
local f = rootDisk:open(path, "w")
local f = rootDisk:open(metaPath, "w")
f.write(data)
f.close()
end)
if not ok then
kernel.log("permissions: failed to write /" .. path .. ": " .. tostring(err), "WARN", 8)
kernel.log("permissions: failed to write " .. metaPath .. ": " .. tostring(err), "WARN", 8)
end
end
local REG = 0x00
if rootDisk:fileExists(".meta") then
kernel.log("Permissions already seeded, skipping.", "INFO")
else
kernel.log("Seeding filesystem permissions...", "INFO")
-- /
writeMeta("/", {
{"bin", 0, 0, RWX_R_R},
{"boot", 0, 0, RWX_R_R},
{"dev", 0, 0, RWX_R_R},
{"etc", 0, 0, RWX_R_R},
{"home", 0, 0, RWX_R_R},
{"lib", 0, 0, RWX_R_R},
{"root", 0, 0, RW____ },
{"sbin", 0, 0, RWX_R_R},
{"tmp", 0, 0, bor(P_OWNER_R, P_OWNER_W, P_GROUP_R, P_GROUP_W, P_WORLD_R, P_WORLD_W)},
{"usr", 0, 0, RWX_R_R},
{"var", 0, 0, RWX_R_R},
{"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},
})
-- /bin
writeMeta("/bin", {
{"cat", 0, 0, RWX_R_R},
{"clear", 0, 0, RWX_R_R},
{"echo", 0, 0, RWX_R_R},
{"hfetch", 0, 0, RWX_R_R},
{"hysh", 0, 0, RWX_R_R},
{"hyshex", 0, 0, RWX_R_R},
{"install", 0, 0, RWX_R_R},
{"login", 0, 0, SUID_755},
{"ls", 0, 0, RWX_R_R},
{"lua", 0, 0, RWX_R_R},
{"luaold", 0, 0, RWX_R_R},
{"mkdir", 0, 0, RWX_R_R},
{"ps", 0, 0, RWX_R_R},
{"pwd", 0, 0, RWX_R_R},
{"spm", 0, 0, RWX_R_R},
{"su", 0, 0, SUID_755},
{"sudo", 0, 0, SUID_755},
{"sysdump", 0, 0, RWX_R_R},
{"whoami", 0, 0, RWX_R_R},
{"yes", 0, 0, RWX_R_R},
{"startup", 0, 0, RWX_R_R},
{"cat", 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},
{"hysh", REG, 0, 0, RWX_RX_RX},
{"hyshex", REG, 0, 0, RWX_RX_RX},
{"install", REG, 0, 0, RWX_RX_RX},
{"login", REG, 0, 0, SUID_755 },
{"ls", REG, 0, 0, RWX_RX_RX},
{"lua", REG, 0, 0, RWX_RX_RX},
{"luaold", REG, 0, 0, RWX_RX_RX},
{"mkdir", REG, 0, 0, RWX_RX_RX},
{"ps", REG, 0, 0, RWX_RX_RX},
{"pwd", REG, 0, 0, RWX_RX_RX},
{"spm", 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},
{"whoami", REG, 0, 0, RWX_RX_RX},
{"yes", REG, 0, 0, RWX_RX_RX},
{"startup", REG, 0, 0, RWX_RX_RX},
{"ln", REG, 0, 0, RWX_RX_RX},
{"readlink", REG, 0, 0, RWX_RX_RX},
})
writeMeta("/bin/startup", {
{"test.lua", 0, 0, RWX_R_R},
{"test.lua", REG, 0, 0, RWX_RX_RX},
})
-- /etc
writeMeta("/etc", {
{"passwd", 0, 0, RW_R_R},
{"shadow", 0, 0, RW____ },
{"pam.d", 0, 0, RWX_R_R},
{"passwd", REG, 0, 0, RW_R_R},
{"shadow", REG, 0, 0, RW____},
{"pam.d", REG, 0, 0, RWX_RX_RX},
})
writeMeta("/etc/pam.d", {
{"secret", 0, 0, RW____},
{"secret", REG, 0, 0, RW____},
})
-- /sbin
writeMeta("/sbin", {
{"init.lua", 0, 0, RWX_R_R},
{"init.lua", REG, 0, 0, RWX_RX_RX},
})
-- /boot
writeMeta("/boot", {
{"kernel.lua", 0, 0, RW_R_R},
{"boot.cfg", 0, 0, RW_R_R},
{"safeboot.cfg", 0, 0, RW_R_R},
{"fstab", 0, 0, RW_R_R},
{"initfs", 0, 0, RW_R_R},
{"cct", 0, 0, RWX_R_R},
{"oc", 0, 0, RWX_R_R},
{"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},
})
-- /lib
writeMeta("/lib", {
{"sys", 0, 0, RWX_R_R},
{"modules", 0, 0, RWX_R_R},
{"crypto", 0, 0, RWX_R_R},
{"store", 0, 0, RWX_R_R},
{"snip", 0, 0, RW_R_R},
{"io", 0, 0, RW_R_R},
{"bit32", 0, 0, RW_R_R},
{"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 },
})
kernel.log("Filesystem permissions seeded.", "INFO")
end
-- TODO: move this to vfs.kmod
local _orig_open = kernel.vfs.open
kernel.vfs.open = function(path, mode)
local fd = _orig_open(path, mode)
if mode == "r" then
local task = kernel.currentTask
local fobj = task.fd[fd]
if fobj and fobj.meta then
local suid_set = bit32.extract(fobj.meta.perms, 6) == 1
if suid_set then
fobj.suid_owner = fobj.meta.owner
end
end
end
return fd
end
kernel.syscalls["fget_suid"] = function(fd)
local task = kernel.currentTask
local fobj = task and task.fd[fd]
if fobj and fobj.suid_owner then
return fobj.suid_owner
end
return nil
end
kernel.log("Permission module loaded.", "INFO")