diff --git a/.vscode/settings.json b/.vscode/settings.json index 81d26b7..138447a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "syscall", "printf", "printInline", - "toHex" + "toHex", + "loadcstr" ] } \ No newline at end of file diff --git a/README.md b/README.md index c96f442..f4f2e14 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Download on PineStore](https://raster.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpinestore.cc%2Fapi%2Fproject%2F225&query=%24.project.downloads&suffix=%20downloads&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNzYuOTA0IiBoZWlnaHQ9Ijg5LjI5NSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDc2OS4wNCA4OTIuOTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BCiA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTQuNzQgLTQuNjgyNikiIGZpbGw9IiM5YWIyZjIiPgogIDxwYXRoIGQ9Im00MTAgODUxYzAtMTIgMjYtMjEgNTgtMjEgMTUgMCAyMiA0IDE3IDktMTQgMTItNzUgMjItNzUgMTJ6Ii8%2BCiAgPHBhdGggZD0ibTU4NSA3NDJjLTEtNDkgNC03MiAxNi04NSAyMi0yNCAzMC02OCAxNi04Ni0xMi0xNC0yNy0zOS00OC03OC0xMC0xOS05LTI2IDQtNDEgMjItMjQgMjEtNjctMi0xNDQtMjEtNjktMzktMTQ0LTQ4LTE5NS00LTI2LTItMzMgMTEtMzMgMzEgMCAxMTIgMzMgMTQxIDU4IDI4IDIzIDgxIDkyIDcxIDkyLTIgMCA1IDI2IDE2IDU3IDI4IDc5IDI5IDIyNCAzIDMwOC0xMCAzMy0xOSA2Mi0xOSA2NS00IDI2LTEzMiAxNTAtMTU1IDE1MC0zIDAtNi0zMC02LTY4eiIvPgogIDxwYXRoIGQ9Im02OCA2NzNjLTcyLTEwOS03MS0yNzggMy00MjMgMzYtNzEgNjItMTAwIDEyOC0xNDAgNDMtMjcgNjUtMzQgMTE4LTM2IDEwMC00IDk4IDExLTE5IDEzNi0zNCAzNy03OCA4OC05NiAxMTMtMjggMzktMzEgNDgtMjEgNjUgMTEgMTcgNiAyNy0zMyA3OS00MCA1My00NCA2Mi0zMiA3OCAxNyAyMyAxOCA1NyAyIDczLTYgNi0xNCAzMS0xNyA1NC02IDQyLTYgNDItMzMgMXoiLz4KIDwvZz4KIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNC43NCAtNC42ODI2KSIgZmlsbD0iIzU5YTY0ZiI%2BCiAgPHBhdGggZD0ibTM2NSA4MTNjLTUzLTYtMTM5LTMzLTE5Mi02MS02OC0zNS04My02Ny01OC0xMjIgMjYtNTkgNDAtNjcgNzgtNDkgNjggMzMgMTY3IDU4IDI2NiA2OSA1OCA1IDEwNiAxMiAxMDkgMTQgMiAzIDYgMzIgOSA2NSA4IDg1IDAgOTEtMTAxIDkwLTQ0LTEtOTQtNC0xMTEtNnoiLz4KICA8cGF0aCBkPSJtNDEwIDQ1OWMtNjctNy0xNjAtMjktMTk5LTQ4LTI3LTE0LTM0LTM2LTIwLTYzIDIxLTM4IDk3LTEzNiAxNTAtMTkzIDI1LTI3IDU4LTcxIDczLTk3IDI1LTQzIDMxLTQ3IDU0LTQyIDQwIDEwIDQyIDEyIDQyIDUyIDAgMjAgNiA1NyAxNCA4MiAyNCA3MyA1NCAxOTIgNjIgMjM2IDUgMzUgMyA0NS0xNSA2My0yMyAyMy0zNiAyNC0xNjEgMTB6Ii8%2BCiA8L2c%2BCiA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTQuNzQgLTQuNjgyNikiIGZpbGw9IiM3ZWNiMjUiPgogIDxwYXRoIGQ9Im01NTggNjc0Yy0yLTItNTEtOS0xMDktMTQtMTAyLTExLTIwNC0zNy0yNjQtNjktMTYtOC0zMi0xNC0zNC0xMi00IDMtMzEtNDgtMzEtNjEgMC01IDIxLTMxIDQ2LTU4IDUxLTU0IDcxLTYwIDEzMC0zNSAxOSA4IDgzIDE5IDE0MiAyNSA1OCA2IDEwNyAxMiAxMDcgMTNzMTUgMjYgMzMgNTZjMjcgNDMgMzIgNjMgMzAgOTktMiAzNS04IDQ3LTI1IDUzLTExIDQtMjMgNi0yNSAzeiIvPgogPC9nPgogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE0Ljc0IC00LjY4MjYpIiBmaWxsPSIjZWNlZGVmIj4KICA8cGF0aCBkPSJtMjYwIDg5MGMtMzQtOC03MC00MS03MC02NSAwLTYtOS0yMC0yMC0zMHMtMjAtMjItMjAtMjctMTMtMjEtMzAtMzVjLTM1LTI5LTQxLTgzLTEzLTEyMiAxNS0yMiAxNS0yNi0xLTU2LTE4LTMzLTE4LTMzIDI3LTkxIDI4LTM2IDQyLTYzIDM2LTY4LTIzLTI1IDktNzggMTIwLTE5NyAzNi0zOCA3Mi04MSA4Mi05NiAxMC0xNCAyNS0zMCAzMy0zNSAzNi0yMCA3IDMyLTUzIDk3LTQ4IDUxLTEyNiAxNTAtMTQ5IDE4OS0xMCAxOC05IDI0IDEwIDQwIDIzIDE5IDIzIDE5LTI5IDcxLTUzIDUyLTUzIDUyLTM4IDgyIDE0IDI4IDE0IDMzLTEwIDc2LTMyIDU3LTIzIDgxIDQ2IDEyMCAzNCAxOSA0OSAzMyA0NSA0Mi0xNCAzNyAzNiA3NSA5OCA3NSAyNSAwIDQwLTcgNTQtMjUgMTgtMjMgMjctMjUgOTUtMjUgOTQgMCAxMDItOCA5My04OS02LTUzLTUtNTkgMTQtNjQgMzItOCAyNi02NC0xNS0xMzItMzUtNTgtMzUtNTgtOS04MiAyMS0xOSAyNC0yOSAxOS01Ni0xMC00Ny00NC0xNzUtNjEtMjI3LTgtMjUtMTQtNjItMTQtODMgMC0yNy01LTM5LTE3LTQzLTEwLTMtMjUtOC0zMy0xMC0xMi00LTEyLTYtMS0xNCAyNy0xNiA1NiA1IDY5IDUxIDM1IDExNyA0MyAxNDggNDYgMTcwIDIgMTMgMTEgNTEgMjEgODQgMjEgNzEgMjEgMTIxIDAgMTQ1LTE0IDE1LTEzIDE5IDUgNDMgMTEgMTQgMjAgMzAgMjAgMzVzNyAxNSAxNSAyMmMyMSAxNyAxNiA3NS0xMCAxMDItMTggMTktMjAgMzItMTcgNzkgNCA1MCAyIDU4LTE5IDcyLTEyIDktNTAgMTktODMgMjMtNDUgNS02NSAxMy04MyAzMi0yNiAyOC05MiAzOC0xNTMgMjJ6Ii8%2BCiA8L2c%2BCiA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTQuNzQgLTQuNjgyNikiIGZpbGw9IiM3ZTY3NGQiPgogIDxwYXRoIGQ9Im0yNDggODU0Yy0zMC0xNi00Ny01OS0zMC03NiA4LTggMjMtNyA1NCAyIDI0IDcgNjEgMTQgODMgMTcgNTQgNyA1OSAxNSAzNSA0Ni0xOCAyMy0yOSAyNy02OCAyNy0yNi0xLTU5LTctNzQtMTZ6Ii8%2BCiA8L2c%2BCjwvc3ZnPgo%3D&label=PineStore)](https://pinestore.cc/projects/225/hyperionos) # HyperionOS HyperionOS is a modular, hybrid kernel operating system written entirely in Lua. It features a custom task scheduler, virtual filesystem, syscall interface, and separates core functionality from user-space services. diff --git a/Src/Hyperion-bash/bin/cat b/Src/Hyperion-bash/bin/cat deleted file mode 100644 index 1156880..0000000 --- a/Src/Hyperion-bash/bin/cat +++ /dev/null @@ -1,33 +0,0 @@ -local args = {...} -local name = syscall.getTask(syscall.getpid()).name -local fs = require("sys.fs") - -if not args[1] then - while true do - local content = syscall.read(0, 1024) - if not content or content == "" then break end - printInline(content) - end - print("") - return -end - -for _, arg in ipairs(args) do - local filePath = arg - if filePath:sub(1,1) ~= "/" then - filePath = syscall.getcwd().."/"..filePath - end - - if not fs.exists(filePath) then - print(name..": Cannot access '"..arg.."': No such file.") - else - local fd = syscall.open(filePath, "r") - while true do - local content = syscall.read(fd, 1024) - if not content or content == "" then break end - printInline(content) - end - syscall.close(fd) - end -end -print("") diff --git a/Src/Hyperion-bash/bin/clear b/Src/Hyperion-bash/bin/clear deleted file mode 100644 index 8519315..0000000 --- a/Src/Hyperion-bash/bin/clear +++ /dev/null @@ -1 +0,0 @@ -syscall.devctl(1,"clear") \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/echo b/Src/Hyperion-bash/bin/echo deleted file mode 100644 index 7b5a803..0000000 --- a/Src/Hyperion-bash/bin/echo +++ /dev/null @@ -1,2 +0,0 @@ -local args = {...} -print(table.concat(args, " ")) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/hyshex b/Src/Hyperion-bash/bin/hyshex deleted file mode 100644 index b69309a..0000000 --- a/Src/Hyperion-bash/bin/hyshex +++ /dev/null @@ -1,94 +0,0 @@ ---: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 diff --git a/Src/Hyperion-bash/bin/luaold b/Src/Hyperion-bash/bin/luaold deleted file mode 100644 index 6226c17..0000000 --- a/Src/Hyperion-bash/bin/luaold +++ /dev/null @@ -1,50 +0,0 @@ ---: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 \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/mkdir b/Src/Hyperion-bash/bin/mkdir deleted file mode 100644 index 025a830..0000000 --- a/Src/Hyperion-bash/bin/mkdir +++ /dev/null @@ -1,23 +0,0 @@ -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) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/pwd b/Src/Hyperion-bash/bin/pwd deleted file mode 100644 index f583060..0000000 --- a/Src/Hyperion-bash/bin/pwd +++ /dev/null @@ -1 +0,0 @@ -print(syscall.getcwd()) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/sysdump b/Src/Hyperion-bash/bin/sysdump deleted file mode 100644 index e532fbc..0000000 --- a/Src/Hyperion-bash/bin/sysdump +++ /dev/null @@ -1,5 +0,0 @@ -local syscalls=syscall.sysdump() -for i=1, #syscalls do - print(syscalls[i]) -end -print("Total # of syscalls: "..tostring(#syscalls)) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/whoami b/Src/Hyperion-bash/bin/whoami deleted file mode 100644 index 3d832ca..0000000 --- a/Src/Hyperion-bash/bin/whoami +++ /dev/null @@ -1 +0,0 @@ -print((syscall.getUsername() or "Unknown")) \ No newline at end of file diff --git a/Src/Hyperion-core/lib/sys/fs b/Src/Hyperion-core/lib/fs similarity index 100% rename from Src/Hyperion-core/lib/sys/fs rename to Src/Hyperion-core/lib/fs diff --git a/Src/Hyperion-core/lib/snip b/Src/Hyperion-core/lib/http similarity index 100% rename from Src/Hyperion-core/lib/snip rename to Src/Hyperion-core/lib/http diff --git a/Src/Hyperion-core/lib/io b/Src/Hyperion-core/lib/io index 9a94a48..e0eb470 100644 --- a/Src/Hyperion-core/lib/io +++ b/Src/Hyperion-core/lib/io @@ -1,5 +1,5 @@ local io = {} -local fs = require("sys.fs") +local fs = require("fs") function io.open(path, mode) return fs.open(path, mode) diff --git a/Src/Hyperion-core/lib/sys/hpv b/Src/Hyperion-core/lib/sys/hpv deleted file mode 100644 index b6ad280..0000000 --- a/Src/Hyperion-core/lib/sys/hpv +++ /dev/null @@ -1,6 +0,0 @@ -local sys = {} -local fs = require("sys.fs") - - - -return sys \ No newline at end of file diff --git a/Src/Hyperion-core/lib/sys/init b/Src/Hyperion-core/lib/sys/init deleted file mode 100644 index 3ce5639..0000000 --- a/Src/Hyperion-core/lib/sys/init +++ /dev/null @@ -1,5 +0,0 @@ -local sys = {} -sys.fs = require("sys.fs") -sys.hpv = require("sys.hpv") -sys.ipc = require("sys.ipc") -return sys \ No newline at end of file diff --git a/Src/Hyperion-core/lib/sys/ipc b/Src/Hyperion-core/lib/sys/ipc deleted file mode 100644 index bcc01b9..0000000 --- a/Src/Hyperion-core/lib/sys/ipc +++ /dev/null @@ -1,3 +0,0 @@ -local ipc = {} - -return ipc \ No newline at end of file diff --git a/Src/Hyperion-core/lib/sys/term b/Src/Hyperion-core/lib/sys/term deleted file mode 100644 index 88db99e..0000000 --- a/Src/Hyperion-core/lib/sys/term +++ /dev/null @@ -1,71 +0,0 @@ -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 \ No newline at end of file diff --git a/Src/Hyperion-firmware-ccpc/boot/cct/boot.lua b/Src/Hyperion-firmware-ccpc/boot/cct/boot.lua new file mode 100644 index 0000000..7bdae02 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/boot/cct/boot.lua @@ -0,0 +1,316 @@ +-- :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) + + 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] == "modem_message" then + queueEvent("modem_message", table.unpack(event, 2)) + elseif event[1] == "rednet_message" then + queueEvent("rednet_message", table.unpack(event, 2)) + elseif event[1] == "http_success" then + queueEvent("http_success", table.unpack(event, 2)) + elseif event[1] == "http_failure" then + queueEvent("http_failure", table.unpack(event, 2)) + 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 diff --git a/Src/Hyperion-firmware-ccpc/boot/cct/eeprom b/Src/Hyperion-firmware-ccpc/boot/cct/eeprom new file mode 100644 index 0000000..e3bb2a4 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/boot/cct/eeprom @@ -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 \ No newline at end of file diff --git a/Src/Hyperion-firmware-ccpc/boot/cct/initdisks b/Src/Hyperion-firmware-ccpc/boot/cct/initdisks new file mode 100644 index 0000000..3669757 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/boot/cct/initdisks @@ -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} diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/25_tty.kmod similarity index 84% rename from Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod rename to Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/25_tty.kmod index 0edd9ec..8b586e1 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod +++ b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/25_tty.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local apis = kernel.apis local native = apis.peripheral @@ -308,31 +308,6 @@ end local fifo = kernel.newFifo() -local ctrlLetterKeys = nil -local specialKeys = nil - -local function buildKeyMaps() - if ctrlLetterKeys then return end - local k = apis.keys - ctrlLetterKeys = {} - local letters = { - {k.a,1},{k.b,2},{k.c,3},{k.d,4},{k.e,5},{k.f,6},{k.g,7}, - {k.h,8}, {k.j,10},{k.k,11},{k.l,12},{k.m,13}, - {k.n,14},{k.o,15},{k.p,16}, - {k.u,21},{k.v,22},{k.w,23},{k.x,24},{k.y,25},{k.z,26}, - } - for _, pair in ipairs(letters) do - ctrlLetterKeys[pair[1]] = string.char(pair[2]) - end - specialKeys = { - [k.home] = "\1", - [k.delete] = "\4", - [k["end"]] = "\5", - [k.pageUp] = "\2", - [k.pageDown]= "\12", - } -end - kernel.processes.cctmond = function() local timeout = false while true do @@ -342,7 +317,17 @@ kernel.processes.cctmond = function() local eventType = event[1] local charOrKey = event[3] - buildKeyMaps() + local ctrlKeyMap = { + [apis.keys.a]=1, [apis.keys.b]=2, [apis.keys.c]=3, + [apis.keys.d]=4, [apis.keys.e]=5, [apis.keys.f]=6, + [apis.keys.g]=7, [apis.keys.h]=8, [apis.keys.i]=9, + [apis.keys.j]=10, [apis.keys.k]=11, [apis.keys.l]=12, + [apis.keys.m]=13, [apis.keys.n]=14, [apis.keys.o]=15, + [apis.keys.p]=16, [apis.keys.q]=17, [apis.keys.r]=18, + [apis.keys.s]=19, [apis.keys.t]=20, [apis.keys.u]=21, + [apis.keys.v]=22, [apis.keys.w]=23, [apis.keys.x]=24, + [apis.keys.y]=25, [apis.keys.z]=26, + } if eventType == "keyPressed" then if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then @@ -351,25 +336,40 @@ kernel.processes.cctmond = function() alt = true end - if ctrl and charOrKey == apis.keys.c then - for _, task in ipairs(syscall.getTasks()) do - syscall.sigsend(task, 1) + if ctrl then + local ctrlByte = ctrlKeyMap[charOrKey] + if ctrlByte then + if ctrlByte == 3 then + for _, task in ipairs(syscall.getTasks()) do + syscall.sigsend(task, 1) + end + else + fifo.push(string.char(ctrlByte)) + end end + else + local specialKeyMap = { + [apis.keys.up] = "", + [apis.keys.down] = "", + [apis.keys.right] = "", + [apis.keys.left] = "", + [apis.keys.home] = "", + [apis.keys["end"]] = "", + [apis.keys.pageUp] = "[5~", + [apis.keys.pageDown] = "[6~", + [apis.keys.delete] = "[3~", + } + local special = specialKeyMap[charOrKey] + if special then fifo.push(special) end end - if ctrl and ctrlLetterKeys[charOrKey] then - fifo.push(ctrlLetterKeys[charOrKey]) - end - - if specialKeys[charOrKey] then - fifo.push(specialKeys[charOrKey]) - end elseif eventType == "keyReleased" then if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then ctrl = false elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then alt = false end + elseif eventType == "keyTyped" then if charOrKey then fifo.push(charOrKey) end end @@ -385,11 +385,10 @@ kernel.processes.cctmond = function() end end -newtty(apis.term, "tty1", fifo.pop) - +newtty(apis.term, "1", 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) + newtty(v,tostring(i+1),function () end) end \ No newline at end of file diff --git a/Src/Hyperion-installer/bin/install b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_http.kmod similarity index 100% rename from Src/Hyperion-installer/bin/install rename to Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_http.kmod diff --git a/Src/Hyperion-spm/bin/spm b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_modem.kmod similarity index 100% rename from Src/Hyperion-spm/bin/spm rename to Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_modem.kmod diff --git a/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_redstone.kmod b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_redstone.kmod new file mode 100644 index 0000000..8e06ca6 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_redstone.kmod @@ -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) \ No newline at end of file diff --git a/Src/Hyperion-firmware-cct/boot/cct/boot.lua b/Src/Hyperion-firmware-cct/boot/cct/boot.lua index 7bdae02..b902780 100644 --- a/Src/Hyperion-firmware-cct/boot/cct/boot.lua +++ b/Src/Hyperion-firmware-cct/boot/cct/boot.lua @@ -71,7 +71,6 @@ local ok, err = xpcall(function() collectgarbage = true, error = true, gcinfo = true, - getfenv = true, getmetatable = true, ipairs = true, __inext = true, @@ -85,7 +84,6 @@ local ok, err = xpcall(function() rawlen = true, rawset = true, select = true, - setfenv = true, setmetatable = true, string = true, table = true, diff --git a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod index 9c5e168..8163ad2 100644 --- a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod +++ b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local apis = kernel.apis local native = apis.peripheral @@ -243,6 +243,22 @@ local function serializeBool(bool) end local function newtty(obj, id, ev) + obj.setPaletteColor(0x1, 0xFFFFFF) -- #000000 + obj.setPaletteColor(0x2, 0xFF0000) -- #FFFFFF + obj.setPaletteColor(0x4, 0x00FF00) -- #FF0000 + obj.setPaletteColor(0x8, 0x0000FF) -- #00FF00 + obj.setPaletteColor(0x10, 0x00FFFF) -- #0000FF + obj.setPaletteColor(0x20, 0xFF00FF) -- #00FFFF + obj.setPaletteColor(0x40, 0xFFFF00) -- #FF00FF + obj.setPaletteColor(0x80, 0xFF6D00) -- #FFFF00 + obj.setPaletteColor(0x100, 0x6DFF55) -- #FF6D00 + obj.setPaletteColor(0x200, 0x24FFFF) -- #6DFF55 + obj.setPaletteColor(0x400, 0x924900) -- #24FFFF + obj.setPaletteColor(0x800, 0x6D6D55) -- #924900 + obj.setPaletteColor(0x1000, 0xDBDBAA) -- #6D6D55 + obj.setPaletteColor(0x2000, 0x6D00FF) -- #DBDBAA + obj.setPaletteColor(0x4000, 0xB6FF00) -- #6D00FF + obj.setPaletteColor(0x8000, 0x000000) -- #B6FF00 kernel.devfs.data["tty"][id] = function(op, mode) if op=="type" then return "character device" @@ -317,7 +333,18 @@ kernel.processes.cctmond = function() local eventType = event[1] local charOrKey = event[3] - -- Update modifier keys + local ctrlKeyMap = { + [apis.keys.a]=1, [apis.keys.b]=2, [apis.keys.c]=3, + [apis.keys.d]=4, [apis.keys.e]=5, [apis.keys.f]=6, + [apis.keys.g]=7, [apis.keys.h]=8, [apis.keys.i]=9, + [apis.keys.j]=10, [apis.keys.k]=11, [apis.keys.l]=12, + [apis.keys.m]=13, [apis.keys.n]=14, [apis.keys.o]=15, + [apis.keys.p]=16, [apis.keys.q]=17, [apis.keys.r]=18, + [apis.keys.s]=19, [apis.keys.t]=20, [apis.keys.u]=21, + [apis.keys.v]=22, [apis.keys.w]=23, [apis.keys.x]=24, + [apis.keys.y]=25, [apis.keys.z]=26, + } + if eventType == "keyPressed" then if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then ctrl = true @@ -325,11 +352,31 @@ kernel.processes.cctmond = function() 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 + if ctrl then + local ctrlByte = ctrlKeyMap[charOrKey] + if ctrlByte then + if ctrlByte == 3 then + for _, task in ipairs(syscall.getTasks()) do + syscall.sigsend(task, 1) + end + else + fifo.push(string.char(ctrlByte)) + end end + else + local specialKeyMap = { + [apis.keys.up] = "", + [apis.keys.down] = "", + [apis.keys.right] = "", + [apis.keys.left] = "", + [apis.keys.home] = "", + [apis.keys["end"]] = "", + [apis.keys.pageUp] = "[5~", + [apis.keys.pageDown] = "[6~", + [apis.keys.delete] = "[3~", + } + local special = specialKeyMap[charOrKey] + if special then fifo.push(special) end end elseif eventType == "keyReleased" then @@ -354,10 +401,10 @@ kernel.processes.cctmond = function() end end -newtty(apis.term, "TTY1", fifo.pop) +newtty(apis.term, "1", 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) + newtty(v,tostring(i+1),function () end) end \ No newline at end of file diff --git a/Src/Hyperion-kernel/boot/fstab b/Src/Hyperion-kernel/boot/fstab index ba670ab..0f7573d 100644 --- a/Src/Hyperion-kernel/boot/fstab +++ b/Src/Hyperion-kernel/boot/fstab @@ -1,3 +1,4 @@ U $;/ U devfs0000;/dev/ -U tmpfs0000;/tmp/ \ No newline at end of file +U tmpfs0000;/tmp/ +U procfs0000;/proc/ \ No newline at end of file diff --git a/Src/Hyperion-kernel/boot/kernel.lua b/Src/Hyperion-kernel/boot/kernel.lua index 326b527..518b2e4 100644 --- a/Src/Hyperion-kernel/boot/kernel.lua +++ b/Src/Hyperion-kernel/boot/kernel.lua @@ -8,7 +8,7 @@ local computer = args[6] local ifs = args[7] local kernel = {} kernel.LOG_Text="" -kernel.version="HyperionOS V1.2.0" +kernel.version="HyperionOS V1.2.3" kernel.process = "Kernel" kernel.users={[0]="root",[1]="User"} kernel.hostname = "hyperion" @@ -27,15 +27,15 @@ 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" + kernel.LOG_Text = kernel.LOG_Text..string.format("%X",c-1).." "..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) + screen:print(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.vfs.write(1,tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n") kernel.currentTask=kernel.standbyTask end end @@ -113,8 +113,9 @@ local split = function(str, delim, maxResultCountOrNil) end if not ifs.isFile("/boot/boot.cfg") then - kernel.log("boot.cfg missing or corrupted!, Attempting to write recovery boot.cfg", "ERROR", 2) + kernel.log("First boot detected writing boot.cfg", "INFO", 3) ifs.writeAllText("/boot/boot.cfg",ifs.readAllText("/boot/safeboot.cfg")) + kernel.firstBoot=true end local initCfgFunc, err = load(ifs.readAllText("/boot/boot.cfg"), "@boot.cfg") @@ -251,7 +252,12 @@ kernel.syscalls["sysdump"]=function() end return rv end -kernel.syscalls["test"]=function() return true end +kernel.syscalls["reboot"]=function() + kernel.computer:reboot() +end +kernel.syscalls["shutdown"]=function() + kernel.computer:reboot() +end kernel.log("Running modules") for _,p in ipairs(modules) do @@ -272,6 +278,7 @@ for _,p in ipairs(modules) do end kernel.log("Kernel initialized successfully.") +kernel.saveLog() kernel.status="running" kernel.main() if kernel.status=="panic" then diff --git a/Src/Hyperion-kernel/boot/safeboot.cfg b/Src/Hyperion-kernel/boot/safeboot.cfg index 78bea9d..8005543 100644 --- a/Src/Hyperion-kernel/boot/safeboot.cfg +++ b/Src/Hyperion-kernel/boot/safeboot.cfg @@ -4,7 +4,7 @@ -- This file is auto-generated during the build process. -- DEFAULT BOOT CONFIGURATION FILE return { - initPath = "/sbin/init.lua", + initPath = "/sbin/init", maxOpenFiles = 128, maxFilesPerTask = 16, preempt=true diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod index 5fa01c0..0c0420a 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod @@ -1,4 +1,5 @@ --- :Minify:-- +--:Minify:-- +--- @diagnostic disable: duplicate-set-field local kernel = ... kernel.allowGlobalOverwrites = true @@ -172,6 +173,27 @@ function table.indexOf(t, value) return -1 end +function table.merge(...) + local args={...} + local out = {} + local outi = {} + + for _,t in ipairs(args) do + for i,v in pairs(t) do + out[i]=v + end + for i,v in ipairs(t) do + outi[#outi+1]=v + end + end + + for i,v in ipairs(outi) do + out[i]=v + end + + return out +end + function string.replace(s, target, repl) local result = {} local i = 1 @@ -207,17 +229,30 @@ 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) +local function makeSyscallProxy() + local backing = {} + return setmetatable(backing, { + __index = function(self, name) + local raw = rawget(self, name) + if raw ~= nil then return raw end + 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 - end -}) + end, + __newindex = function(self, k, v) + rawset(self, k, v) + end, + __metatable=false + }) +end + +syscall = makeSyscallProxy() + +_makeSyscallProxy = makeSyscallProxy table.serialize = serialize diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod index 51f3301..156cc71 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local vfs = {} kernel.vfs = vfs @@ -113,57 +113,23 @@ end local SAFE_COMPONENT_PATTERN = "^[A-Za-z0-9_.+%-%@%(%)%[%]]+$" -local function normalizePath(path) - local task = kernel.currentTask - local cwd = task.cwd or "/" - - if path:sub(1, 1) ~= "/" then - path = cwd .. "/" .. path +local function tokenizePath(path) + local isAbsolute = (path:sub(1,1) == "/") + local tokens = {} + for comp in (path .. "/"):gmatch("([^/]*)/") do + table.insert(tokens, comp) end + return isAbsolute, tokens +end - local stack = {} - local i = 1 - local len = #path - while i <= len do - local j = path:find("/", i, true) - local comp - if j then - comp = path:sub(i, j - 1) - i = j + 1 - else - comp = path:sub(i) - i = len + 1 - end - - comp = comp:match("^%s*(.-)%s*$") - - if comp == "" or comp == "." then - elseif comp == ".." then - if #stack > 0 then - table.remove(stack) - end - else - comp = comp:lower() - if not comp:match(SAFE_COMPONENT_PATTERN) then - error("EINVAL: illegal characters in path component: " .. comp, 2) - end - if comp == ".meta" then - error("EINVAL: reserved path component: " .. comp, 2) - end - table.insert(stack, comp) - end +local function validateComponent(comp) + local lower = comp:lower() + if not lower:match(SAFE_COMPONENT_PATTERN) then + error("EINVAL: illegal characters in path component: " .. comp, 3) end - - local result = "/" .. table.concat(stack, "/") - - local root = task and task.root - if root and root ~= "/" then - if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then - result = root - end + if lower == ".meta" then + error("EINVAL: reserved path component: .meta", 3) end - - return result end function vfs.splitPath(path) @@ -222,47 +188,205 @@ local function readMetaEntry(disk, parentDiskPath, filename) end local MAX_SYMLINK = 16 -local function resolveSymlinks(path, noFollow, _depth) - _depth = _depth or 0 - if _depth > MAX_SYMLINK then error("ELOOP") end - path = normalizePath(path) - local parts = {} - for p in path:gmatch("[^/]+") do table.insert(parts, p) end +local function namei(path, noFollow, symDepth) + symDepth = symDepth or 0 + if symDepth > MAX_SYMLINK then error("ELOOP") end - local resolved = "" + local task = kernel.currentTask + local euid = (task and (task.euid or task.uid)) or kernel.uid + local groups = (task and task.groups) or kernel.groups or {} + local root = (task and task.root) or "/" + local cwd = (task and task.cwd) or "/" - for i, part in ipairs(parts) do - local candidate = resolved == "" and ("/" .. part) or (resolved .. "/" .. part) + if root ~= "/" and root:sub(-1) == "/" then root = root:sub(1,-2) end - if noFollow and i == #parts then - resolved = candidate - break - end - - local disk, parentDisk = resolveMount(resolved == "" and "/" or resolved) - local entry = readMetaEntry(disk, parentDisk, part) - - if entry and entry.etype == 0x01 then - local target = entry.cmeta - if target:sub(1,1) ~= "/" then - target = (resolved == "" and "/" or resolved) .. "/" .. target + local function canTraverse(entry) + if euid == 0 then return true end + if not entry then return true end + local bits = entry.perms + if euid == entry.owner and bit_is_set(bits, 9) then return true end + if entry.group then + for _, gid in ipairs(groups) do + if gid == entry.group and bit_is_set(bits, 8) then return true end end - if i < #parts then - target = target .. "/" .. table.concat(parts, "/", i+1, #parts) - end - return resolveSymlinks(normalizePath(target), noFollow, _depth + 1) end - - resolved = candidate + return bit_is_set(bits, 7) end - if resolved == "" then resolved = "/" end - return resolved + local isAbsolute, tokens = tokenizePath(path) + + local stack = {} + + if isAbsolute then + stack = {} + else + for seg in cwd:gmatch("[^/]+") do table.insert(stack, seg) end + end + + local i = 1 + while i <= #tokens do + local comp = tokens[i] + i = i + 1 + + comp = comp:match("^%s*(.-)%s*$") + + if comp == "" or comp == "." then + elseif comp == ".." then + local currentPath = "/" .. table.concat(stack, "/") + + local jailStack = {} + if root ~= "/" then + for seg in root:gmatch("[^/]+") do table.insert(jailStack, seg) end + end + + if #stack <= #jailStack then + stack = {} + for _, seg in ipairs(jailStack) do table.insert(stack, seg) end + else + local exitName = stack[#stack] + local parentPath = "/" .. table.concat(stack, "/", 1, #stack - 1) + if parentPath == "/" then parentPath = "/" end + + local okM, diskM, dpM = pcall(resolveMount, parentPath == "" and "/" or parentPath) + if okM and diskM then + local entry = readMetaEntry(diskM, dpM, exitName) + if entry then + if entry.etype ~= 0x00 then + error("ENOTDIR: not a directory: " .. currentPath) + end + if not canTraverse(entry) then + error("EACCES: permission denied traversing " .. currentPath) + end + else + local okD, diskD, dpD = pcall(resolveMount, currentPath) + if okD and diskD then + local dtype = diskD:type(dpD) + if dtype ~= nil and dtype ~= "directory" then + error("ENOTDIR: not a directory: " .. currentPath) + end + end + end + end + + table.remove(stack) + end + + else + validateComponent(comp) + local lname = comp:lower() + + local curPath = "/" .. table.concat(stack, "/") + + local okM, diskM, dpM = pcall(resolveMount, curPath == "/" and "/" or curPath) + local entry = nil + if okM and diskM then + entry = readMetaEntry(diskM, dpM, lname) + end + + local isFinal = (i > #tokens) + + if entry and entry.etype == 0x01 then + if isFinal and noFollow then + table.insert(stack, lname) + else + symDepth = symDepth + 1 + if symDepth > MAX_SYMLINK then error("ELOOP") end + + local target = entry.cmeta + if not target or target == "" then + error("ENOENT: empty symlink target") + end + + local symIsAbs, symTokens = tokenizePath(target) + + if symIsAbs then + stack = {} + if root ~= "/" then + for seg in root:gmatch("[^/]+") do table.insert(stack, seg) end + end + end + + local fresh = {} + for j = 1, i - 2 do table.insert(fresh, tokens[j]) end + local insertAt = #fresh + 1 + for _, t in ipairs(symTokens) do table.insert(fresh, t) end + for j = i, #tokens do table.insert(fresh, tokens[j]) end + tokens = fresh + i = insertAt + end + else + table.insert(stack, lname) + + if not isFinal then + local newPath = "/" .. table.concat(stack, "/") + local okD, diskD, dpD = pcall(resolveMount, newPath) + if okD and diskD then + local dtype = diskD:type(dpD) + if dtype ~= nil and dtype ~= "directory" then + error("ENOTDIR: not a directory: " .. newPath) + end + end + if not canTraverse(entry) then + error("EACCES: permission denied traversing " .. newPath) + end + end + end + end + end + + local result = "/" .. table.concat(stack, "/") + + if root ~= "/" then + if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then + result = root + end + end + + return result +end + +local function normalizePath(path) + local task = kernel.currentTask + local cwd = (task and task.cwd) or "/" + local root = (task and task.root) or "/" + if root ~= "/" and root:sub(-1) == "/" then root = root:sub(1,-2) end + + local isAbsolute, tokens = tokenizePath(path) + local stack = {} + + if not isAbsolute then + for seg in cwd:gmatch("[^/]+") do table.insert(stack, seg) end + end + + local jailStack = {} + if root ~= "/" then + for seg in root:gmatch("[^/]+") do table.insert(jailStack, seg) end + end + + for _, comp in ipairs(tokens) do + comp = comp:match("^%s*(.-)%s*$") + if comp == "" or comp == "." then + elseif comp == ".." then + if #stack > #jailStack then + table.remove(stack) + end + else + table.insert(stack, comp:lower()) + end + end + + local result = "/" .. table.concat(stack, "/") + if root ~= "/" then + if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then + result = root + end + end + return result end local function resolvePath(path, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) local disk, diskPath = resolveMount(real) if kernel.config.logPathResolution then kernel.log("resolvePath '"..path.."' -> '"..real.."' diskPath '"..diskPath.."'") @@ -271,24 +395,38 @@ local function resolvePath(path, noFollow) end local function getFileMeta(path, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) - local parts = {} - for p in real:gmatch("[^/]+") do table.insert(parts, p) end + if real == "/" then + return { etype = 0x00, owner = 0, group = 0, perms = 62, cmeta = "" } + end - local default = { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } - if #parts == 0 then return default end + local cur = real - local parentNorm = "/" .. table.concat(parts, "/", 1, #parts - 1) - if parentNorm == "" then parentNorm = "/" end - local disk, parentDiskPath = resolveMount(parentNorm) - local entry = readMetaEntry(disk, parentDiskPath, parts[#parts]) - if entry then return entry end - return default + -- FML i hated implementing this - Astronand + while true do + local parent, name = cur:match("^(.*)/([^/]+)$") + if not parent or parent == "" then parent = "/" end + + local disk, parentDiskPath = resolveMount(parent) + local entry = readMetaEntry(disk, parentDiskPath, name) + + if entry then + return entry + end + + if parent == "/" or cur == "/" then + break + end + + cur = parent + end + + return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } end local function writeMetaEntry(path, name, entry, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) local disk, diskPath = resolveMount(real) local mp @@ -487,6 +625,7 @@ function vfs.open(path, mode) if mode == "r" and bit_is_set(meta.perms, 6) then fobj.suid_owner = meta.owner end + if disk.isvirt then fobj.isvirt=true end task.fd[fd] = fobj if not disk.isvirt then total = total + 1 end return fd @@ -535,8 +674,10 @@ function vfs.close(fd) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end + if not task.fd[fd].isvirt then + total = total - 1 + end task.fd[fd] = nil - total = total - 1 file.refcount = file.refcount - 1 if file.refcount <= 0 and file.handle and file.handle.close then file.handle.close() @@ -668,7 +809,7 @@ function vfs.mkdir(path) end function vfs.remove(path) - local norm = resolveSymlinks(path, true) + local norm = namei(path, true) local parent = norm:match("^(.*)/[^/]+$") or "/" if parent == "" then parent = "/" end local parentMeta = getFileMeta(parent) @@ -681,7 +822,7 @@ function vfs.remove(path) end if meta.etype == 0x01 then - local norm = resolveSymlinks(path, true) + local norm = namei(path, true) local parent = norm:match("^(.*)/[^/]+$") or "/" if parent == "" then parent = "/" end local name = norm:match("[^/]+$") @@ -747,7 +888,7 @@ function vfs.access(path, mode) end local function updateMeta(path, fn, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) local norm = real local parent = norm:match("^(.*)/[^/]+$") or "/" if parent == "" then parent = "/" end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod index ebb7a40..7068837 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local cache = {} kernel.searchpaths = { diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod index 50d705b..6ca94fa 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod @@ -16,7 +16,7 @@ proxy.getLabel = function() return "devfs" end proxy.attributes = function(path) return { size = 0, modified = 0, - created = 0, + created = 0 } end function proxy:open(path, mode) diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod new file mode 100644 index 0000000..36ffcff --- /dev/null +++ b/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod @@ -0,0 +1,227 @@ +--:Minify:-- +local kernel = ... + +local proxy = {} +local data = {} + +proxy.address = "procfs0000" +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 "procfs" end +proxy.attributes = function(path) return { + size = 0, + modified = 0, + created = 0 +} end + +local function buildMeta(entries, opts) + opts = opts or {} + local uid = opts.uid or 0 + local gid = opts.gid or 0 + local perms = opts.perms or 0x3F -- default read/write for owner/group/world + + local chunks = {} + table.insert(chunks, string.char(0x02)) -- version header + + for path, target in pairs(entries) do + local name = path + local nameLen = #name + if nameLen > 255 then + error("Filename too long (>255 bytes): "..name) + end + + -- Determine entry type: 0x01 = symlink if target ~= nil and target ~= "" + local entryType = 0x00 + local cmeta = "" + if target and target ~= "" then + entryType = 0x01 + cmeta = target + end + local cmetaLen = #cmeta + if cmetaLen > 255 then + error("cmeta too long (>255 bytes) for "..name) + end + + -- Build entry as bytes + table.insert(chunks, string.char(nameLen)) -- name length + table.insert(chunks, name) -- name + table.insert(chunks, string.char(entryType)) -- entry type + table.insert(chunks, string.char(uid % 256, math.floor(uid/256) % 256)) -- uid + table.insert(chunks, string.char(gid % 256, math.floor(gid/256) % 256)) -- gid + table.insert(chunks, string.char(perms % 256, math.floor(perms/256) % 256)) -- perms + table.insert(chunks, string.char(cmetaLen)) -- cmeta length + if cmetaLen > 0 then + table.insert(chunks, cmeta) + end + end + + return table.concat(chunks) +end + +local function simpleFile(r,w) + return function(op, mode) + if op=="type" then + return "character device" + elseif op=="open" then + if mode=="r" then + return { + read=r + } + elseif mode=="w" then + return { + write=w + } + end + end + end +end + +local function strFile(str) + local dat=tostring(str) + local pos=1 + return simpleFile(function(amount) + pos=pos+amount + return dat:sub(pos-amount, pos) + end,function() error("EACCES") end) +end + +local function newtaskproxy(task) + local files,siblings,children={},{},{} + if task.fd[0] then files["0"]=task.fd[0].path end + for i,v in ipairs(task.fd) do + files[tostring(i)]=tostring(v.path) + end + for i,v in ipairs(task.siblings) do + siblings[tostring(v.pid)]="/proc/"..tostring(v.pid) + end + for i,v in ipairs(task.children) do + children[tostring(v.pid)]="/proc/"..tostring(v.pid) + end + return { + [".meta"]=strFile(buildMeta({cwd=task.cwd,parent="/proc/"..tostring(task.parent.pid)})), + uid=strFile(task.uid), + comm=strFile(task.name), + fd={ + [".meta"]=strFile(buildMeta(files)) + }, + siblings={ + [".meta"]=strFile(buildMeta(siblings)) + }, + children={ + [".meta"]=strFile(buildMeta(children)) + } + } +end + +function proxy:open(path, mode) + local steps = kernel.vfs.splitPath(path) + local step = data + if tonumber(steps[1]) then + local task=kernel.tasks[tostring(steps[1])] + local step = newtaskproxy(task) + for i=2, #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 + else + 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 + 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 + if tonumber(steps[1]) then + local task=kernel.tasks[steps[1]] + if #steps==1 then return "directory" end + local step = newtaskproxy(task) + for i=2, #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 + else + 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 + end + error("ENOENT") +end + +function proxy:list(path) + local steps = kernel.vfs.splitPath(path) + local step = data + if #steps == 0 then + return table.merge(table.keys(data),table.keys(kernel.tasks)) + end + if tonumber(steps[1]) then + local task=kernel.tasks[steps[1]] + local step = newtaskproxy(task) + if #steps==1 then return table.keys(step) end + for i=2, #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 + else + 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 + end + error("ENOENT") +end + +function proxy:fileExists(path) + local ok = pcall(function() + return self:type(path) + end) + return ok +end + +data.uptime=simpleFile(function()return tostring(kernel.computer:getUptime())end,function()error("EACCES")end) +kernel.procfs={} +kernel.procfs.data=data +kernel.procfs.proxy=proxy +kernel.disks["procfs0000"]=proxy \ No newline at end of file diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod index 371409d..eecb14f 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod @@ -1,3 +1,4 @@ +--:Minify:-- local kernel = ... local proxy = {} diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod index f1dcf2c..5a50bcc 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- -- Loop device driver: -- -- BIND (directory) - re-routes VFS calls into a host directory subtree. diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod deleted file mode 100644 index 1ee28c1..0000000 --- a/Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod +++ /dev/null @@ -1,22 +0,0 @@ ----- :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 diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod index c95e70b..f1a2380 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- -- Supports: -- AF_UNIX - local IPC via /var/run/*.sock paths -- AF_INET - network sockets with three backends: diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod index 2621138..a7a2e90 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local args = {...} local kernel = args[1] kernel._G = _G @@ -55,3 +55,19 @@ local origLoad = load kernel._U = readonly(kernel._G) kernel._U._G = kernel._U kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end + +function kernel.freshUserEnv() + local locals = {} + locals.syscall = _makeSyscallProxy() + + local env = setmetatable(locals, { + __index = kernel._U, + __newindex = function(_, k, v) rawset(locals, k, v) end, + __metatable=false + }) + + locals._G = env + locals.load = function(a, b, c, d) return origLoad(a, b, c, d or env) end + + return env +end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod index c23be56..7f414e6 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod @@ -11,14 +11,16 @@ local function bit_is_set(num, bit) return math.floor(num / (2 ^ bit)) % 2 == 1 end -local function loadExecutable(path, env) +local function loadExecutable(path) kernel.vfs.access(path, "rx") local fd = kernel.vfs.open(path, "r") local data = kernel.vfs.read(fd, 1024 * 1024 * 4) kernel.vfs.close(fd) - local func, err = load(data, "@" .. path, "t", env or kernel._U) + local env = kernel.freshUserEnv() + + local func, err = load(data, "@" .. path, "t", env) if not func then error("ENOEXEC: " .. tostring(err)) end local meta = kernel.vfs.lstat(path) @@ -92,7 +94,7 @@ local function createTask(func, name, envars, args, tgid, real_uid, eff_uid) end function sys.spawn(func, name, envars, args, tgid) - local caller = kernel.currentTask + local caller = kernel.currentTask local real_uid = caller and caller.uid or kernel.uid local eff_uid = caller and caller.euid or real_uid return createTask(func, name, envars, args, tgid, real_uid, eff_uid) @@ -357,10 +359,25 @@ function kernel.main() if task.sigq and #task.sigq ~= 0 and task.sigh then local coro = coroutine.create(task.sigh) - if kernel.config.preempt then - resumeWithTimeout(coro, task.timeSlice, table.remove(task.sigq, 1)) - else - coroutine.resume(coro, table.remove(task.sigq, 1)) + local sigret = { coroutine.resume(coro, table.remove(task.sigq, 1)) } + while coroutine.status(coro) ~= "dead" do + if sigret[1] == false then break end + if sigret[2] == "syscall" then + local scname = sigret[3] + local sysret + if kernel.syscalls[scname] then + sysret = { xpcall(kernel.syscalls[scname], debug.traceback, table.unpack(sigret, 4)) } + else + sysret = { false, "Unknown syscall: " .. tostring(scname) } + end + if not sysret[1] then + sigret = { coroutine.resume(coro, false, sysret[2]) } + else + sigret = { coroutine.resume(coro, true, table.unpack(sysret, 2)) } + end + else + sigret = { coroutine.resume(coro) } + end end end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod index fbb705c..1e43f63 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod @@ -1,3 +1,4 @@ +--:Minify:-- local kernel=... local sysc=kernel.syscalls kernel.gpio={} diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod index a8148cb..6f768bc 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... function print(...) local args = {...} diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod index 2cfc4ef..0e94960 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... kernel.log("Loading init system...") kernel.log("InitPath: " .. kernel.config.initPath) diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod index 621f94b..229b9b2 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod @@ -1,8 +1,8 @@ --- :Minify:-- +--:Minify:-- local kernel = ... kernel.processes.login = function() - local ok, err = pcall(syscall.execspawn, "/bin/login", "login") + local ok, err = pcall(kernel.hpv.execspawn, "/bin/login", "login") if not ok then kernel.log("Failed to exec /bin/login: " .. tostring(err), "ERROR", 2) end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod deleted file mode 100644 index cb84c38..0000000 --- a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod +++ /dev/null @@ -1,233 +0,0 @@ --- :Minify:-- -local kernel = ... - -local P = kernel.vfs.P -local PERM = kernel.vfs.PERM - -local RW_R_R = P.OWNER_R + P.OWNER_W + P.GROUP_R + P.WORLD_R -local RWX_RX_RX = P.OWNER_R + P.OWNER_W + P.OWNER_X - + P.GROUP_R + P.GROUP_X - + P.WORLD_R + P.WORLD_X -local RW_R__ = P.OWNER_R + P.OWNER_W + P.GROUP_R -local RW____ = P.OWNER_R + P.OWNER_W -local RWXRWXRWX = PERM.RWXRWXRWX -local SUID_755 = PERM.SUID_755 - -local META_VERSION = 0x02 -local rootDisk = kernel.disks["$"] - -local function makeEntry(name, etype, owner, group, perms, cmeta) - cmeta = cmeta or "" - local plo = perms % 256 - local phi = math.floor(perms / 256) % 256 - local olo = (owner or 0) % 256 - local ohi = math.floor((owner or 0) / 256) % 256 - local glo = (group or 0) % 256 - local ghi = math.floor((group or 0) / 256) % 256 - return string.char(#name) .. name - .. string.char(etype, olo, ohi, glo, ghi, plo, phi) - .. string.char(#cmeta) .. cmeta -end - -local REG = 0x00 - -local function mergeMeta(dir, entries) - local diskDir = dir - if diskDir:sub(1,1) == "/" then diskDir = diskDir:sub(2) end - local metaPath = (diskDir == "" and ".meta" or diskDir .. "/.meta") - - local existing = {} - local rok, rf = pcall(function() return rootDisk:open(metaPath, "r") end) - if rok and rf then - local raw = rf.read(65535) - if rf.close then rf.close() end - existing = (kernel.vfs._parseMetafile and kernel.vfs._parseMetafile(raw)) or {} - end - - for _, e in ipairs(entries) do - local name = e[1] - local etype = e[2] or REG - local owner = e[3] or 0 - local group = e[4] or 0 - local perms = e[5] or RWX_RX_RX - local cmeta = e[6] or "" - existing[name] = { - etype = etype, - owner = owner, - group = group, - perms = perms, - cmeta = cmeta, - } - end - - local data = string.char(META_VERSION) - for name, m in pairs(existing) do - data = data .. makeEntry( - name, - m.etype or REG, - m.owner or 0, - m.group or 0, - m.perms or RWX_RX_RX, - m.cmeta or "" - ) - end - - local ok, err = pcall(function() - local f = rootDisk:open(metaPath, "w") - f.write(data) - f.close() - end) - if not ok then - kernel.log("permissions: failed to write " .. metaPath .. ": " .. tostring(err), "WARN", 8) - end -end - -kernel.log("Seeding filesystem permissions...", "INFO") - -mergeMeta("/", { - {"bin", REG, 0, 0, RWX_RX_RX}, - {"boot", REG, 0, 0, RWX_RX_RX}, - {"dev", REG, 0, 0, RWX_RX_RX}, - {"etc", REG, 0, 0, RWX_RX_RX}, - {"home", REG, 0, 0, RWX_RX_RX}, - {"lib", REG, 0, 0, RWX_RX_RX}, - {"root", REG, 0, 0, RW____ }, - {"sbin", REG, 0, 0, RWX_RX_RX}, - {"tmp", REG, 0, 0, RWXRWXRWX}, - {"usr", REG, 0, 0, RWX_RX_RX}, - {"var", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/boot", { - {"kernel.lua", REG, 0, 0, RW_R_R }, - {"boot.cfg", REG, 0, 0, RW_R_R }, - {"safeboot.cfg", REG, 0, 0, RW_R_R }, - {"fstab", REG, 0, 0, RW_R_R }, - {"initfs", REG, 0, 0, RW_R_R }, - {"cct", REG, 0, 0, RWX_RX_RX}, - {"oc", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/boot/cct", { - {"boot.lua", REG, 0, 0, RW_R_R}, - {"initdisks", REG, 0, 0, RW_R_R}, - {"eeprom", REG, 0, 0, RW_R_R}, -}) - -mergeMeta("/boot/oc", { - {"boot.lua", REG, 0, 0, RW_R_R}, - {"initfs.lua",REG, 0, 0, RW_R_R}, - {"eeprom", REG, 0, 0, RW_R_R}, -}) - -mergeMeta("/sbin", { - {"init.lua", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/bin", { - {"cat", REG, 0, 0, RWX_RX_RX}, - {"chattr", REG, 0, 0, RWX_RX_RX}, - {"chgrp", REG, 0, 0, RWX_RX_RX}, - {"chmod", REG, 0, 0, RWX_RX_RX}, - {"chown", REG, 0, 0, RWX_RX_RX}, - {"chroot", REG, 0, 0, RWX_RX_RX}, - {"clear", REG, 0, 0, RWX_RX_RX}, - {"echo", REG, 0, 0, RWX_RX_RX}, - {"hfetch", REG, 0, 0, RWX_RX_RX}, - {"help", REG, 0, 0, RWX_RX_RX}, - {"hysh", REG, 0, 0, RWX_RX_RX}, - {"hyshex", REG, 0, 0, RWX_RX_RX}, - {"id", REG, 0, 0, RWX_RX_RX}, - {"install", REG, 0, 0, RWX_RX_RX}, - {"ln", REG, 0, 0, RWX_RX_RX}, - {"login", REG, 0, 0, SUID_755 }, - {"loimgcreate", REG, 0, 0, RWX_RX_RX}, - {"looptest", REG, 0, 0, RWX_RX_RX}, - {"losetup", REG, 0, 0, RWX_RX_RX}, - {"ls", REG, 0, 0, RWX_RX_RX}, - {"lsusers", REG, 0, 0, RWX_RX_RX}, - {"lua", REG, 0, 0, RWX_RX_RX}, - {"luaold", REG, 0, 0, RWX_RX_RX}, - {"micro", REG, 0, 0, RWX_RX_RX}, - {"mkdir", REG, 0, 0, RWX_RX_RX}, - {"mount", REG, 0, 0, RWX_RX_RX}, - {"passwd", REG, 0, 0, RWX_RX_RX}, - {"ps", REG, 0, 0, RWX_RX_RX}, - {"pwd", REG, 0, 0, RWX_RX_RX}, - {"readlink", REG, 0, 0, RWX_RX_RX}, - {"sed", REG, 0, 0, RWX_RX_RX}, - {"socktest", REG, 0, 0, RWX_RX_RX}, - {"spm", REG, 0, 0, RWX_RX_RX}, - {"startup", REG, 0, 0, RWX_RX_RX}, - {"su", REG, 0, 0, SUID_755 }, - {"sudo", REG, 0, 0, SUID_755 }, - {"sysdump", REG, 0, 0, RWX_RX_RX}, - {"umount", REG, 0, 0, RWX_RX_RX}, - {"useradd", REG, 0, 0, RWX_RX_RX}, - {"userdel", REG, 0, 0, RWX_RX_RX}, - {"usermod", REG, 0, 0, RWX_RX_RX}, - {"whoami", REG, 0, 0, RWX_RX_RX}, - {"yes", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/bin/startup", { - {"test.lua", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/lib", { - {"sys", REG, 0, 0, RWX_RX_RX}, - {"modules", REG, 0, 0, RWX_RX_RX}, - {"crypto", REG, 0, 0, RWX_RX_RX}, - {"store", REG, 0, 0, RWX_RX_RX}, - {"snip", REG, 0, 0, RW_R_R }, - {"io", REG, 0, 0, RW_R_R }, - {"bit32", REG, 0, 0, RW_R_R }, -}) - -mergeMeta("/lib/sys", { - {"fs", REG, 0, 0, RW_R_R}, - {"hpv", REG, 0, 0, RW_R_R}, - {"ipc", REG, 0, 0, RW_R_R}, - {"term", REG, 0, 0, RW_R_R}, - {"init", REG, 0, 0, RW_R_R}, -}) - -mergeMeta("/lib/modules", { - {"hyperion", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/lib/modules/hyperion", { - {"01_stdlib.kmod", REG, 0, 0, RW_R_R}, - {"10_vfs.kmod", REG, 0, 0, RW_R_R}, - {"11_require.kmod", REG, 0, 0, RW_R_R}, - {"12_devfs.kmod", REG, 0, 0, RW_R_R}, - {"12_tmpfs.kmod", REG, 0, 0, RW_R_R}, - {"13_loopdev.kmod", REG, 0, 0, RW_R_R}, - {"14_keventd.kmod", REG, 0, 0, RW_R_R}, - {"19_fstab.kmod", REG, 0, 0, RW_R_R}, - {"20_signals.kmod", REG, 0, 0, RW_R_R}, - {"20_socket.kmod", REG, 0, 0, RW_R_R}, - {"26_tty.kmod", REG, 0, 0, RW_R_R}, - {"30_userspace.kmod", REG, 0, 0, RW_R_R}, - {"40_auth.kmod", REG, 0, 0, RW_R_R}, - {"45_hypervisor.kmod", REG, 0, 0, RW_R_R}, - {"47_dbg.kmod", REG, 0, 0, RW_R_R}, - {"50_gpio.kmod", REG, 0, 0, RW_R_R}, - {"70_stdlibadv.kmod", REG, 0, 0, RW_R_R}, - {"90_init.kmod", REG, 0, 0, RW_R_R}, - {"91_login.kmod", REG, 0, 0, RW_R_R}, - {"92_permissions.kmod", REG, 0, 0, RW_R_R}, - {"99_final.kmod", REG, 0, 0, RW_R_R}, -}) - -mergeMeta("/etc", { - {"passwd", REG, 0, 0, RW_R_R }, - {"shadow", REG, 0, 0, RW____ }, - {"pam.d", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/etc/pam.d", { - {"secret", REG, 0, 0, RW____}, -}) - -kernel.log("Filesystem permissions seeded.", "INFO") diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod new file mode 100644 index 0000000..926eb32 --- /dev/null +++ b/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod @@ -0,0 +1,120 @@ +--:Minify:-- +local kernel = ... + +local P = kernel.vfs.P +local PERM = kernel.vfs.PERM + +local RW_R_R = P.OWNER_R + P.OWNER_W + P.GROUP_R + P.WORLD_R +local RWX_RX_RX = P.OWNER_R + P.OWNER_W + P.OWNER_X + + P.GROUP_R + P.GROUP_X + + P.WORLD_R + P.WORLD_X +local RW_R__ = P.OWNER_R + P.OWNER_W + P.GROUP_R +local RW____ = P.OWNER_R + P.OWNER_W +local RWXRWXRWX = PERM.RWXRWXRWX +local SUID_755 = PERM.SUID_755 + +local META_VERSION = 0x02 +local rootDisk = kernel.disks["$"] + +local function makeEntry(name, etype, owner, group, perms, cmeta) + cmeta = cmeta or "" + local plo = perms % 256 + local phi = math.floor(perms / 256) % 256 + local olo = (owner or 0) % 256 + local ohi = math.floor((owner or 0) / 256) % 256 + local glo = (group or 0) % 256 + local ghi = math.floor((group or 0) / 256) % 256 + return string.char(#name) .. name + .. string.char(etype, olo, ohi, glo, ghi, plo, phi) + .. string.char(#cmeta) .. cmeta +end + +local REG = 0x00 + +local function mergeMeta(dir, entries) + local diskDir = dir + if diskDir:sub(1,1) == "/" then diskDir = diskDir:sub(2) end + local metaPath = (diskDir == "" and ".meta" or diskDir .. "/.meta") + + local existing = {} + local rok, rf = pcall(function() return rootDisk:open(metaPath, "r") end) + if rok and rf then + local raw = rf.read(65535) + if rf.close then rf.close() end + existing = (kernel.vfs._parseMetafile and kernel.vfs._parseMetafile(raw)) or {} + end + + for _, e in ipairs(entries) do + local name = e[1] + local etype = e[2] or REG + local owner = e[3] or 0 + local group = e[4] or 0 + local perms = e[5] or RWX_RX_RX + local cmeta = e[6] or "" + existing[name] = { + etype = etype, + owner = owner, + group = group, + perms = perms, + cmeta = cmeta, + } + end + + local data = string.char(META_VERSION) + for name, m in pairs(existing) do + data = data .. makeEntry( + name, + m.etype or REG, + m.owner or 0, + m.group or 0, + m.perms or RWX_RX_RX, + m.cmeta or "" + ) + end + + local ok, err = pcall(function() + local f = rootDisk:open(metaPath, "w") + f.write(data) + f.close() + end) + if not ok then + kernel.log("permissions: failed to write " .. metaPath .. ": " .. tostring(err), "WARN", 8) + end +end + +if kernel.firstBoot then + kernel.log("Seeding filesystem permissions...") + + mergeMeta("/", { + {"bin", REG, 0, 0, RWX_RX_RX}, + {"boot", REG, 0, 0, RWX_RX_RX}, + {"dev", REG, 0, 0, RWXRWXRWX}, + {"etc", REG, 0, 0, RWX_RX_RX}, + {"home", REG, 0, 0, RWX_RX_RX}, + {"lib", REG, 0, 0, RWX_RX_RX}, + {"root", REG, 0, 0, RW____ }, + {"sbin", REG, 0, 0, RWX_RX_RX}, + {"tmp", REG, 0, 0, RWXRWXRWX}, + {"usr", REG, 0, 0, RWX_RX_RX}, + {"var", REG, 0, 0, RWX_RX_RX}, + {"opt", REG, 0, 0, RWXRWXRWX}, + }) + + mergeMeta("/bin", { + {"login", REG, 0, 0, SUID_755 }, + {"su", REG, 0, 0, SUID_755 }, + {"sudo", REG, 0, 0, SUID_755 }, + }) + + mergeMeta("/etc", { + {"passwd", REG, 0, 0, RW_R_R }, + {"shadow", REG, 0, 0, RW____ }, + {"pam.d", REG, 0, 0, RW____ }, + }) + + mergeMeta("/etc/pam.d", { + {"secret", REG, 0, 0, RW____}, + }) + + kernel.log("Filesystem permissions seeded.") +end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod index 15ee521..d592766 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod @@ -1,2 +1,3 @@ +--:Minify:-- local kernel = ... -kernel.allowGlobalOverwrites = false \ No newline at end of file +kernel.allowGlobalOverwrites = false diff --git a/Src/Hyperion-core/lib/bit32 b/Src/bit32/lib/bit32 similarity index 100% rename from Src/Hyperion-core/lib/bit32 rename to Src/bit32/lib/bit32 diff --git a/Src/Hyperion-core/lib/crypto/blake2s b/Src/blake2s/lib/crypto/blake2s similarity index 100% rename from Src/Hyperion-core/lib/crypto/blake2s rename to Src/blake2s/lib/crypto/blake2s diff --git a/Src/Hyperion-core/lib/store/LibDeflate b/Src/deflate/lib/LibDeflate similarity index 100% rename from Src/Hyperion-core/lib/store/LibDeflate rename to Src/deflate/lib/LibDeflate diff --git a/Src/Hyperion-core/lib/store/deflate b/Src/deflate/lib/deflate similarity index 100% rename from Src/Hyperion-core/lib/store/deflate rename to Src/deflate/lib/deflate diff --git a/Src/Hyperion-bash/bin/chattr b/Src/hysh/bin/chattr similarity index 100% rename from Src/Hyperion-bash/bin/chattr rename to Src/hysh/bin/chattr diff --git a/Src/Hyperion-bash/bin/chgrp b/Src/hysh/bin/chgrp similarity index 100% rename from Src/Hyperion-bash/bin/chgrp rename to Src/hysh/bin/chgrp diff --git a/Src/Hyperion-bash/bin/chmod b/Src/hysh/bin/chmod similarity index 100% rename from Src/Hyperion-bash/bin/chmod rename to Src/hysh/bin/chmod diff --git a/Src/Hyperion-bash/bin/chown b/Src/hysh/bin/chown similarity index 100% rename from Src/Hyperion-bash/bin/chown rename to Src/hysh/bin/chown diff --git a/Src/Hyperion-bash/bin/chroot b/Src/hysh/bin/chroot similarity index 100% rename from Src/Hyperion-bash/bin/chroot rename to Src/hysh/bin/chroot diff --git a/Src/Hyperion-bash/bin/help b/Src/hysh/bin/help similarity index 100% rename from Src/Hyperion-bash/bin/help rename to Src/hysh/bin/help diff --git a/Src/Hyperion-bash/bin/hfetch b/Src/hysh/bin/hfetch similarity index 100% rename from Src/Hyperion-bash/bin/hfetch rename to Src/hysh/bin/hfetch diff --git a/Src/Hyperion-bash/bin/hysh b/Src/hysh/bin/hysh similarity index 77% rename from Src/Hyperion-bash/bin/hysh rename to Src/hysh/bin/hysh index a1bc15a..23ce7a5 100644 --- a/Src/Hyperion-bash/bin/hysh +++ b/Src/hysh/bin/hysh @@ -1,11 +1,11 @@ --: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) +syscall.open("/dev/tty/1","r") --stdin (Device 0) +syscall.open("/dev/tty/1","w") --stdout (Device 1) +syscall.open("/dev/null","w") --stderr (device 2) local success, errorMsg = xpcall(function() -local fs = require("sys.fs") +local fs = require("fs") syscall.devctl(1,"clear") syscall.devctl(1,"sfgc",1) @@ -15,7 +15,7 @@ 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("SHELL","hysh") syscall.setEnviron("PATH","/bin/") local _home = syscall.getEnviron("HOME") if _home and _home ~= "" then @@ -133,8 +133,6 @@ local function copyfile(src, dst) if not data then return false, err end local ok, err2 = writefile(dst, data) if not ok then return false, err2 end - local ok2, stat = pcall(syscall.stat, src) - if ok2 and stat and stat.perms then pcall(syscall.chmod, dst, stat.perms) end return true end @@ -810,6 +808,160 @@ builtinCmds.df = function(...) end end +local function listDir(dir, prefix) + local ok, entries = pcall(syscall.listdir, dir) + if not ok or not entries then return {} end + local results = {} + for _, e in ipairs(entries) do + if prefix == "" or e:sub(1, #prefix) == prefix then + local fullpath = (dir == "/" and "/" or dir.."/")..e + local t = syscall.type(fullpath) + results[#results+1] = t == "directory" and (e.."/") or e + end + end + table.sort(results) + return results +end + +local function listCommands(prefix) + local results = {} + local seen = {} + for name in pairs(builtinCmds) do + if prefix == "" or name:sub(1, #prefix) == prefix then + if not seen[name] then results[#results+1] = name; seen[name] = true end + end + end + local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":") + for _, p in ipairs(paths) do + local ok, entries = pcall(syscall.listdir, p) + if ok and entries then + for _, e in ipairs(entries) do + local fullpath = (p:sub(-1)=="/" and p or p.."/")..e + local xok = pcall(syscall.access, fullpath, "x") + if xok and (prefix == "" or e:sub(1, #prefix) == prefix) then + if not seen[e] then results[#results+1] = e; seen[e] = true end + end + end + end + end + table.sort(results) + return results +end + +local function commonPrefix(list) + if #list == 0 then return "" end + local pre = list[1] + for i = 2, #list do + local s = list[i] + local j = 1 + while j <= #pre and j <= #s and pre:sub(j,j) == s:sub(j,j) do j = j+1 end + pre = pre:sub(1, j-1) + if pre == "" then return "" end + end + return pre +end + +local function showCompletions(completions) + syscall.write(1, "\n") + local W = 51 + local maxlen = 0 + for _, c in ipairs(completions) do if #c > maxlen then maxlen = #c end end + local colw = maxlen + 2 + local cols = math.max(1, math.floor(W / colw)) + local i = 0 + for _, c in ipairs(completions) do + local padded = c .. string.rep(" ", colw - #c) + syscall.write(1, padded) + i = i + 1 + if i % cols == 0 then syscall.write(1, "\n") end + end + if i % cols ~= 0 then syscall.write(1, "\n") end +end + +local function parseForCompletion(input, cursorPos) + local sofar = input:sub(1, cursorPos - 1) + local tokens = {} + for tok in sofar:gmatch("%S+") do tokens[#tokens+1] = tok end + local partial = sofar:match("%S+$") or "" + local isFirst = (#tokens == 0) or (sofar:sub(-1) ~= " " and #tokens == 1) + + local partialDir, partialBase + if partial:find("/") then + partialDir = partial:match("^(.*/)") or "/" + partialBase = partial:match("[^/]*$") or "" + else + partialDir = nil + partialBase = partial + end + + return tokens, partial, isFirst, partialDir, partialBase +end + +local tabState = { last = nil, idx = 0, list = {} } + +local function doTabComplete(input, cursorPos) + local tokens, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos) + + local candidates + if isFirst then + if partialDir then + local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + candidates = listDir(dir, partialBase) + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + else + candidates = listCommands(partialBase) + end + else + local dir, base + if partialDir then + dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + base = partialBase + else + dir = syscall.getcwd() + base = partialBase + end + candidates = listDir(dir, base) + if partialDir then + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + end + end + + if #candidates == 0 then + return input, cursorPos, false + end + + local context = input.."\0"..tostring(cursorPos) + if tabState.last ~= context then + tabState.last = context + tabState.idx = 0 + tabState.list = candidates + end + + if #candidates == 1 then + local completed = candidates[1] + local before = input:sub(1, cursorPos - 1 - #partial) + local after = input:sub(cursorPos) + local newInput = before .. completed .. after + local newCursor = #before + #completed + 1 + tabState.last = nil + return newInput, newCursor, true + end + + local pre = commonPrefix(candidates) + if #pre > #partial then + local before = input:sub(1, cursorPos - 1 - #partial) + local after = input:sub(cursorPos) + local newInput = before .. pre .. after + local newCursor = #before + #pre + 1 + tabState.last = newInput.."\0"..tostring(newCursor) + tabState.list = candidates + return newInput, newCursor, true + else + showCompletions(candidates) + return input, cursorPos, true + end +end + local function getUserInput() syscall.devctl(1,"sfgc",3) syscall.write(1, userhost) @@ -829,6 +981,41 @@ local function getUserInput() local history = 0 local dirty = true + local function getGhostSuffix() + if #input == 0 then return "" end + local _, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos) + if cursorPos ~= #input + 1 then return "" end + local candidates + if isFirst then + if partialDir then + local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + candidates = listDir(dir, partialBase) + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + else + candidates = listCommands(partialBase) + end + else + local dir, base + if partialDir then + dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + base = partialBase + else + dir = syscall.getcwd() + base = partialBase + end + candidates = listDir(dir, base) + if partialDir then + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + end + end + if #candidates == 0 then return "" end + local pre = commonPrefix(candidates) + if #pre > #partial then + return pre:sub(#partial + 1) + end + return "" + end + local function redraw() syscall.devctl(1,"spos",curOffsetX,curOffsetY) syscall.write(1, string.sub(input, 1, cursorPos-1)) @@ -841,21 +1028,31 @@ local function getUserInput() syscall.write(1, string.sub(input, cursorPos, cursorPos)) end syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16) - syscall.write(1, string.sub(input, cursorPos+1) .. " ") + local after = string.sub(input, cursorPos+1) + syscall.write(1, after) + local ghost = getGhostSuffix() + if #ghost > 0 then + syscall.devctl(1,"sfgc",14) + syscall.write(1, ghost) + syscall.devctl(1,"sfgc",1) + syscall.write(1, " ") + else + syscall.write(1, " ") + end end while true do local key = syscall.read(0) if key and key ~= "" then - if key=="\19" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end - elseif key=="\20" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end - elseif key=="\17" then + if key=="" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end + elseif key=="" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end + elseif key=="" then if history<#commandHistory then history=history+1 input=commandHistory[#commandHistory-history+1] cursorPos=#input+1;dirty=true end - elseif key=="\18" then + elseif key=="" then if history>1 then history=history-1 input=commandHistory[#commandHistory-history+1] @@ -863,6 +1060,38 @@ local function getUserInput() elseif history==1 then history=0;input="";cursorPos=1;dirty=true end + elseif key=="" then cursorPos=1;dirty=true + elseif key=="" then cursorPos=#input+1;dirty=true + elseif key=="[3~" then + if cursorPos<=#input then + input=string.sub(input,1,cursorPos-1)..string.sub(input,cursorPos+1) + dirty=true + end + elseif key=="\t" then + local newInput, newCursor, needsRedraw = doTabComplete(input, cursorPos) + if needsRedraw then + input = newInput; cursorPos = newCursor + local posStr = syscall.devctl(1, "gpos") + local sep = posStr:find(";") + local px = tonumber(posStr:sub(1, sep-1)) + local py = tonumber(posStr:sub(sep+1)) + if px > 1 then + syscall.devctl(1,"spos",1,py) + local tsz = syscall.devctl(1,"size") or "51;19" + local tw = tonumber(tsz:match("^(%d+)")) or 51 + syscall.write(1, string.rep(" ", tw)) + syscall.devctl(1,"spos",1,py) + end + 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, "$ ") + posStr = syscall.devctl(1, "gpos") + sep = posStr:find(";") + curOffsetX = tonumber(posStr:sub(1, sep-1)) + curOffsetY = tonumber(posStr:sub(sep+1)) + dirty = true + end elseif key=="\b" then if cursorPos>1 then input=string.sub(input,1,cursorPos-2)..string.sub(input,cursorPos) @@ -873,7 +1102,7 @@ local function getUserInput() syscall.devctl(1,"spos",curOffsetX,curOffsetY) syscall.write(1, input.." \n") return input - else + elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then input=string.sub(input,1,cursorPos-1)..key..string.sub(input,cursorPos) cursorPos=cursorPos+1;dirty=true end @@ -946,23 +1175,16 @@ local function runCommand(command) return end - local text = fs.readAllText(cmdPath) - local program, err = load(text, progName) - if not program then - syscall.devctl(1,"sfgc",2) - local line, rest = tostring(err):match(":(%d+): (.+)$") - if line then printInline(progName..": load error on line "..line..": "); print(rest) - else print(progName..": load error: "..tostring(err)) end - syscall.devctl(1,"sfgc",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 ok2, msg = pcall(program, ...) - if not ok2 then printError(progName, msg) end - end, progName, nil, {table.unpack(args, 2)}) + local proc = syscall.spawn(function() + -- Open standard fds so programs that don't do it themselves work correctly. + syscall.open("/dev/tty/1", "r") -- fd 0 stdin + syscall.open("/dev/tty/1", "w") -- fd 1 stdout + syscall.open("/dev/null", "w") -- fd 2 stderr + -- exec replaces this coroutine's code with a fresh isolated environment + -- compiled from disk by the kernel (via loadExecutable -> freshUserEnv), + -- so the child cannot share any upvalue or syscall table state with hysh. + syscall.exec(cmdPath, {table.unpack(args, 2)}) + end, progName) while true do local exited, code = syscall.collect(proc) diff --git a/Src/Hyperion-bash/bin/id b/Src/hysh/bin/id similarity index 100% rename from Src/Hyperion-bash/bin/id rename to Src/hysh/bin/id diff --git a/Src/hysh/bin/ll b/Src/hysh/bin/ll new file mode 100644 index 0000000..5cefbfd --- /dev/null +++ b/Src/hysh/bin/ll @@ -0,0 +1,3 @@ +local args={...} +table.insert(args, "-lah") +syscall.exec("/bin/ls", args) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/ln b/Src/hysh/bin/ln similarity index 100% rename from Src/Hyperion-bash/bin/ln rename to Src/hysh/bin/ln diff --git a/Src/Hyperion-bash/bin/login b/Src/hysh/bin/login similarity index 97% rename from Src/Hyperion-bash/bin/login rename to Src/hysh/bin/login index a81c82e..50c3a2e 100644 --- a/Src/Hyperion-bash/bin/login +++ b/Src/hysh/bin/login @@ -1,6 +1,6 @@ --:Minify:-- -syscall.open("/dev/tty/tty1", "r") --stdin (fd 0) -syscall.open("/dev/tty/tty1", "w") --stdout (fd 1) +syscall.open("/dev/tty/1", "r") --stdin (fd 0) +syscall.open("/dev/tty/1", "w") --stdout (fd 1) syscall.open("/dev/null", "w") --stderr (fd 2) diff --git a/Src/Hyperion-bash/bin/loimgcreate b/Src/hysh/bin/loimgcreate similarity index 99% rename from Src/Hyperion-bash/bin/loimgcreate rename to Src/hysh/bin/loimgcreate index 76d9699..6fca0dc 100644 --- a/Src/Hyperion-bash/bin/loimgcreate +++ b/Src/hysh/bin/loimgcreate @@ -43,7 +43,7 @@ if opts.help then return end -local fs = require("sys.fs") +local fs = require("fs") if opts.x then if #args < 2 then diff --git a/Src/Hyperion-bash/bin/losetup b/Src/hysh/bin/losetup similarity index 100% rename from Src/Hyperion-bash/bin/losetup rename to Src/hysh/bin/losetup diff --git a/Src/Hyperion-bash/bin/ls b/Src/hysh/bin/ls similarity index 97% rename from Src/Hyperion-bash/bin/ls rename to Src/hysh/bin/ls index 97c7126..cf54cc4 100644 --- a/Src/Hyperion-bash/bin/ls +++ b/Src/hysh/bin/ls @@ -45,7 +45,7 @@ if cloptions.help then return end -local fs = require("sys.fs") +local fs = require("fs") local dir = args[1] or "" if dir:sub(1, 1) ~= "/" then dir = syscall.getcwd() .. "/" .. dir @@ -84,7 +84,7 @@ local function humanSize(size) size = size / 1024 scale = scale + 1 end - if scale == 0 then return tostring(size) end + if scale == 0 then return tostring(size).."B" end if size < 10 then return string.format("%.1f%s", size, sizePrefixes[scale]) end @@ -148,7 +148,7 @@ if cloptions.l then syscall.devctl(1, "sfgc", 1) end elseif isDir then - syscall.devctl(1, "sfgc", 4) + syscall.devctl(1, "sfgc", 14) printInline(v) syscall.devctl(1, "sfgc", 1) else @@ -177,7 +177,7 @@ for i, v in ipairs(list) do if isSym then syscall.devctl(1, "sfgc", 6) elseif isDir then - syscall.devctl(1, "sfgc", 4) + syscall.devctl(1, "sfgc", 14) else local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1) syscall.devctl(1, "sfgc", isExec and 3 or 1) diff --git a/Src/Hyperion-bash/bin/lsusers b/Src/hysh/bin/lsusers similarity index 100% rename from Src/Hyperion-bash/bin/lsusers rename to Src/hysh/bin/lsusers diff --git a/Src/Hyperion-bash/bin/mount b/Src/hysh/bin/mount similarity index 100% rename from Src/Hyperion-bash/bin/mount rename to Src/hysh/bin/mount diff --git a/Src/Hyperion-bash/bin/passwd b/Src/hysh/bin/passwd similarity index 100% rename from Src/Hyperion-bash/bin/passwd rename to Src/hysh/bin/passwd diff --git a/Src/Hyperion-bash/bin/ps b/Src/hysh/bin/ps similarity index 91% rename from Src/Hyperion-bash/bin/ps rename to Src/hysh/bin/ps index 97a99ea..4004fc1 100644 --- a/Src/Hyperion-bash/bin/ps +++ b/Src/hysh/bin/ps @@ -1,3 +1,4 @@ +--:Minify:-- for i,v in ipairs(syscall.getTasks()) do local task = syscall.getTask(v) print(task.pid,task.username,task.name,task.status) diff --git a/Src/Hyperion-bash/bin/readlink b/Src/hysh/bin/readlink similarity index 100% rename from Src/Hyperion-bash/bin/readlink rename to Src/hysh/bin/readlink diff --git a/Src/Hyperion-bash/bin/su b/Src/hysh/bin/su similarity index 100% rename from Src/Hyperion-bash/bin/su rename to Src/hysh/bin/su diff --git a/Src/Hyperion-bash/bin/sudo b/Src/hysh/bin/sudo similarity index 98% rename from Src/Hyperion-bash/bin/sudo rename to Src/hysh/bin/sudo index a843cba..6fc29eb 100644 --- a/Src/Hyperion-bash/bin/sudo +++ b/Src/hysh/bin/sudo @@ -1,5 +1,5 @@ --:Minify:-- -local fs = require("sys.fs") +local fs = require("fs") local cmdArgs = {...} local targetUser = "root" diff --git a/Src/hysh/bin/sysdump b/Src/hysh/bin/sysdump new file mode 100644 index 0000000..d027d7d --- /dev/null +++ b/Src/hysh/bin/sysdump @@ -0,0 +1,10 @@ +--:Minify:-- +local path=... +path=path or "/dev/tty/1" +local syscalls=syscall.sysdump() +local fd=syscall.open(path,"w") +for i=1, #syscalls do + syscall.write(fd,syscalls[i].."\n") +end +syscall.write(fd,"Total # of syscalls: "..tostring(#syscalls)) +syscall.close(fd) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/umount b/Src/hysh/bin/umount similarity index 100% rename from Src/Hyperion-bash/bin/umount rename to Src/hysh/bin/umount diff --git a/Src/Hyperion-bash/bin/useradd b/Src/hysh/bin/useradd similarity index 100% rename from Src/Hyperion-bash/bin/useradd rename to Src/hysh/bin/useradd diff --git a/Src/Hyperion-bash/bin/userdel b/Src/hysh/bin/userdel similarity index 97% rename from Src/Hyperion-bash/bin/userdel rename to Src/hysh/bin/userdel index 8f4e798..e4d5842 100644 --- a/Src/Hyperion-bash/bin/userdel +++ b/Src/hysh/bin/userdel @@ -29,7 +29,7 @@ if not ok then end if removeHome and pwent and pwent.homedir then - local fs = require("sys.fs") + local fs = require("fs") local ok2, err2 = pcall(function() local function rmdir(path) for _, f in ipairs(fs.list(path) or {}) do diff --git a/Src/Hyperion-bash/bin/usermod b/Src/hysh/bin/usermod similarity index 100% rename from Src/Hyperion-bash/bin/usermod rename to Src/hysh/bin/usermod diff --git a/Src/Hyperion-bash/bin/yes b/Src/hysh/bin/yes similarity index 91% rename from Src/Hyperion-bash/bin/yes rename to Src/hysh/bin/yes index 9490c89..bf0d571 100644 --- a/Src/Hyperion-bash/bin/yes +++ b/Src/hysh/bin/yes @@ -1,3 +1,4 @@ +--:Minify:-- local args = {...} while true do if #args == 0 then diff --git a/Src/iniparse/lib/iniparse b/Src/iniparse/lib/iniparse new file mode 100644 index 0000000..f6b0e4b --- /dev/null +++ b/Src/iniparse/lib/iniparse @@ -0,0 +1,38 @@ +local ini = {} + +function ini.parse(str) + local config = {} + local section = nil + + for line in str:gmatch("[^\r\n]+") do + -- trim whitespace + line = line:match("^%s*(.-)%s*$") + + -- skip empty lines and comments + if line ~= "" and not line:match("^[;#]") then + -- section + local sec = line:match("^%[(.-)%]$") + if sec then + section = sec + config[section] = config[section] or {} + else + -- key=value + local key, value = line:match("^(.-)=(.*)$") + if key then + key = key:match("^%s*(.-)%s*$") + value = value:match("^%s*(.-)%s*$") + + if section then + config[section][key] = value + else + config[key] = value + end + end + end + end + end + + return config +end + +return ini \ No newline at end of file diff --git a/Src/json/lib/json b/Src/json/lib/json new file mode 100644 index 0000000..484c997 --- /dev/null +++ b/Src/json/lib/json @@ -0,0 +1,388 @@ +--:Minify:-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/lua b/Src/lua/bin/lua similarity index 96% rename from Src/Hyperion-bash/bin/lua rename to Src/lua/bin/lua index e10d3ff..d7a1a7b 100644 --- a/Src/Hyperion-bash/bin/lua +++ b/Src/lua/bin/lua @@ -260,17 +260,17 @@ local function getUserInput(prompt, history) while true do local key = syscall.read(0) if key and key ~= "" then - if key == "\19" then + if key == "" then if cursor > 1 then cursor = cursor - 1; dirty = true end - elseif key == "\20" then + elseif key == "" then if cursor <= #input then cursor = cursor + 1; dirty = true end - elseif key == "\17" then + elseif key == "" 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 + elseif key == "" then if histIdx > 1 then histIdx = histIdx - 1 input = history[#history - histIdx + 1] @@ -288,9 +288,9 @@ local function getUserInput(prompt, history) 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 + elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then + input=string.sub(input,1,cursor-1)..key..string.sub(input,cursor) + cursor=cursor+1;dirty=true end end local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0 @@ -299,8 +299,6 @@ local function getUserInput(prompt, history) 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") diff --git a/Src/Hyperion-bash/bin/micro b/Src/micro/bin/micro similarity index 95% rename from Src/Hyperion-bash/bin/micro rename to Src/micro/bin/micro index 3a21d9d..63b49ff 100644 --- a/Src/Hyperion-bash/bin/micro +++ b/Src/micro/bin/micro @@ -223,7 +223,7 @@ local function prompt(label, default) tbg(16); tfg(1) local key = syscall.read(0) if not key or key == "" then sleep(0.02) - elseif key == "\27" then return nil + elseif key == "" then return nil elseif key == "\n" then return inp elseif key == "\b" then if #inp > 0 then inp = inp:sub(1,-2) end else @@ -359,31 +359,29 @@ while running do local key = syscall.read(0) if key and key ~= "" then local b = key:byte(1) - if key == "\17" then moveCursorUp(map); dirty=true - elseif key == "\18" then moveCursorDown(map); dirty=true - elseif key == "\19" then - if cx > 1 then cx=cx-1 - elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end - dirty=true - elseif key == "\20" then + if key == "" then moveCursorUp(map); dirty=true + elseif key == "" then moveCursorDown(map); dirty=true + elseif key == "" then if cx <= #lines[cy] then cx=cx+1 elseif cy < #lines then cy=cy+1; cx=1 end dirty=true + elseif key == "" then + if cx > 1 then cx=cx-1 + elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end + dirty=true + elseif key == "" then cx=1; dirty=true + elseif key == "" then cx=#lines[cy]+1; dirty=true + elseif key == "[5~" then for _=1,ROWS do moveCursorUp(map) end; dirty=true + elseif key == "[6~" then for _=1,ROWS do moveCursorDown(map) end; dirty=true + elseif key == "[3~" then delRight() elseif key == "\n" then newline() elseif key == "\b" then delLeft() elseif key == "\t" then for _=1,4 do insChar(" ") end - elseif b == 1 then cx=1; dirty=true - elseif b == 2 then - for _=1,ROWS do moveCursorUp(map) end; dirty=true - elseif b == 4 then delRight() - elseif b == 5 then cx=#lines[cy]+1; dirty=true elseif b == 6 then local p=prompt("Find: ",sPat); dirty=true if p then sPat=p; sLine=0; findNext() end elseif b == 7 then goToLine() elseif b == 11 then cutLine() - elseif b == 12 then - for _=1,ROWS do moveCursorDown(map) end; dirty=true elseif b == 14 then if sPat=="" then local p=prompt("Find: ",""); dirty=true @@ -415,8 +413,6 @@ while running do redraw() dirty = false end - - sleep(0.05) end tclear(); tfg(1); tbg(16); tpos(1,1) diff --git a/Src/Hyperion-bash/bin/sed b/Src/sed/bin/sed similarity index 100% rename from Src/Hyperion-bash/bin/sed rename to Src/sed/bin/sed diff --git a/Src/spm/bin/spm b/Src/spm/bin/spm new file mode 100644 index 0000000..9756ac9 --- /dev/null +++ b/Src/spm/bin/spm @@ -0,0 +1,6 @@ +local args={...} + +local json=require("json") +local http=require("http") +local rootRepo="https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/spm/spm.src" + diff --git a/Src/sysinit/$PKGCONFIG.ini b/Src/sysinit/$PKGCONFIG.ini new file mode 100644 index 0000000..c5153d4 --- /dev/null +++ b/Src/sysinit/$PKGCONFIG.ini @@ -0,0 +1 @@ +[symlinks] diff --git a/Src/sysinit/sbin/init b/Src/sysinit/sbin/init new file mode 100644 index 0000000..5071168 --- /dev/null +++ b/Src/sysinit/sbin/init @@ -0,0 +1,4 @@ +local args={...} +syscall.remove("/sbin/init") +syscall.symlink("/usr/lib/sysinit/sysinit", "/sbin/init") +syscall.exec("/sbin/init", args) \ No newline at end of file diff --git a/Src/Hyperion-core/sbin/init.lua b/Src/sysinit/usr/lib/sysinit/sysinit similarity index 97% rename from Src/Hyperion-core/sbin/init.lua rename to Src/sysinit/usr/lib/sysinit/sysinit index 574c93a..58be0c9 100644 --- a/Src/Hyperion-core/sbin/init.lua +++ b/Src/sysinit/usr/lib/sysinit/sysinit @@ -1,6 +1,6 @@ --:Minify:-- local kernel=... -local fs=require("sys.fs") +local fs=require("fs") for i,v in pairs(kernel.processes) do kernel.log("Spawning kernel task "..i) @@ -41,6 +41,6 @@ for i,v in ipairs(files) do end while true do - sleep(1) + sleep(5) kernel.saveLog() end \ No newline at end of file diff --git a/Test/HyperionOS-units/bin/hunit b/Test/HyperionOS-units/bin/hunit index 7e96c8d..cf6e040 100644 --- a/Test/HyperionOS-units/bin/hunit +++ b/Test/HyperionOS-units/bin/hunit @@ -1,4 +1,4 @@ -local fs=require("sys.fs") +local fs=require("fs") local units=fs.list("/usr/lib/hunit/") fs.mkdir("/tmp/hunit/") local errors={} diff --git a/Src/Hyperion-bash/bin/looptest b/Test/HyperionOS-units/bin/looptest similarity index 100% rename from Src/Hyperion-bash/bin/looptest rename to Test/HyperionOS-units/bin/looptest diff --git a/Src/Hyperion-bash/bin/socktest b/Test/HyperionOS-units/bin/socktest similarity index 100% rename from Src/Hyperion-bash/bin/socktest rename to Test/HyperionOS-units/bin/socktest diff --git a/Test/HyperionOS-units/usr/lib/hunit/dir.unit b/Test/HyperionOS-units/usr/lib/hunit/dir.unit index 78c5a3f..1d3c79e 100644 --- a/Test/HyperionOS-units/usr/lib/hunit/dir.unit +++ b/Test/HyperionOS-units/usr/lib/hunit/dir.unit @@ -1,3 +1,3 @@ -local fs = require("sys.fs") +local fs = require("fs") assert(fs.mkdir("/tmp/hunit/testdir"), "failed to make directory") assert(fs.isDir("/tmp/hunit/testdir"), "directory does not exist") \ No newline at end of file diff --git a/build.py b/build.py index f6e3b23..d3b0765 100644 --- a/build.py +++ b/build.py @@ -6,8 +6,10 @@ Usage: Targets: build build-mini + build-micro build-test build-mini-test + build-micro-test clean Arch flags: @@ -23,9 +25,6 @@ import sys import shutil import argparse import subprocess -import hashlib -import random -import string from pathlib import Path from typing import Union @@ -48,7 +47,34 @@ def clean(): print("Nothing to clean.") -def process_root(src_root: Path, out_root: Path, minify: bool): +def has_minify_header(path: Path) -> bool: + try: + with path.open("r", encoding="utf-8", errors="ignore") as f: + for _ in range(3): + if "--:Minify:--" in f.readline(): + return True + except OSError: + pass + return False + + +def minify_file(src: Path) -> str: + result = subprocess.run( + ["luamin.cmd", "-f", str(src)], + capture_output=True, + text=True + ) + if result.returncode != 0: + print(f" ! luamin failed: {result.stderr.strip()}", file=sys.stderr) + sys.exit(1) + return result.stdout + + +def compress_lz4(data: bytes) -> bytes: + return lz4.frame.compress(data) + + +def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool): print(f"Building from {src_root}") print(f"Output to {out_root}") print() @@ -64,21 +90,28 @@ def process_root(src_root: Path, out_root: Path, minify: bool): continue rel = src.relative_to(pkg_dir) + + if rel.name=="$PKGCONFIG.ini": + continue + dst = out_root / rel dst.parent.mkdir(parents=True, exist_ok=True) print(f" Processing: {src.relative_to(src_root)}") - if minify and has_minify_header(src): - print(" > Minifying") - result = subprocess.run( - ["luamin", "-f", str(src)], - capture_output=True, text=True - ) - if result.returncode != 0: - print(f" ! luamin failed: {result.stderr.strip()}", file=sys.stderr) - sys.exit(1) - dst.write_text(result.stdout, encoding="utf-8") + if has_minify_header(src): + if minify: + print(" > Minifying") + content = minify_file(src) + if micro: + print(" > LZ4 compressing") + compressed = compress_lz4(content.encode("utf-8")) + dst.write_bytes(compressed) + else: + dst.write_text(content, encoding="utf-8") + else: + print(" > Copying") + shutil.copy2(src, dst) else: print(" > Copying") shutil.copy2(src, dst) @@ -88,42 +121,22 @@ def process_root(src_root: Path, out_root: Path, minify: bool): def install_bootloader(arch: str, release: bool): boot_dir = BUILD_ROOT / "$" / ARCH_BOOT_DIR[arch] - boot_lua = boot_dir / "boot.lua" eeprom = boot_dir / "eeprom" - for src in (boot_lua, eeprom): - if not src.exists(): - print(f" ! Bootloader file not found: {src}", file=sys.stderr) - sys.exit(1) - - print(f" Installing: boot.lua -> Build/boot.lua") - shutil.copy2(boot_lua, BUILD_ROOT / "boot.lua") - eeprom_dst_name = "startup.lua" if release else "eeprom" print(f" Installing: eeprom -> Build/{eeprom_dst_name}") shutil.copy2(eeprom, BUILD_ROOT / eeprom_dst_name) -def has_minify_header(path: Path) -> bool: - try: - with path.open("r", encoding="utf-8", errors="ignore") as f: - for _ in range(3): - if "--:Minify:--" in f.readline(): - return True - except OSError: - pass - return False - - -def run_build(minify: bool, include_test: bool, arch: Union[str, None], release: bool): +def run_build(minify: bool, micro: bool, include_test: bool, arch: Union[str, None], release: bool): clean() BUILD_ROOT.mkdir() out_root = BUILD_ROOT / "$" if arch else BUILD_ROOT - process_root(SRC_ROOT, out_root, minify) + process_root(SRC_ROOT, out_root, minify, micro) if include_test: - process_root(TEST_ROOT, out_root, minify) + process_root(TEST_ROOT, out_root, minify, micro) if arch: print("Installing bootloader files ...") @@ -131,46 +144,6 @@ def run_build(minify: bool, include_test: bool, arch: Union[str, None], release: print() -def main(): - parser = argparse.ArgumentParser(description="HyperionOS build script") - parser.add_argument("target", choices=["build", "build-mini", "build-test", "build-mini-test", "clean"]) - parser.add_argument("--arch", choices=["cct", "oc"], default=None, - help="Target architecture (cct or oc)") - parser.add_argument("--release", dest="release", action="store_true", default=True, - help="Release build: eeprom placed as startup.lua (default)") - parser.add_argument("--dev", dest="release", action="store_false", - help="Dev build: boot.lua and eeprom copied unchanged") - parser.add_argument( - "--makeuser", metavar=("USERNAME", "PASSWORD"), nargs=2, action="append", - default=[], - help=( - "Pre-create a user on first boot (dev builds only). " - "May be specified multiple times. " - "Example: --makeuser root secretpass --makeuser alice alicepass" - ), - ) - - args = parser.parse_args() - - if args.makeuser and args.release: - parser.error("--makeuser is only allowed with --dev builds") - - if args.target == "clean": - clean() - return - - minify = "mini" in args.target - include_test = "test" in args.target - - run_build(minify=minify, include_test=include_test, arch=args.arch, release=args.release) - - if args.makeuser: - print("Injecting first-boot user setup ...") - inject_makeusers(args.makeuser, args.arch) - print() - - print("Build complete.") - def _make_firstboot_kmod(users): lines = [] lines.append("local kernel = ...") @@ -203,7 +176,7 @@ def _make_firstboot_kmod(users): lines.append("do") lines.append(" local ok, err = pcall(function()") - lines.append(" kernel.vfs.remove('/lib/modules/hyperion/50_firstboot_users.kmod')") + lines.append(" kernel.vfs.remove('/lib/modules/Hyperion/50_firstboot_users.kmod')") lines.append(" end)") lines.append(" if not ok then") lines.append(" kernel.log('FIRSTBOOT: could not self-delete: ' .. tostring(err), 'WARN')") @@ -215,11 +188,56 @@ def _make_firstboot_kmod(users): def inject_makeusers(users, arch): base = BUILD_ROOT / "$" if arch else BUILD_ROOT - kmod_path = base / "lib" / "modules" / "hyperion" / "50_firstboot_users.kmod" + kmod_path = base / "lib" / "modules" / "Hyperion" / "50_firstboot_users.kmod" kmod_path.parent.mkdir(parents=True, exist_ok=True) kmod_path.write_text(_make_firstboot_kmod(users), encoding="utf-8") print(" Wrote first-boot user setup -> " + str(kmod_path.relative_to(BUILD_ROOT))) +def main(): + parser = argparse.ArgumentParser(description="HyperionOS build script") + parser.add_argument("target", choices=["build", "build-mini", "build-micro", "build-test", "build-mini-test", "build-micro-test", "clean"]) + parser.add_argument("--arch", choices=["cct", "oc"], default=None, + help="Target architecture (cct or oc)") + parser.add_argument("--release", dest="release", action="store_true", default=True, + help="Release build: eeprom placed as startup.lua (default)") + parser.add_argument("--dev", dest="release", action="store_false", + help="Dev build: boot.lua and eeprom copied unchanged") + parser.add_argument( + "--makeuser", metavar=("USERNAME", "PASSWORD"), nargs=2, action="append", + default=[], + help=( + "Pre-create a user on first boot (dev builds only). " + "May be specified multiple times. " + "Example: --makeuser root secretpass --makeuser alice alicepass" + ), + ) + + args = parser.parse_args() + + if args.makeuser and args.release: + parser.error("--makeuser is only allowed with --dev builds") + + if args.target == "clean": + clean() + return + + minify = "mini" in args.target or "micro" in args.target + micro = "micro" in args.target + include_test = "test" in args.target + + if micro: + import lz4.block + + run_build(minify=minify, micro=micro, include_test=include_test, arch=args.arch, release=args.release) + + if args.makeuser: + print("Injecting first-boot user setup ...") + inject_makeusers(args.makeuser, args.arch) + print() + + print("Build complete.") + + if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/building.md b/building.md index 585f45e..f26e86c 100644 --- a/building.md +++ b/building.md @@ -8,24 +8,23 @@ Run: make build ``` -Optional variables: +**Optional variables:** -* **`ARCH=`** +* **`ARCH=`** – Select bootloader type: - * `cct` Build using the cct bootloader - * `oc` Build using the oc bootloader + * `cct` – Build using the CCT bootloader + * `oc` – Build using the OC bootloader -* **`DEV=1`** +* **`DEV=1`** – Enable development mode: - * Builds in development mode - * Bootloader does not start automatically on system startup + * Bootloader does **not** start automatically on system startup -If `DEV` is not specified: +**Default behavior (if `DEV` is not specified):** -* Default is release mode -* Bootloader starts automatically on system startup +* Builds in **release mode** +* Bootloader starts automatically on system startup -**Examples** +**Examples:** ```bash make build ARCH=cct @@ -42,38 +41,46 @@ Run: python build.py build ``` -Optional arguments: +**Optional arguments:** -* **`--arch {cct|oc}`** - Select bootloader +* **`--arch {cct|oc}`** – Select bootloader: - * `cct` Use the cct bootloader - * `oc` Use the oc bootloader + * `cct` – Use the CCT bootloader + * `oc` – Use the OC bootloader -* **`--dev`** +* **`--dev`** – Development mode: - * Development mode - * Bootloader does not start automatically. You must run `eeprom` in CraftOS to start Hyperion. + * Bootloader does **not** start automatically + * You must run `eeprom` in CraftOS to start Hyperion -* **`--release`** (default) +* **`--release`** (default) – Release mode: - * Release mode * Bootloader starts automatically - -* **`--makeuser username password`** - Makes a username upon startup. Only works for `--dev` builds. - - * `--makeuser root rootpass` - - Makes the root account already exist on first boot with rootpass as password - - * `--makeuser root rootpass --makeuser alice alicepass` - - Makes the root account and alice account already exist on first boot with defined passwords -**Examples** +* **`--makeuser username password`** – Pre-create user accounts (only works with `--dev` builds): + + ```bash + --makeuser root rootpass + --makeuser root rootpass --makeuser alice alicepass + ``` + + * Example: The first command creates the `root` account with the given password on first boot + * Example: The second command creates both `root` and `alice` accounts with defined passwords on first boot + +**Examples:** ```bash python build.py build --arch cct python build.py build --arch oc --dev ``` + +--- + +### Build Requirements + +* **`build`** – No additional requirements +* **`build-mini`** – Requires [`luamin`](https://www.npmjs.com/package/luamin) +* **`build-micro`** – Requires: + + * [`luamin`](https://www.npmjs.com/package/luamin) + * [`LZ4 binaries`](https://github.com/lz4/lz4/releases) \ No newline at end of file diff --git a/install/data/Build.tar b/install/data/Build.tar new file mode 100644 index 0000000..b33dd6a Binary files /dev/null and b/install/data/Build.tar differ diff --git a/install/data/tarbad b/install/data/tarbad new file mode 100644 index 0000000..caa6983 --- /dev/null +++ b/install/data/tarbad @@ -0,0 +1,157 @@ + +local function octal_to_number(str) + str = str:gsub("%z", ""):match("^%s*(.-)%s*$") + return tonumber(str, 8) or 0 +end + +local function dedupe_path(path) + + local parts = {} + for p in path:gmatch("[^/]+") do table.insert(parts, p) end + + for prefix_len = 1, math.floor(#parts / 2) do + local ok = true + for i = 1, prefix_len do + if parts[i] ~= parts[i + prefix_len] then + ok = false + break + end + end + if ok then + local cleaned = {} + for i = 1, #parts - prefix_len do + cleaned[#cleaned + 1] = parts[i + prefix_len] + end + return table.concat(cleaned, "/") + end + end + + return path +end + + +local function make_dirs(root, path) + local cur = root + for part in path:gmatch("([^/]+)/") do + if not cur[part] then + cur[part] = { __type = "dir", __entries = {} } + end + cur = cur[part].__entries + end + return cur +end + +local function flatten(node, prefix) + local out = {} + prefix = prefix or "" + + for name, obj in pairs(node) do + local full = prefix .. name + if obj.__type == "file" then + out[#out+1] = { + name = full, + type = "file", + contents = obj.__contents + } + elseif obj.__type == "dir" then + out[#out+1] = { + name = full .. "/", + type = "dir", + contents = flatten(obj.__entries, full .. "/") + } + end + end + + return out +end + +local function unpack_tar(tarstr) + local i = 1 + local len = #tarstr + local root = {} + + while i + 512 <= len do + local header = tarstr:sub(i, i + 511) + + if header:match("^\0+$") then break end + + local name_raw = header:sub(1, 100):gsub("%z.*", "") + local prefix_raw = header:sub(346, 500):gsub("%z.*", "") + + local name + if prefix_raw ~= "" then + name = prefix_raw .. "/" .. name_raw + else + name = name_raw + end + + name = name:gsub("^%./", ""):gsub("/+", "/") + + name = dedupe_path(name) + + local size = octal_to_number(header:sub(125,136)) + local typeflag = header:sub(157,157) + + i = i + 512 + + local contents = tarstr:sub(i, i + size - 1) + local pad = (512 - (size % 512)) % 512 + i = i + size + pad + + if name == "" then goto continue end + + local is_dir = typeflag == "5" or name:sub(-1) == "/" + + local clean_name = name:gsub("/$", "") + if clean_name == "" then goto continue end + + local parent_path = clean_name:match("(.+)/") + local fname = clean_name:match("([^/]+)$") + if not fname then goto continue end + + local parent = root + if parent_path then + parent = make_dirs(root, parent_path .. "/") + end + + if is_dir then + parent[fname] = parent[fname] or { __type = "dir", __entries = {} } + else + parent[fname] = { __type = "file", __contents = contents } + end + + ::continue:: + end + + return flatten(root) +end + +local function write_directory(prefix, items) + for _, v in ipairs(items) do + if v.type == "dir" then + fs.makeDir(prefix..v.name) + write_directory(prefix, v.contents) + elseif v.type == "file" then + local file = fs.open(prefix..v.name, "w") + file.write(v.contents) + file.close() + end + end +end + +local in_tar = ({...})[1] +local out_dir = ({...})[2] + +if not in_tar or not out_dir then + print("Usage: unpack_tar ") + return +end + +local f = fs.open(in_tar, "r") +local tarstr = f.readAll() +f.close() + +local list = unpack_tar(tarstr) +write_directory(out_dir, list) + +print("TAR extracted into: " .. out_dir) diff --git a/install/installcc.lua b/install/installcc.lua new file mode 100644 index 0000000..fe683ca --- /dev/null +++ b/install/installcc.lua @@ -0,0 +1,27 @@ +print("Hello, World!") +sleep(1) +term.clear() +print("Do you want to install HyperionOS? [Y/n]") +local input=read() +if input=="y" or input=="Y" or input=="" then + goto install +else + goto exit +end + +::install:: +print("Installing tar but bad...") +shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/1.2-dev/install/data/tarbad /tar.lua") +print("Installing HyperionOS...") +print("Installing precompiled tar") +shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/1.2-dev/install/data/Build.tar /Build.tar") +shell.run("tar Build.tar /") +print("Removing tar but bad...") +shell.run("rm /tar.lua") +shell.run("rm $") +shell.run("cp Build $") +shell.run("rm Build") +shell.run("rm Build.tar") +fs.copy("/$/boot/cct/eeprom","/startup.lua") +dofile("startup.lua") +::exit:: \ No newline at end of file diff --git a/manifest.lua b/manifest.lua new file mode 100644 index 0000000..f6c8716 --- /dev/null +++ b/manifest.lua @@ -0,0 +1,662 @@ +--- @version 1.2.3 +--- @diagnostic disable: missing-return +--- @diagnostic disable: duplicate-set-field +syscall={} + +--- Sets home directory of User with corresponding uid to homedir +--- @param uid integer +--- @param homedir string +--- @return true|nil, nil|string +syscall.sethomedir=function(uid, homedir) end + +--- Reads amount from fd and returns content or nil +--- @param fd integer +--- @param amount? integer +--- @return string|nil +syscall.read=function(fd, amount) end + +--- Gets information of task with id of pid +---@param pid integer +---@return table|nil +syscall.getTask=function(pid) end + +--- Connects a client socket to a server address +---@param fd integer +---@param address string +---@return boolean, string|nil +syscall.connect=function(fd, address) end + +--- Get current working directory +--- @return string +syscall.getcwd=function() end + +--- Detach loop device (must be unmounted first) +--- @param id string +--- @return boolean, string|nil +syscall.lodetach=function(id) end + +--- Stops task with id of pid +--- @param pid integer +--- @return boolean, string|nil +syscall.stop=function(pid) return true end + +--- Receive bytes from socket (blocking poll, returns "" if no data) +--- @param fd integer +--- @param amount integer +--- @return string +syscall.recv=function(fd, amount) end + +--- Write data to file descriptor +--- @param fd integer +--- @param data string +--- @return boolean, string|nil +syscall.write=function(fd, data) end + +--- Get parent process ID +--- @return integer +syscall.getppid=function() end + +--- Get file information (metadata) +--- @param path string +--- @return table|nil +syscall.lstat=function(path) end + +--- Open a file with mode ("r", "w", etc.) +--- @param path string +--- @param mode string +--- @return integer|nil, string|nil +syscall.open=function(path, mode) end + +--- Seek in a file descriptor +--- @param fd integer +--- @param offset integer +--- @param whence integer +--- @return integer|nil +syscall.lseek=function(fd, offset, whence) end + +--- Set system hostname +--- @param hostname string +--- @return boolean +syscall.setHostname=function(hostname) end + +--- Change root directory +--- @param path string +--- @return boolean, string|nil +syscall.chroot=function(path) end + +--- Duplicate file descriptor +--- @param src integer +--- @param dst integer +--- @return integer|nil, string|nil +syscall.dup2=function(src, dst) end + +--- Get current process ID +--- @return integer +syscall.getpid=function() end + +--- Change ownership of a file descriptor +--- @param fd integer +--- @param uid integer +--- @param gid integer +--- @return boolean, string|nil +syscall.fchown=function(fd, uid, gid) end + +--- Close a file descriptor +--- @param fd integer +--- @return boolean, string|nil +syscall.close=function(fd) end + +--- Unmount a target +--- @param target string +--- @return boolean, string|nil +syscall.umount=function(target) end + +--- Get all task IDs +--- @return integer[] +syscall.getTasks=function() end + +--- Dump all syscalls for debugging +--- @return table +syscall.sysdump=function() end + +--- Change permissions of a file descriptor +--- @param fd integer +--- @param perms integer +--- @return boolean, string|nil +syscall.fchmod=function(fd, perms) end + +--- Get system hostname +--- @return string +syscall.getHostname=function() end + +--- Listen for incoming connections +--- @param fd integer +--- @param backlog integer +--- @return boolean, string|nil +syscall.listen=function(fd, backlog) end + +--- Duplicate a file descriptor +--- @param fd integer +--- @return integer|nil +syscall.dup=function(fd) end + +--- Read GPIO pin +--- @param pin integer +--- @return number|nil +syscall.gpio_read=function(pin) end + +--- Get SUID bit from fd +--- @param fd integer +--- @return boolean +syscall.fget_suid=function(fd) end + +--- Write GPIO pin +--- @param pin integer +--- @param data number +--- @return boolean +syscall.gpio_write=function(pin, data) end + +--- Set password for user +--- @param uid integer +--- @param newPassword string +--- @return boolean, string|nil +syscall.setpassword=function(uid, newPassword) end + +--- Set environment variable +--- @param key string +--- @param value string +--- @return boolean +syscall.setEnviron=function(key, value) end + +--- Setup a loop device with filePath +--- @param filePath string +--- @param forceImage boolean +--- @return string|nil, string|nil +syscall.losetup=function(filePath, forceImage) end + +--- Reboot the system +syscall.reboot=function() end + +--- Get current user ID +--- @return integer +syscall.getuid=function() end + +--- Send signal to task +--- @param pid integer +--- @param sigid integer|string +--- @return boolean, string|nil +syscall.sigsend=function(pid, sigid) end + +--- Sleep current task for time seconds +--- @param time number +syscall.sleep=function(time) end + +--- Exit current task +--- @param code integer|nil +syscall.exit=function(code) end + +--- Get environment variable +--- @param key string +--- @return string|nil +syscall.getEnviron=function(key) end + +--- Continue a stopped task +--- @param pid integer +--- @return boolean, string|nil +syscall.continue=function(pid) end + +--- Create a socket +--- @param domain integer +--- @param socktype integer +--- @return integer|nil +syscall.socket=function(domain, socktype) end + +--- Log a message +--- @param text string +--- @param tag string +--- @param color integer +syscall.log=function(text, tag, color) end + +--- Write an image to disk +--- @param imgStr string +--- @param destPath string +--- @return boolean, string|nil +syscall.loimgwrite=function(imgStr, destPath) end + +--- Check if file exists +--- @param path string +--- @return boolean +syscall.exists=function(path) end + +--- Set user ID of current task +--- @param uid integer +--- @return boolean, string|nil +syscall.setuid=function(uid) end + +--- Replace current task with executable +--- @param path string +--- @param args? table +--- @param envars? table +syscall.exec=function(path, args, envars) end + +--- Spawn a new task from executable +--- @param path string +--- @param name? string +--- @param envars? table +--- @param args? table +--- @param tgid? integer +--- @return integer +syscall.execspawn=function(path, name, envars, args, tgid) end + +--- Create image from file +--- @param srcPath string +--- @return string|nil +syscall.loimgcreate=function(srcPath) end + +--- Get system time in ms +--- @return number +syscall.time=function() end + +--- Create a new user +--- @param username string +--- @param password string +--- @param gid integer +--- @param homedir string +--- @param shell string +--- @return integer|nil +syscall.newuser=function(username, password, gid, homedir, shell) end + +--- Spawn a new task from function +--- @param func function +--- @param name? string +--- @param envars? table +--- @param args? table +--- @param tgid? integer +--- @return integer +syscall.spawn=function(func, name, envars, args, tgid) end + +--- Collect exit code of a dead child task +--- @param pid integer +--- @return boolean, integer|string +syscall.collect=function(pid) end + +--- Set shell of user +--- @param uid integer +--- @param shell string +--- @return boolean +syscall.setshell=function(uid, shell) end + +--- Device control +--- @param fd integer +--- @param funcname string +--- @param ... any +--- @return any +syscall.devctl=function(fd, funcname, ...) end + +--- List all users +--- @return table +syscall.listusers=function() end + +--- Unlock a user account +--- @param uid integer +--- @return boolean +syscall.unlockuser=function(uid) end + +--- Mount a disk or loop device +--- @param target string +--- @param diskOrId string +--- @return boolean, string|nil +syscall.mount=function(target, diskOrId) end + +--- Accept a client connection on a socket +--- @param fd integer +--- @return integer|nil +syscall.accept=function(fd) end + +--- List loop devices +--- @return table +syscall.lolist=function() end + +--- Read a symbolic link +--- @param path string +--- @return string|nil +syscall.readlink=function(path) end + +--- Delete a user +--- @param uid integer +--- @return boolean +syscall.deleteuser=function(uid) end + +--- Remove a file +--- @param path string +--- @return boolean, string|nil +syscall.remove=function(path) end + +--- Get type of a path (file, dir, link) +--- @param path string +--- @return string|nil +syscall.type=function(path) end + +--- Elevate to another user with password +--- @param targetUsername string +--- @param password string +--- @return boolean +syscall.elevate=function(targetUsername, password) end + +--- Make a directory +--- @param path string +--- @return boolean, string|nil +syscall.mkdir=function(path) end + +--- Get UID by username +--- @param username string +--- @return integer|nil +syscall.getuidbyname=function(username) end + +--- Get current user name +--- @return string +syscall.whoami=function() end + +--- Send file content +--- @param src string +--- @param dest string +--- @param amount integer +--- @return boolean, string|nil +syscall.sendfile=function(src, dest, amount) end + +--- Change username of user +--- @param uid integer +--- @param newUsername string +--- @return boolean +syscall.setusername=function(uid, newUsername) end + +--- Get effective UID +--- @return integer +syscall.geteuid=function() end + +--- Login user +--- @param username string +--- @param password string +--- @return boolean +syscall.login=function(username, password) end + +--- Get system hostname +--- @return string +syscall.getHost=function() end + +--- Get system uptime in ms +--- @return number +syscall.getUptime=function() end + +--- HTTP GET request +--- @param url string +--- @param headers table|nil +--- @return string|nil +syscall.httpget=function(url, headers) end + +--- Get file metadata +--- @param path string +--- @return table|nil +syscall.stat=function(path) end + +--- Create symbolic link +--- @param target string +--- @param linkPath string +--- @return boolean, string|nil +syscall.symlink=function(target, linkPath) end + +--- Read from fd at offset +--- @param fd integer +--- @param count integer +--- @param offset integer +--- @return string|nil +syscall.pread=function(fd, count, offset) end + +--- Change current working directory +--- @param path string +--- @return boolean, string|nil +syscall.chdir=function(path) end + +--- Get system architecture +--- @return string +syscall.arch=function() end + +--- Write to fd at offset +--- @param fd integer +--- @param data string +--- @param offset integer +--- @return boolean, string|nil +syscall.pwrite=function(fd, data, offset) end + +--- Shutdown socket +--- @param fd integer +--- @return boolean, string|nil +syscall.sockshutdown=function(fd) end + +--- Resolve hostname to IP +--- @param hostname string +--- @return string|nil +syscall.resolve=function(hostname) end + +--- Send data over socket +--- @param fd integer +--- @param data string +--- @return boolean, string|nil +syscall.send=function(fd, data) end + +--- Get file descriptor info +--- @param fd integer +--- @return table|nil +syscall.fstat=function(fd) end + +--- Change ownership of path +--- @param path string +--- @param uid integer +--- @param gid integer +--- @return boolean, string|nil +syscall.chown=function(path, uid, gid) end + +--- Flush file descriptor +--- @param fd integer +--- @return boolean, string|nil +syscall.fsync=function(fd) end + +--- Lock user account +--- @param uid integer +--- @return boolean +syscall.lockuser=function(uid) end + +--- Get username by UID +--- @param uid integer +--- @return string|nil +syscall.getUsername=function(uid) end + +--- Get socket name +--- @param fd integer +--- @return string|nil +syscall.getsockname=function(fd) end + +--- Bind socket to address +--- @param fd integer +--- @param address string +--- @return boolean, string|nil +syscall.bind=function(fd, address) end + +--- Kill a task +--- @param pid integer +--- @return boolean, string|nil +syscall.kill=function(pid) end + +--- Set GID for user +--- @param uid integer +--- @param gid integer +--- @return boolean +syscall.setgid=function(uid, gid) end + +--- Get peer name of socket +--- @param fd integer +--- @return string|nil +syscall.getpeername=function(fd) end + +--- Set signal handler +--- @param handler function +syscall.sigcatch=function(handler) end + +--- Shutdown the system +syscall.shutdown=function() end + +--- Check file access mode +--- @param path string +--- @param mode string +--- @return boolean +syscall.access=function(path, mode) end + +--- Ignore current signal +syscall.sigignore=function() end + +--- Get user password hash +--- @param uid integer +--- @return string|nil +syscall.getpasswd=function(uid) end + +--- Get OS version +--- @return string +syscall.version=function() end + +--- Change file permissions +--- @param path string +--- @param perms integer +--- @return boolean +syscall.chmod=function(path, perms) end + +--- List directory contents +--- @param path string +--- @return table +syscall.listdir=function(path) end + + +---------------------------------------------- +--- STDLib manifest +---------------------------------------------- + +--- Gets the index of value or -1 +--- @param tabl table +--- @param value string|integer +--- @return integer +table.indexOf=function(tabl, value) end + +-- Returns true if tabl has key else false +--- @param tabl table +--- @param query string +--- @return boolean +table.hasKey=function(tabl, query) end + +--- Returns true if tabl has value else false +--- @param tabl table +--- @param query any +--- @return boolean +table.hasVal=function(tabl, query) end + +--- Creates a deepcopy of tabl +--- @param tabl table +--- @return table +table.deepcopy=function(tabl) end + +--- Returns the keys of tabl +--- @param tabl table +--- @return table +table.keys=function(tabl) end + +--- Returns the values of tabl +--- @param tabl table +--- @return table +table.values=function(tabl) end + +--- Returns a serialized version of tabl +--- @param tabl table +--- @return string +table.serialize=function(tabl) end + +--- Returns a merged table with a and b +--- @param ... table +--- @return table +table.merge=function(...) end + +--- Gets prefix of string with suffix +--- @param str string +--- @param suffix string +--- @return string +string.getPrefix=function(str, suffix) end + +--- Gets suffix of string with prefix +--- @param str string +--- @param prefix string +--- @return string +string.getSuffix=function(str, prefix) end + +--- Returns if sting has prefix +--- @param str string +--- @param prefix string +--- @return boolean +string.hasPrefix=function(str, prefix) end + +--- Returns if sting has suffix +--- @param str string +--- @param suffix string +--- @return boolean +string.hasSuffix=function(str, suffix) end + +--- Joins all args +--- @param str string +--- @param ... string +--- @return string +string.join=function(str, ...) end + +--- Joins all strings with delim +--- @param delim string +--- @param ... string +--- @return string +string.delim=function(delim, ...) end + +--- Splits a string by delim +--- @param str string +--- @param delim string +--- @return table +string.split=function(str, delim) end + +--- Replaces all instances of target with repl +--- @param str string +--- @param target string +--- @param repl string +--- @return string +string.replace=function(str, target, repl) end + +--- Converts a number to hex +--- @param num integer +--- @return string +toHex=function(num) end + +--- Returns if obj is equal to all in ... +--- @param obj any +--- @param ... any +--- @return boolean +isEqualToAll=function(obj, ...) end + +--- Returns if obj is equal to any in ... +--- @param obj any +--- @param ... any +--- @return boolean +isEqualToAny=function(obj, ...) end + +--- Prints text to stdout +--- @param ... any +print=function(...) end + +--- Prints text to stdout but with no trailing newline +--- @param ... any +printInline=function(...) end + +--- Prints text to stdout with format +--- @param fmt string +--- @param ... any +printf=function(fmt, ...) end \ No newline at end of file