forked from Hyperion/HyperionOS
Import
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,295 @@
|
||||
--:Minify:--
|
||||
syscall.open("/dev/tty/TTY1","r") --stdin (Device 0)
|
||||
syscall.open("/dev/tty/TTY1","w") --stdout (Device 1)
|
||||
syscall.open("/dev/null","w") --stderr (device 2)
|
||||
|
||||
local success, errorMsg = xpcall(function()
|
||||
|
||||
|
||||
|
||||
local fs = require("sys.fs")
|
||||
|
||||
syscall.devctl(1,"clear")
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"spos",1,1)
|
||||
print("HyperionOS Bash Shell")
|
||||
|
||||
local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown")
|
||||
local commandHistory = {}
|
||||
local terminate = false
|
||||
syscall.setEnviron("SHELL","rtbash")
|
||||
syscall.setEnviron("PATH","/bin/")
|
||||
syscall.chdir("/")
|
||||
local oldWD = ""
|
||||
|
||||
for i = 1, 16 do
|
||||
syscall.devctl(1,"sbgc",i)
|
||||
printInline(" ");
|
||||
end
|
||||
print("\n")
|
||||
|
||||
syscall.sigcatch(function(sig)
|
||||
if sig == 1 then
|
||||
terminate = true
|
||||
end
|
||||
end)
|
||||
|
||||
local builtinCmds = {}
|
||||
|
||||
builtinCmds.cd = function(path)
|
||||
local cwd = syscall.getcwd()
|
||||
local dirIn = (path or "")
|
||||
if dirIn == "-" then
|
||||
if oldWD == "" then
|
||||
print("bash-cd: No previous working directory set.")
|
||||
else
|
||||
print(oldWD)
|
||||
syscall.chdir(oldWD)
|
||||
oldWD = cwd
|
||||
end
|
||||
return
|
||||
end
|
||||
local dirInMod = dirIn
|
||||
if dirIn:sub(1, 1) ~= "/" then dirInMod = cwd .. "/" .. dirIn end
|
||||
local parts = {}
|
||||
for part in dirInMod:gmatch("[^/]+") do
|
||||
if part == ".." then
|
||||
if #parts > 0 then table.remove(parts) end
|
||||
elseif part ~= "." and part ~= "" then
|
||||
table.insert(parts, part)
|
||||
end
|
||||
end
|
||||
local normDir = "/" .. table.concat(parts, "/")
|
||||
if normDir:sub(#normDir, #normDir) ~= "/" then normDir = normDir .. "/" end
|
||||
|
||||
if not fs.isDir(normDir) then
|
||||
print("bash-cd: "..dirIn..": No such directory.")
|
||||
return
|
||||
end
|
||||
oldWD = cwd
|
||||
syscall.chdir(normDir)
|
||||
end
|
||||
|
||||
|
||||
local function getUserInput()
|
||||
syscall.devctl(1,"sfgc",3)
|
||||
printInline(userhost)
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
printInline(":")
|
||||
syscall.devctl(1,"sfgc",10)
|
||||
printInline(syscall.getcwd())
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
printInline("$ ")
|
||||
local curOffsetStr = syscall.devctl(1, "gpos")
|
||||
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
|
||||
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
|
||||
|
||||
local input = ""
|
||||
local blinkState = false
|
||||
local cursorPos = 1
|
||||
local history = 0
|
||||
|
||||
while true do
|
||||
local key=syscall.read(0)
|
||||
if key then
|
||||
if key == "\19" then --TODO: REPLACE WITH LEFT ARROW
|
||||
if cursorPos > 1 then
|
||||
cursorPos = cursorPos - 1
|
||||
end
|
||||
elseif key == "\20" then --TODO: REPLACE WITH RIGHT ARROW
|
||||
if cursorPos <= #input then
|
||||
cursorPos = cursorPos + 1
|
||||
end
|
||||
elseif key == "\17" then --TODO: REPLACE WITH UP ARROW
|
||||
if history < #commandHistory then
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
printInline((" "):rep(#input + 1))
|
||||
history = history + 1
|
||||
input = commandHistory[#commandHistory - history + 1]
|
||||
cursorPos = #input + 1
|
||||
end
|
||||
elseif key == "\18" then --TODO: REPLACE WITH DOWN ARROW
|
||||
if history > 1 then
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
printInline((" "):rep(#input + 1))
|
||||
history = history - 1
|
||||
input = commandHistory[#commandHistory - history + 1]
|
||||
cursorPos = #input + 1
|
||||
elseif history == 1 then
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
printInline((" "):rep(#input + 1))
|
||||
history = 0
|
||||
input = ""
|
||||
cursorPos = 1
|
||||
end
|
||||
elseif key == "\b" then
|
||||
if cursorPos > 1 then
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
printInline((" "):rep(#input + 1))
|
||||
input = string.sub(input, 1, cursorPos-2)..string.sub(input, cursorPos)
|
||||
cursorPos = cursorPos - 1
|
||||
end
|
||||
elseif key == "\n" then
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
print(input.." ")
|
||||
return input
|
||||
else
|
||||
input = string.sub(input, 1, cursorPos-1)..key..string.sub(input, cursorPos)
|
||||
cursorPos = cursorPos + 1
|
||||
end
|
||||
local screenSizeStr = syscall.devctl(1, "size")
|
||||
local sizeX = tonumber(screenSizeStr:sub(1, screenSizeStr:find(";")-1))
|
||||
local sizeY = tonumber(screenSizeStr:sub(screenSizeStr:find(";")+1))
|
||||
local totalChars = sizeX * sizeY
|
||||
local eocCharNum = ((curOffsetY - 1) * sizeX) + curOffsetX + #input
|
||||
if eocCharNum >= totalChars then
|
||||
syscall.devctl(1,"spos",sizeX,sizeY)
|
||||
printInline(" ")
|
||||
curOffsetY = curOffsetY - 1
|
||||
end
|
||||
end
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
printInline(string.sub(input, 1, cursorPos-1))
|
||||
if blinkState then
|
||||
syscall.devctl(1,"sfgc",16)
|
||||
syscall.devctl(1,"sbgc",1)
|
||||
end
|
||||
if cursorPos > #input then
|
||||
printInline(" ")
|
||||
else
|
||||
printInline(string.sub(input, cursorPos, cursorPos))
|
||||
end
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"sbgc",16)
|
||||
printInline(string.sub(input, cursorPos+1))
|
||||
if cursorPos <= #input then
|
||||
printInline(" ")
|
||||
end
|
||||
local curBlink = ((math.floor(syscall.getUptime() / 500) % 2) == 0)
|
||||
if curBlink ~= blinkState then
|
||||
blinkState = curBlink
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function runCommand(command)
|
||||
do
|
||||
local func = load("return " .. command, "@equation", "t", {})
|
||||
if func then
|
||||
local success, result = pcall(func)
|
||||
if success and type(result) == "number" then
|
||||
print(result)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
terminate = false
|
||||
local args = string.split(command, " ")
|
||||
if builtinCmds[args[1]] then
|
||||
local success, msg = pcall(builtinCmds[args[1]], table.unpack(args, 2))
|
||||
if not success then
|
||||
local errSL = string.sub(msg, string.find(msg, "]") + 2)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
printInline(args[1]..": Program runtime error on line ")
|
||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local cmdPath = ""
|
||||
if string.find(args[1], "/") then
|
||||
if fs.exists(args[1]) then
|
||||
cmdPath = args[1]
|
||||
end
|
||||
else
|
||||
local paths = string.split(syscall.getEnviron("PATH"), ":")
|
||||
for _, path in pairs(paths) do
|
||||
if fs.exists(path..args[1]) then
|
||||
cmdPath = path..args[1]
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if cmdPath == "" then
|
||||
print(args[1]..": Command not found")
|
||||
return
|
||||
end
|
||||
|
||||
local progName = string.sub(cmdPath, #cmdPath - string.find(string.reverse(cmdPath), "/") + 2)
|
||||
|
||||
local text = fs.readAllText(cmdPath)
|
||||
local program, err = load(text, progName)
|
||||
if not program then
|
||||
local errSL = string.sub(err, string.find(err, ":") + 1)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
printInline(progName..": Program load error on line ")
|
||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||
return
|
||||
end
|
||||
|
||||
local proc = syscall.spawn(function(...)
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","w")
|
||||
local success, msg = pcall(program, ...)
|
||||
if not success then
|
||||
local errSL = string.sub(msg, string.find(msg, ":") + 1)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
printInline(progName..": Program runtime error on line ")
|
||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||
end
|
||||
end, progName, nil, {table.unpack(args, 2)})
|
||||
|
||||
while true do
|
||||
local exited, code = syscall.collect(proc)
|
||||
if exited then
|
||||
if code then
|
||||
print("\nTask exited with code:\n"..tostring(code))
|
||||
end
|
||||
return
|
||||
end
|
||||
if terminate then
|
||||
local success, err = syscall.kill(proc)
|
||||
if success then
|
||||
syscall.devctl(1,"sbgc",16)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
print("\nProgram Terminated.")
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
end
|
||||
terminate = false
|
||||
break
|
||||
end
|
||||
sleep(0.05)
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local command = getUserInput()
|
||||
if command ~= "" then
|
||||
if command ~= commandHistory[#commandHistory] then
|
||||
table.insert(commandHistory, command)
|
||||
end
|
||||
runCommand(command)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--ERROR HANDLING
|
||||
end, debug.traceback)
|
||||
|
||||
if not success then
|
||||
syscall.log("Error running shell: "..errorMsg, "ERROR")
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
syscall.devctl(1,"sbgc",16)
|
||||
print()
|
||||
print("Error running shell: ")
|
||||
print(errorMsg)
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
--:Minify:--
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","r")
|
||||
syscall.devctl(1,"clear")
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"spos",1,1)
|
||||
print("HyperionOS Bash Shell")
|
||||
local str=""
|
||||
local stopInput=false
|
||||
local proc=0
|
||||
local fs=require("sys.fs")
|
||||
local timeout=false
|
||||
syscall.setEnviron("SHELL","simpleshell")
|
||||
printInline("> ")
|
||||
syscall.sigcatch(function(sig)
|
||||
if sig==1 then
|
||||
syscall.kill(proc)
|
||||
print("Terminated")
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
end
|
||||
end)
|
||||
|
||||
while true do
|
||||
if not stopInput then
|
||||
local input=syscall.read(0)
|
||||
if input then
|
||||
if input=="\b" then
|
||||
if #str>0 then
|
||||
str=str:sub(1,#str-1)
|
||||
printInline("\b")
|
||||
end
|
||||
elseif input=="\n" then
|
||||
print("")
|
||||
stopInput=true
|
||||
if str == "" then
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
else
|
||||
local path=nil
|
||||
local split=string.split(str, " ")
|
||||
if fs.exists("/bin/"..split[1]) then
|
||||
path="/bin/"..split[1]
|
||||
elseif fs.exists("/bin/"..split[1]..".lua") then
|
||||
path="/bin/"..split[1]..".lua"
|
||||
end
|
||||
if not path then
|
||||
print("Program not found")
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
else
|
||||
local text = fs.readAllText(path)
|
||||
local program, err = load(text, path)
|
||||
if not program then
|
||||
print(err)
|
||||
printInline("> ")
|
||||
end
|
||||
proc = syscall.spawn(function(...)
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","w")
|
||||
program(...)
|
||||
end, path, nil, {table.unpack(split, 2)})
|
||||
end
|
||||
str=""
|
||||
end
|
||||
else
|
||||
str=str..input
|
||||
printInline(input)
|
||||
end
|
||||
timeout=false
|
||||
else
|
||||
timeout=true
|
||||
end
|
||||
else
|
||||
local exited, code = syscall.collect(proc)
|
||||
if exited then
|
||||
if code then
|
||||
print("\nTask exited with code:\n"..tostring(code))
|
||||
end
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
end
|
||||
timeout=true
|
||||
end
|
||||
if timeout then
|
||||
if stopInput then
|
||||
sleep(.5)
|
||||
else
|
||||
sleep(.05)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
local args = {...}
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
local fs = require("sys.fs")
|
||||
local filePath = (args[1] or "")
|
||||
if filePath:sub(1, 1) ~= "/" then
|
||||
filePath = syscall.getcwd().."/"..filePath
|
||||
end
|
||||
|
||||
if not fs.exists(filePath) and args[1] then
|
||||
print(name..": Cannot access '"..args[1].."': No such file.")
|
||||
return
|
||||
end
|
||||
|
||||
local file = 0
|
||||
if args[1] then
|
||||
file = syscall.open(filePath, "r")
|
||||
end
|
||||
local content=""
|
||||
while content~=nil or file == 0 do
|
||||
content=syscall.read(file, 1024)
|
||||
printInline(content)
|
||||
end
|
||||
syscall.close(file)
|
||||
print("")
|
||||
@@ -0,0 +1 @@
|
||||
syscall.devctl(1,"clear")
|
||||
@@ -0,0 +1,2 @@
|
||||
local args = {...}
|
||||
print(table.concat(args, " "))
|
||||
@@ -0,0 +1,292 @@
|
||||
--:Minify:--
|
||||
-- help: display command reference with paged scrolling
|
||||
|
||||
local COMMANDS = {
|
||||
-- {name, usage, description, {flags...}}
|
||||
-- flags: {flag, desc}
|
||||
{
|
||||
name = "cat",
|
||||
usage = "cat [file]",
|
||||
desc = "Print file contents to stdout. Reads from stdin if no file given.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "cd",
|
||||
usage = "cd [dir]",
|
||||
desc = "Change working directory. Use '-' to return to previous directory.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "clear",
|
||||
usage = "clear",
|
||||
desc = "Clear the terminal screen.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "echo",
|
||||
usage = "echo [text...]",
|
||||
desc = "Print arguments to stdout.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "hfetch",
|
||||
usage = "hfetch",
|
||||
desc = "Display system information in a neofetch-style layout.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "id",
|
||||
usage = "id [username]",
|
||||
desc = "Print user identity (uid, gid). Defaults to current user.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "login",
|
||||
usage = "login",
|
||||
desc = "System login prompt. Launched automatically at boot.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "ls",
|
||||
usage = "ls [-alh] [dir]",
|
||||
desc = "List directory contents.",
|
||||
flags = {
|
||||
{"-a", "Show hidden files (starting with .)"},
|
||||
{"-l", "Long format: permissions, owner, size"},
|
||||
{"-h", "Human-readable file sizes"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "lsusers",
|
||||
usage = "lsusers",
|
||||
desc = "List all user accounts with uid, gid, home, and shell.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "lua",
|
||||
usage = "lua",
|
||||
desc = "Interactive Lua REPL prompt.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "mkdir",
|
||||
usage = "mkdir <dir>",
|
||||
desc = "Create a directory.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "passwd",
|
||||
usage = "passwd [username]",
|
||||
desc = "Change a user password. Non-root must verify current password first.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "ps",
|
||||
usage = "ps",
|
||||
desc = "List running tasks with pid, user, name, and status.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "pwd",
|
||||
usage = "pwd",
|
||||
desc = "Print current working directory.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "su",
|
||||
usage = "su [username]",
|
||||
desc = "Switch user. Defaults to root. Root can switch without a password.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "sudo",
|
||||
usage = "sudo [-u user] <command> [args...]",
|
||||
desc = "Run a command as another user (default root). Authenticates as current user.",
|
||||
flags = {
|
||||
{"-u user", "Run as the specified user (name or uid)"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "sysdump",
|
||||
usage = "sysdump",
|
||||
desc = "List all registered kernel syscalls.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "useradd",
|
||||
usage = "useradd [-p pw] [-g gid] [-d home] [-s shell] [-M] <username>",
|
||||
desc = "Create a new user account.",
|
||||
flags = {
|
||||
{"-p pw", "Set password (prompted interactively if omitted)"},
|
||||
{"-g gid", "Set primary group id"},
|
||||
{"-d home", "Set home directory (default: /home/username)"},
|
||||
{"-s shell", "Set login shell (default: /bin/hysh)"},
|
||||
{"-M", "Do not create home directory"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "userdel",
|
||||
usage = "userdel [-r] <username>",
|
||||
desc = "Delete a user account.",
|
||||
flags = {
|
||||
{"-r", "Also recursively remove the user's home directory"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "usermod",
|
||||
usage = "usermod [-l name] [-p pw] [-g gid] [-d home] [-s shell] [-L] [-U] <username>",
|
||||
desc = "Modify an existing user account.",
|
||||
flags = {
|
||||
{"-l name", "Rename the user"},
|
||||
{"-p pw", "Set new password"},
|
||||
{"-g gid", "Change primary group id"},
|
||||
{"-d home", "Change home directory"},
|
||||
{"-s shell", "Change login shell"},
|
||||
{"-L", "Lock the account (disable login)"},
|
||||
{"-U", "Unlock the account"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "whoami",
|
||||
usage = "whoami",
|
||||
desc = "Print the current username.",
|
||||
flags = {}
|
||||
},
|
||||
{
|
||||
name = "yes",
|
||||
usage = "yes [text]",
|
||||
desc = "Repeatedly print 'y' (or given text) until interrupted with Ctrl+C.",
|
||||
flags = {}
|
||||
},
|
||||
}
|
||||
|
||||
-- Build lines to display
|
||||
local C_HEAD = 7 -- yellow (section headers)
|
||||
local C_CMD = 5 -- cyan (command name)
|
||||
local C_USAGE = 1 -- white (usage line)
|
||||
local C_DESC = 13 -- grey (description)
|
||||
local C_FLAG = 3 -- green (flag name)
|
||||
local C_DIM = 12 -- dark grey
|
||||
|
||||
-- Each entry is {text, color}
|
||||
local lines = {}
|
||||
local function push(text, col) lines[#lines+1] = {text, col or 1} end
|
||||
|
||||
push("HyperionOS Command Reference", C_HEAD)
|
||||
push(string.rep("=", 50), C_DIM)
|
||||
push("", 1)
|
||||
|
||||
local args = {...}
|
||||
local filter = args[1] -- optional: help <command>
|
||||
|
||||
local function addCmd(cmd)
|
||||
push(cmd.name, C_CMD)
|
||||
push(" Usage: " .. cmd.usage, C_USAGE)
|
||||
push(" " .. cmd.desc, C_DESC)
|
||||
if #cmd.flags > 0 then
|
||||
for _, f in ipairs(cmd.flags) do
|
||||
push(" " .. f[1], C_FLAG)
|
||||
push(" " .. f[2], C_DESC)
|
||||
end
|
||||
end
|
||||
push("", 1)
|
||||
end
|
||||
|
||||
if filter then
|
||||
local found = false
|
||||
for _, cmd in ipairs(COMMANDS) do
|
||||
if cmd.name == filter then addCmd(cmd); found = true; break end
|
||||
end
|
||||
if not found then
|
||||
push("help: unknown command '" .. filter .. "'", 2)
|
||||
push("Run 'help' with no arguments for the full list.", C_DESC)
|
||||
end
|
||||
else
|
||||
push("Run 'help <command>' for details on a specific command.", C_DESC)
|
||||
push("", 1)
|
||||
for _, cmd in ipairs(COMMANDS) do addCmd(cmd) end
|
||||
end
|
||||
|
||||
-- Pager
|
||||
local sizeStr = syscall.devctl(1, "size")
|
||||
local screenW = tonumber(sizeStr:sub(1, sizeStr:find(";")-1)) or 51
|
||||
local screenH = tonumber(sizeStr:sub(sizeStr:find(";")+1)) or 19
|
||||
local pageSize = screenH - 2 -- reserve bottom row for status bar
|
||||
|
||||
local scroll = 0
|
||||
local totalLines = #lines
|
||||
local dirty = true
|
||||
|
||||
local function render()
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
for row = 1, pageSize do
|
||||
local li = scroll + row
|
||||
if li <= totalLines then
|
||||
local text, col = lines[li][1], lines[li][2]
|
||||
syscall.devctl(1, "sfgc", col)
|
||||
-- truncate to screen width
|
||||
if #text > screenW then text = text:sub(1, screenW) end
|
||||
syscall.write(1, text .. "\n")
|
||||
else
|
||||
syscall.write(1, "\n")
|
||||
end
|
||||
end
|
||||
-- status bar
|
||||
syscall.devctl(1, "sfgc", 16)
|
||||
syscall.devctl(1, "sbgc", 13)
|
||||
local pct = math.floor(math.min(100, (scroll + pageSize) / totalLines * 100))
|
||||
local status = string.format(" help -- line %d/%d (%d%%) [up/down: scroll q: quit] ",
|
||||
scroll + 1, totalLines, pct)
|
||||
if #status > screenW then status = status:sub(1, screenW) end
|
||||
syscall.devctl(1, "spos", 1, screenH)
|
||||
syscall.write(1, status .. string.rep(" ", screenW - #status))
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
syscall.devctl(1, "sbgc", 16)
|
||||
dirty = false
|
||||
end
|
||||
|
||||
-- If output fits on one screen, just print without pager
|
||||
if totalLines <= pageSize then
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
for _, line in ipairs(lines) do
|
||||
syscall.devctl(1, "sfgc", line[2])
|
||||
syscall.write(1, line[1] .. "\n")
|
||||
end
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
return
|
||||
end
|
||||
|
||||
render()
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
-- idle
|
||||
elseif ch == "q" or ch == "Q" then
|
||||
break
|
||||
elseif ch == "\17" then -- up arrow
|
||||
if scroll > 0 then
|
||||
scroll = scroll - 1
|
||||
dirty = true
|
||||
end
|
||||
elseif ch == "\18" then -- down arrow
|
||||
if scroll + pageSize < totalLines then
|
||||
scroll = scroll + 1
|
||||
dirty = true
|
||||
end
|
||||
elseif ch == "\19" then -- left / page up (reuse left arrow)
|
||||
scroll = math.max(0, scroll - pageSize)
|
||||
dirty = true
|
||||
elseif ch == "\20" then -- right / page down
|
||||
scroll = math.min(totalLines - pageSize, scroll + pageSize)
|
||||
dirty = true
|
||||
end
|
||||
if dirty then render() end
|
||||
end
|
||||
|
||||
-- Restore screen
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
syscall.devctl(1, "sbgc", 16)
|
||||
@@ -0,0 +1,104 @@
|
||||
--:Minify:--
|
||||
-- Color indices (sfgc):
|
||||
-- 1=white, 2=red, 3=green, 4=blue, 5=cyan, 6=magenta, 7=yellow
|
||||
-- 8=orange, 9=lime, 10=lightcyan, 11=brown, 12=darkgrey, 13=lightgrey, 14=purple, 15=chartreuse, 16=black
|
||||
|
||||
local C_LOGO = 5 -- cyan
|
||||
local C_WHITE = 1 -- white
|
||||
local C_LABEL = 13 -- light grey (key names)
|
||||
local C_SEP = 12 -- dark grey (---- separator)
|
||||
local C_USER = 3 -- green (user@host)
|
||||
|
||||
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||||
|
||||
local username = syscall.getUsername() or "Unknown"
|
||||
local hostname = syscall.getHostname() or "Unknown"
|
||||
local userhost = username .. "@" .. hostname
|
||||
|
||||
local function formatUptime(ms)
|
||||
local s = math.floor(ms / 1000)
|
||||
local m = math.floor(s / 60)
|
||||
local h = math.floor(m / 60)
|
||||
local d = math.floor(h / 24)
|
||||
s = s % 60; m = m % 60; h = h % 24
|
||||
local parts = {}
|
||||
if d > 0 then parts[#parts+1] = d .. "d" end
|
||||
if h > 0 then parts[#parts+1] = h .. "h" end
|
||||
if m > 0 then parts[#parts+1] = m .. "m" end
|
||||
parts[#parts+1] = s .. "s"
|
||||
return table.concat(parts, " ")
|
||||
end
|
||||
|
||||
local host_str = syscall.getHost() or "Unknown"
|
||||
local cc_ver = host_str:match("ComputerCraft ([%d%.]+)") or host_str
|
||||
|
||||
local info = {
|
||||
-- {label, value} label=nil means print value as-is (userhost / separator)
|
||||
{nil, userhost},
|
||||
{nil, string.rep("-", #userhost)},
|
||||
{"OS", syscall.version() or "Unknown"},
|
||||
{"Host", cc_ver},
|
||||
{"Arch", syscall.arch() or "Unknown"},
|
||||
{"Uptime", formatUptime(syscall.getUptime() or 0)},
|
||||
{"Tasks", tostring(#(syscall.getTasks() or {}))},
|
||||
{"Shell", syscall.getEnviron("SHELL") or "Unknown"},
|
||||
{"Terminal", "TTY1"},
|
||||
{"UID", tostring(syscall.getuid())},
|
||||
{"Packages", "n/a (spm)"},
|
||||
}
|
||||
|
||||
local logo = {
|
||||
".. *. .. ",
|
||||
" *= +@* +* ",
|
||||
" .@#. -@@@= :#@. ",
|
||||
" =@@+ *@@@# +@@= ",
|
||||
" %@@%: *@@@# -%@@% ",
|
||||
" :@@@@+ *@@@# .*@@@@: ",
|
||||
" :*@@@%- *@@@# -@@@@*: ",
|
||||
" =%@@#. *@@@# .#@@%= ",
|
||||
" :=. :*@@= *@@@# =@@+: .=: ",
|
||||
" %@#=..*# +@@@# #*..=#@# ",
|
||||
" .@@@@+=# .%@%: #=+@@@@. ",
|
||||
" .....=# -@= *+...:. ",
|
||||
" -*%*-@= - =@-*%*- ",
|
||||
" -@*. -@%. :%@- :*@- ",
|
||||
" .#@#@* ",
|
||||
" -#- ",
|
||||
" ",
|
||||
}
|
||||
|
||||
local lines = math.max(#logo, #info)
|
||||
for i = 1, lines do
|
||||
local logo_str = logo[i] or string.rep(" ", 36)
|
||||
|
||||
-- print logo segment in cyan
|
||||
c(C_LOGO)
|
||||
printInline(logo_str)
|
||||
|
||||
-- print separator pipe
|
||||
c(C_LABEL)
|
||||
printInline("| ")
|
||||
|
||||
-- print info segment
|
||||
local row = info[i]
|
||||
if row then
|
||||
if row[1] == nil and i == 1 then
|
||||
-- user@host line
|
||||
c(C_USER)
|
||||
printInline(row[2])
|
||||
elseif row[1] == nil and i == 2 then
|
||||
-- separator line
|
||||
c(C_SEP)
|
||||
printInline(row[2])
|
||||
elseif row[1] then
|
||||
-- label: value
|
||||
c(C_LABEL)
|
||||
printInline(row[1] .. ": ")
|
||||
c(C_WHITE)
|
||||
printInline(row[2])
|
||||
end
|
||||
end
|
||||
|
||||
c(C_WHITE)
|
||||
print("")
|
||||
end
|
||||
@@ -0,0 +1,299 @@
|
||||
--:Minify:--
|
||||
syscall.open("/dev/tty/TTY1","r") --stdin (Device 0)
|
||||
syscall.open("/dev/tty/TTY1","w") --stdout (Device 1)
|
||||
syscall.open("/dev/null","w") --stderr (device 2)
|
||||
|
||||
local success, errorMsg = xpcall(function()
|
||||
|
||||
|
||||
|
||||
local fs = require("sys.fs")
|
||||
|
||||
syscall.devctl(1,"clear")
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"spos",1,1)
|
||||
print("HyperionOS hysh Shell")
|
||||
|
||||
local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown")
|
||||
local commandHistory = {}
|
||||
local terminate = false
|
||||
syscall.setEnviron("SHELL","rtbash")
|
||||
syscall.setEnviron("PATH","/bin/")
|
||||
syscall.chdir("/")
|
||||
local oldWD = ""
|
||||
|
||||
for i = 1, 16 do
|
||||
syscall.devctl(1,"sbgc",i)
|
||||
printInline(" ");
|
||||
end
|
||||
print("\n")
|
||||
|
||||
syscall.sigcatch(function(sig)
|
||||
if sig == 1 then
|
||||
terminate = true
|
||||
end
|
||||
end)
|
||||
|
||||
local builtinCmds = {}
|
||||
|
||||
builtinCmds.cd = function(path)
|
||||
local cwd = syscall.getcwd()
|
||||
local dirIn = (path or "")
|
||||
if dirIn == "-" then
|
||||
if oldWD == "" then
|
||||
print("hysh-cd: No previous working directory set.")
|
||||
else
|
||||
print(oldWD)
|
||||
syscall.chdir(oldWD)
|
||||
oldWD = cwd
|
||||
end
|
||||
return
|
||||
end
|
||||
local dirInMod = dirIn
|
||||
if dirIn:sub(1, 1) ~= "/" then dirInMod = cwd .. "/" .. dirIn end
|
||||
local parts = {}
|
||||
for part in dirInMod:gmatch("[^/]+") do
|
||||
if part == ".." then
|
||||
if #parts > 0 then table.remove(parts) end
|
||||
elseif part ~= "." and part ~= "" then
|
||||
table.insert(parts, part)
|
||||
end
|
||||
end
|
||||
local normDir = "/" .. table.concat(parts, "/")
|
||||
if normDir:sub(#normDir, #normDir) ~= "/" then normDir = normDir .. "/" end
|
||||
|
||||
if not fs.isDir(normDir) then
|
||||
print("hysh-cd: "..dirIn..": No such directory.")
|
||||
return
|
||||
end
|
||||
oldWD = cwd
|
||||
syscall.chdir(normDir)
|
||||
end
|
||||
|
||||
|
||||
local function getUserInput()
|
||||
syscall.devctl(1,"sfgc",3)
|
||||
syscall.write(1, userhost)
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.write(1, ":")
|
||||
syscall.devctl(1,"sfgc",10)
|
||||
syscall.write(1, syscall.getcwd())
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.write(1, "$ ")
|
||||
local curOffsetStr = syscall.devctl(1, "gpos")
|
||||
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
|
||||
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
|
||||
|
||||
local input = ""
|
||||
local blinkState = false
|
||||
local cursorPos = 1
|
||||
local history = 0
|
||||
local dirty = true -- redraw on first iteration
|
||||
|
||||
local function redraw()
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
-- text before cursor
|
||||
syscall.write(1, string.sub(input, 1, cursorPos-1))
|
||||
-- cursor character (inverted if blinking on)
|
||||
if blinkState then
|
||||
syscall.devctl(1,"sfgc",16)
|
||||
syscall.devctl(1,"sbgc",1)
|
||||
end
|
||||
if cursorPos > #input then
|
||||
syscall.write(1, " ")
|
||||
else
|
||||
syscall.write(1, string.sub(input, cursorPos, cursorPos))
|
||||
end
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"sbgc",16)
|
||||
-- text after cursor + trailing space to erase old chars
|
||||
syscall.write(1, string.sub(input, cursorPos+1) .. " ")
|
||||
end
|
||||
|
||||
while true do
|
||||
local key = syscall.read(0)
|
||||
if key and key ~= "" then
|
||||
if key == "\19" then -- left arrow
|
||||
if cursorPos > 1 then
|
||||
cursorPos = cursorPos - 1
|
||||
dirty = true
|
||||
end
|
||||
elseif key == "\20" then -- right arrow
|
||||
if cursorPos <= #input then
|
||||
cursorPos = cursorPos + 1
|
||||
dirty = true
|
||||
end
|
||||
elseif key == "\17" then -- up arrow
|
||||
if history < #commandHistory then
|
||||
history = history + 1
|
||||
input = commandHistory[#commandHistory - history + 1]
|
||||
cursorPos = #input + 1
|
||||
dirty = true
|
||||
end
|
||||
elseif key == "\18" then -- down arrow
|
||||
if history > 1 then
|
||||
history = history - 1
|
||||
input = commandHistory[#commandHistory - history + 1]
|
||||
cursorPos = #input + 1
|
||||
dirty = true
|
||||
elseif history == 1 then
|
||||
history = 0
|
||||
input = ""
|
||||
cursorPos = 1
|
||||
dirty = true
|
||||
end
|
||||
elseif key == "\b" then
|
||||
if cursorPos > 1 then
|
||||
input = string.sub(input, 1, cursorPos-2) .. string.sub(input, cursorPos)
|
||||
cursorPos = cursorPos - 1
|
||||
dirty = true
|
||||
end
|
||||
elseif key == "\n" then
|
||||
-- redraw cleanly with no cursor highlight before committing
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"sbgc",16)
|
||||
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
|
||||
syscall.write(1, input .. " \n")
|
||||
return input
|
||||
else
|
||||
input = string.sub(input, 1, cursorPos-1) .. key .. string.sub(input, cursorPos)
|
||||
cursorPos = cursorPos + 1
|
||||
dirty = true
|
||||
end
|
||||
end
|
||||
|
||||
-- cursor blink
|
||||
local curBlink = ((math.floor(syscall.getUptime() / 500) % 2) == 0)
|
||||
if curBlink ~= blinkState then
|
||||
blinkState = curBlink
|
||||
dirty = true
|
||||
end
|
||||
|
||||
if dirty then
|
||||
redraw()
|
||||
dirty = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function runCommand(command)
|
||||
do
|
||||
local func = load("return " .. command, "@equation", "t", {})
|
||||
if func then
|
||||
local success, result = pcall(func)
|
||||
if success and type(result) == "number" then
|
||||
print(result)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
terminate = false
|
||||
local args = string.split(command, " ")
|
||||
if builtinCmds[args[1]] then
|
||||
local success, msg = pcall(builtinCmds[args[1]], table.unpack(args, 2))
|
||||
if not success then
|
||||
local errSL = string.sub(msg, string.find(msg, "]") + 2)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
printInline(args[1]..": Program runtime error on line ")
|
||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local cmdPath = ""
|
||||
if string.find(args[1], "/") then
|
||||
if fs.exists(args[1]) then
|
||||
cmdPath = args[1]
|
||||
end
|
||||
else
|
||||
local paths = string.split(syscall.getEnviron("PATH"), ":")
|
||||
for _, path in pairs(paths) do
|
||||
if fs.exists(path..args[1]) then
|
||||
cmdPath = path..args[1]
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if cmdPath == "" then
|
||||
print(args[1]..": Command not found")
|
||||
return
|
||||
end
|
||||
|
||||
local progName = string.sub(cmdPath, #cmdPath - string.find(string.reverse(cmdPath), "/") + 2)
|
||||
|
||||
local text = fs.readAllText(cmdPath)
|
||||
local program, err = load(text, progName)
|
||||
if not program then
|
||||
local errSL = string.sub(err, string.find(err, ":") + 1)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
printInline(progName..": Program load error on line ")
|
||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||
return
|
||||
end
|
||||
|
||||
local proc = syscall.spawn(function(...)
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","w")
|
||||
local success, msg = pcall(program, ...)
|
||||
if not success then
|
||||
local errSL = string.sub(msg, string.find(msg, ":") + 1)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
printInline(progName..": Program runtime error on line ")
|
||||
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
print(string.sub(errSL, string.find(errSL, ":") + 1))
|
||||
end
|
||||
end, progName, nil, {table.unpack(args, 2)})
|
||||
|
||||
while true do
|
||||
local exited, code = syscall.collect(proc)
|
||||
if exited then
|
||||
if code then
|
||||
print("\nTask exited with code:\n"..tostring(code))
|
||||
end
|
||||
return
|
||||
end
|
||||
if terminate then
|
||||
local success, err = syscall.kill(proc)
|
||||
if success then
|
||||
syscall.devctl(1,"sbgc",16)
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
print("\nProgram Terminated.")
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
end
|
||||
terminate = false
|
||||
break
|
||||
end
|
||||
sleep(0.05)
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local command = getUserInput()
|
||||
if command ~= "" then
|
||||
if command ~= commandHistory[#commandHistory] then
|
||||
table.insert(commandHistory, command)
|
||||
end
|
||||
runCommand(command)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--ERROR HANDLING
|
||||
end, debug.traceback)
|
||||
|
||||
if not success then
|
||||
syscall.log("Error running shell: "..errorMsg, "ERROR")
|
||||
syscall.devctl(1,"sfgc",2)
|
||||
syscall.devctl(1,"sbgc",16)
|
||||
print()
|
||||
print("Error running shell: ")
|
||||
print(errorMsg)
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
--:Minify:--
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","r")
|
||||
syscall.devctl(1,"clear")
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
syscall.devctl(1,"spos",1,1)
|
||||
print("HyperionOS hysh Shell")
|
||||
local str=""
|
||||
local stopInput=false
|
||||
local proc=0
|
||||
local fs=require("sys.fs")
|
||||
local timeout=false
|
||||
syscall.setEnviron("SHELL","simpleshell")
|
||||
printInline("> ")
|
||||
syscall.sigcatch(function(sig)
|
||||
if sig==1 then
|
||||
syscall.kill(proc)
|
||||
print("Terminated")
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
end
|
||||
end)
|
||||
|
||||
while true do
|
||||
if not stopInput then
|
||||
local input=syscall.read(0)
|
||||
if input then
|
||||
if input=="\b" then
|
||||
if #str>0 then
|
||||
str=str:sub(1,#str-1)
|
||||
printInline("\b")
|
||||
end
|
||||
elseif input=="\n" then
|
||||
print("")
|
||||
stopInput=true
|
||||
if str == "" then
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
else
|
||||
local path=nil
|
||||
local split=string.split(str, " ")
|
||||
if fs.exists("/bin/"..split[1]) then
|
||||
path="/bin/"..split[1]
|
||||
elseif fs.exists("/bin/"..split[1]..".lua") then
|
||||
path="/bin/"..split[1]..".lua"
|
||||
end
|
||||
if not path then
|
||||
print("Program not found")
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
else
|
||||
local text = fs.readAllText(path)
|
||||
local program, err = load(text, path)
|
||||
if not program then
|
||||
print(err)
|
||||
printInline("> ")
|
||||
end
|
||||
proc = syscall.spawn(function(...)
|
||||
syscall.open("/dev/tty/TTY1","r")
|
||||
syscall.open("/dev/tty/TTY1","w")
|
||||
syscall.open("/dev/null","w")
|
||||
program(...)
|
||||
end, path, nil, {table.unpack(split, 2)})
|
||||
end
|
||||
str=""
|
||||
end
|
||||
else
|
||||
str=str..input
|
||||
printInline(input)
|
||||
end
|
||||
timeout=false
|
||||
else
|
||||
timeout=true
|
||||
end
|
||||
else
|
||||
local exited, code = syscall.collect(proc)
|
||||
if exited then
|
||||
if code then
|
||||
print("\nTask exited with code:\n"..tostring(code))
|
||||
end
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
end
|
||||
timeout=true
|
||||
end
|
||||
if timeout then
|
||||
if stopInput then
|
||||
sleep(.5)
|
||||
else
|
||||
sleep(.05)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local uid
|
||||
|
||||
if args[1] then
|
||||
uid = syscall.auth_getuid(args[1])
|
||||
if not uid then
|
||||
print("id: user '" .. args[1] .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
else
|
||||
uid = syscall.getuid()
|
||||
end
|
||||
|
||||
local pwent = syscall.auth_getpasswd(uid)
|
||||
local name = (pwent and pwent.username) or tostring(uid)
|
||||
local gid = (pwent and pwent.gid) or uid
|
||||
|
||||
print(string.format("uid=%d(%s) gid=%d(%s)", uid, name, gid, name))
|
||||
@@ -0,0 +1,178 @@
|
||||
--:Minify:--
|
||||
syscall.open("/dev/tty/TTY1","r") --stdin (fd 0)
|
||||
syscall.open("/dev/tty/TTY1","w") --stdout (fd 1)
|
||||
syscall.open("/dev/null","w") --stderr (fd 2)
|
||||
|
||||
local fs = require("sys.fs")
|
||||
|
||||
local MAX_ATTEMPTS = 3
|
||||
|
||||
local function readLine(mask)
|
||||
local input = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
-- buffer empty, spin
|
||||
elseif ch == "\n" then
|
||||
syscall.write(1, "\n")
|
||||
return input
|
||||
elseif ch == "\b" then
|
||||
if #input > 0 then
|
||||
input = input:sub(1, -2)
|
||||
syscall.write(1, "\b \b")
|
||||
end
|
||||
else
|
||||
input = input .. ch
|
||||
syscall.write(1, mask or ch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function firstBoot()
|
||||
local shadow = fs.readAllText("/etc/shadow") or ""
|
||||
if shadow:match("%S") then return end
|
||||
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
syscall.devctl(1, "sfgc", 3)
|
||||
syscall.write(1, "HyperionOS First Boot Setup\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
syscall.write(1, "No root password is set. Please create one now.\n\n")
|
||||
|
||||
while true do
|
||||
syscall.write(1, "New root password: ")
|
||||
local pw1 = readLine("*")
|
||||
syscall.write(1, "Confirm password: ")
|
||||
local pw2 = readLine("*")
|
||||
|
||||
if pw1 ~= pw2 then
|
||||
syscall.devctl(1, "sfgc", 2)
|
||||
syscall.write(1, "Passwords do not match. Try again.\n\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
elseif #pw1 < 6 then
|
||||
syscall.devctl(1, "sfgc", 2)
|
||||
syscall.write(1, "Password too short (minimum 6 characters).\n\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
else
|
||||
local ok, err = syscall.auth_setpassword(0, pw1)
|
||||
if ok then
|
||||
syscall.devctl(1, "sfgc", 3)
|
||||
syscall.write(1, "Root password set.\n\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
sleep(0.5)
|
||||
break
|
||||
else
|
||||
syscall.devctl(1, "sfgc", 2)
|
||||
syscall.write(1, "Error: " .. tostring(err) .. "\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function spawnShell(username, uid, shell, homedir)
|
||||
local shellText = fs.readAllText(shell)
|
||||
if not shellText then
|
||||
syscall.write(1, "login: shell not found: " .. shell .. "\n")
|
||||
sleep(2)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Spawn a wrapper that loads and runs the shell, reporting any error back
|
||||
-- via exit code channel so we can display it
|
||||
local errFifo = {}
|
||||
|
||||
local proc = syscall.spawn(function()
|
||||
syscall.setuid(uid)
|
||||
syscall.chdir(homedir)
|
||||
syscall.setEnviron("HOME", homedir)
|
||||
syscall.setEnviron("USER", username)
|
||||
syscall.setEnviron("SHELL", shell)
|
||||
syscall.setEnviron("PATH", "/bin/")
|
||||
|
||||
local shellFn, loadErr = load(shellText, "@" .. shell)
|
||||
if not shellFn then
|
||||
-- Report load error via log and a recognizable exit code
|
||||
syscall.log("login: shell load error: " .. tostring(loadErr), "ERROR")
|
||||
syscall.exit(-1)
|
||||
return
|
||||
end
|
||||
|
||||
local ok, runErr = xpcall(shellFn, debug.traceback)
|
||||
if not ok then
|
||||
syscall.log("login: shell runtime error: " .. tostring(runErr), "ERROR")
|
||||
end
|
||||
end, username .. ":shell")
|
||||
|
||||
while true do
|
||||
local exited, code = syscall.collect(proc)
|
||||
if exited then
|
||||
if code then
|
||||
syscall.devctl(1, "sfgc", 2)
|
||||
syscall.write(1, "\nShell exited with code: " .. tostring(code) .. "\n")
|
||||
syscall.write(1, "(Check /var/log/syslog.log for details)\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
sleep(2)
|
||||
end
|
||||
return true
|
||||
end
|
||||
sleep(0.1)
|
||||
end
|
||||
end
|
||||
|
||||
local function doLogin()
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
syscall.devctl(1, "sbgc", 16)
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
|
||||
local hostname = syscall.getHostname() or "hyperion"
|
||||
syscall.write(1, "HyperionOS\n")
|
||||
syscall.write(1, hostname .. " login\n\n")
|
||||
|
||||
local attempts = 0
|
||||
while attempts < MAX_ATTEMPTS do
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
syscall.write(1, "Username: ")
|
||||
local username = readLine(nil)
|
||||
|
||||
if username == "" then goto continue end
|
||||
|
||||
syscall.write(1, "Password: ")
|
||||
local password = readLine("*")
|
||||
|
||||
local ok, err = syscall.auth_login(username, password)
|
||||
if ok then
|
||||
local uid = syscall.auth_getuid(username)
|
||||
local pwent = uid and syscall.auth_getpasswd(uid)
|
||||
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||
local homedir = (pwent and pwent.homedir) or "/"
|
||||
|
||||
syscall.devctl(1, "sfgc", 3)
|
||||
syscall.write(1, "\nWelcome, " .. username .. "!\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
sleep(0.3)
|
||||
|
||||
spawnShell(username, uid, shell, homedir)
|
||||
return -- back to login prompt
|
||||
else
|
||||
attempts = attempts + 1
|
||||
sleep(1)
|
||||
syscall.devctl(1, "sfgc", 2)
|
||||
syscall.write(1, "Login incorrect.\n\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
syscall.devctl(1, "sfgc", 2)
|
||||
syscall.write(1, "Maximum login attempts exceeded.\n")
|
||||
syscall.devctl(1, "sfgc", 1)
|
||||
sleep(5)
|
||||
end
|
||||
|
||||
firstBoot()
|
||||
while true do
|
||||
doLogin()
|
||||
end
|
||||
@@ -0,0 +1,145 @@
|
||||
local cloptions = {
|
||||
a = false,
|
||||
h = false,
|
||||
l = false,
|
||||
help = false,
|
||||
}
|
||||
|
||||
local inpArgs = {...}
|
||||
local args = {}
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
|
||||
local optToSet = false
|
||||
for _, v in pairs(inpArgs) do
|
||||
if optToSet then
|
||||
cloptions[optToSet] = v
|
||||
optToSet = false
|
||||
elseif v:sub(1, 2) == "--" then
|
||||
local opt = v:sub(3)
|
||||
if cloptions[opt] == nil then
|
||||
print(name..": unrecognized option '"..v.."'.")
|
||||
if cloptions.help ~= nil then
|
||||
print("try '"..name.." --help' for more information.")
|
||||
end
|
||||
return
|
||||
elseif cloptions[opt] == false then
|
||||
cloptions[opt] = true
|
||||
else
|
||||
optToSet = opt
|
||||
end
|
||||
elseif v:sub(1, 1) == "-" then
|
||||
for i = 2, #v do
|
||||
local opt = v:sub(i, i)
|
||||
if cloptions[opt] == nil then
|
||||
print(name..": invalid option '-"..opt.."'.")
|
||||
if cloptions.help ~= nil then
|
||||
print("try '"..name.." --help' for more information.")
|
||||
end
|
||||
return
|
||||
else
|
||||
cloptions[opt] = true
|
||||
end
|
||||
end
|
||||
else
|
||||
table.insert(args, v)
|
||||
end
|
||||
end
|
||||
|
||||
if cloptions.help then
|
||||
print("Usage: "..name.." [OPTION]... [DIR]")
|
||||
print("List all entries in the specified DIRectory, or cwd if not specified")
|
||||
print("\nOptions:")
|
||||
print(" -a Do not ignore entries starting with .")
|
||||
print(" -h with -l, print sizes in a human readble format")
|
||||
print(" -l Use a long listing format")
|
||||
print(" --help Display this help and exit")
|
||||
return
|
||||
end
|
||||
|
||||
local fs = require("sys.fs")
|
||||
local dir = (args[1] or "")
|
||||
if dir:sub(1, 1) ~= "/" then
|
||||
dir = syscall.getcwd().."/"..dir
|
||||
end
|
||||
|
||||
if dir:sub(#dir, #dir) ~= "/" then
|
||||
dir = dir.."/"
|
||||
end
|
||||
|
||||
if not fs.isDir(dir) then
|
||||
print(name..": Cannot access '"..args[1].."': No such directory.")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local screenSizeStr = syscall.devctl(1, "size")
|
||||
local sizeX = tonumber(screenSizeStr:sub(1, screenSizeStr:find(";")-1))
|
||||
local sizeY = tonumber(screenSizeStr:sub(screenSizeStr:find(";")+1))
|
||||
|
||||
local list = fs.list(dir)
|
||||
if not cloptions.a then
|
||||
for i = #list, 1, -1 do
|
||||
if list[i]:sub(1, 1) == "." then
|
||||
table.remove(list, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local colWidth = 0
|
||||
local numCols = 1
|
||||
if not cloptions.l then
|
||||
for _, item in pairs(list) do
|
||||
if #item + 2 > colWidth then
|
||||
colWidth = #item + 2
|
||||
end
|
||||
end
|
||||
numCols = math.floor(sizeX / colWidth)
|
||||
end
|
||||
|
||||
local sizePrefixes = {"K", "M", "G"}
|
||||
|
||||
for i,v in ipairs(list) do
|
||||
local fileStats = syscall.stat(dir..v)
|
||||
local isDir = fs.isDir(dir..v)
|
||||
if cloptions.l then
|
||||
if isDir then
|
||||
printInline("d")
|
||||
else
|
||||
printInline("-")
|
||||
end
|
||||
printInline("------ ")
|
||||
printInline(fileStats.owner.." ")
|
||||
printInline(fileStats.group.." ")
|
||||
local size = fileStats.size
|
||||
if cloptions.h then
|
||||
local scale = 0
|
||||
while size > 1024 do
|
||||
size = size / 1024
|
||||
scale = scale + 1
|
||||
end
|
||||
if scale > 0 then
|
||||
if size < 10 then
|
||||
size = math.floor(size).."."..math.floor((size * 10) % 10)..sizePrefixes[scale]
|
||||
else
|
||||
size = math.floor(size)..sizePrefixes[scale]
|
||||
end
|
||||
end
|
||||
end
|
||||
printInline(size.." ")
|
||||
printInline(math.floor(fileStats.modified / 1000).." ")
|
||||
end
|
||||
if isDir then
|
||||
syscall.devctl(1,"sfgc",4)
|
||||
else
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
end
|
||||
printInline(v)
|
||||
printInline((" "):rep(colWidth - #v))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
if i % numCols == 0 then
|
||||
print("")
|
||||
end
|
||||
end
|
||||
if #list % numCols ~= 0 then
|
||||
print("")
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
--:Minify:--
|
||||
local users = syscall.auth_listusers()
|
||||
if not users or #users == 0 then
|
||||
print("No users found.")
|
||||
return
|
||||
end
|
||||
|
||||
syscall.devctl(1,"sfgc",13)
|
||||
print(string.format("%-6s %-6s %-16s %-20s %s", "UID", "GID", "Username", "Home", "Shell"))
|
||||
print(string.rep("-", 65))
|
||||
syscall.devctl(1,"sfgc",1)
|
||||
|
||||
for _, u in ipairs(users) do
|
||||
local lock_marker = u.locked and " [locked]" or ""
|
||||
if u.locked then syscall.devctl(1,"sfgc",2) end
|
||||
print(string.format("%-6d %-6d %-16s %-20s %s%s",
|
||||
u.uid, u.gid, u.username, u.homedir, u.shell, lock_marker))
|
||||
if u.locked then syscall.devctl(1,"sfgc",1) end
|
||||
end
|
||||
@@ -0,0 +1,325 @@
|
||||
--:Minify:--
|
||||
local C_PROMPT = 7
|
||||
local C_CONT = 13
|
||||
local C_OUT = 5
|
||||
local C_ERR = 2
|
||||
local C_KEY = 3
|
||||
local C_STR = 9
|
||||
local C_NUM = 10
|
||||
local C_BOOL = 8
|
||||
local C_NIL = 12
|
||||
local C_TABLE = 13
|
||||
|
||||
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||||
local function w(s) syscall.write(1, tostring(s)) end
|
||||
|
||||
local MAX_DEPTH = 6
|
||||
local MAX_ENTRIES = 64
|
||||
|
||||
local function prettyVal(val, indent, seen)
|
||||
indent = indent or 0
|
||||
seen = seen or {}
|
||||
local t = type(val)
|
||||
|
||||
if t == "nil" then
|
||||
c(C_NIL); w("nil")
|
||||
elseif t == "boolean" then
|
||||
c(C_BOOL); w(tostring(val))
|
||||
elseif t == "number" then
|
||||
c(C_NUM)
|
||||
if val ~= val then
|
||||
w("nan")
|
||||
elseif val == math.huge then
|
||||
w("inf")
|
||||
elseif val == -math.huge then
|
||||
w("-inf")
|
||||
elseif val == math.floor(val) and math.abs(val) < 1e15 then
|
||||
w(tostring(math.floor(val)))
|
||||
else
|
||||
w(tostring(val))
|
||||
end
|
||||
elseif t == "string" then
|
||||
c(C_STR)
|
||||
local s = string.format("%q", val)
|
||||
w(s)
|
||||
elseif t == "function" then
|
||||
c(C_TABLE); w(tostring(val))
|
||||
elseif t == "table" then
|
||||
if seen[val] then
|
||||
c(C_TABLE); w("<circular " .. tostring(val) .. ">")
|
||||
return
|
||||
end
|
||||
if indent >= MAX_DEPTH then
|
||||
c(C_TABLE); w("<table " .. tostring(val) .. ">")
|
||||
return
|
||||
end
|
||||
seen[val] = true
|
||||
|
||||
local pad = string.rep(" ", indent)
|
||||
local padIn = string.rep(" ", indent + 1)
|
||||
|
||||
local arrKeys = {}
|
||||
local hashKeys = {}
|
||||
local arrMax = #val
|
||||
|
||||
for k in pairs(val) do
|
||||
if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
|
||||
arrKeys[#arrKeys+1] = k
|
||||
else
|
||||
hashKeys[#hashKeys+1] = k
|
||||
end
|
||||
end
|
||||
table.sort(arrKeys)
|
||||
|
||||
table.sort(hashKeys, function(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
if ta == tb then
|
||||
if ta == "string" then return a < b end
|
||||
if ta == "number" then return a < b end
|
||||
return tostring(a) < tostring(b)
|
||||
end
|
||||
return ta < tb
|
||||
end)
|
||||
|
||||
local total = #arrKeys + #hashKeys
|
||||
if total == 0 then
|
||||
c(C_TABLE); w("{}")
|
||||
seen[val] = nil
|
||||
return
|
||||
end
|
||||
|
||||
c(C_TABLE); w("{\n")
|
||||
|
||||
local shown = 0
|
||||
local function printEntry(k, v, isLast)
|
||||
shown = shown + 1
|
||||
if shown > MAX_ENTRIES then return true end
|
||||
w(padIn)
|
||||
if type(k) == "number" and arrKeys[k] then
|
||||
else
|
||||
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||
c(C_KEY); w(k)
|
||||
else
|
||||
c(C_TABLE); w("[")
|
||||
prettyVal(k, indent+1, seen)
|
||||
c(C_TABLE); w("]")
|
||||
end
|
||||
c(C_TABLE); w(" = ")
|
||||
end
|
||||
prettyVal(v, indent+1, seen)
|
||||
c(C_TABLE)
|
||||
if not isLast then w(",") end
|
||||
w("\n")
|
||||
end
|
||||
|
||||
for i, k in ipairs(arrKeys) do
|
||||
w(padIn)
|
||||
prettyVal(val[k], indent+1, seen)
|
||||
c(C_TABLE)
|
||||
if i < total then w(",") end
|
||||
w("\n")
|
||||
shown = shown + 1
|
||||
if shown >= MAX_ENTRIES then
|
||||
c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if shown < MAX_ENTRIES then
|
||||
for i, k in ipairs(hashKeys) do
|
||||
local isLast = (shown + 1 >= total)
|
||||
w(padIn)
|
||||
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||||
c(C_KEY); w(k)
|
||||
else
|
||||
c(C_TABLE); w("[")
|
||||
prettyVal(k, indent+1, seen)
|
||||
c(C_TABLE); w("]")
|
||||
end
|
||||
c(C_TABLE); w(" = ")
|
||||
prettyVal(val[k], indent+1, seen)
|
||||
c(C_TABLE)
|
||||
shown = shown + 1
|
||||
if shown < total then w(",") end
|
||||
w("\n")
|
||||
if shown >= MAX_ENTRIES then
|
||||
local rem = total - shown
|
||||
if rem > 0 then
|
||||
c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
c(C_TABLE); w(pad .. "}")
|
||||
seen[val] = nil
|
||||
else
|
||||
c(C_TABLE); w(tostring(val))
|
||||
end
|
||||
c(1)
|
||||
end
|
||||
|
||||
local function printResults(...)
|
||||
local n = select("#", ...)
|
||||
if n == 0 then return end
|
||||
for i = 1, n do
|
||||
if i > 1 then c(C_TABLE); w("\t") end
|
||||
prettyVal(select(i, ...), 0, {})
|
||||
end
|
||||
w("\n")
|
||||
c(1)
|
||||
end
|
||||
|
||||
local luaEnv = setmetatable({}, {__index = _ENV})
|
||||
luaEnv._G = luaEnv
|
||||
|
||||
luaEnv.print = function(...)
|
||||
local n = select("#", ...)
|
||||
for i = 1, n do
|
||||
if i > 1 then w("\t") end
|
||||
prettyVal(select(i, ...), 0, {})
|
||||
end
|
||||
w("\n")
|
||||
c(1)
|
||||
end
|
||||
|
||||
luaEnv.pp = function(val)
|
||||
prettyVal(val, 0, {})
|
||||
w("\n")
|
||||
c(1)
|
||||
end
|
||||
|
||||
luaEnv.exit = setmetatable({}, {
|
||||
__tostring = function() return "function: exit()" end,
|
||||
__call = function() syscall.exit() end,
|
||||
})
|
||||
|
||||
local function compile(code)
|
||||
local exprFn = load("return " .. code, "@lua", "t", luaEnv)
|
||||
if exprFn then return exprFn, true end
|
||||
local stmtFn, err = load(code, "@lua", "t", luaEnv)
|
||||
return stmtFn, false, err
|
||||
end
|
||||
|
||||
local function isIncomplete(code)
|
||||
local _, err = load(code, "@lua", "t", luaEnv)
|
||||
return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
|
||||
or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
|
||||
end
|
||||
|
||||
local function cleanErr(msg)
|
||||
return tostring(msg)
|
||||
:gsub("^%[string .-%]:", "")
|
||||
:gsub("^@lua:", "")
|
||||
:gsub("stack traceback:.*", "")
|
||||
:match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
local function runCode(code)
|
||||
local fn, isExpr, err = compile(code)
|
||||
if not fn then
|
||||
c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
|
||||
return
|
||||
end
|
||||
|
||||
local results = table.pack(xpcall(fn, debug.traceback))
|
||||
local ok = table.remove(results, 1)
|
||||
results.n = results.n - 1
|
||||
|
||||
if not ok then
|
||||
c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
|
||||
elseif isExpr and results.n > 0 then
|
||||
c(C_OUT); w("= ")
|
||||
printResults(table.unpack(results, 1, results.n))
|
||||
end
|
||||
end
|
||||
|
||||
local function getUserInput(prompt, history)
|
||||
c(C_PROMPT); w(prompt); c(1)
|
||||
local pos = syscall.devctl(1, "gpos")
|
||||
local ox = tonumber(pos:sub(1, pos:find(";")-1))
|
||||
local oy = tonumber(pos:sub(pos:find(";")+1))
|
||||
|
||||
local input = ""
|
||||
local cursor = 1
|
||||
local histIdx = 0
|
||||
local blink = false
|
||||
local dirty = true
|
||||
|
||||
local function redraw()
|
||||
syscall.devctl(1, "spos", ox, oy)
|
||||
w(input:sub(1, cursor-1))
|
||||
if blink then syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1) end
|
||||
w(cursor > #input and " " or input:sub(cursor, cursor))
|
||||
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||
w(input:sub(cursor+1) .. " ")
|
||||
dirty = false
|
||||
end
|
||||
|
||||
while true do
|
||||
local key = syscall.read(0)
|
||||
if key and key ~= "" then
|
||||
if key == "\19" then
|
||||
if cursor > 1 then cursor = cursor - 1; dirty = true end
|
||||
elseif key == "\20" then
|
||||
if cursor <= #input then cursor = cursor + 1; dirty = true end
|
||||
elseif key == "\17" then
|
||||
if history and histIdx < #history then
|
||||
histIdx = histIdx + 1
|
||||
input = history[#history - histIdx + 1]
|
||||
cursor = #input + 1; dirty = true
|
||||
end
|
||||
elseif key == "\18" then
|
||||
if histIdx > 1 then
|
||||
histIdx = histIdx - 1
|
||||
input = history[#history - histIdx + 1]
|
||||
cursor = #input + 1; dirty = true
|
||||
elseif histIdx == 1 then
|
||||
histIdx = 0; input = ""; cursor = 1; dirty = true
|
||||
end
|
||||
elseif key == "\b" then
|
||||
if cursor > 1 then
|
||||
input = input:sub(1, cursor-2) .. input:sub(cursor)
|
||||
cursor = cursor - 1; dirty = true
|
||||
end
|
||||
elseif key == "\n" then
|
||||
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||||
syscall.devctl(1,"spos",ox,oy)
|
||||
w(input .. " \n")
|
||||
return input
|
||||
else
|
||||
input = input:sub(1, cursor-1) .. key .. input:sub(cursor)
|
||||
cursor = cursor + 1; dirty = true
|
||||
end
|
||||
end
|
||||
local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
|
||||
if nb ~= blink then blink = nb; dirty = true end
|
||||
if dirty then redraw() end
|
||||
end
|
||||
end
|
||||
|
||||
syscall.devctl(1, "clear")
|
||||
syscall.devctl(1, "spos", 1, 1)
|
||||
c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
|
||||
c(C_NIL)
|
||||
w("Interactive Lua REPL. exit() to quit.\n\n")
|
||||
c(1)
|
||||
|
||||
local history = {}
|
||||
|
||||
while true do
|
||||
local code = getUserInput("lua> ", history)
|
||||
if code == "" then goto continue end
|
||||
|
||||
while isIncomplete(code) do
|
||||
code = code .. "\n" .. getUserInput("... ", nil)
|
||||
end
|
||||
|
||||
if code ~= history[#history] then
|
||||
history[#history+1] = code
|
||||
end
|
||||
|
||||
runCode(code)
|
||||
::continue::
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
--:Minify:--
|
||||
print("HyperionOS lua")
|
||||
local str=""
|
||||
local stopInput=false
|
||||
local timeout=false
|
||||
local luaEnv=setmetatable({},{__index=_ENV})
|
||||
printInline("> ")
|
||||
while true do
|
||||
local input=syscall.read(0)
|
||||
if input then
|
||||
if input=="\b" then
|
||||
if #str>0 then
|
||||
str=str:sub(1,#str-1)
|
||||
printInline("\b")
|
||||
end
|
||||
elseif input=="\n" then
|
||||
print("")
|
||||
stopInput=true
|
||||
if str == "" then
|
||||
printInline("> ")
|
||||
stopInput=false
|
||||
elseif str == "exit()" then
|
||||
break
|
||||
else
|
||||
local func=load(str,"@Lua","t",luaEnv)
|
||||
local ok,err = xpcall(func, debug.traceback)
|
||||
if not ok then
|
||||
print(err)
|
||||
end
|
||||
printInline("\n> ")
|
||||
str=""
|
||||
end
|
||||
str=""
|
||||
else
|
||||
str=str..input
|
||||
printInline(input)
|
||||
end
|
||||
timeout=false
|
||||
else
|
||||
timeout=true
|
||||
end
|
||||
|
||||
if timeout then
|
||||
if stopInput then
|
||||
sleep(.5)
|
||||
else
|
||||
sleep(.05)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
local args = {...}
|
||||
local name = syscall.getTask(syscall.getpid()).name
|
||||
if #args == 0 then
|
||||
print(name..": Missing operand.")
|
||||
return
|
||||
end
|
||||
|
||||
local fs = require("sys.fs")
|
||||
local newDir = args[1]
|
||||
if newDir:sub(1, 1) ~= "/" then
|
||||
newDir = syscall.getcwd().."/"..newDir
|
||||
end
|
||||
|
||||
if newDir:sub(#newDir, #newDir) ~= "/" then
|
||||
newDir = newDir.."/"
|
||||
end
|
||||
|
||||
if fs.isDir(newDir) then
|
||||
print(name..": Cannot create directory '"..args[1].."': Directory already exists.")
|
||||
return
|
||||
end
|
||||
|
||||
fs.mkdir(newDir)
|
||||
@@ -0,0 +1,18 @@
|
||||
local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown")
|
||||
print(".. *. .. | "..userhost)
|
||||
print(" *= +@* +* | "..string.rep("-",#userhost))
|
||||
print(" .@#. -@@@= :#@. | OS: "..(syscall.version() or "Unknown"))
|
||||
print(" =@@+ *@@@# +@@= | Host: "..(syscall.getHost() or "Unknown"))
|
||||
print(" %@@%: *@@@# -%@@% | Uptime: "..(syscall.getUptime() or "Unknown"))
|
||||
print(" :@@@@+ *@@@# .*@@@@: | Tasks: "..tostring((#syscall.getTasks() or "Unknown")))
|
||||
print(" :*@@@%- *@@@# -@@@@*: | Packages: ".."Unknown")
|
||||
print(" =%@@#. *@@@# .#@@%= | Shell: "..(syscall.getEnviron("SHELL") or "Unknown"))
|
||||
print(" :=. :*@@= *@@@# =@@+: .=: | ")
|
||||
print(" %@#=..*# +@@@# #*..=#@# | ")
|
||||
print(" .@@@@+=# .%@%: #=+@@@@. | ")
|
||||
print(" .....=# -@= *+...:. | ")
|
||||
print(" -*%*-@= - =@-*%*- | ")
|
||||
print(" -@*. -@%. :%@- :*@- | ")
|
||||
print(" .#@#@* | ")
|
||||
print(" -#- | ")
|
||||
print(" | ")
|
||||
@@ -0,0 +1,80 @@
|
||||
--:Minify:--
|
||||
-- passwd: change a user's password
|
||||
-- Usage: passwd [username] (default: current user)
|
||||
|
||||
local args = {...}
|
||||
local targetName = args[1]
|
||||
|
||||
local currentUid = syscall.getuid()
|
||||
|
||||
local targetUid
|
||||
if targetName then
|
||||
targetUid = syscall.auth_getuid(targetName)
|
||||
if not targetUid then
|
||||
print("passwd: user '" .. targetName .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
-- Only root can change another user's password
|
||||
if currentUid ~= 0 and targetUid ~= currentUid then
|
||||
print("passwd: permission denied")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
else
|
||||
targetUid = currentUid
|
||||
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||
end
|
||||
|
||||
-- Non-root must verify their current password first
|
||||
if currentUid ~= 0 then
|
||||
printInline("Current password: ")
|
||||
local cur = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else cur=cur..ch; syscall.write(1,"*") end
|
||||
end
|
||||
local ok, err = syscall.auth_elevate(targetName, cur)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("passwd: authentication failure")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
printInline("New password: ")
|
||||
local pw1 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw1=pw1..ch; syscall.write(1,"*") end
|
||||
end
|
||||
|
||||
printInline("Confirm password: ")
|
||||
local pw2 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||
end
|
||||
|
||||
if pw1 ~= pw2 then
|
||||
print("passwd: passwords do not match")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local ok, err = syscall.auth_setpassword(targetUid, pw1)
|
||||
if not ok then
|
||||
print("passwd: " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
print("passwd: password updated for '" .. targetName .. "'")
|
||||
@@ -0,0 +1,4 @@
|
||||
for i,v in ipairs(syscall.getTasks()) do
|
||||
local task = syscall.getTask(v)
|
||||
print(task.pid,task.username,task.name,task.status)
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
print(syscall.getcwd())
|
||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
local fs = require("sys.fs")
|
||||
local bashStr = fs.readAllText("/bin/bash")
|
||||
local bashFun = load(bashStr)
|
||||
syscall.spawn(bashFun, "bash")
|
||||
@@ -0,0 +1 @@
|
||||
syscall.chown("/bin", 0, 0)
|
||||
@@ -0,0 +1,72 @@
|
||||
--:Minify:--
|
||||
local fs = require("sys.fs")
|
||||
|
||||
local targetUser = ({...})[1] or "root"
|
||||
|
||||
local currentUid = syscall.getuid()
|
||||
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||
|
||||
local targetUid = syscall.auth_getuid(targetUser)
|
||||
if not targetUid then
|
||||
print("su: user '" .. targetUser .. "' does not exist")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local ok, err
|
||||
if currentUid == 0 then
|
||||
ok = true
|
||||
else
|
||||
printInline("Password: ")
|
||||
local pw = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then
|
||||
syscall.write(1, "\n")
|
||||
break
|
||||
elseif ch == "\b" then
|
||||
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
|
||||
else
|
||||
pw = pw .. ch
|
||||
syscall.write(1, "*")
|
||||
end
|
||||
end
|
||||
|
||||
ok, err = syscall.auth_elevate(targetUser, pw)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("su: Authentication failure")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if currentUid == 0 then
|
||||
syscall.setuid(targetUid)
|
||||
end
|
||||
|
||||
local pwent = syscall.auth_getpasswd(targetUid)
|
||||
local shell = (pwent and pwent.shell) or "/bin/hysh"
|
||||
local homedir = (pwent and pwent.homedir) or "/"
|
||||
|
||||
syscall.chdir(homedir)
|
||||
syscall.setEnviron("HOME", homedir)
|
||||
syscall.setEnviron("USER", targetUser)
|
||||
syscall.setEnviron("SHELL", shell)
|
||||
|
||||
local shellText = fs.readAllText(shell)
|
||||
if not shellText then
|
||||
print("su: shell not found: " .. shell)
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local shellFn, loadErr = load(shellText, "@" .. shell)
|
||||
if not shellFn then
|
||||
print("su: cannot load shell: " .. tostring(loadErr))
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
shellFn()
|
||||
@@ -0,0 +1,110 @@
|
||||
--:Minify:--
|
||||
local fs = require("sys.fs")
|
||||
|
||||
local cmdArgs = {...}
|
||||
local targetUser = "root"
|
||||
local i = 1
|
||||
|
||||
if cmdArgs[i] == "-u" then
|
||||
i = i + 1
|
||||
local uarg = cmdArgs[i] or "root"
|
||||
local numUid = tonumber(uarg)
|
||||
if numUid then
|
||||
local pwent = syscall.auth_getpasswd(numUid)
|
||||
targetUser = (pwent and pwent.username) or uarg
|
||||
else
|
||||
targetUser = uarg
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
local cmd = cmdArgs[i]
|
||||
if not cmd or cmd == "" then
|
||||
print("usage: sudo [-u user] <command> [args...]")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local restArgs = {}
|
||||
for j = i + 1, #cmdArgs do restArgs[#restArgs + 1] = cmdArgs[j] end
|
||||
|
||||
local currentUid = syscall.getuid()
|
||||
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
|
||||
|
||||
local targetUid = syscall.auth_getuid(targetUser)
|
||||
if not targetUid then
|
||||
print("sudo: user '" .. targetUser .. "' does not exist")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
if currentUid ~= 0 then
|
||||
printInline("[sudo] password for " .. currentUser .. ": ")
|
||||
local pw = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then
|
||||
syscall.write(1, "\n")
|
||||
break
|
||||
elseif ch == "\b" then
|
||||
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
|
||||
else
|
||||
pw = pw .. ch
|
||||
syscall.write(1, "*")
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = syscall.auth_elevate(currentUser, pw)
|
||||
if not ok then
|
||||
sleep(1)
|
||||
print("sudo: Authentication failure")
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
if targetUid ~= 0 then
|
||||
syscall.setuid(targetUid)
|
||||
end
|
||||
else
|
||||
if targetUid ~= currentUid then
|
||||
syscall.setuid(targetUid)
|
||||
end
|
||||
end
|
||||
|
||||
local cmdPath = ""
|
||||
if cmd:find("/") then
|
||||
if fs.exists(cmd) then cmdPath = cmd end
|
||||
else
|
||||
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
|
||||
for _, p in ipairs(paths) do
|
||||
local full = p .. cmd
|
||||
if fs.exists(full) then cmdPath = full; break end
|
||||
end
|
||||
end
|
||||
|
||||
if cmdPath == "" then
|
||||
print("sudo: command not found: " .. cmd)
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local text = fs.readAllText(cmdPath)
|
||||
local program, loadErr = load(text, "@" .. cmdPath)
|
||||
if not program then
|
||||
print("sudo: cannot load " .. cmd .. ": " .. tostring(loadErr))
|
||||
syscall.exit(1)
|
||||
return
|
||||
end
|
||||
|
||||
local pwent = syscall.auth_getpasswd(targetUid)
|
||||
if pwent and pwent.homedir then
|
||||
syscall.setEnviron("HOME", pwent.homedir)
|
||||
end
|
||||
syscall.setEnviron("USER", targetUser)
|
||||
|
||||
local ok, err = xpcall(program, debug.traceback, table.unpack(restArgs))
|
||||
if not ok then
|
||||
print("sudo: " .. cmd .. ": " .. tostring(err))
|
||||
syscall.exit(1)
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
local syscalls=syscall.sysdump()
|
||||
for i=1, #syscalls do
|
||||
print(syscalls[i])
|
||||
end
|
||||
print("Total # of syscalls: "..tostring(#syscalls))
|
||||
@@ -0,0 +1,67 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local i = 1
|
||||
local opt = { createHome = true }
|
||||
|
||||
while i <= #args do
|
||||
local a = args[i]
|
||||
if a == "-p" then i=i+1; opt.password = args[i]
|
||||
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
|
||||
elseif a == "-d" then i=i+1; opt.homedir = args[i]
|
||||
elseif a == "-s" then i=i+1; opt.shell = args[i]
|
||||
elseif a == "-M" then opt.createHome = false
|
||||
elseif a:sub(1,1) ~= "-" then opt.username = a
|
||||
else print("useradd: unknown option: " .. a); return end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if not opt.username then
|
||||
print("Usage: useradd [-p password] [-g gid] [-d homedir] [-s shell] [-M] <username>")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local password = opt.password
|
||||
if not password then
|
||||
printInline("New password: ")
|
||||
password = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #password > 0 then password=password:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else password=password..ch; syscall.write(1,"*") end
|
||||
end
|
||||
printInline("Confirm password: ")
|
||||
local pw2 = ""
|
||||
while true do
|
||||
local ch = syscall.read(0)
|
||||
if not ch or ch == "" then
|
||||
elseif ch == "\n" then syscall.write(1,"\n"); break
|
||||
elseif ch == "\b" then
|
||||
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
|
||||
else pw2=pw2..ch; syscall.write(1,"*") end
|
||||
end
|
||||
if password ~= pw2 then
|
||||
print("useradd: passwords do not match")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
end
|
||||
|
||||
local uid, err = syscall.auth_newuser(
|
||||
opt.username, password, opt.gid, opt.homedir, opt.shell or "/bin/hysh"
|
||||
)
|
||||
if not uid then
|
||||
print("useradd: " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if opt.createHome then
|
||||
local home = opt.homedir or ("/home/" .. opt.username)
|
||||
local ok, e = pcall(syscall.mkdir, home)
|
||||
if not ok then
|
||||
print("useradd: warning: could not create home " .. home .. ": " .. tostring(e))
|
||||
end
|
||||
end
|
||||
|
||||
print("useradd: created user '" .. opt.username .. "' with uid=" .. tostring(uid))
|
||||
@@ -0,0 +1,49 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local removeHome = false
|
||||
local username = nil
|
||||
|
||||
for _, a in ipairs(args) do
|
||||
if a == "-r" then removeHome = true
|
||||
elseif a:sub(1,1) ~= "-" then username = a
|
||||
else print("userdel: unknown option: " .. a); syscall.exit(1); return end
|
||||
end
|
||||
|
||||
if not username then
|
||||
print("Usage: userdel [-r] <username>")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local uid = syscall.auth_getuid(username)
|
||||
if not uid then
|
||||
print("userdel: user '" .. username .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local pwent = syscall.auth_getpasswd(uid)
|
||||
|
||||
local ok, err = syscall.auth_deleteuser(uid)
|
||||
if not ok then
|
||||
print("userdel: " .. tostring(err))
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if removeHome and pwent and pwent.homedir then
|
||||
local fs = require("sys.fs")
|
||||
local ok2, err2 = pcall(function()
|
||||
local function rmdir(path)
|
||||
for _, f in ipairs(fs.list(path) or {}) do
|
||||
local full = path .. "/" .. f
|
||||
if fs.isDir(full) then rmdir(full)
|
||||
else syscall.remove(full) end
|
||||
end
|
||||
syscall.remove(path)
|
||||
end
|
||||
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
|
||||
end)
|
||||
if not ok2 then
|
||||
print("userdel: warning: could not remove home: " .. tostring(err2))
|
||||
end
|
||||
end
|
||||
|
||||
print("userdel: deleted user '" .. username .. "'")
|
||||
@@ -0,0 +1,49 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local i = 1
|
||||
local opt = {}
|
||||
|
||||
while i <= #args do
|
||||
local a = args[i]
|
||||
if a == "-l" then i=i+1; opt.newname = args[i]
|
||||
elseif a == "-p" then i=i+1; opt.password = args[i]
|
||||
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
|
||||
elseif a == "-d" then i=i+1; opt.homedir = args[i]
|
||||
elseif a == "-s" then i=i+1; opt.shell = args[i]
|
||||
elseif a == "-L" then opt.lock = true
|
||||
elseif a == "-U" then opt.unlock = true
|
||||
elseif a:sub(1,1) ~= "-" then opt.username = a
|
||||
else print("usermod: unknown option: " .. a); syscall.exit(1); return end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if not opt.username then
|
||||
print("Usage: usermod [-l newname] [-p password] [-g gid] [-d homedir] [-s shell] [-L] [-U] <username>")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
if opt.lock and opt.unlock then
|
||||
print("usermod: -L and -U are mutually exclusive")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local uid = syscall.auth_getuid(opt.username)
|
||||
if not uid then
|
||||
print("usermod: user '" .. opt.username .. "' does not exist")
|
||||
syscall.exit(1); return
|
||||
end
|
||||
|
||||
local function apply(fn, ...)
|
||||
local ok, err = fn(...)
|
||||
if not ok then print("usermod: " .. tostring(err)); syscall.exit(1) end
|
||||
end
|
||||
|
||||
if opt.newname then apply(syscall.auth_setusername, uid, opt.newname) end
|
||||
if opt.password then apply(syscall.auth_setpassword, uid, opt.password) end
|
||||
if opt.gid then apply(syscall.auth_setgid, uid, opt.gid) end
|
||||
if opt.homedir then apply(syscall.auth_sethomedir, uid, opt.homedir) end
|
||||
if opt.shell then apply(syscall.auth_setshell, uid, opt.shell) end
|
||||
if opt.lock then apply(syscall.auth_lockuser, uid) end
|
||||
if opt.unlock then apply(syscall.auth_unlockuser, uid) end
|
||||
|
||||
print("usermod: updated user '" .. opt.username .. "'")
|
||||
@@ -0,0 +1 @@
|
||||
print((syscall.auth_whoami() or "Unknown"))
|
||||
@@ -0,0 +1,8 @@
|
||||
local args = {...}
|
||||
while true do
|
||||
if #args == 0 then
|
||||
print("y")
|
||||
else
|
||||
print(table.concat(args, " "))
|
||||
end
|
||||
end
|
||||
Binary file not shown.
@@ -0,0 +1,137 @@
|
||||
--:Minify:--
|
||||
|
||||
local bit32 = {}
|
||||
|
||||
local MOD32 = 2^32
|
||||
local MOD31 = 2^31
|
||||
|
||||
local function norm(x)
|
||||
return x % MOD32
|
||||
end
|
||||
|
||||
-- Convert number to bit table
|
||||
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
|
||||
|
||||
-- Convert bit table to number
|
||||
local function frombits(t)
|
||||
local x = 0
|
||||
local p = 1
|
||||
for i = 0, 31 do
|
||||
if t[i] == 1 then
|
||||
x = x + p
|
||||
end
|
||||
p = p * 2
|
||||
end
|
||||
return norm(x)
|
||||
end
|
||||
|
||||
-- ===== Logical ops =====
|
||||
|
||||
function bit32.band(...)
|
||||
local args = {...}
|
||||
if #args == 0 then return 0xFFFFFFFF 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 and b[j] == 1) and 1 or 0
|
||||
end
|
||||
end
|
||||
return frombits(bits)
|
||||
end
|
||||
|
||||
function bit32.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
|
||||
|
||||
function bit32.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
|
||||
|
||||
function bit32.bnot(x)
|
||||
local bits = tobits(x)
|
||||
for i = 0, 31 do
|
||||
bits[i] = bits[i] == 1 and 0 or 1
|
||||
end
|
||||
return frombits(bits)
|
||||
end
|
||||
|
||||
-- ===== Shifts =====
|
||||
|
||||
function bit32.lshift(x, n)
|
||||
return norm(norm(x) * 2^n)
|
||||
end
|
||||
|
||||
function bit32.rshift(x, n)
|
||||
return math.floor(norm(x) / 2^n)
|
||||
end
|
||||
|
||||
function bit32.arshift(x, n)
|
||||
x = norm(x)
|
||||
if x >= MOD31 then
|
||||
return math.floor((x - MOD32) / 2^n)
|
||||
else
|
||||
return math.floor(x / 2^n)
|
||||
end
|
||||
end
|
||||
|
||||
-- ===== Rotates =====
|
||||
|
||||
function bit32.lrotate(x, n)
|
||||
n = n % 32
|
||||
x = norm(x)
|
||||
local left = (x * 2^n) % MOD32
|
||||
local right = math.floor(x / 2^(32 - n))
|
||||
return norm(left + right)
|
||||
end
|
||||
|
||||
function bit32.rrotate(x, n)
|
||||
n = n % 32
|
||||
x = norm(x)
|
||||
local right = math.floor(x / 2^n)
|
||||
local left = (x * 2^(32 - n)) % MOD32
|
||||
return norm(left + right)
|
||||
end
|
||||
|
||||
-- ===== Bit fields =====
|
||||
|
||||
function bit32.extract(x, field, width)
|
||||
width = width or 1
|
||||
return bit32.band(bit32.rshift(x, field), 2^width - 1)
|
||||
end
|
||||
|
||||
function bit32.replace(x, v, field, width)
|
||||
width = width or 1
|
||||
local mask = bit32.lshift(2^width - 1, field)
|
||||
x = bit32.band(x, bit32.bnot(mask))
|
||||
return bit32.bor(x, bit32.lshift(v, field))
|
||||
end
|
||||
|
||||
return bit32
|
||||
@@ -0,0 +1,117 @@
|
||||
--:Minify:--
|
||||
-- blake2s.lua
|
||||
-- Pure Lua 5.2, 32-bit only, supports keyed hashing
|
||||
|
||||
local bit32 = require("bit32")
|
||||
local band, bor, bxor = bit32.band, bit32.bor, bit32.bxor
|
||||
local rshift, lshift = bit32.rshift, bit32.lshift
|
||||
|
||||
local MOD32 = 2^32
|
||||
|
||||
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
|
||||
|
||||
local function blake2s(msg, key)
|
||||
key = key or ""
|
||||
|
||||
local h = {}
|
||||
for i = 1, 8 do h[i] = IV[i] end
|
||||
|
||||
local outlen = 32 -- bytes
|
||||
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
|
||||
|
||||
return blake2s
|
||||
@@ -0,0 +1,6 @@
|
||||
local io = {}
|
||||
local fs = require("sys.fs")
|
||||
|
||||
function io.open(path, mode)
|
||||
return fs.open(path, mode)
|
||||
end
|
||||
@@ -0,0 +1,363 @@
|
||||
-- :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 -- Peripheral name passed
|
||||
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 -- Peripheral name passed
|
||||
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()
|
||||
|
||||
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]
|
||||
|
||||
-- Update modifier keys
|
||||
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
|
||||
|
||||
-- Handle Ctrl+C
|
||||
if ctrl and charOrKey == apis.keys.c then
|
||||
for _, task in ipairs(syscall.getTasks()) do
|
||||
syscall.sigsend(task, 1) -- SIGINT
|
||||
end
|
||||
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
|
||||
@@ -0,0 +1,26 @@
|
||||
local args={...}
|
||||
local kernel=args[1]
|
||||
local driver={}
|
||||
|
||||
driver.name="CCT Term Module"
|
||||
driver.version="0.1.0"
|
||||
driver.type="gpio"
|
||||
driver.description="CCT redstone Module Kernel Module"
|
||||
driver.arch="cct"
|
||||
driver.author="HyperionOS Dev Team"
|
||||
driver.license="MIT"
|
||||
driver.api={}
|
||||
|
||||
function driver.load()
|
||||
-- will
|
||||
end
|
||||
|
||||
function driver.unload()
|
||||
-- Nothing to unload
|
||||
end
|
||||
|
||||
function driver.main()
|
||||
-- Nothing to run
|
||||
end
|
||||
|
||||
-- kernel.drivers.register(driver)
|
||||
@@ -0,0 +1,226 @@
|
||||
-- :Minify:--
|
||||
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 we've seen this table before, return a placeholder to prevent infinite loops
|
||||
if seen[tbl] then return '"[Circular Reference]"' end
|
||||
|
||||
-- Mark this table as seen
|
||||
seen[tbl] = true
|
||||
|
||||
local output = "{"
|
||||
local first = true
|
||||
|
||||
for i, v in pairs(tbl) do
|
||||
-- Handle comma placement more cleanly
|
||||
if not first then output = output .. "," end
|
||||
first = false
|
||||
|
||||
-- Serialize Key
|
||||
if type(i) == "string" then
|
||||
output = output .. "[\"" .. i .. "\"]="
|
||||
elseif type(i) == "number" then
|
||||
output = output .. "[" .. tostring(i) .. "]="
|
||||
end
|
||||
|
||||
-- Serialize Value
|
||||
if type(v) == "table" then
|
||||
-- Pass the 'seen' table down to the recursive call
|
||||
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
|
||||
@@ -0,0 +1,629 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
local vfs = {}
|
||||
kernel.vfs = vfs
|
||||
vfs.mounts = {["$"] = "/"}
|
||||
vfs.disks = kernel.disks
|
||||
|
||||
-- Path normalization
|
||||
local function normalizePath(path)
|
||||
local task = kernel.currentTask
|
||||
local cwd = task.cwd or "/"
|
||||
if path:sub(1, 1) ~= "/" then path = cwd .. "/" .. path end
|
||||
local parts = {}
|
||||
for part in path:gmatch("[^/]+") do
|
||||
if part == ".." then
|
||||
if #parts > 0 then table.remove(parts) end
|
||||
elseif part ~= "." and part ~= "" then
|
||||
table.insert(parts, part)
|
||||
end
|
||||
end
|
||||
return "/" .. table.concat(parts, "/")
|
||||
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
|
||||
|
||||
-- Resolve mount and disk path
|
||||
local function resolvePath(path)
|
||||
path = normalizePath(path)
|
||||
|
||||
local mountPoint = nil
|
||||
local mountId = nil
|
||||
|
||||
for id, mp in pairs(vfs.mounts) do
|
||||
if path == mp or (mp == "/" and path:sub(1, 1) == "/") or path:sub(1, #mp + 1) == mp .. "/" then
|
||||
if not mountPoint or #mp > #mountPoint then
|
||||
mountPoint = mp
|
||||
mountId = id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not mountId then
|
||||
error("ENODEV")
|
||||
end
|
||||
|
||||
local diskPath = path:sub(#mountPoint + 1)
|
||||
if diskPath == "" then
|
||||
diskPath = "/"
|
||||
end
|
||||
|
||||
if kernel.config.logPathResolution then
|
||||
kernel.log("Path '"..path.."' resolved to disk '"..mountId.."' and path '"..diskPath.."'")
|
||||
end
|
||||
|
||||
return vfs.disks[mountId], diskPath
|
||||
end
|
||||
|
||||
-- Allocate file descriptor for current task
|
||||
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
|
||||
|
||||
-- System-wide open file limit
|
||||
local total = 0
|
||||
local function checkSystemLimit()
|
||||
if total >= kernel.config.maxOpenFiles - 16 then error("ENFILE") end
|
||||
end
|
||||
|
||||
-- File object constructor
|
||||
local function newFileObj(handle, mode, path, meta, type)
|
||||
return {
|
||||
handle = handle,
|
||||
mode = mode,
|
||||
path = path,
|
||||
meta = meta,
|
||||
type = type,
|
||||
refcount = 1
|
||||
}
|
||||
end
|
||||
|
||||
function vfs.newfd(fdobj)
|
||||
checkSystemLimit()
|
||||
total=total+1
|
||||
local fd = allocFD(kernel.currentTask)
|
||||
kernel.currentTask.fd[fd]=fdobj
|
||||
end
|
||||
|
||||
-- Parse metafile
|
||||
local function parseMetafile(file)
|
||||
if not file or file == "" then return {} end
|
||||
|
||||
local ret = {}
|
||||
local pointer = 1
|
||||
|
||||
while pointer <= #file do
|
||||
local namelen = file:byte(pointer)
|
||||
pointer = pointer + 1
|
||||
|
||||
local name = file:sub(pointer, pointer + namelen - 1)
|
||||
pointer = pointer + namelen
|
||||
|
||||
local owner = file:byte(pointer)
|
||||
local group = file:byte(pointer + 1)
|
||||
local perms = file:byte(pointer + 2)
|
||||
pointer = pointer + 3
|
||||
|
||||
local cmetalen = file:byte(pointer)
|
||||
pointer = pointer + 1
|
||||
|
||||
local cmeta = ""
|
||||
if cmetalen > 0 then
|
||||
cmeta = file:sub(pointer, pointer + cmetalen - 1)
|
||||
pointer = pointer + cmetalen
|
||||
end
|
||||
|
||||
ret[name] = {owner = owner, group = group, perms = perms, cmeta = cmeta}
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
-- Build metafile
|
||||
local function makeMetafile(meta)
|
||||
local file = ""
|
||||
for name, m in pairs(meta) do
|
||||
local entry = ""
|
||||
entry = entry .. string.char(#name) .. name
|
||||
entry = entry .. string.char(m.owner, m.group, m.perms)
|
||||
entry = entry .. string.char(#m.cmeta) .. m.cmeta
|
||||
file = file .. entry
|
||||
end
|
||||
return file
|
||||
end
|
||||
|
||||
-- Get file metadata object
|
||||
local function getFileMeta(path)
|
||||
local disk, fullPath = resolvePath(path)
|
||||
fullPath = normalizePath(fullPath)
|
||||
|
||||
local parts = {}
|
||||
for p in fullPath:gmatch("[^/]+") do table.insert(parts, p) end
|
||||
|
||||
-- default fallback
|
||||
local default = {owner = 0, group = 0, perms = 63, cmeta = ""}
|
||||
|
||||
-- walk from deepest parent upward
|
||||
for i = #parts, 1, -1 do
|
||||
local parent = "/" .. table.concat(parts, "/", 1, i - 1)
|
||||
if parent ~= "/" then parent = parent .. "/" end
|
||||
|
||||
local target = parts[i]
|
||||
if target == ".meta" then error("Cannot open metafile") end
|
||||
local metaPath = parent .. ".meta"
|
||||
|
||||
if disk:fileExists(metaPath) then
|
||||
local f = disk:open(metaPath, "r")
|
||||
local text = f.read(65535)
|
||||
f.close()
|
||||
|
||||
local parsed = parseMetafile(text)
|
||||
if parsed[target] then return parsed[target] end
|
||||
end
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
local function ensureParentMeta(path)
|
||||
local disk, fullPath = resolvePath(path)
|
||||
fullPath = normalizePath(fullPath)
|
||||
|
||||
-- split parent + name
|
||||
local parent, name = fullPath:match("^(.*)/([^/]+)$")
|
||||
if not parent then
|
||||
parent = "/"
|
||||
name = fullPath:gsub("^/", "")
|
||||
end
|
||||
|
||||
if name == ".meta" then error("Cannot open metafile") end
|
||||
|
||||
if parent ~= "/" and parent:sub(-1) ~= "/" then parent = parent .. "/" end
|
||||
|
||||
local metaPath = parent .. ".meta"
|
||||
|
||||
if not disk:fileExists(metaPath) then
|
||||
local f = disk:open(metaPath, "w")
|
||||
f.write("")
|
||||
f.close()
|
||||
end
|
||||
|
||||
return metaPath, name
|
||||
end
|
||||
|
||||
-- Permission checking
|
||||
local function checkperms(meta, mode)
|
||||
local modes = {
|
||||
r = {owner = 5, group = 3, everyone = 1},
|
||||
w = {owner = 4, group = 2, everyone = 0},
|
||||
a = {owner = 4, group = 2, everyone = 0}
|
||||
}
|
||||
|
||||
local bits = meta.perms
|
||||
local function bit_is_set(num, bit)
|
||||
return math.floor(num / (2 ^ bit)) % 2 == 1
|
||||
end
|
||||
|
||||
if kernel.uid == 0 then return true end
|
||||
if kernel.uid == meta.owner and bit_is_set(bits, modes[mode].owner) then
|
||||
return true
|
||||
end
|
||||
|
||||
if meta.group and kernel.groups then
|
||||
for _, gid in ipairs(kernel.groups) do
|
||||
if gid == meta.group and bit_is_set(bits, modes[mode].group) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if bit_is_set(bits, modes[mode].everyone) then return true end
|
||||
error("EACCES")
|
||||
end
|
||||
|
||||
-- mounts
|
||||
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 check(disk)
|
||||
for _, name in ipairs(required) do
|
||||
if type(disk[name]) ~= "function" then
|
||||
error("Invalid disk: missing method '" .. name .. "'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function vfs.mount(target, diskOrId)
|
||||
if kernel.uid ~= 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
|
||||
local id
|
||||
|
||||
if type(diskOrId) == "string" then
|
||||
disk = kernel.disks[diskOrId]
|
||||
if not disk then error("ENODEV") end
|
||||
check(disk)
|
||||
id = diskOrId
|
||||
elseif type(diskOrId) == "table" then
|
||||
check(disk)
|
||||
disk = diskOrId
|
||||
id = disk.address
|
||||
vfs.disks[id] = disk
|
||||
else
|
||||
error("EINVAL")
|
||||
end
|
||||
|
||||
-- Prevent shadowing an existing mount
|
||||
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)
|
||||
if kernel.uid ~= 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 -- root fs
|
||||
vfs.mounts[id] = nil
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
error("EINVAL")
|
||||
end
|
||||
|
||||
-- Open file
|
||||
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)
|
||||
checkperms(meta, mode)
|
||||
|
||||
local handle
|
||||
if disk:type(diskPath)~="directory" then
|
||||
handle = disk:open(diskPath, mode)
|
||||
if type(handle)~="table" then error("ENFILE") end
|
||||
end
|
||||
|
||||
task.fd[fd] = newFileObj(handle, mode, path, meta, disk:type(diskPath))
|
||||
if not disk.isvirt then
|
||||
total = total + 1
|
||||
end
|
||||
return fd
|
||||
end
|
||||
|
||||
-- Read
|
||||
function vfs.read(fd, count)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.handle.read then error("EBADF") end
|
||||
return file.handle.read(count or 1) or ""
|
||||
end
|
||||
|
||||
-- Write
|
||||
function vfs.write(fd, content)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.handle.write then error("EBADF") end
|
||||
return file.handle.write(content)
|
||||
end
|
||||
|
||||
-- Pread / Pwrite
|
||||
function vfs.pread(fd, count, offset)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.handle.read then error("EBADF") end
|
||||
if 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 task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.handle.write then error("EBADF") end
|
||||
if not file.handle.seek then error("EBADF") end
|
||||
file.handle.seek("set", offset)
|
||||
return file.handle.write(content)
|
||||
end
|
||||
|
||||
-- Seek
|
||||
function vfs.lseek(fd, offset, whence)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.handle.seek then error("EBADF") end
|
||||
return file.handle.seek(whence or "set", offset)
|
||||
end
|
||||
|
||||
-- Fsync
|
||||
function vfs.fsync(fd)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
if not file.handle.flush then error("EBADF") end
|
||||
if file.mode ~= "w" and file.mode ~= "a" then error("EBADF") end
|
||||
file.handle.flush()
|
||||
end
|
||||
|
||||
-- Close
|
||||
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 then
|
||||
if file.handle.close then
|
||||
file.handle.close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sendfile
|
||||
function vfs.sendfile(outfd, infd, count)
|
||||
local task = kernel.currentTask
|
||||
local inFile = task.fd[infd]
|
||||
local outFile = task.fd[outfd]
|
||||
if not inFile or not outFile then error("EBADF") end
|
||||
if not inFile.handle.read then error("EBADF") end
|
||||
if 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
|
||||
|
||||
-- Stat / Fstat
|
||||
function vfs.stat(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
local attrs = disk:attributes(diskPath)
|
||||
return {
|
||||
size = attrs.size,
|
||||
modified = attrs.modified,
|
||||
created = attrs.created,
|
||||
owner = meta.owner,
|
||||
group = meta.group,
|
||||
xattr = meta.cmeta
|
||||
}
|
||||
end
|
||||
|
||||
function vfs.fstat(fd)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
local disk, path = resolvePath(file.path)
|
||||
local attrs = disk:attributes(path)
|
||||
return {
|
||||
size = attrs.size,
|
||||
modified = attrs.modified,
|
||||
created = attrs.created,
|
||||
owner = file.meta.owner,
|
||||
group = file.meta.group,
|
||||
xattr = file.meta.cmeta
|
||||
}
|
||||
end
|
||||
|
||||
-- Directory operations
|
||||
function vfs.listdir(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
if disk:type(diskPath) ~= "directory" then error("ENOENT") end
|
||||
local meta = getFileMeta(path)
|
||||
checkperms(meta, "r")
|
||||
local list = disk:list(diskPath)
|
||||
if table.indexOf(list, ".meta") ~= -1 then
|
||||
table.remove(list, table.indexOf(list, ".meta"))
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function vfs.mkdir(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
checkperms(meta, "w")
|
||||
disk:makeDirectory(diskPath)
|
||||
end
|
||||
|
||||
function vfs.remove(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
checkperms(meta, "w")
|
||||
disk:remove(diskPath)
|
||||
end
|
||||
|
||||
-- Permission functions
|
||||
function vfs.chmod(path, perms)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
|
||||
if meta.owner ~= kernel.currentTask.uid then error("EACCES") end
|
||||
meta.perms = perms
|
||||
|
||||
local mpath, target = ensureParentMeta(path)
|
||||
|
||||
local mf = disk:open(mpath, "r")
|
||||
local text = mf.read(65535)
|
||||
mf.close()
|
||||
|
||||
local parsed = parseMetafile(text)
|
||||
parsed[target] = meta
|
||||
|
||||
local f = disk:open(mpath, "w")
|
||||
f.write(makeMetafile(parsed))
|
||||
f.close()
|
||||
end
|
||||
|
||||
function vfs.fchmod(fd, perms)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
vfs.chmod(file.path, perms)
|
||||
end
|
||||
|
||||
function vfs.chown(path, uid, gid)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
|
||||
if meta.owner ~= kernel.currentTask.uid then error("EACCES") end
|
||||
meta.owner = uid
|
||||
meta.group = gid
|
||||
|
||||
local mpath, target = ensureParentMeta(path)
|
||||
|
||||
local mf = disk:open(mpath, "r")
|
||||
local text = mf.read(65535)
|
||||
mf.close()
|
||||
|
||||
local parsed = parseMetafile(text)
|
||||
parsed[target] = meta
|
||||
|
||||
local f = disk:open(mpath, "w")
|
||||
f.write(makeMetafile(parsed))
|
||||
f.close()
|
||||
end
|
||||
|
||||
function vfs.fchown(fd, uid, gid)
|
||||
local task = kernel.currentTask
|
||||
local file = task.fd[fd]
|
||||
if not file then error("EBADF") end
|
||||
vfs.chown(file.path, uid, gid)
|
||||
end
|
||||
|
||||
function vfs.exists(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
checkperms(meta, "r")
|
||||
return disk:fileExists(diskPath)
|
||||
end
|
||||
|
||||
function vfs.type(path)
|
||||
local disk, diskPath = resolvePath(path)
|
||||
local meta = getFileMeta(path)
|
||||
checkperms(meta, "r")
|
||||
return disk:type(diskPath)
|
||||
end
|
||||
|
||||
function vfs.getcwd() return kernel.currentTask.cwd end
|
||||
|
||||
function vfs.chdir(path) kernel.currentTask.cwd = path 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
|
||||
|
||||
-- Export syscalls
|
||||
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["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["dup"] = vfs.dup
|
||||
sys["dup2"] = vfs.dup2
|
||||
sys["devctl"] = vfs.devctl
|
||||
|
||||
kernel.log("VFS module loaded")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,129 @@
|
||||
local kernel = ...
|
||||
|
||||
local proxy = {}
|
||||
local data = {}
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
-- Open files
|
||||
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
|
||||
}
|
||||
elseif mode == "w" then
|
||||
step[filename] = ""
|
||||
return {
|
||||
write = function(str)
|
||||
step[filename] = str
|
||||
end
|
||||
}
|
||||
elseif mode == "a" then
|
||||
if type(step[filename]) ~= "string" then step[filename] = "" end
|
||||
return {
|
||||
write = function(str)
|
||||
step[filename] = step[filename] .. str
|
||||
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)
|
||||
return pcall(function() return self:type(path) end)
|
||||
end
|
||||
|
||||
kernel.disks["tmpfs0000"] = proxy
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
--:Minify:--
|
||||
local kernel = ...
|
||||
local socket = {}
|
||||
|
||||
function socket.socket()
|
||||
|
||||
end
|
||||
|
||||
function socket.bind()
|
||||
|
||||
end
|
||||
|
||||
kernel.socket=socket
|
||||
kernel.log("Loaded socket module")
|
||||
@@ -0,0 +1,6 @@
|
||||
--: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"
|
||||
@@ -0,0 +1,56 @@
|
||||
-- :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()
|
||||
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, tbl, 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
|
||||
|
||||
kernel._U = readonly(kernel._G)
|
||||
kernel.allowGlobalOverwrites = true
|
||||
kernel._U._G = kernel._U
|
||||
kernel.allowGlobalOverwrites = false
|
||||
@@ -0,0 +1,625 @@
|
||||
--: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
|
||||
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
|
||||
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/sh"
|
||||
|
||||
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)
|
||||
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]
|
||||
|
||||
-- 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
|
||||
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
|
||||
|
||||
-- Prefix hash with ! to lock (standard Linux convention)
|
||||
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
|
||||
|
||||
-- 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"
|
||||
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
|
||||
|
||||
-- Directly set the calling task's uid — trusted kernel path
|
||||
local task = kernel.currentTask
|
||||
local prevUid = task.uid
|
||||
task.uid = uid
|
||||
task.euid = uid
|
||||
task.gid = tonumber(entry[2]) or uid
|
||||
task.egid = tonumber(entry[2]) or uid
|
||||
kernel.uid = uid
|
||||
|
||||
kernel.log("AUTH: elevate uid=" .. tostring(prevUid) .. " -> " .. tostring(uid) .. " (" .. targetUsername .. ")")
|
||||
return true, uid
|
||||
end
|
||||
|
||||
if kernel.syscalls then
|
||||
kernel.syscalls["auth_login"] = auth.login
|
||||
kernel.syscalls["auth_setpassword"] = auth.setPassword
|
||||
kernel.syscalls["auth_setusername"] = auth.setUsername
|
||||
kernel.syscalls["auth_newuser"] = auth.newUser
|
||||
kernel.syscalls["auth_whoami"] = auth.whoami
|
||||
kernel.syscalls["auth_getuid"] = auth.getUID
|
||||
kernel.syscalls["auth_getpasswd"] = auth.getPasswd
|
||||
kernel.syscalls["auth_elevate"] = auth.elevate
|
||||
kernel.syscalls["auth_deleteuser"] = auth.deleteUser
|
||||
kernel.syscalls["auth_lockuser"] = auth.lockUser
|
||||
kernel.syscalls["auth_unlockuser"] = auth.unlockUser
|
||||
kernel.syscalls["auth_listusers"] = auth.listUsers
|
||||
kernel.syscalls["auth_setshell"] = auth.setShell
|
||||
kernel.syscalls["auth_sethomedir"] = auth.setHomedir
|
||||
kernel.syscalls["auth_setgid"] = auth.setGID
|
||||
end
|
||||
@@ -0,0 +1,447 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
local tasks = {}
|
||||
local sys = {}
|
||||
local nextpid = 2
|
||||
kernel.exitMain = false
|
||||
|
||||
function sys.spawn(func, name, envars, args, tgid)
|
||||
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 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
|
||||
end
|
||||
end
|
||||
for v, _ in ipairs(tasks[tostring(id)].fd) do pcall(kernel.vfs.close,v) 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,
|
||||
children = {},
|
||||
parent = kernel.currentTask,
|
||||
siblings = kernel.currentTask.children,
|
||||
syscallReturn = {},
|
||||
cwd = kernel.currentTask.cwd,
|
||||
timeSlice = 0,
|
||||
lastTime = 0,
|
||||
totalTime = 0,
|
||||
numRuns = 0
|
||||
}
|
||||
|
||||
table.insert(kernel.currentTask.children, tasks[tostring(id)])
|
||||
return id
|
||||
end
|
||||
|
||||
function sys.sleep(s)
|
||||
kernel.currentTask.status = "S"
|
||||
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 = {}
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
function sys.collect(pid)
|
||||
local children = {}
|
||||
for i, v in ipairs(kernel.currentTask.children) do children[i] = v.pid end
|
||||
|
||||
if not tasks[tostring(pid)] then
|
||||
return false, "Task does not exist"
|
||||
|
||||
elseif not isEqualToAny(tasks[tostring(pid)].pid, table.unpack(children)) then
|
||||
return false, "You do not own this task"
|
||||
|
||||
elseif tasks[tostring(pid)].status ~= "Z" then
|
||||
return false, "Task must exit to collect status"
|
||||
|
||||
else
|
||||
tasks[tostring(pid)].reapTime = 0
|
||||
return true, tasks[tostring(pid)].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
|
||||
return false, "Task does not exist"
|
||||
|
||||
elseif not isEqualToAny(tasks[tostring(pid)].pid, table.unpack(children)) and kernel.uid ~= 0 then
|
||||
return false, "You do not own this task"
|
||||
|
||||
elseif tasks[tostring(pid)].status == "Z" then
|
||||
return false, "Task is already dead"
|
||||
|
||||
else
|
||||
tasks[tostring(pid)].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
|
||||
return false, "Task does not exist"
|
||||
|
||||
elseif not isEqualToAny(tasks[tostring(pid)].pid, table.unpack(children)) and kernel.uid ~= 0 then
|
||||
return false, "You do not own this task"
|
||||
|
||||
elseif tasks[tostring(pid)].status ~= "R" then
|
||||
return false, "Cannot stop non running task"
|
||||
|
||||
else
|
||||
tasks[tostring(pid)].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
|
||||
return false, "Task does not exist"
|
||||
|
||||
elseif not isEqualToAny(tasks[tostring(pid)].pid, table.unpack(children)) and kernel.uid ~= 0 then
|
||||
return false, "You do not own this task"
|
||||
|
||||
elseif tasks[tostring(pid)].status ~= "T" then
|
||||
return false, "Task is not stopped"
|
||||
|
||||
else
|
||||
tasks[tostring(pid)].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 i, 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.exit(code)
|
||||
if kernel.config.logTaskExit then
|
||||
if code then
|
||||
kernel.log("Task " .. tostring(kernel.currentTask.pid) .. " exited with code: " .. tostring(code), "INFO")
|
||||
else
|
||||
kernel.log("Task " .. tostring(kernel.currentTask.pid) .. " exited without code", "INFO")
|
||||
end
|
||||
end
|
||||
|
||||
tasks[tostring(kernel.currentTask.pid)].status = "Z"
|
||||
if type(code) == "number" then
|
||||
tasks[tostring(kernel.currentTask.pid)].exit = code
|
||||
end
|
||||
end
|
||||
|
||||
function sys.setuid(uid)
|
||||
if kernel.uid ~= 0 then error("EACCES") end
|
||||
kernel.currentTask.uid = uid
|
||||
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.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" then
|
||||
if kernel.computer:time() >= task.sleep then
|
||||
task.status = "R"
|
||||
task.sleep = 0
|
||||
end
|
||||
end
|
||||
if task.status == "R" then
|
||||
kernel.currentTask = task
|
||||
kernel.uid = 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
|
||||
local coro = coroutine.create(task.sigh)
|
||||
if kernel.config.preempt then
|
||||
coroutine.resumeWithTimeout(coro, task.timeSlice, table.remove(task.sigq, 1))
|
||||
else
|
||||
coroutine.resume(coro, table.remove(task.sigq, 1))
|
||||
end
|
||||
end
|
||||
|
||||
-- check for exit/stop
|
||||
if task.status=="R" then
|
||||
-- measure execution time
|
||||
local startTime = kernel.computer:time()
|
||||
local ret
|
||||
if kernel.config.preempt then
|
||||
ret = {
|
||||
coroutine.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
|
||||
|
||||
-- handle task results
|
||||
if ret[1] == "error" or ret[1] == false then
|
||||
kernel.log("processHandlerException: " .. ret[2], "ERROR", 2)
|
||||
task.status = "Z"
|
||||
task.exit = "processHandlerException: " .. 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
|
||||
if kernel.syscalls[ret[3]] then
|
||||
if kernel.config.debugSyscalls then
|
||||
kernel.log("Task " .. task.pid .. " invoking syscall: " .. ret[3], "DBUG", 5)
|
||||
|
||||
for i = 4, #ret do
|
||||
kernel.log(" inval[" .. tostring(i - 3) .. "] = " .. tostring(ret[i]), "DBUG", 5)
|
||||
end
|
||||
end
|
||||
|
||||
local sysret = {
|
||||
xpcall(kernel.syscalls[ret[3]], 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
|
||||
)
|
||||
|
||||
else
|
||||
kernel.log(
|
||||
"Task " .. task.pid .. " syscall " .. ret[3] .. " completed returning " .. tostring(#sysret - 1) .. " values", "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
|
||||
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(ret[3])
|
||||
}
|
||||
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
|
||||
|
||||
-- clean up dead tasks
|
||||
reapDeadTasks()
|
||||
end
|
||||
end
|
||||
|
||||
kernel.tasks = tasks
|
||||
kernel.hpv = sys
|
||||
@@ -0,0 +1,7 @@
|
||||
--:Minify:--
|
||||
local kernel=...
|
||||
local debug=debug
|
||||
kernel._G.debug={
|
||||
getinfo=debug.getinfo,
|
||||
traceback=debug.traceback
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,24 @@
|
||||
-- :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, ...)
|
||||
coroutine.yield()
|
||||
local output = string.format(fmt, ...)
|
||||
syscall.write(1, output.."\n")
|
||||
end
|
||||
|
||||
function printInline(...)
|
||||
coroutine.yield()
|
||||
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
|
||||
@@ -0,0 +1,47 @@
|
||||
-- :Minify:--
|
||||
local kernel = ...
|
||||
kernel.log("Loading init system...")
|
||||
kernel.log("InitPath: " .. kernel.config.initPath)
|
||||
|
||||
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...")
|
||||
@@ -0,0 +1,16 @@
|
||||
--: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
|
||||
end
|
||||
fn()
|
||||
end
|
||||
@@ -0,0 +1,165 @@
|
||||
--:Minify:--
|
||||
local kernel = ...
|
||||
|
||||
local bit32 = require("bit32")
|
||||
local bor = bit32.bor
|
||||
local lshift = bit32.lshift
|
||||
|
||||
-- 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 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 data = ""
|
||||
for _, e in ipairs(entries) do
|
||||
data = data .. metaEntry(e[1], e[2], e[3], e[4])
|
||||
end
|
||||
|
||||
local ok, err = pcall(function()
|
||||
local f = rootDisk:open(path, "w")
|
||||
f.write(data)
|
||||
f.close()
|
||||
end)
|
||||
if not ok then
|
||||
kernel.log("permissions: failed to write /" .. path .. ": " .. tostring(err), "WARN", 8)
|
||||
end
|
||||
end
|
||||
|
||||
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},
|
||||
})
|
||||
|
||||
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},
|
||||
})
|
||||
|
||||
writeMeta("/bin/startup", {
|
||||
{"test.lua", 0, 0, RWX_R_R},
|
||||
})
|
||||
|
||||
writeMeta("/etc", {
|
||||
{"passwd", 0, 0, RW_R_R},
|
||||
{"shadow", 0, 0, RW____ },
|
||||
{"pam.d", 0, 0, RWX_R_R},
|
||||
})
|
||||
|
||||
writeMeta("/etc/pam.d", {
|
||||
{"secret", 0, 0, RW____},
|
||||
})
|
||||
|
||||
writeMeta("/sbin", {
|
||||
{"init.lua", 0, 0, RWX_R_R},
|
||||
})
|
||||
|
||||
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},
|
||||
})
|
||||
|
||||
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},
|
||||
})
|
||||
|
||||
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")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
local deflate=require("LibDeflate")
|
||||
local lib={}
|
||||
lib.compress=function(data)
|
||||
return deflate:CompressDeflate(data)
|
||||
end
|
||||
lib.decompress=function(data)
|
||||
return deflate:DecompressDeflate(data)
|
||||
end
|
||||
return lib
|
||||
@@ -0,0 +1,143 @@
|
||||
--:Minify:--
|
||||
local fs={}
|
||||
|
||||
-- "open" : open
|
||||
-- "read" : read
|
||||
-- "write" : write
|
||||
-- "close" : close
|
||||
|
||||
function fs.open(path, mode)
|
||||
local fd=syscall.open(path,mode)
|
||||
local ret={
|
||||
close=function()
|
||||
-- close file
|
||||
return syscall.close(fd)
|
||||
end,
|
||||
flush=function()
|
||||
-- close and reopen file to flush buffers
|
||||
syscall.fsync(fd)
|
||||
end
|
||||
}
|
||||
if mode=="r" then
|
||||
ret.read=function(count)
|
||||
local data = syscall.read(fd,count)
|
||||
return data
|
||||
end
|
||||
ret.readAll=function(chunkSize)
|
||||
local chunks={} -- to store read chunks
|
||||
while true do
|
||||
local chunk=syscall.read(fd,chunkSize or 65536)
|
||||
if chunk==nil or #chunk==0 then break end
|
||||
table.insert(chunks,chunk)
|
||||
end
|
||||
return table.concat(chunks)
|
||||
end
|
||||
ret.readLine = function(chunkSize)
|
||||
local buffer = {} -- stores leftover data
|
||||
local buffer_str = "" -- concatenated buffer
|
||||
local chunk_size = chunkSize or 65536 -- adjust chunk size for performance
|
||||
local eof = false
|
||||
|
||||
while true do
|
||||
-- Try to find a newline in the current buffer
|
||||
local line_end = buffer_str:find("\n")
|
||||
if line_end then
|
||||
local line = buffer_str:sub(1, line_end - 1)
|
||||
buffer_str = buffer_str:sub(line_end + 1)
|
||||
return line
|
||||
end
|
||||
|
||||
-- If EOF was reached previously and buffer is empty, stop
|
||||
if eof then
|
||||
if buffer_str ~= "" then
|
||||
local last_line = buffer_str
|
||||
buffer_str = ""
|
||||
return last_line
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Read the next chunk
|
||||
local chunk = syscall.read(fd, chunk_size)
|
||||
if not chunk or chunk == "" then
|
||||
eof = true
|
||||
else
|
||||
buffer_str = buffer_str .. chunk
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif mode=="w" then
|
||||
ret.write=function(data)
|
||||
-- write data to file
|
||||
return syscall.write(fd,data)
|
||||
end
|
||||
elseif mode=="a" then
|
||||
ret.write=function(data)
|
||||
-- append data to file
|
||||
return syscall.write(fd,data)
|
||||
end
|
||||
else
|
||||
error("Invalid mode '"..mode.."'",2)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function fs.readAllText(path)
|
||||
local file=fs.open(path,"r")
|
||||
if not file then return false end
|
||||
local content=file.readAll()
|
||||
file.close()
|
||||
return content
|
||||
end
|
||||
|
||||
function fs.writeAllText(path, data)
|
||||
local file=fs.open(path,"w")
|
||||
file.write(data)
|
||||
file.close()
|
||||
end
|
||||
|
||||
function fs.appendAllText(path, data)
|
||||
local file=fs.open(path,"a")
|
||||
if not file then return false end
|
||||
file.write(data)
|
||||
file.close()
|
||||
end
|
||||
|
||||
function fs.mkdir(path)
|
||||
return syscall.mkdir(path)
|
||||
end
|
||||
|
||||
function fs.remove(path)
|
||||
return syscall.remove(path)
|
||||
end
|
||||
|
||||
function fs.list(path)
|
||||
return syscall.listdir(path)
|
||||
end
|
||||
|
||||
function fs.type(path)
|
||||
return syscall.type(path)
|
||||
end
|
||||
|
||||
function fs.stat(path)
|
||||
return syscall.stat(path)
|
||||
end
|
||||
|
||||
function fs.exists(path)
|
||||
return syscall.exists(path)
|
||||
end
|
||||
|
||||
function fs.getcwd()
|
||||
return syscall.getcwd()
|
||||
end
|
||||
|
||||
function fs.chdir(path)
|
||||
return syscall.chdir(path)
|
||||
end
|
||||
|
||||
function fs.isDir(path)
|
||||
return syscall.type(path) == "directory"
|
||||
end
|
||||
|
||||
return fs
|
||||
@@ -0,0 +1,6 @@
|
||||
local sys = {}
|
||||
local fs = require("sys.fs")
|
||||
|
||||
|
||||
|
||||
return sys
|
||||
@@ -0,0 +1,5 @@
|
||||
local sys = {}
|
||||
sys.fs = require("sys.fs")
|
||||
sys.hpv = require("sys.hpv")
|
||||
sys.ipc = require("sys.ipc")
|
||||
return sys
|
||||
@@ -0,0 +1,3 @@
|
||||
local ipc = {}
|
||||
|
||||
return ipc
|
||||
@@ -0,0 +1,71 @@
|
||||
local term = {}
|
||||
|
||||
function term.clear()
|
||||
coroutine.yield("VFS_write", 1, "\27C\25")
|
||||
end
|
||||
|
||||
function term.setCursorPos(x, y)
|
||||
coroutine.yield("VFS_write", 1, "\27cs"..tostring(y)..";"..tostring(x).."\25")
|
||||
end
|
||||
|
||||
function term.size()
|
||||
coroutine.yield("VFS_write", 1, "\27ts\25")
|
||||
local ok, data = coroutine.yield("VFS_read", 0, 16) -- read response
|
||||
if not ok then error("Failed to get terminal size") end
|
||||
local x, y = string.match(data, "%R(%d+);(%d+)\25")
|
||||
return tonumber(x), tonumber(y)
|
||||
end
|
||||
|
||||
function term.getCursorPos()
|
||||
coroutine.yield("VFS_write", 1, "\27gc\25")
|
||||
local ok, data = coroutine.yield("VFS_read", 0, 16) -- read response
|
||||
if not ok then error("Failed to get cursor position") end
|
||||
local y, x = string.match(data, "%R(%d+);(%d+)\25")
|
||||
return tonumber(x), tonumber(y)
|
||||
end
|
||||
|
||||
function term.write(data)
|
||||
coroutine.yield("VFS_write", 1, data)
|
||||
end
|
||||
|
||||
function term.setTextColor(color)
|
||||
local ok, err = coroutine.yield("VFS_type", 1)
|
||||
if not ok then error(err) end
|
||||
if ok ~= "tty" then return end
|
||||
coroutine.yield("VFS_write", 1, "\27f"..tostring(color).."\25")
|
||||
end
|
||||
|
||||
function term.setBackgroundColor(color)
|
||||
local ok, err = coroutine.yield("VFS_type", 1)
|
||||
if not ok then error(err) end
|
||||
if ok ~= "tty" then return end
|
||||
coroutine.yield("VFS_write", 1, "\27b"..tostring(color).."\25")
|
||||
end
|
||||
|
||||
function term.isColor()
|
||||
local ok, err = coroutine.yield("VFS_type", 1)
|
||||
if not ok then error(err) end
|
||||
return ok == "tty"
|
||||
end
|
||||
|
||||
function term.scroll(n)
|
||||
coroutine.yield("VFS_write", 1, "\27S"..tostring(n).."\25")
|
||||
end
|
||||
|
||||
function term.setDefault(color, layer)
|
||||
if layer then
|
||||
coroutine.yield("VFS_write", 1, "\27F"..tostring(color).."\25")
|
||||
else
|
||||
coroutine.yield("VFS_write", 1, "\27B"..tostring(color).."\25")
|
||||
end
|
||||
end
|
||||
|
||||
function term.showCursor(show)
|
||||
if show then
|
||||
coroutine.yield("VFS_write", 1, "\27sc\25")
|
||||
else
|
||||
coroutine.yield("VFS_write", 1, "\27hc\25")
|
||||
end
|
||||
end
|
||||
|
||||
return term
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
--:Minify:--
|
||||
local kernel=...
|
||||
local fs=require("sys.fs")
|
||||
|
||||
for i,v in pairs(kernel.processes) do
|
||||
kernel.log("Spawning kernel task "..i)
|
||||
syscall.spawn(function()
|
||||
local status, err = pcall(v)
|
||||
if not status then
|
||||
kernel.log("Error executing kernel task '" .. i .. "': " .. err, "ERROR")
|
||||
else
|
||||
kernel.log("Successfully executed kernel task: " .. i, "INFO")
|
||||
end
|
||||
end, i)
|
||||
end
|
||||
|
||||
local files = fs.list("/bin/startup")
|
||||
if not files then error("Failed to list /bin/startup") end
|
||||
for i,v in ipairs(files) do
|
||||
if v:sub(-4) == ".lua" then
|
||||
local filepath = "/bin/startup/" .. v
|
||||
kernel.log("Executing startup script: " .. filepath, "INFO")
|
||||
local startupFunc, err = load(fs.readAllText(filepath), "@" .. filepath)
|
||||
if not startupFunc then
|
||||
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR")
|
||||
else
|
||||
syscall.spawn(function()
|
||||
syscall.setuid(1)
|
||||
local status, err = pcall(startupFunc)
|
||||
if not status then
|
||||
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR")
|
||||
else
|
||||
kernel.log("Successfully executed startup script: " .. filepath, "INFO")
|
||||
end
|
||||
end, "startup:" .. v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
sleep(1)
|
||||
kernel.saveLog()
|
||||
end
|
||||
@@ -0,0 +1,309 @@
|
||||
-- :Minify:--
|
||||
local BOOT_DRIVE_PATH = ({...})[1] or "/$"
|
||||
---@diagnostic disable-next-line: undefined-global
|
||||
local term = term
|
||||
local os = os
|
||||
local function write(text)
|
||||
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
|
||||
|
||||
local function displaySuperBadError(err)
|
||||
term.setBackgroundColor(0x1)
|
||||
term.setTextColor(0x4)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
term.write("A critical error occurred while loading the system:")
|
||||
term.setCursorPos(1, 3)
|
||||
write(err)
|
||||
while true do end
|
||||
end
|
||||
|
||||
term.setCursorBlink(false)
|
||||
local ok, err = xpcall(function()
|
||||
local apis = {BOOT_DRIVE_PATH = BOOT_DRIVE_PATH}
|
||||
|
||||
local lua = {
|
||||
coroutine = true,
|
||||
debug = true,
|
||||
_VERSION = true,
|
||||
assert = true,
|
||||
collectgarbage = true,
|
||||
error = true,
|
||||
gcinfo = true,
|
||||
getfenv = true,
|
||||
getmetatable = true,
|
||||
ipairs = true,
|
||||
__inext = true,
|
||||
load = true,
|
||||
math = true,
|
||||
next = true,
|
||||
pairs = true,
|
||||
pcall = true,
|
||||
rawequal = true,
|
||||
rawget = true,
|
||||
rawlen = true,
|
||||
rawset = true,
|
||||
select = true,
|
||||
setfenv = true,
|
||||
setmetatable = true,
|
||||
string = true,
|
||||
table = true,
|
||||
tonumber = true,
|
||||
tostring = true,
|
||||
type = true,
|
||||
xpcall = true,
|
||||
_G = true
|
||||
}
|
||||
|
||||
local debug = debug
|
||||
for i, v in pairs(_G) do
|
||||
if not lua[i] or lua[i] == nil then
|
||||
apis[i] = v
|
||||
_G[i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local acekeys={
|
||||
[apis.keys.enter]="\n",
|
||||
[apis.keys.tab]="\t",
|
||||
[apis.keys.backspace]="\b",
|
||||
[apis.keys.up]="\17",
|
||||
[apis.keys.down]="\18",
|
||||
[apis.keys.left]="\19",
|
||||
[apis.keys.right]="\20",
|
||||
}
|
||||
|
||||
function sleep(time)
|
||||
local stoptime = apis.os.clock() + (time)
|
||||
while stoptime > apis.os.clock() do end
|
||||
end
|
||||
|
||||
apis.term.setPaletteColor(0x1, 0xFFFFFF) -- #000000
|
||||
apis.term.setPaletteColor(0x2, 0xFF0000) -- #FFFFFF
|
||||
apis.term.setPaletteColor(0x4, 0x00FF00) -- #FF0000
|
||||
apis.term.setPaletteColor(0x8, 0x0000FF) -- #00FF00
|
||||
apis.term.setPaletteColor(0x10, 0x00FFFF) -- #0000FF
|
||||
apis.term.setPaletteColor(0x20, 0xFF00FF) -- #00FFFF
|
||||
apis.term.setPaletteColor(0x40, 0xFFFF00) -- #FF00FF
|
||||
apis.term.setPaletteColor(0x80, 0xFF6D00) -- #FFFF00
|
||||
apis.term.setPaletteColor(0x100, 0x6DFF55) -- #FF6D00
|
||||
apis.term.setPaletteColor(0x200, 0x24FFFF) -- #6DFF55
|
||||
apis.term.setPaletteColor(0x400, 0x924900) -- #24FFFF
|
||||
apis.term.setPaletteColor(0x800, 0x6D6D55) -- #924900
|
||||
apis.term.setPaletteColor(0x1000, 0xDBDBAA) -- #6D6D55
|
||||
apis.term.setPaletteColor(0x2000, 0x6D00FF) -- #DBDBAA
|
||||
apis.term.setPaletteColor(0x4000, 0xB6FF00) -- #6D00FF
|
||||
apis.term.setPaletteColor(0x8000, 0x000000) -- #B6FF00
|
||||
|
||||
local function getFile(path)
|
||||
local file = apis.fs.open(path, "r")
|
||||
if not file then
|
||||
displaySuperBadError("Could not open file: " .. path)
|
||||
end
|
||||
local content = file.readAll()
|
||||
file.close()
|
||||
return content
|
||||
end
|
||||
|
||||
local Kernel = load(getFile(BOOT_DRIVE_PATH .. "/boot/kernel.lua"),"@Kernel")
|
||||
local initFs = load(getFile(BOOT_DRIVE_PATH .. "/boot/cct/initdisks"),"@Init_disks")(apis)
|
||||
local fs = load(getFile(BOOT_DRIVE_PATH .. "/boot/initfs"), "@InitFs")()
|
||||
|
||||
if not Kernel then displaySuperBadError("Could not load kernel.") end
|
||||
if not initFs then displaySuperBadError("Could not load initdisks.") end
|
||||
if not fs then displaySuperBadError("Could not load initfs.") end
|
||||
|
||||
local eventQueue = {}
|
||||
|
||||
local function queueEvent(event, ...)
|
||||
table.insert(eventQueue, {event, ...})
|
||||
end
|
||||
|
||||
local computer = {
|
||||
time = function() return apis.os.epoch("utc") end,
|
||||
clock = function() return apis.os.clock() * 1000 end,
|
||||
shutdown = apis.os.shutdown,
|
||||
reboot = apis.os.reboot,
|
||||
getMachineEvent = function()
|
||||
if #eventQueue > 0 then
|
||||
return table.unpack(table.remove(eventQueue, 1))
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end,
|
||||
getEEPROM = function() return getFile("/startup.lua") end,
|
||||
setEEPROM = function(_, text)
|
||||
local h = apis.fs.open("/startup.lua", "w")
|
||||
h.write(text)
|
||||
h.close()
|
||||
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
|
||||
}
|
||||
|
||||
apis.term.setBackgroundColor(0x8000)
|
||||
apis.term.setTextColor(0x1000)
|
||||
apis.term.clear()
|
||||
apis.term.setCursorPos(1, 1)
|
||||
|
||||
local kernelCoro = coroutine.create(function()
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
local ok, err = xpcall(Kernel, debug.traceback, apis, initFs, "cct", "/sbin/init",
|
||||
{
|
||||
print = function(_, text) write(text .. "\n") end,
|
||||
printInline = function(_, text) write(text) end,
|
||||
clear = function()
|
||||
apis.term.clear()
|
||||
apis.term.setCursorPos(1, 1)
|
||||
end,
|
||||
setCursorPos = function(_, x, y)
|
||||
apis.term.setCursorPos(x, y)
|
||||
end,
|
||||
getCursorPos = function() return apis.term.getCursorPos() end,
|
||||
getSize = function() return apis.term.getSize() end,
|
||||
setBackgroundColor = function(_, color)
|
||||
apis.term.setBackgroundColor(colors[color])
|
||||
end,
|
||||
setTextColor = function(_, color)
|
||||
apis.term.setTextColor(colors[color])
|
||||
end,
|
||||
getBackgroundColor = function()
|
||||
return icolors[apis.term.getBackgroundColor()]
|
||||
end,
|
||||
getTextColor = function()
|
||||
return icolors[apis.term.getTextColor()]
|
||||
end
|
||||
}, computer, fs, "$")
|
||||
if not ok then displaySuperBadError(err) end
|
||||
end)
|
||||
|
||||
-- time is in milliseconds
|
||||
function coroutine.resumeWithTimeout(co, timeout, ...)
|
||||
local startTime = computer.time()
|
||||
debug.sethook(co, function()
|
||||
if computer.time() > startTime + timeout then
|
||||
return coroutine.yield("timeout")
|
||||
end
|
||||
end, "", 1000)
|
||||
local ret = {coroutine.resume(co, ...)}
|
||||
if ret[1] and ret[2] == "timeout" then
|
||||
return "timeout"
|
||||
elseif ret[1] == false then
|
||||
return "error", ret[2]
|
||||
else
|
||||
debug.sethook(co)
|
||||
return "success", table.unpack(ret, 2)
|
||||
end
|
||||
end
|
||||
|
||||
write("Loaded in " .. tostring(apis.os.clock()) .. " seconds.\n")
|
||||
|
||||
while true do
|
||||
local status, err = coroutine.resumeWithTimeout(kernelCoro, 50)
|
||||
apis.os.queueEvent("NoSleep")
|
||||
local exit = false
|
||||
while not exit do
|
||||
local event = {coroutine.yield()}
|
||||
if event[1] == "key" then
|
||||
queueEvent("keyPressed", 1, event[2])
|
||||
if acekeys[event[2]] then
|
||||
queueEvent("keyTyped", 1, acekeys[event[2]])
|
||||
end
|
||||
elseif event[1] == "char" then
|
||||
queueEvent("keyTyped", 1, event[2])
|
||||
elseif event[1] == "key_up" then
|
||||
queueEvent("keyReleased", 1, event[2])
|
||||
elseif event[1] == "disk" then
|
||||
queueEvent("componentAdded", "disk")
|
||||
elseif event[1] == "disk_eject" then
|
||||
queueEvent("componentRemoved", "disk")
|
||||
elseif event[1] == "NoSleep" then
|
||||
exit = true
|
||||
end
|
||||
end
|
||||
if status == "error" or coroutine.status(kernelCoro) == "dead" then
|
||||
displaySuperBadError("Kernel error: " .. tostring(err))
|
||||
coroutine.yield("key")
|
||||
end
|
||||
end
|
||||
end, debug.traceback)
|
||||
|
||||
if not ok then displaySuperBadError("Fatal error during boot: " .. err) end
|
||||
while true do coroutine.yield() end
|
||||
@@ -0,0 +1,120 @@
|
||||
--:Minify:--
|
||||
sleep(1)
|
||||
local BOOT_DRIVE_PATH=({...})[1] or "/$"
|
||||
-- UnBIOS by JackMacWindows
|
||||
-- This will undo most of the changes/additions made in the BIOS, but some things may remain wrapped if `debug` is unavailable
|
||||
-- To use, just place a `bios.lua` in the root of the drive, and run this program
|
||||
-- Here's a list of things that are irreversibly changed:
|
||||
-- * both `bit` and `bit32` are kept for compatibility
|
||||
-- * string metatable blocking (on old versions of CC)
|
||||
-- In addition, if `debug` is not available these things are also irreversibly changed:
|
||||
-- * old Lua 5.1 `load` function (for loading from a function)
|
||||
-- * `loadstring` prefixing (before CC:T 1.96.0)
|
||||
-- * `http.request`
|
||||
-- * `os.shutdown` and `os.reboot`
|
||||
-- * `peripheral`
|
||||
-- * `turtle.equip[Left|Right]`
|
||||
-- Licensed under the MIT license
|
||||
local args = {...}
|
||||
local keptAPIs = {keys=true, bit32 = true, bit = true, ccemux = true, config = true, coroutine = true, debug = true, fs = true, http = true, mounter = true, os = true, periphemu = true, peripheral = true, redstone = true, rs = true, term = true, utf8 = true, _HOST = true, _CC_DEFAULT_SETTINGS = true, _CC_DISABLE_LUA51_FEATURES = true, _VERSION = true, assert = true, collectgarbage = true, error = true, gcinfo = true, getfenv = true, getmetatable = true, ipairs = true, __inext = true,load = true, loadstring = true, math = true, newproxy = true, next = true, pairs = true, pcall = true, rawequal = true, rawget = true, rawlen = true, rawset = true, select = true, setfenv = true, setmetatable = true, string = true, table = true, tonumber = true, tostring = true, type = true, unpack = true, xpcall = true, turtle = true, pocket = true, commands = true, _G = true}
|
||||
local t = {}
|
||||
for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end
|
||||
for _,k in ipairs(t) do _G[k] = nil end
|
||||
local native = _G.term.native()
|
||||
for _, method in ipairs {"nativePaletteColor", "nativePaletteColour", "screenshot"} do native[method] = _G.term[method] end
|
||||
_G.term = native
|
||||
if _G.http then
|
||||
_G.http.checkURL = _G.http.checkURLAsync
|
||||
_G.http.websocket = _G.http.websocketAsync
|
||||
end
|
||||
if _G.commands then _G.commands = _G.commands.native end
|
||||
if _G.turtle then _G.turtle.native, _G.turtle.craft = nil end
|
||||
local delete = {os = {"version", "pullEventRaw", "pullEvent", "run", "loadAPI", "unloadAPI", "sleep"}, http = _G.http and {"get", "post", "put", "delete", "patch", "options", "head", "trace", "listen", "checkURLAsync", "websocketAsync"}, fs = {"complete", "isDriveRoot"}}
|
||||
for k,v in pairs(delete) do for _,a in ipairs(v) do _G[k][a] = nil end end
|
||||
-- Set up TLCO
|
||||
-- This functions by crashing `rednet.run` by removing `os.pullEventRaw`. Normally
|
||||
-- this would cause `parallel` to throw an error, but we replace `error` with an
|
||||
-- empty placeholder to let it continue and return without throwing. This results
|
||||
-- in the `pcall` returning successfully, preventing the error-displaying code
|
||||
-- from running - essentially making it so that `os.shutdown` is called immediately
|
||||
-- after the new BIOS exits.
|
||||
--
|
||||
-- From there, the setup code is placed in `term.native` since it's the first
|
||||
-- thing called after `parallel` exits. This loads the new BIOS and prepares it
|
||||
-- for execution. Finally, it overwrites `os.shutdown` with the new function to
|
||||
-- allow it to be the last function called in the original BIOS, and returns.
|
||||
-- From there execution continues, calling the `term.redirect` dummy, skipping
|
||||
-- over the error-handling code (since `pcall` returned ok), and calling
|
||||
-- `os.shutdown()`. The real `os.shutdown` is re-added, and the new BIOS is tail
|
||||
-- called, which effectively makes it run as the main chunk.
|
||||
local olderror = error
|
||||
_G.error = function() end
|
||||
_G.term.redirect = function() end
|
||||
function _G.term.native()
|
||||
_G.term.native = nil
|
||||
_G.term.redirect = nil
|
||||
_G.error = olderror
|
||||
term.setBackgroundColor(32768)
|
||||
term.setTextColor(1)
|
||||
term.setCursorPos(1, 1)
|
||||
term.setCursorBlink(true)
|
||||
term.clear()
|
||||
local file = fs.open(BOOT_DRIVE_PATH.."/boot/cct/boot.lua", "r")
|
||||
if file == nil then
|
||||
term.setCursorBlink(false)
|
||||
term.setTextColor(16384)
|
||||
term.write("Could not find /boot/cct/boot.lua. UnBIOS cannot continue.")
|
||||
term.setCursorPos(1, 2)
|
||||
term.write("Press any key to continue")
|
||||
coroutine.yield("key")
|
||||
os.shutdown()
|
||||
end
|
||||
local fn, err = loadstring(file.readAll(), "@bootloader")
|
||||
file.close()
|
||||
if fn == nil then
|
||||
term.setCursorBlink(false)
|
||||
term.setTextColor(16384)
|
||||
term.write("Could not load /boot/cc/boot.lua. UnBIOS cannot continue.")
|
||||
term.setCursorPos(1, 2)
|
||||
term.write(err)
|
||||
term.setCursorPos(1, 3)
|
||||
term.write("Press any key to continue")
|
||||
coroutine.yield("key")
|
||||
os.shutdown()
|
||||
end
|
||||
setfenv(fn, _G)
|
||||
local oldshutdown = os.shutdown
|
||||
os.shutdown = function()
|
||||
os.shutdown = oldshutdown
|
||||
return fn(BOOT_DRIVE_PATH)
|
||||
end
|
||||
end
|
||||
if debug then
|
||||
-- Restore functions that were overwritten in the BIOS
|
||||
-- Apparently this has to be done *after* redefining term.native
|
||||
local function restoreValue(tab, idx, name, hint)
|
||||
local i, key, value = 1, debug.getupvalue(tab[idx], hint)
|
||||
while key ~= name and key ~= nil do
|
||||
key, value = debug.getupvalue(tab[idx], i)
|
||||
i=i+1
|
||||
end
|
||||
tab[idx] = value or tab[idx]
|
||||
end
|
||||
restoreValue(_G, "loadstring", "nativeloadstring", 1)
|
||||
restoreValue(_G, "load", "nativeload", 5)
|
||||
if http then restoreValue(http, "request", "nativeHTTPRequest", 3) end
|
||||
restoreValue(os, "shutdown", "nativeShutdown", 1)
|
||||
restoreValue(os, "reboot", "nativeReboot", 1)
|
||||
if turtle then
|
||||
restoreValue(turtle, "equipLeft", "v", 1)
|
||||
restoreValue(turtle, "equipRight", "v", 1)
|
||||
end
|
||||
do
|
||||
local i, key, value = 1, debug.getupvalue(peripheral.isPresent, 2)
|
||||
while key ~= "native" and key ~= nil do
|
||||
key, value = debug.getupvalue(peripheral.isPresent, i)
|
||||
i=i+1
|
||||
end
|
||||
_G.peripheral = value or peripheral
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,155 @@
|
||||
-- :Minify:--
|
||||
local apis = ({...})[1]
|
||||
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH or "/$"
|
||||
local fs = apis.fs
|
||||
local native = apis.peripheral
|
||||
local peripheral = {}
|
||||
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
||||
|
||||
function peripheral.getType(name)
|
||||
if native.isPresent(name) then return native.getType(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, "getTypeRemote", name)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function peripheral.getNames()
|
||||
local names = {}
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.isPresent(side) then table.insert(names, side) end
|
||||
if native.hasType(side, "peripheral_hub") then
|
||||
local hubSides = native.call(side, "getConnectedSides")
|
||||
for _, hubSide in ipairs(hubSides) do
|
||||
table.insert(names, hubSide)
|
||||
end
|
||||
end
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
local disks = {}
|
||||
local internal = {}
|
||||
|
||||
local function norm(path)
|
||||
if not path or path == "" then return "/" end
|
||||
return fs.combine("/", path)
|
||||
end
|
||||
|
||||
local function createDisk(id, basePath, readonly, periph)
|
||||
basePath = norm(basePath)
|
||||
|
||||
local disk = {address = id, isReadOnly = function() return readonly end}
|
||||
|
||||
function disk:spaceUsed()
|
||||
return fs.getCapacity(basePath) - fs.getFreeSpace(basePath)
|
||||
end
|
||||
|
||||
function disk:spaceTotal() return fs.getCapacity(basePath) end
|
||||
|
||||
function disk:list(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
if not fs.exists(p) or not fs.isDir(p) then
|
||||
return nil, "not directory"
|
||||
end
|
||||
return fs.list(p)
|
||||
end
|
||||
|
||||
function disk:fileExists(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.exists(p) and not fs.isDir(p)
|
||||
end
|
||||
|
||||
function disk:directoryExists(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.exists(p) and fs.isDir(p)
|
||||
end
|
||||
|
||||
function disk:type(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
if not fs.exists(p) then
|
||||
return nil
|
||||
elseif fs.isDir(p) then
|
||||
return "directory"
|
||||
else
|
||||
return "file"
|
||||
end
|
||||
end
|
||||
|
||||
function disk:makeDirectory(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
fs.makeDir(p)
|
||||
return true
|
||||
end
|
||||
|
||||
function disk:remove(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
if fs.exists(p) then fs.delete(p) end
|
||||
return true
|
||||
end
|
||||
|
||||
function disk:setLabel(label) periph.setLabel(label) end
|
||||
|
||||
function disk:getLabel(label) return periph.getLabel() end
|
||||
|
||||
function disk:attributes(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.attributes(p)
|
||||
end
|
||||
|
||||
function disk:open(path, mode)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.open(p, mode)
|
||||
end
|
||||
|
||||
return disk
|
||||
end
|
||||
|
||||
internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, {
|
||||
setLabel = function(label)
|
||||
local h = fs.open("/.label", "w")
|
||||
h.write(label)
|
||||
h.close()
|
||||
end,
|
||||
getLabel = function()
|
||||
local h = fs.open("/.label", "r")
|
||||
if not h then return "$" end
|
||||
local label = h.readAll()
|
||||
h.close()
|
||||
return label
|
||||
end
|
||||
})
|
||||
|
||||
local function refresh()
|
||||
for id, _ in pairs(disks) do
|
||||
if not peripheral.getType(id) then disks[id] = nil end
|
||||
end
|
||||
|
||||
for _, name in ipairs(peripheral.getNames()) do
|
||||
if peripheral.getType(name) == "disk" then
|
||||
if not disks[name] then
|
||||
local mount = disk.getMountPath(name)
|
||||
if mount then
|
||||
disks[name] = createDisk(name, mount, false, disk)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function iter()
|
||||
refresh()
|
||||
local combined = {}
|
||||
|
||||
for id, obj in pairs(internal) do combined[id] = obj end
|
||||
for id, obj in pairs(disks) do combined[id] = obj end
|
||||
|
||||
return pairs(combined)
|
||||
end
|
||||
|
||||
return {refresh = refresh, list = iter}
|
||||
@@ -0,0 +1,363 @@
|
||||
-- :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 -- Peripheral name passed
|
||||
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 -- Peripheral name passed
|
||||
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()
|
||||
|
||||
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]
|
||||
|
||||
-- Update modifier keys
|
||||
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
|
||||
|
||||
-- Handle Ctrl+C
|
||||
if ctrl and charOrKey == apis.keys.c then
|
||||
for _, task in ipairs(syscall.getTasks()) do
|
||||
syscall.sigsend(task, 1) -- SIGINT
|
||||
end
|
||||
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
|
||||
@@ -0,0 +1,26 @@
|
||||
local args={...}
|
||||
local kernel=args[1]
|
||||
local driver={}
|
||||
|
||||
driver.name="CCT Term Module"
|
||||
driver.version="0.1.0"
|
||||
driver.type="gpio"
|
||||
driver.description="CCT redstone Module Kernel Module"
|
||||
driver.arch="cct"
|
||||
driver.author="HyperionOS Dev Team"
|
||||
driver.license="MIT"
|
||||
driver.api={}
|
||||
|
||||
function driver.load()
|
||||
-- will
|
||||
end
|
||||
|
||||
function driver.unload()
|
||||
-- Nothing to unload
|
||||
end
|
||||
|
||||
function driver.main()
|
||||
-- Nothing to run
|
||||
end
|
||||
|
||||
-- kernel.drivers.register(driver)
|
||||
@@ -0,0 +1,41 @@
|
||||
local lua = {
|
||||
coroutine = true,
|
||||
debug = true,
|
||||
_HOST = true,
|
||||
_VERSION = true,
|
||||
assert = true,
|
||||
collectgarbage = true,
|
||||
error = true,
|
||||
gcinfo = true,
|
||||
getfenv = true,
|
||||
getmetatable = true,
|
||||
ipairs = true,
|
||||
__inext = true,
|
||||
load = true,
|
||||
math = true,
|
||||
next = true,
|
||||
pairs = true,
|
||||
pcall = true,
|
||||
rawequal = true,
|
||||
rawget = true,
|
||||
rawlen = true,
|
||||
rawset = true,
|
||||
select = true,
|
||||
setfenv = true,
|
||||
setmetatable = true,
|
||||
string = true,
|
||||
table = true,
|
||||
tonumber = true,
|
||||
tostring = true,
|
||||
type = true,
|
||||
xpcall = true,
|
||||
_G=true
|
||||
}
|
||||
|
||||
local apis={}
|
||||
for i,v in pairs(_G) do
|
||||
if not lua[i] or lua[i]==nil then
|
||||
apis[i]=v
|
||||
_G[i]=nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
checkArg=nil
|
||||
local oldcomputer=computer
|
||||
_G.computer=nil
|
||||
local os=os
|
||||
_G.os=nil
|
||||
|
||||
function component.wrap(address)
|
||||
local methods=oldcomponent.methods(address)
|
||||
local object={}
|
||||
for _,method in ipairs(methods) do
|
||||
object[method]=function(_,...)
|
||||
return oldcomponent.invoke(address,method,...)
|
||||
end
|
||||
end
|
||||
return object
|
||||
end
|
||||
|
||||
local
|
||||
@@ -0,0 +1 @@
|
||||
local fs={}
|
||||
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
-- DO NOT EDIT THIS FILE IF YOU DO NOT KNOW WHAT YOU ARE DOING!
|
||||
-- DOING SO MAY RENDER YOUR SYSTEM UNBOOTABLE!
|
||||
|
||||
-- This file is auto-generated during the build process.
|
||||
-- DEFAULT BOOT CONFIGURATION FILE
|
||||
return {
|
||||
initPath = "/sbin/init.lua",
|
||||
maxOpenFiles = 128,
|
||||
maxFilesPerTask = 16,
|
||||
preempt=true
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
-- :Minify:--
|
||||
local BOOT_DRIVE_PATH = ({...})[1] or "/$"
|
||||
---@diagnostic disable-next-line: undefined-global
|
||||
local term = term
|
||||
local os = os
|
||||
local function write(text)
|
||||
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
|
||||
|
||||
local function displaySuperBadError(err)
|
||||
term.setBackgroundColor(0x1)
|
||||
term.setTextColor(0x4)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
term.write("A critical error occurred while loading the system:")
|
||||
term.setCursorPos(1, 3)
|
||||
write(err)
|
||||
while true do end
|
||||
end
|
||||
|
||||
term.setCursorBlink(false)
|
||||
local ok, err = xpcall(function()
|
||||
local apis = {BOOT_DRIVE_PATH = BOOT_DRIVE_PATH}
|
||||
|
||||
local lua = {
|
||||
coroutine = true,
|
||||
debug = true,
|
||||
_VERSION = true,
|
||||
assert = true,
|
||||
collectgarbage = true,
|
||||
error = true,
|
||||
gcinfo = true,
|
||||
getfenv = true,
|
||||
getmetatable = true,
|
||||
ipairs = true,
|
||||
__inext = true,
|
||||
load = true,
|
||||
math = true,
|
||||
next = true,
|
||||
pairs = true,
|
||||
pcall = true,
|
||||
rawequal = true,
|
||||
rawget = true,
|
||||
rawlen = true,
|
||||
rawset = true,
|
||||
select = true,
|
||||
setfenv = true,
|
||||
setmetatable = true,
|
||||
string = true,
|
||||
table = true,
|
||||
tonumber = true,
|
||||
tostring = true,
|
||||
type = true,
|
||||
xpcall = true,
|
||||
_G = true
|
||||
}
|
||||
|
||||
local debug = debug
|
||||
for i, v in pairs(_G) do
|
||||
if not lua[i] or lua[i] == nil then
|
||||
apis[i] = v
|
||||
_G[i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local acekeys={
|
||||
[apis.keys.enter]="\n",
|
||||
[apis.keys.tab]="\t",
|
||||
[apis.keys.backspace]="\b",
|
||||
[apis.keys.up]="\17",
|
||||
[apis.keys.down]="\18",
|
||||
[apis.keys.left]="\19",
|
||||
[apis.keys.right]="\20",
|
||||
}
|
||||
|
||||
function sleep(time)
|
||||
local stoptime = apis.os.clock() + (time)
|
||||
while stoptime > apis.os.clock() do end
|
||||
end
|
||||
|
||||
apis.term.setPaletteColor(0x1, 0xFFFFFF) -- #000000
|
||||
apis.term.setPaletteColor(0x2, 0xFF0000) -- #FFFFFF
|
||||
apis.term.setPaletteColor(0x4, 0x00FF00) -- #FF0000
|
||||
apis.term.setPaletteColor(0x8, 0x0000FF) -- #00FF00
|
||||
apis.term.setPaletteColor(0x10, 0x00FFFF) -- #0000FF
|
||||
apis.term.setPaletteColor(0x20, 0xFF00FF) -- #00FFFF
|
||||
apis.term.setPaletteColor(0x40, 0xFFFF00) -- #FF00FF
|
||||
apis.term.setPaletteColor(0x80, 0xFF6D00) -- #FFFF00
|
||||
apis.term.setPaletteColor(0x100, 0x6DFF55) -- #FF6D00
|
||||
apis.term.setPaletteColor(0x200, 0x24FFFF) -- #6DFF55
|
||||
apis.term.setPaletteColor(0x400, 0x924900) -- #24FFFF
|
||||
apis.term.setPaletteColor(0x800, 0x6D6D55) -- #924900
|
||||
apis.term.setPaletteColor(0x1000, 0xDBDBAA) -- #6D6D55
|
||||
apis.term.setPaletteColor(0x2000, 0x6D00FF) -- #DBDBAA
|
||||
apis.term.setPaletteColor(0x4000, 0xB6FF00) -- #6D00FF
|
||||
apis.term.setPaletteColor(0x8000, 0x000000) -- #B6FF00
|
||||
|
||||
local function getFile(path)
|
||||
local file = apis.fs.open(path, "r")
|
||||
if not file then
|
||||
displaySuperBadError("Could not open file: " .. path)
|
||||
end
|
||||
local content = file.readAll()
|
||||
file.close()
|
||||
return content
|
||||
end
|
||||
|
||||
local Kernel = load(getFile(BOOT_DRIVE_PATH .. "/boot/kernel.lua"),"@Kernel")
|
||||
local initFs = load(getFile(BOOT_DRIVE_PATH .. "/boot/cct/initdisks"),"@Init_disks")(apis)
|
||||
local fs = load(getFile(BOOT_DRIVE_PATH .. "/boot/initfs"), "@InitFs")()
|
||||
|
||||
if not Kernel then displaySuperBadError("Could not load kernel.") end
|
||||
if not initFs then displaySuperBadError("Could not load initdisks.") end
|
||||
if not fs then displaySuperBadError("Could not load initfs.") end
|
||||
|
||||
local eventQueue = {}
|
||||
|
||||
local function queueEvent(event, ...)
|
||||
table.insert(eventQueue, {event, ...})
|
||||
end
|
||||
|
||||
local computer = {
|
||||
time = function() return apis.os.epoch("utc") end,
|
||||
clock = function() return apis.os.clock() * 1000 end,
|
||||
shutdown = apis.os.shutdown,
|
||||
reboot = apis.os.reboot,
|
||||
getMachineEvent = function()
|
||||
if #eventQueue > 0 then
|
||||
return table.unpack(table.remove(eventQueue, 1))
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end,
|
||||
getEEPROM = function() return getFile("/startup.lua") end,
|
||||
setEEPROM = function(_, text)
|
||||
local h = apis.fs.open("/startup.lua", "w")
|
||||
h.write(text)
|
||||
h.close()
|
||||
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
|
||||
}
|
||||
|
||||
apis.term.setBackgroundColor(0x8000)
|
||||
apis.term.setTextColor(0x1000)
|
||||
apis.term.clear()
|
||||
apis.term.setCursorPos(1, 1)
|
||||
|
||||
local kernelCoro = coroutine.create(function()
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
local ok, err = xpcall(Kernel, debug.traceback, apis, initFs, "cct", "/sbin/init",
|
||||
{
|
||||
print = function(_, text) write(text .. "\n") end,
|
||||
printInline = function(_, text) write(text) end,
|
||||
clear = function()
|
||||
apis.term.clear()
|
||||
apis.term.setCursorPos(1, 1)
|
||||
end,
|
||||
setCursorPos = function(_, x, y)
|
||||
apis.term.setCursorPos(x, y)
|
||||
end,
|
||||
getCursorPos = function() return apis.term.getCursorPos() end,
|
||||
getSize = function() return apis.term.getSize() end,
|
||||
setBackgroundColor = function(_, color)
|
||||
apis.term.setBackgroundColor(colors[color])
|
||||
end,
|
||||
setTextColor = function(_, color)
|
||||
apis.term.setTextColor(colors[color])
|
||||
end,
|
||||
getBackgroundColor = function()
|
||||
return icolors[apis.term.getBackgroundColor()]
|
||||
end,
|
||||
getTextColor = function()
|
||||
return icolors[apis.term.getTextColor()]
|
||||
end
|
||||
}, computer, fs, "$")
|
||||
if not ok then displaySuperBadError(err) end
|
||||
end)
|
||||
|
||||
-- time is in milliseconds
|
||||
function coroutine.resumeWithTimeout(co, timeout, ...)
|
||||
local startTime = computer.time()
|
||||
debug.sethook(co, function()
|
||||
if computer.time() - startTime > timeout then
|
||||
return coroutine.yield("timeout")
|
||||
end
|
||||
end, "", 1000)
|
||||
local ret = {coroutine.resume(co, ...)}
|
||||
if ret[1] and ret[2] == "timeout" then
|
||||
return "timeout"
|
||||
elseif ret[1] == false then
|
||||
return "error", ret[2]
|
||||
else
|
||||
debug.sethook(co)
|
||||
return "success", table.unpack(ret, 2)
|
||||
end
|
||||
end
|
||||
|
||||
write("Loaded in " .. tostring(apis.os.clock()) .. " seconds.\n")
|
||||
|
||||
while true do
|
||||
local status, err = coroutine.resumeWithTimeout(kernelCoro, 50)
|
||||
apis.os.queueEvent("NoSleep")
|
||||
local exit = false
|
||||
while not exit do
|
||||
local event = {coroutine.yield()}
|
||||
if event[1] == "key" then
|
||||
queueEvent("keyPressed", 1, event[2])
|
||||
if acekeys[event[2]] then
|
||||
queueEvent("keyTyped", 1, acekeys[event[2]])
|
||||
end
|
||||
elseif event[1] == "char" then
|
||||
queueEvent("keyTyped", 1, event[2])
|
||||
elseif event[1] == "key_up" then
|
||||
queueEvent("keyReleased", 1, event[2])
|
||||
elseif event[1] == "disk" then
|
||||
queueEvent("componentAdded", "disk")
|
||||
elseif event[1] == "disk_eject" then
|
||||
queueEvent("componentRemoved", "disk")
|
||||
elseif event[1] == "NoSleep" then
|
||||
exit = true
|
||||
end
|
||||
end
|
||||
if status == "error" or coroutine.status(kernelCoro) == "dead" then
|
||||
displaySuperBadError("Kernel error: " .. tostring(err))
|
||||
coroutine.yield("key")
|
||||
end
|
||||
end
|
||||
end, debug.traceback)
|
||||
|
||||
if not ok then displaySuperBadError("Fatal error during boot: " .. err) end
|
||||
while true do coroutine.yield() end
|
||||
@@ -0,0 +1,119 @@
|
||||
--:Minify:--
|
||||
local BOOT_DRIVE_PATH=({...})[1] or "/$"
|
||||
-- UnBIOS by JackMacWindows
|
||||
-- This will undo most of the changes/additions made in the BIOS, but some things may remain wrapped if `debug` is unavailable
|
||||
-- To use, just place a `bios.lua` in the root of the drive, and run this program
|
||||
-- Here's a list of things that are irreversibly changed:
|
||||
-- * both `bit` and `bit32` are kept for compatibility
|
||||
-- * string metatable blocking (on old versions of CC)
|
||||
-- In addition, if `debug` is not available these things are also irreversibly changed:
|
||||
-- * old Lua 5.1 `load` function (for loading from a function)
|
||||
-- * `loadstring` prefixing (before CC:T 1.96.0)
|
||||
-- * `http.request`
|
||||
-- * `os.shutdown` and `os.reboot`
|
||||
-- * `peripheral`
|
||||
-- * `turtle.equip[Left|Right]`
|
||||
-- Licensed under the MIT license
|
||||
local args = {...}
|
||||
local keptAPIs = {keys=true, bit32 = true, bit = true, ccemux = true, config = true, coroutine = true, debug = true, fs = true, http = true, mounter = true, os = true, periphemu = true, peripheral = true, redstone = true, rs = true, term = true, utf8 = true, _HOST = true, _CC_DEFAULT_SETTINGS = true, _CC_DISABLE_LUA51_FEATURES = true, _VERSION = true, assert = true, collectgarbage = true, error = true, gcinfo = true, getfenv = true, getmetatable = true, ipairs = true, __inext = true,load = true, loadstring = true, math = true, newproxy = true, next = true, pairs = true, pcall = true, rawequal = true, rawget = true, rawlen = true, rawset = true, select = true, setfenv = true, setmetatable = true, string = true, table = true, tonumber = true, tostring = true, type = true, unpack = true, xpcall = true, turtle = true, pocket = true, commands = true, _G = true}
|
||||
local t = {}
|
||||
for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end
|
||||
for _,k in ipairs(t) do _G[k] = nil end
|
||||
local native = _G.term.native()
|
||||
for _, method in ipairs {"nativePaletteColor", "nativePaletteColour", "screenshot"} do native[method] = _G.term[method] end
|
||||
_G.term = native
|
||||
if _G.http then
|
||||
_G.http.checkURL = _G.http.checkURLAsync
|
||||
_G.http.websocket = _G.http.websocketAsync
|
||||
end
|
||||
if _G.commands then _G.commands = _G.commands.native end
|
||||
if _G.turtle then _G.turtle.native, _G.turtle.craft = nil end
|
||||
local delete = {os = {"version", "pullEventRaw", "pullEvent", "run", "loadAPI", "unloadAPI", "sleep"}, http = _G.http and {"get", "post", "put", "delete", "patch", "options", "head", "trace", "listen", "checkURLAsync", "websocketAsync"}, fs = {"complete", "isDriveRoot"}}
|
||||
for k,v in pairs(delete) do for _,a in ipairs(v) do _G[k][a] = nil end end
|
||||
-- Set up TLCO
|
||||
-- This functions by crashing `rednet.run` by removing `os.pullEventRaw`. Normally
|
||||
-- this would cause `parallel` to throw an error, but we replace `error` with an
|
||||
-- empty placeholder to let it continue and return without throwing. This results
|
||||
-- in the `pcall` returning successfully, preventing the error-displaying code
|
||||
-- from running - essentially making it so that `os.shutdown` is called immediately
|
||||
-- after the new BIOS exits.
|
||||
--
|
||||
-- From there, the setup code is placed in `term.native` since it's the first
|
||||
-- thing called after `parallel` exits. This loads the new BIOS and prepares it
|
||||
-- for execution. Finally, it overwrites `os.shutdown` with the new function to
|
||||
-- allow it to be the last function called in the original BIOS, and returns.
|
||||
-- From there execution continues, calling the `term.redirect` dummy, skipping
|
||||
-- over the error-handling code (since `pcall` returned ok), and calling
|
||||
-- `os.shutdown()`. The real `os.shutdown` is re-added, and the new BIOS is tail
|
||||
-- called, which effectively makes it run as the main chunk.
|
||||
local olderror = error
|
||||
_G.error = function() end
|
||||
_G.term.redirect = function() end
|
||||
function _G.term.native()
|
||||
_G.term.native = nil
|
||||
_G.term.redirect = nil
|
||||
_G.error = olderror
|
||||
term.setBackgroundColor(32768)
|
||||
term.setTextColor(1)
|
||||
term.setCursorPos(1, 1)
|
||||
term.setCursorBlink(true)
|
||||
term.clear()
|
||||
local file = fs.open(BOOT_DRIVE_PATH.."/boot/cct/boot.lua", "r")
|
||||
if file == nil then
|
||||
term.setCursorBlink(false)
|
||||
term.setTextColor(16384)
|
||||
term.write("Could not find /boot/cct/boot.lua. UnBIOS cannot continue.")
|
||||
term.setCursorPos(1, 2)
|
||||
term.write("Press any key to continue")
|
||||
coroutine.yield("key")
|
||||
os.shutdown()
|
||||
end
|
||||
local fn, err = loadstring(file.readAll(), "@bootloader")
|
||||
file.close()
|
||||
if fn == nil then
|
||||
term.setCursorBlink(false)
|
||||
term.setTextColor(16384)
|
||||
term.write("Could not load /boot/cc/boot.lua. UnBIOS cannot continue.")
|
||||
term.setCursorPos(1, 2)
|
||||
term.write(err)
|
||||
term.setCursorPos(1, 3)
|
||||
term.write("Press any key to continue")
|
||||
coroutine.yield("key")
|
||||
os.shutdown()
|
||||
end
|
||||
setfenv(fn, _G)
|
||||
local oldshutdown = os.shutdown
|
||||
os.shutdown = function()
|
||||
os.shutdown = oldshutdown
|
||||
return fn(BOOT_DRIVE_PATH)
|
||||
end
|
||||
end
|
||||
if debug then
|
||||
-- Restore functions that were overwritten in the BIOS
|
||||
-- Apparently this has to be done *after* redefining term.native
|
||||
local function restoreValue(tab, idx, name, hint)
|
||||
local i, key, value = 1, debug.getupvalue(tab[idx], hint)
|
||||
while key ~= name and key ~= nil do
|
||||
key, value = debug.getupvalue(tab[idx], i)
|
||||
i=i+1
|
||||
end
|
||||
tab[idx] = value or tab[idx]
|
||||
end
|
||||
restoreValue(_G, "loadstring", "nativeloadstring", 1)
|
||||
restoreValue(_G, "load", "nativeload", 5)
|
||||
if http then restoreValue(http, "request", "nativeHTTPRequest", 3) end
|
||||
restoreValue(os, "shutdown", "nativeShutdown", 1)
|
||||
restoreValue(os, "reboot", "nativeReboot", 1)
|
||||
if turtle then
|
||||
restoreValue(turtle, "equipLeft", "v", 1)
|
||||
restoreValue(turtle, "equipRight", "v", 1)
|
||||
end
|
||||
do
|
||||
local i, key, value = 1, debug.getupvalue(peripheral.isPresent, 2)
|
||||
while key ~= "native" and key ~= nil do
|
||||
key, value = debug.getupvalue(peripheral.isPresent, i)
|
||||
i=i+1
|
||||
end
|
||||
_G.peripheral = value or peripheral
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,155 @@
|
||||
-- :Minify:--
|
||||
local apis = ({...})[1]
|
||||
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH or "/$"
|
||||
local fs = apis.fs
|
||||
local native = apis.peripheral
|
||||
local peripheral = {}
|
||||
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
||||
|
||||
function peripheral.getType(name)
|
||||
if native.isPresent(name) then return native.getType(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, "getTypeRemote", name)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function peripheral.getNames()
|
||||
local names = {}
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.isPresent(side) then table.insert(names, side) end
|
||||
if native.hasType(side, "peripheral_hub") then
|
||||
local hubSides = native.call(side, "getConnectedSides")
|
||||
for _, hubSide in ipairs(hubSides) do
|
||||
table.insert(names, hubSide)
|
||||
end
|
||||
end
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
local disks = {}
|
||||
local internal = {}
|
||||
|
||||
local function norm(path)
|
||||
if not path or path == "" then return "/" end
|
||||
return fs.combine("/", path)
|
||||
end
|
||||
|
||||
local function createDisk(id, basePath, readonly, periph)
|
||||
basePath = norm(basePath)
|
||||
|
||||
local disk = {address = id, isReadOnly = function() return readonly end}
|
||||
|
||||
function disk:spaceUsed()
|
||||
return fs.getCapacity(basePath) - fs.getFreeSpace(basePath)
|
||||
end
|
||||
|
||||
function disk:spaceTotal() return fs.getCapacity(basePath) end
|
||||
|
||||
function disk:list(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
if not fs.exists(p) or not fs.isDir(p) then
|
||||
return nil, "not directory"
|
||||
end
|
||||
return fs.list(p)
|
||||
end
|
||||
|
||||
function disk:fileExists(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.exists(p) and not fs.isDir(p)
|
||||
end
|
||||
|
||||
function disk:directoryExists(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.exists(p) and fs.isDir(p)
|
||||
end
|
||||
|
||||
function disk:type(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
if not fs.exists(p) then
|
||||
return nil
|
||||
elseif fs.isDir(p) then
|
||||
return "directory"
|
||||
else
|
||||
return "file"
|
||||
end
|
||||
end
|
||||
|
||||
function disk:makeDirectory(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
fs.makeDir(p)
|
||||
return true
|
||||
end
|
||||
|
||||
function disk:remove(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
if fs.exists(p) then fs.delete(p) end
|
||||
return true
|
||||
end
|
||||
|
||||
function disk:setLabel(label) periph.setLabel(label) end
|
||||
|
||||
function disk:getLabel(label) return periph.getLabel() end
|
||||
|
||||
function disk:attributes(path)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.attributes(p)
|
||||
end
|
||||
|
||||
function disk:open(path, mode)
|
||||
local p = fs.combine(basePath, path)
|
||||
return fs.open(p, mode)
|
||||
end
|
||||
|
||||
return disk
|
||||
end
|
||||
|
||||
internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, {
|
||||
setLabel = function(label)
|
||||
local h = fs.open("/.label", "w")
|
||||
h.write(label)
|
||||
h.close()
|
||||
end,
|
||||
getLabel = function()
|
||||
local h = fs.open("/.label", "r")
|
||||
if not h then return "$" end
|
||||
local label = h.readAll()
|
||||
h.close()
|
||||
return label
|
||||
end
|
||||
})
|
||||
|
||||
local function refresh()
|
||||
for id, _ in pairs(disks) do
|
||||
if not peripheral.getType(id) then disks[id] = nil end
|
||||
end
|
||||
|
||||
for _, name in ipairs(peripheral.getNames()) do
|
||||
if peripheral.getType(name) == "disk" then
|
||||
if not disks[name] then
|
||||
local mount = disk.getMountPath(name)
|
||||
if mount then
|
||||
disks[name] = createDisk(name, mount, false, disk)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function iter()
|
||||
refresh()
|
||||
local combined = {}
|
||||
|
||||
for id, obj in pairs(internal) do combined[id] = obj end
|
||||
for id, obj in pairs(disks) do combined[id] = obj end
|
||||
|
||||
return pairs(combined)
|
||||
end
|
||||
|
||||
return {refresh = refresh, list = iter}
|
||||
@@ -0,0 +1,3 @@
|
||||
U $;/
|
||||
U devfs0000;/dev/
|
||||
U tmpfs0000;/tmp/
|
||||
@@ -0,0 +1,84 @@
|
||||
-- :Minify:--
|
||||
local fs = {}
|
||||
local disks = {}
|
||||
local mounts = {}
|
||||
|
||||
local function resolve(path)
|
||||
local mountPoint = "/"
|
||||
for mount, disk in pairs(mounts) do
|
||||
if path:sub(1, #mount) == mount then
|
||||
if not mountPoint or #mount > #mountPoint then
|
||||
mountPoint = mount
|
||||
end
|
||||
end
|
||||
end
|
||||
local newPath = path:sub(#mountPoint + 1)
|
||||
return disks[mounts[mountPoint]], newPath
|
||||
end
|
||||
|
||||
function fs.update(initdisks)
|
||||
disks = {}
|
||||
for k, v in initdisks.list() do disks[k] = v end
|
||||
end
|
||||
|
||||
function fs.exists(path)
|
||||
local disk, newPath = resolve(path)
|
||||
return disk:directoryExists(newPath) or disk:fileExists(newPath)
|
||||
end
|
||||
|
||||
function fs.isFile(path)
|
||||
local disk, newPath = resolve(path)
|
||||
return disk:fileExists(newPath)
|
||||
end
|
||||
|
||||
function fs.isDir(path)
|
||||
local disk, newPath = resolve(path)
|
||||
return disk:directoryExists(newPath)
|
||||
end
|
||||
|
||||
function fs.list(path)
|
||||
local disk, newPath = resolve(path)
|
||||
return disk:list(newPath)
|
||||
end
|
||||
|
||||
function fs.makeDir(path)
|
||||
local disk, newPath = resolve(path)
|
||||
return disk:makeDirectory(newPath)
|
||||
end
|
||||
|
||||
function fs.remove(path)
|
||||
local disk, newPath = resolve(path)
|
||||
return disk:remove(newPath)
|
||||
end
|
||||
|
||||
function fs.readAllText(path)
|
||||
local disk, newPath = resolve(path)
|
||||
local handle = disk:open(newPath, "r")
|
||||
if not handle then return nil end
|
||||
local content = handle.readAll()
|
||||
handle.close()
|
||||
return content
|
||||
end
|
||||
|
||||
function fs.writeAllText(path, text)
|
||||
local disk, newPath = resolve(path)
|
||||
local handle = disk:open(newPath, "w")
|
||||
handle.write(text)
|
||||
handle.close()
|
||||
end
|
||||
|
||||
function fs.appendAllText(path, text)
|
||||
local disk, newPath = resolve(path)
|
||||
local handle = disk:open(newPath, "a")
|
||||
handle.write(text)
|
||||
handle.close()
|
||||
end
|
||||
|
||||
function fs.load(path) return load(fs.readAllText(path), path) end
|
||||
|
||||
function fs.mount(disk, mountPoint)
|
||||
if not disks[disk] then return end
|
||||
mounts[mountPoint] = disk
|
||||
end
|
||||
|
||||
return fs
|
||||
@@ -0,0 +1,274 @@
|
||||
--:Minify:--
|
||||
local args = {...}
|
||||
local apis = args[1]
|
||||
local disks = args[2]
|
||||
local arch = args[3]
|
||||
local screen = args[5]
|
||||
local computer = args[6]
|
||||
local ifs = args[7]
|
||||
local kernel = {}
|
||||
|
||||
kernel.LOG_Text=""
|
||||
kernel.version="HyperionOS V1.0.0"
|
||||
kernel.process = "Kernel"
|
||||
kernel.users={[0]="root",[1]="User"}
|
||||
kernel.hostname = "hyperion"
|
||||
kernel.groups = {}
|
||||
kernel.uid = 0
|
||||
kernel.gid = 0
|
||||
kernel.status = "start"
|
||||
kernel.key = {}
|
||||
kernel.cache = {}
|
||||
kernel.cache.preload = {}
|
||||
kernel._G=_G
|
||||
kernel.sleep=sleep
|
||||
|
||||
_G.sleep=nil
|
||||
local windowsExp = false
|
||||
|
||||
function kernel.log(msg, level, c)
|
||||
c=c or 12
|
||||
kernel.LOG_Text = kernel.LOG_Text..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n"
|
||||
if kernel.status == "start" then
|
||||
screen:setTextColor(c)
|
||||
screen:print(string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg)
|
||||
elseif kernel.status == "term" then
|
||||
kernel.standbyTask=kernel.currentTask
|
||||
kernel.currentTask=kernel.kernelTask
|
||||
kernel.vfs.devctl(1,"sfgc",c)
|
||||
kernel.vfs.write(1,string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n")
|
||||
kernel.currentTask=kernel.standbyTask
|
||||
end
|
||||
end
|
||||
|
||||
function kernel.PANIC(msg)
|
||||
if kernel.status~="Panic" then
|
||||
kernel.log("PANIC: "..msg, "PANIC")
|
||||
pcall(kernel["saveLog"])
|
||||
kernel.status="Panic"
|
||||
kernel.reason=msg
|
||||
screen:setTextColor(2)
|
||||
screen:setBackgroundColor(16)
|
||||
screen:clear()
|
||||
screen:setCursorPos(1,1)
|
||||
screen:print(kernel.LOG_Text)
|
||||
screen:print("KERNEL PANIC!\n"..msg.."\nSystem halted.")
|
||||
screen:print("Press any key to continue...")
|
||||
kernel.exitMain = true
|
||||
end
|
||||
while true do
|
||||
local event={computer:getMachineEvent()}
|
||||
if event[1]=="keyPressed" then
|
||||
break
|
||||
end
|
||||
end
|
||||
computer:reboot()
|
||||
end
|
||||
kernel.panic=kernel.PANIC
|
||||
|
||||
if windowsExp then
|
||||
screen:setTextColor(1)
|
||||
screen:setBackgroundColor(4)
|
||||
screen:clear()
|
||||
local w,h = screen:getSize()
|
||||
screen:setCursorPos(3,5)
|
||||
screen:print(":(")
|
||||
screen:setCursorPos(3,7)
|
||||
screen:print("Your PC ran into a problem and needs to restart. We're just collecting some error")
|
||||
screen:setCursorPos(3,8)
|
||||
screen:print("info, and then we'll restart for you.\n")
|
||||
screen:setCursorPos(3,h-5)
|
||||
screen:print("Stop code: average windows experience")
|
||||
screen:setCursorPos(1,h)
|
||||
screen:print("Press any key to continue... jk reboot it yourself lazy")
|
||||
while true do end
|
||||
end
|
||||
|
||||
kernel.log("Kernel loaded.")
|
||||
kernel.log("Mounting init disks...")
|
||||
disks.refresh()
|
||||
ifs.update(disks)
|
||||
kernel.disks={}
|
||||
for _,v in disks.list() do
|
||||
kernel.disks[v.address] = v
|
||||
end
|
||||
ifs.mount("$", "/")
|
||||
|
||||
local fstab=ifs.readAllText("/boot/fstab")
|
||||
local split = function(str, delim, maxResultCountOrNil)
|
||||
assert(#delim == 1, "only delim len 1 supported for now")
|
||||
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
|
||||
|
||||
if not ifs.isFile("/boot/boot.cfg") then
|
||||
kernel.log("boot.cfg missing or corrupted!, Attempting to write recovery boot.cfg", "ERROR", 2)
|
||||
ifs.writeAllText("/boot/boot.cfg",ifs.readAllText("/boot/safeboot.cfg"))
|
||||
end
|
||||
|
||||
local initCfgFunc, err = load(ifs.readAllText("/boot/boot.cfg"), "@boot.cfg")
|
||||
if not initCfgFunc then
|
||||
kernel.PANIC("Failed to load /boot/boot.cfg: "..tostring(err))
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
local initCfgStatus, config = pcall(initCfgFunc)
|
||||
if not initCfgStatus then
|
||||
kernel.PANIC("Error in /boot/boot.cfg: "..tostring(config))
|
||||
end
|
||||
kernel.config = config
|
||||
|
||||
for i,v in ipairs(split(fstab,"\n")) do
|
||||
if v:sub(1,1)=="U" then
|
||||
local id=""
|
||||
for i=3,#v do
|
||||
if v:sub(i,i)==";" then
|
||||
if i==3 then kernel.log("Invalid fstab line... Skipping.","WARN") goto endline end
|
||||
id=v:sub(3,i-1)
|
||||
end
|
||||
end
|
||||
local path=v:sub(#id+4)
|
||||
ifs.mount(id,path)
|
||||
::endline::
|
||||
end
|
||||
end
|
||||
kernel.log("Disks initialized")
|
||||
|
||||
function kernel.saveLog()
|
||||
ifs.writeAllText("/var/log/syslog.log", kernel.LOG_Text)
|
||||
end
|
||||
|
||||
function kernel.newFifo()
|
||||
local fifo = {}
|
||||
fifo.push=function(data)
|
||||
table.insert(fifo, data)
|
||||
end
|
||||
fifo.pop=function()
|
||||
return table.remove(fifo,1)
|
||||
end
|
||||
return fifo
|
||||
end
|
||||
|
||||
function kernel.newUUID()
|
||||
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
||||
local uuid = ""
|
||||
for i = 1, #template do
|
||||
local c = template:sub(i,i)
|
||||
if c == "x" then
|
||||
uuid = uuid .. string.format("%x", math.random(0, 15))
|
||||
elseif c == "y" then
|
||||
uuid = uuid .. string.format("%x", math.random(8, 11))
|
||||
else
|
||||
uuid = uuid .. c
|
||||
end
|
||||
end
|
||||
return uuid
|
||||
end
|
||||
|
||||
kernel.syscalls={}
|
||||
local modules={[0]={}}
|
||||
for i=0, 100 do
|
||||
modules[i]={}
|
||||
end
|
||||
|
||||
kernel.log("Gathering modules")
|
||||
for _, i in ipairs(ifs.list("/lib/modules")) do
|
||||
for _,v in ipairs(ifs.list("/lib/modules/"..i)) do
|
||||
local prior=tonumber(v:sub(1,2))
|
||||
modules[prior+1][#modules[prior+1]+1]="/lib/modules/"..i.."/"..v
|
||||
end
|
||||
end
|
||||
|
||||
kernel.ifs=ifs
|
||||
kernel.apis=apis
|
||||
kernel.computer=computer
|
||||
kernel.arch=arch
|
||||
kernel.initdisks=disks
|
||||
kernel.screen=screen
|
||||
kernel.processes={}
|
||||
kernel.fstab=fstab
|
||||
|
||||
kernel.kernelTask = {
|
||||
name="kernel",
|
||||
status="R",
|
||||
pid=0,
|
||||
tgid=0,
|
||||
uid=0,
|
||||
fd={},
|
||||
exit="",
|
||||
sleep=0,
|
||||
ivs=0,
|
||||
vs=0,
|
||||
children={},
|
||||
syscallReturn={},
|
||||
cwd="/",
|
||||
timeSlice=0,
|
||||
lastTime=0,
|
||||
totalTime=0,
|
||||
numRuns=0
|
||||
}
|
||||
kernel.currentTask = kernel.kernelTask
|
||||
|
||||
function kernel.shutdown()
|
||||
kernel.computer:shutdown()
|
||||
end
|
||||
|
||||
function kernel.reboot()
|
||||
kernel.computer:reboot()
|
||||
end
|
||||
|
||||
kernel.syscalls["time"]=function() return kernel.computer:time() end
|
||||
kernel.syscalls["log"]=kernel.log
|
||||
kernel.syscalls["getUptime"]=function() return kernel.computer:clock() end
|
||||
kernel.syscalls["getUsername"]=function(uid) return kernel.users[uid or kernel.uid] end
|
||||
kernel.syscalls["getHostname"]=function() return kernel.hostname end
|
||||
kernel.syscalls["getHost"]=function() return kernel.apis._HOST end
|
||||
kernel.syscalls["version"]=function() return kernel.version end
|
||||
kernel.syscalls["setHostname"]=function(name) if kernel.uid~=0 then error("Permission denied") end kernel.hostname=name end
|
||||
kernel.syscalls["arch"]=function() return arch end
|
||||
kernel.syscalls["sysdump"]=function()
|
||||
local rv={}
|
||||
for i,v in pairs(kernel.syscalls) do
|
||||
rv[#rv+1] = i
|
||||
end
|
||||
return rv
|
||||
end
|
||||
kernel.syscalls["test"]=function() return true end
|
||||
|
||||
kernel.log("Running modules")
|
||||
for _,p in ipairs(modules) do
|
||||
for _,v in ipairs(p) do
|
||||
if kernel.config.showModLoad then kernel.log("Loading module "..v, "DBUG", 5) end
|
||||
local code=ifs.readAllText(v)
|
||||
if not code then
|
||||
kernel.log("ModuReadErr: "..v, "WARN", 8)
|
||||
goto skip
|
||||
end
|
||||
local func,err=load(code,"@"..v)
|
||||
if not func then kernel.panic("ModuLoadErr: "..tostring(err)) goto skip end
|
||||
local status, err = xpcall(func,debug.traceback, kernel)
|
||||
if not status then kernel.panic("ModuRunErr: "..tostring(err)) end
|
||||
if kernel.config.showModLoad then kernel.log("Loaded module "..v, "DBUG", 5) end
|
||||
::skip::
|
||||
end
|
||||
end
|
||||
|
||||
kernel.log("Kernel initialized successfully.")
|
||||
kernel.status="running"
|
||||
kernel.main()
|
||||
if kernel.status=="panic" then
|
||||
kernel.panic()
|
||||
end
|
||||
kernel.PANIC("Execution complete")
|
||||
@@ -0,0 +1,41 @@
|
||||
local lua = {
|
||||
coroutine = true,
|
||||
debug = true,
|
||||
_HOST = true,
|
||||
_VERSION = true,
|
||||
assert = true,
|
||||
collectgarbage = true,
|
||||
error = true,
|
||||
gcinfo = true,
|
||||
getfenv = true,
|
||||
getmetatable = true,
|
||||
ipairs = true,
|
||||
__inext = true,
|
||||
load = true,
|
||||
math = true,
|
||||
next = true,
|
||||
pairs = true,
|
||||
pcall = true,
|
||||
rawequal = true,
|
||||
rawget = true,
|
||||
rawlen = true,
|
||||
rawset = true,
|
||||
select = true,
|
||||
setfenv = true,
|
||||
setmetatable = true,
|
||||
string = true,
|
||||
table = true,
|
||||
tonumber = true,
|
||||
tostring = true,
|
||||
type = true,
|
||||
xpcall = true,
|
||||
_G=true
|
||||
}
|
||||
|
||||
local apis={}
|
||||
for i,v in pairs(_G) do
|
||||
if not lua[i] or lua[i]==nil then
|
||||
apis[i]=v
|
||||
_G[i]=nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
checkArg=nil
|
||||
local oldcomputer=computer
|
||||
_G.computer=nil
|
||||
local os=os
|
||||
_G.os=nil
|
||||
|
||||
function component.wrap(address)
|
||||
local methods=oldcomponent.methods(address)
|
||||
local object={}
|
||||
for _,method in ipairs(methods) do
|
||||
object[method]=function(_,...)
|
||||
return oldcomponent.invoke(address,method,...)
|
||||
end
|
||||
end
|
||||
return object
|
||||
end
|
||||
|
||||
local
|
||||
@@ -0,0 +1 @@
|
||||
local fs={}
|
||||
@@ -0,0 +1,11 @@
|
||||
-- DO NOT EDIT THIS FILE IF YOU DO NOT KNOW WHAT YOU ARE DOING!
|
||||
-- DOING SO MAY RENDER YOUR SYSTEM UNBOOTABLE!
|
||||
|
||||
-- This file is auto-generated during the build process.
|
||||
-- DEFAULT BOOT CONFIGURATION FILE
|
||||
return {
|
||||
initPath = "/sbin/init.lua",
|
||||
maxOpenFiles = 128,
|
||||
maxFilesPerTask = 16,
|
||||
preempt=true
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user