64 Commits

Author SHA1 Message Date
5172da1b5b Merge pull request '1.2-dev' (#11) from 1.2-dev into main
Reviewed-on: #11
2026-03-12 19:32:16 -04:00
d08935b68a remove gotos 2026-03-12 19:31:37 -04:00
45b46cf3c4 Merge pull request 'main' (#10) from main into 1.2-dev
Reviewed-on: #10
2026-03-12 19:27:44 -04:00
aeea68bc9b Update install/data/tarbad 2026-03-12 12:12:48 -04:00
f983b13d56 Update install/installcc.lua
make it use right stuff
2026-03-12 12:10:37 -04:00
03c5b106c4 Update Src/Hyperion-kernel/boot/kernel.lua 2026-03-12 11:59:35 -04:00
08323e00ff Update install/data/tarbad 2026-03-12 11:56:58 -04:00
5e3cdbe40c Update install/installcc.lua 2026-03-12 11:51:55 -04:00
8762b8f022 Merge pull request '1.2-dev' (#9) from 1.2-dev into main
Reviewed-on: #9
2026-03-11 10:37:44 -04:00
677b2cccec user login fixed 2026-03-11 10:37:13 -04:00
a5e8624368 forgot to edit login 2026-03-11 08:54:47 -04:00
bbda3b3937 fixed elevate VULN 2026-03-11 08:52:41 -04:00
585d39bec2 Merge pull request '1.2-dev' (#8) from 1.2-dev into main
Reviewed-on: #8
2026-03-11 08:23:21 -04:00
b08b14763a i am a we bit stupid (fixed cct bug) 2026-03-11 08:22:53 -04:00
de6696003b fix capitaization part 2 2026-03-11 07:26:38 -04:00
9220281365 fix capitalization part 1 2026-03-11 07:25:52 -04:00
813ddabd9d fixed disk detection i think 2026-03-10 21:13:52 -04:00
5177639d71 Merge pull request 'update build.tar' (#7) from 1.2-dev into main
Reviewed-on: #7
i was dumb
2026-03-10 20:05:26 -04:00
528b4f31bd update build.tar 2026-03-10 20:04:53 -04:00
60162c7c57 Merge pull request '1.2.4' (#6) from 1.2-dev into main
Reviewed-on: #6
2026-03-10 20:02:20 -04:00
18f5c454bb fixed bug where cct drivers are inaccessable 2026-03-10 20:00:21 -04:00
849ecb7dd6 made procfs self 2026-03-10 19:41:54 -04:00
e41bd6bee7 Merge pull request '1.2-dev merge' (#5) from 1.2-dev into main
Reviewed-on: #5
2026-03-10 12:27:08 -04:00
359198c1ea Merge branch '1.2-dev' of https://git.astronand.dev/Hyperion/HyperionOS into 1.2-dev 2026-03-10 10:57:27 -04:00
beebf01223 remove tar during install 2026-03-10 10:57:10 -04:00
2d4ea1bbf4 Update README.md 2026-03-10 10:39:01 -04:00
ea3a7e99a7 Update install/installcc.lua 2026-03-10 10:34:36 -04:00
be0fe5dc5a install works (i think) 2026-03-10 10:32:32 -04:00
12669d9f82 adde /proc fs and working on install 2026-03-10 09:17:21 -04:00
f12159bfb9 fixed build script 2026-03-09 11:31:31 -04:00
1590e1f3f7 more reorganizeing and $PKGCONFIG.ini files added B to ls -lh for "Bytes" 2026-03-09 11:28:09 -04:00
a69f945b91 did some reorganizing 2026-03-06 09:57:45 -05:00
7da67899db finished hyperion manifest :D 2026-03-05 10:36:43 -05:00
62e032e4c5 made sysinit in /usr/lib/sysinit and sped up micro added kernel.firstBoot in kernel table 2026-03-05 09:53:30 -05:00
6fefa2d9ff cp should not copy perms 2026-03-03 12:25:25 -05:00
bb354cc706 mv syscallautofill 2026-03-03 11:45:05 -05:00
fabc061731 made lua not clear screen 2026-03-03 10:06:04 -05:00
82c3e2b346 fix ll 2026-03-03 08:57:45 -05:00
e2e1d5b8a5 making descriptions for syscalls 2026-03-03 08:52:55 -05:00
9342b9b2b3 Merge branch '1.2-dev' of https://git.astronand.dev/Hyperion/HyperionOS into 1.2-dev 2026-03-03 08:02:09 -05:00
9a7db6c243 syscalls now autocomplete is vsc 2026-03-03 08:02:05 -05:00
c7545e6947 Update build.py 2026-03-03 07:31:59 -05:00
b7f52dd17b working on syscall manifest and fixed anther exploit 2026-03-02 22:25:37 -05:00
eb5bed0f09 fixed asyncsyscall5 2026-03-02 21:58:51 -05:00
1827a463eb added ll for ghxx 2026-03-02 21:56:05 -05:00
b532a63fc6 fixed stupid dumbass mistake i made me dumb 2026-03-02 21:32:56 -05:00
4e5a4172bf hopfully fixed it omfg 2026-03-02 21:29:46 -05:00
31ce894fda fixed build script 2026-03-02 21:26:50 -05:00
16c900de84 fixed ls links, modules writeable 2026-03-02 21:23:35 -05:00
413afd96de Merge branch '1.2-dev' of https://git.astronand.dev/Hyperion/HyperionOS into 1.2-dev 2026-03-02 07:27:46 -05:00
a0a0ac69d4 remove old shell files 2026-03-02 07:27:45 -05:00
17453983ad Path traversal fixes, 26_tty removal, ctrl+key fixes 2026-03-01 00:21:02 -06:00
a6550aa069 fixed minify header and build script 2026-02-25 11:41:41 -05:00
02e7b3897c fixed tty naming 2026-02-25 09:03:32 -05:00
34b89a8e34 fixed build script 2026-02-25 08:04:53 -05:00
0eabfebd0f fixed new ghxx exploit 2026-02-25 08:01:28 -05:00
5b2e5eac65 added more libs and fixed build script 2026-02-24 17:54:09 -05:00
415064480a Patch the AsyncSyscall v4 exploit from working 2026-02-24 02:00:37 -06:00
f00453f703 Merge pull request '1.2-dev merge' (#5) from 1.2-dev into main
Reviewed-on: #5
2026-02-24 02:12:36 -05:00
ab1e847d1c Fix exploit with trailing whitespace 2026-02-24 00:48:20 -06:00
62a03bfe6b Potential windows case insensitive filesystem issue fix 2026-02-24 00:34:59 -06:00
e77a8b3636 AsyncSyscall3 exploit fix 2026-02-24 00:01:39 -06:00
6bb7f03a3e file permissions fixes 2026-02-23 23:50:37 -06:00
8798a2f4fe 2 potential vulnerability fixes 2026-02-23 23:26:21 -06:00
107 changed files with 3429 additions and 1042 deletions

View File

@@ -5,6 +5,7 @@
"syscall",
"printf",
"printInline",
"toHex"
"toHex",
"loadcstr"
]
}

View File

@@ -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.

View File

@@ -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("")

View File

@@ -1 +0,0 @@
syscall.devctl(1,"clear")

View File

@@ -1,2 +0,0 @@
local args = {...}
print(table.concat(args, " "))

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -1 +0,0 @@
print(syscall.getcwd())

View File

@@ -1,5 +0,0 @@
local syscalls=syscall.sysdump()
for i=1, #syscalls do
print(syscalls[i])
end
print("Total # of syscalls: "..tostring(#syscalls))

View File

@@ -1 +0,0 @@
print((syscall.getUsername() or "Unknown"))

View File

@@ -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)

View File

@@ -1,6 +0,0 @@
local sys = {}
local fs = require("sys.fs")
return sys

View File

@@ -1,5 +0,0 @@
local sys = {}
sys.fs = require("sys.fs")
sys.hpv = require("sys.hpv")
sys.ipc = require("sys.ipc")
return sys

View File

@@ -1,3 +0,0 @@
local ipc = {}
return ipc

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View File

@@ -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,

View File

@@ -6,31 +6,147 @@ 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
function peripheral.getNames()
local results = {}
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)
if native.isPresent(side) then
table.insert(results, side)
if native.hasType(side, "peripheral_hub") then
local remote = native.call(side, "getNamesRemote")
for _, name in ipairs(remote) do
table.insert(results, name)
end
end
end
end
return results
end
function peripheral.isPresent(name)
if native.isPresent(name) then
return true
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return true
end
end
return false
end
function peripheral.getType(peripheral)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.getType(peripheral)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "getTypeRemote", peripheral)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return table.unpack(mt.types)
end
end
function peripheral.hasType(peripheral, peripheral_type)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.hasType(peripheral, peripheral_type)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.types[peripheral_type] ~= nil
end
end
function peripheral.getMethods(name)
if native.isPresent(name) then
return native.getMethods(name)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "getMethodsRemote", name)
end
end
return nil
end
function peripheral.getNames()
local names = {}
function peripheral.getName(peripheral)
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.name
end
function peripheral.call(name, method, ...)
if native.isPresent(name) then
return native.call(name, method, ...)
end
for n = 1, #sides do
local side = sides[n]
if native.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)
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "callRemote", name, method, ...)
end
end
return nil
end
function peripheral.wrap(name)
local methods = peripheral.getMethods(name)
if not methods then
return nil
end
local types = { peripheral.getType(name) }
for i = 1, #types do types[types[i]] = true end
local result = setmetatable({}, {
__name = "peripheral",
name = name,
type = types[1],
types = types,
})
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)
end
end
return result
end
function peripheral.find(ty, filter)
local results = {}
for _, name in ipairs(peripheral.getNames()) do
if peripheral.hasType(name, ty) then
local wrapped = peripheral.wrap(name)
if filter == nil or filter(name, wrapped) then
table.insert(results, wrapped)
end
end
end
return names
return table.unpack(results)
end
local disks = {}
@@ -130,14 +246,9 @@ local function refresh()
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
for _, disk in ipairs({peripheral.find("drive")}) do
if disk.isDiskPresent() then
disks[tostring(disk.getDiskID())]=createDisk("cctdisk"..tostring(disk.getDiskID()), disk.getMountPath(), false, fs)
end
end
end

View File

@@ -1,4 +1,5 @@
-- :Minify:--
--:Minify:--
local kernel = ...
local apis = kernel.apis
local native = apis.peripheral
@@ -243,6 +244,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 +334,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 +353,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 +402,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

View File

@@ -1,3 +1,4 @@
U $;/
U devfs0000;/dev/
U tmpfs0000;/tmp/
U tmpfs0000;/tmp/
U procfs0000;/proc/

View File

@@ -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")
@@ -129,18 +130,22 @@ if not initCfgStatus then
end
kernel.config = config
local skip=false
for i,v in ipairs(split(fstab,"\n")) do
if v:sub(1,1)=="U" then
local id=""
for i=3,#v do
if v:sub(i,i)==";" then
if i==3 then kernel.log("Invalid fstab line... Skipping.","WARN") goto endline end
if i==3 then kernel.log("Invalid fstab line... Skipping.","WARN") skip = true break end
id=v:sub(3,i-1)
end
end
local path=v:sub(#id+4)
ifs.mount(id,path)
::endline::
if not skip then
local path=v:sub(#id+4)
ifs.mount(id,path)
else
skip=false
end
end
end
kernel.log("Disks initialized")
@@ -184,9 +189,16 @@ end
kernel.log("Gathering modules")
for _, i in ipairs(ifs.list("/lib/modules")) do
for _,v in ipairs(ifs.list("/lib/modules/"..i)) do
local prior=tonumber(v:sub(1,2))
modules[prior+1][#modules[prior+1]+1]="/lib/modules/"..i.."/"..v
local modlist = ifs.list("/lib/modules/"..i)
if not modlist then
kernel.log("WARNING: could not list /lib/modules/"..i.." (skipping)", "WARN", 8)
else
for _,v in ipairs(modlist) do
local prior=tonumber(v:sub(1,2))
if prior then
modules[prior+1][#modules[prior+1]+1]="/lib/modules/"..i.."/"..v
end
end
end
end
@@ -244,7 +256,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
@@ -252,19 +269,18 @@ for _,p in ipairs(modules) do
if kernel.config.showModLoad then kernel.log("Loading module "..v, "DBUG", 5) end
local code=ifs.readAllText(v)
if not code then
kernel.log("ModuReadErr: "..v, "WARN", 8)
goto skip
kernel.panic("Failed to read module "..v)
end
local func,err=load(code,"@"..v)
if not func then kernel.panic("ModuLoadErr: "..tostring(err)) goto skip end
if not func then kernel.panic("ModuLoadErr: "..tostring(err)) end
local status, err = xpcall(func,debug.traceback, kernel)
if not status then kernel.panic("ModuRunErr: "..tostring(err)) end
if kernel.config.showModLoad then kernel.log("Loaded module "..v, "DBUG", 5) end
::skip::
end
end
kernel.log("Kernel initialized successfully.")
kernel.saveLog()
kernel.status="running"
kernel.main()
if kernel.status=="panic" then

View File

@@ -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

View File

@@ -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

View File

@@ -1,214 +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 function writeMeta(dir, entries)
local diskDir = dir
if diskDir:sub(1,1) == "/" then diskDir = diskDir:sub(2) end
local metaPath = (diskDir == "" and ".meta" or diskDir .. "/.meta")
local data = string.char(META_VERSION)
for _, e in ipairs(entries) do
data = data .. makeEntry(e[1], e[2] or 0x00, e[3], e[4], e[5], e[6])
end
local ok, err = pcall(function()
local f = rootDisk:open(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
local REG = 0x00
-- All known /bin entries with their permissions
local BIN_ENTRIES = {
{"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},
{"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},
{"startup", REG, 0, 0, RWX_RX_RX},
}
-- Merge entries: always ensure all known entries exist with correct permissions.
-- This handles both fresh installs and upgrades (adds missing entries, upgrades
-- the on-disk format to v2 by rewriting).
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")
-- Read existing meta (may be v1 or v2)
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
-- Parse using the VFS parser (handles v0/v1/v2)
existing = kernel.vfs and kernel.vfs._parseMetafile and kernel.vfs._parseMetafile(raw) or {}
end
-- Add any missing entries (don't overwrite existing customised perms)
for _, e in ipairs(entries) do
if not existing[e[1]] then
existing[e[1]] = {
etype = e[2] or 0x00,
owner = e[3] or 0,
group = e[4] or 0,
perms = e[5] or RWX_RX_RX,
cmeta = e[6] or "",
}
end
end
-- Write back as v2
local data = string.char(META_VERSION)
for name, m in pairs(existing) do
data = data .. makeEntry(name, m.etype or 0x00, 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
local freshInstall = not rootDisk:fileExists(".meta")
if freshInstall then
kernel.log("Seeding filesystem permissions...", "INFO")
-- / (only on fresh install — these dirs are stable)
writeMeta("/", {
{"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},
})
writeMeta("/bin/startup", {
{"test.lua", REG, 0, 0, RWX_RX_RX},
})
writeMeta("/etc", {
{"passwd", REG, 0, 0, RW_R_R},
{"shadow", REG, 0, 0, RW____},
{"pam.d", REG, 0, 0, RWX_RX_RX},
})
writeMeta("/etc/pam.d", {
{"secret", REG, 0, 0, RW____},
})
writeMeta("/sbin", {
{"init.lua", REG, 0, 0, RWX_RX_RX},
})
writeMeta("/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},
})
writeMeta("/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 },
})
kernel.log("Filesystem permissions seeded.", "INFO")
else
kernel.log("Permissions already seeded, merging /bin updates...", "INFO")
end
-- Always merge /bin — adds missing entries and upgrades format to v2
mergeMeta("/bin", BIN_ENTRIES)
kernel.log("Permission module loaded.", "INFO")

View File

@@ -1,5 +0,0 @@
local args = {...}
local kernel = args[1]
local origLoad = load
--kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end

View File

@@ -1,4 +1,8 @@
-- :Minify:--
--:Minify:--
--- @diagnostic disable: duplicate-set-field
local kernel = ...
kernel.allowGlobalOverwrites = true
function string.hasSuffix(str, suffix)
return string.sub(str, #suffix + 1) == suffix
end
@@ -68,30 +72,24 @@ end
local function serialize(tbl, seen)
seen = seen or {}
-- If we've seen this table before, return a placeholder to prevent infinite loops
if seen[tbl] then return '"[Circular Reference]"' end
-- Mark this table as seen
seen[tbl] = true
local output = "{"
local first = true
for i, v in pairs(tbl) do
-- Handle comma placement more cleanly
if not first then output = output .. "," end
first = false
-- Serialize Key
if type(i) == "string" then
output = output .. "[\"" .. i .. "\"]="
elseif type(i) == "number" then
output = output .. "[" .. tostring(i) .. "]="
end
-- Serialize Value
if type(v) == "table" then
-- Pass the 'seen' table down to the recursive call
output = output .. serialize(v, seen)
elseif type(v) == "string" then
output = output .. "[=[" .. v .. "]=]"
@@ -175,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
@@ -210,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

View File

@@ -1,4 +1,4 @@
-- :Minify:--
--:Minify:--
local kernel = ...
local vfs = {}
kernel.vfs = vfs
@@ -60,21 +60,18 @@ local function parseMetafile(raw)
local etype, owner, group, perms, cmeta
if version == 0x02 then
-- v2: etype(1) + owner(2) + group(2) + perms(2) = 7 bytes
if p + 6 > #raw then break end
etype = raw:byte(p); p = p + 1
owner = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
group = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
perms = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
elseif version == 0x01 then
-- v1: etype(1) + owner(1) + group(1) + perms(2) = 5 bytes
if p + 4 > #raw then break end
etype = raw:byte(p); p = p + 1
owner = raw:byte(p); p = p + 1
group = raw:byte(p); p = p + 1
perms = raw:byte(p) + raw:byte(p+1) * 256; p = p + 2
else
-- v0: owner(1) + group(1) + perms(1) = 3 bytes
if p + 2 > #raw then break end
etype = 0x00
owner = raw:byte(p); p = p + 1
@@ -114,26 +111,25 @@ local function makeMetafile(meta)
return out
end
local function normalizePath(path)
local task = kernel.currentTask
local cwd = task.cwd or "/"
if path:sub(1,1) ~= "/" then path = cwd .. "/" .. path end
local parts = {}
for part in path:gmatch("[^/]+") do
if part == ".." then
if #parts > 0 then table.remove(parts) end
elseif part ~= "." and part ~= "" then
table.insert(parts, part)
end
local SAFE_COMPONENT_PATTERN = "^[A-Za-z0-9_.+%-%@%(%)%[%]]+$"
local function tokenizePath(path)
local isAbsolute = (path:sub(1,1) == "/")
local tokens = {}
for comp in (path .. "/"):gmatch("([^/]*)/") do
table.insert(tokens, comp)
end
local result = "/" .. table.concat(parts, "/")
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
return isAbsolute, tokens
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
if lower == ".meta" then
error("EINVAL: reserved path component: .meta", 3)
end
return result
end
function vfs.splitPath(path)
@@ -164,11 +160,10 @@ local function resolveMount(normalPath)
return vfs.disks[mountId], diskPath
end
-- Expose parser for use by other modules (e.g. permissions seeder)
vfs._parseMetafile = parseMetafile
local function readMetaEntry(disk, parentDiskPath, filename)
if filename == ".meta" then error("Cannot open metafile") end
if filename == ".meta" then error("EACCES: Cannot open metafile") end
local mp
if parentDiskPath == "/" then
mp = ".meta"
@@ -181,7 +176,6 @@ local function readMetaEntry(disk, parentDiskPath, filename)
local raw = f.read(65535)
if f.close then f.close() end
-- Auto-upgrade: if this .meta file is not v2, rewrite it as v2 now
if raw and #raw > 0 and raw:byte(1) ~= META_VERSION then
local upgraded = makeMetafile(parseMetafile(raw))
local wok, wf = pcall(function() return disk:open(mp, "w") end)
@@ -194,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.."'")
@@ -243,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
@@ -440,7 +606,6 @@ function vfs.open(path, mode)
if type(handle) ~= "table" then error("ENFILE") end
end
-- If this is a newly created file, stamp it with the creating user's ownership
if isNew then
local euid = (task and (task.euid or task.uid)) or kernel.uid
local egid = (task and task.gid) or 0
@@ -460,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
@@ -508,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()
@@ -629,7 +797,6 @@ function vfs.mkdir(path)
checkperms(parentMeta, "w")
local disk, diskPath = resolvePath(path)
disk:makeDirectory(diskPath)
-- Stamp the new directory with the creating user's ownership
local task = kernel.currentTask
local euid = (task and (task.euid or task.uid)) or kernel.uid
local egid = (task and task.gid) or 0
@@ -642,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)
@@ -655,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("[^/]+$")
@@ -721,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

View File

@@ -1,4 +1,4 @@
-- :Minify:--
--:Minify:--
local kernel = ...
local cache = {}
kernel.searchpaths = {

View File

@@ -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)

View File

@@ -0,0 +1,265 @@
--: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
elseif tostring(steps[1])=="self" then
local task=kernel.currentTask
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
elseif tostring(steps[1])=="self" then
local task=kernel.currentTask
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),{"self"})
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
elseif tostring(steps[1])=="self" then
local task=kernel.currentTask
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

View File

@@ -1,3 +1,4 @@
--:Minify:--
local kernel = ...
local proxy = {}

View File

@@ -1,4 +1,4 @@
-- :Minify:--
--:Minify:--
-- Loop device driver:
--
-- BIND (directory) - re-routes VFS calls into a host directory subtree.

View File

@@ -1,4 +1,4 @@
-- :Minify:--
--:Minify:--
-- Supports:
-- AF_UNIX - local IPC via /var/run/*.sock paths
-- AF_INET - network sockets with three backends:

View File

@@ -1,4 +1,4 @@
-- :Minify:--
--:Minify:--
local args = {...}
local kernel = args[1]
kernel._G = _G
@@ -21,7 +21,7 @@ local function readonly(tbl)
error("Attempt to modify global variable '" .. k .. "'", 2)
end,
__pairs = function()
__pairs = function(self)
local function iter(_, key)
local nextKey, value = next(tbl, key)
if type(value) == "table" then
@@ -29,7 +29,7 @@ local function readonly(tbl)
end
return nextKey, value
end
return iter, tbl, nil
return iter, self, nil
end,
__ipairs = function()
@@ -50,10 +50,24 @@ local function readonly(tbl)
__metatable = false
})
end
--local origLoad = load
local origLoad = load
kernel._U = readonly(kernel._G)
kernel.allowGlobalOverwrites = true
kernel._U._G = kernel._U
--kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end
kernel.allowGlobalOverwrites = false
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

View File

@@ -236,19 +236,18 @@ local function nextUID()
return max + 1
end
function auth.login(username, password)
if type(username) ~= "string" or type(password) ~= "string" then
function auth.login(uid, password)
if type(uid) ~= "number" or type(password) ~= "string" then
return nil, "Authentication failure"
end
local entry = getPasswdByUsername(username)
local entry = getPasswdByUID(uid)
if not entry then
-- timing attack resistance
hashPassword(password, "aaaaaaaaaaaaaaaa")
return nil, "Authentication failure"
end
local uid = tonumber(entry[1])
local sEntry = getShadowByUID(uid)
if not sEntry then
hashPassword(password, "aaaaaaaaaaaaaaaa")
@@ -273,7 +272,7 @@ function auth.login(username, password)
_task.egid = tonumber(entry[2]) or uid
end
kernel.log("AUTH: login uid=" .. tostring(uid) .. " (" .. username .. ")")
kernel.log("AUTH: login uid=" .. tostring(uid) .. " (" .. getPasswdByUID(uid)[3] .. ")")
return true
end
@@ -619,4 +618,4 @@ if kernel.syscalls then
kernel.syscalls["setshell"] = auth.setShell
kernel.syscalls["sethomedir"] = auth.setHomedir
kernel.syscalls["setgid"] = auth.setGID
end
end

View File

@@ -5,18 +5,22 @@ local sys = {}
local nextpid = 2
kernel.exitMain = false
local resumeWithTimeout = coroutine.resumeWithTimeout
local function bit_is_set(num, bit)
return math.floor(num / (2 ^ bit)) % 2 == 1
end
local function loadExecutable(path, env)
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)
@@ -90,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)
@@ -206,10 +210,14 @@ function sys.kill(pid)
return false, "Task does not exist"
elseif task.status == "Z" then
return false, "Task is already dead"
else
task.status = "Z"
return true
end
local caller = kernel.currentTask
local ceuid = caller and (caller.euid or caller.uid) or kernel.uid
if ceuid ~= 0 and task.uid ~= (caller and caller.uid or kernel.uid) then
return false, "EPERM"
end
task.status = "Z"
return true
end
function sys.stop(pid)
@@ -351,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
coroutine.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
@@ -363,7 +386,7 @@ function kernel.main()
local ret
if kernel.config.preempt then
ret = { coroutine.resumeWithTimeout(task.coro, task.timeSlice, table.unpack(task.syscallReturn)) }
ret = { resumeWithTimeout(task.coro, task.timeSlice, table.unpack(task.syscallReturn)) }
else
ret = { coroutine.resume(task.coro, table.unpack(task.syscallReturn)) }
end

View File

@@ -1,3 +1,4 @@
--:Minify:--
local kernel=...
local sysc=kernel.syscalls
kernel.gpio={}

View File

@@ -1,4 +1,4 @@
-- :Minify:--
--:Minify:--
local kernel = ...
function print(...)
local args = {...}

View File

@@ -1,4 +1,4 @@
-- :Minify:--
--:Minify:--
local kernel = ...
kernel.log("Loading init system...")
kernel.log("InitPath: " .. kernel.config.initPath)

View File

@@ -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

View File

@@ -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, RWXRWXRWX},
{"opt", REG, 0, 0, RWX_RX_RX},
})
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

View File

@@ -0,0 +1,3 @@
--:Minify:--
local kernel = ...
kernel.allowGlobalOverwrites = false

View File

@@ -33,7 +33,6 @@ local host_str = syscall.getHost() or "Unknown"
local cc_ver = host_str:match("ComputerCraft ([%d%.]+)") or host_str
local info = {
-- {label, value} label=nil means print value as-is (userhost / separator)
{nil, userhost},
{nil, string.rep("-", #userhost)},
{"OS", syscall.version() or "Unknown"},
@@ -42,7 +41,7 @@ local info = {
{"Uptime", formatUptime(syscall.getUptime() or 0)},
{"Tasks", tostring(#(syscall.getTasks() or {}))},
{"Shell", syscall.getEnviron("SHELL") or "Unknown"},
{"Terminal", "TTY1"},
{"Terminal", "tty1"},
{"UID", tostring(syscall.getuid())},
{"Packages", "n/a (spm)"},
}
@@ -71,15 +70,12 @@ local lines = math.max(#logo, #info)
for i = 1, lines do
local logo_str = logo[i] or string.rep(" ", 36)
-- print logo segment in cyan
c(C_LOGO)
printInline(logo_str)
-- print separator pipe
c(C_LABEL)
printInline("| ")
-- print info segment
local row = info[i]
if row then
if row[1] == nil and i == 1 then

View File

@@ -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)

3
Src/hysh/bin/ll Normal file
View File

@@ -0,0 +1,3 @@
local args={...}
table.insert(args, "-lah")
syscall.exec("/bin/ls", args)

View File

@@ -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)
@@ -126,35 +126,35 @@ local function doLogin()
syscall.write(1, "Username: ")
local username = readLine(nil)
if username == "" then goto continue end
if username ~= "" then
syscall.write(1, "Password: ")
local password = readLine("*")
syscall.write(1, "Password: ")
local password = readLine("*")
local uid = syscall.getuidbyname(username)
local ok, err = syscall.login(username, password)
if ok then
local uid = syscall.getuid()
local pwent = syscall.getpasswd(uid)
local ok, err = syscall.login(uid, password)
if ok then
local uid = syscall.getuid()
local pwent = syscall.getpasswd(uid)
local shell = (pwent and pwent.shell) or "/bin/hysh"
local homedir = (pwent and pwent.homedir) or "/"
local shell = (pwent and pwent.shell) or "/bin/hysh"
local homedir = (pwent and pwent.homedir) or "/"
syscall.devctl(1, "sfgc", 3)
syscall.write(1, "\nWelcome, " .. username .. "!\n")
syscall.devctl(1, "sfgc", 1)
sleep(0.3)
syscall.devctl(1, "sfgc", 3)
syscall.write(1, "\nWelcome, " .. username .. "!\n")
syscall.devctl(1, "sfgc", 1)
sleep(0.3)
spawnShell(username, uid, shell, homedir)
return -- back to login prompt
else
attempts = attempts + 1
sleep(1)
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Login incorrect.\n\n")
syscall.devctl(1, "sfgc", 1)
spawnShell(username, uid, shell, homedir)
return -- back to login prompt
else
attempts = attempts + 1
sleep(1)
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Login incorrect.\n\n")
syscall.devctl(1, "sfgc", 1)
end
end
::continue::
end
syscall.devctl(1, "sfgc", 2)

View File

@@ -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

View File

@@ -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)

View File

@@ -9,7 +9,7 @@ local currentUid = syscall.getuid()
local targetUid
if targetName then
targetUid = syscall.getuid(targetName)
targetUid = syscall.getuid()
if not targetUid then
print("passwd: user '" .. targetName .. "' does not exist")
syscall.exit(1); return
@@ -36,7 +36,7 @@ if currentUid ~= 0 then
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
else cur=cur..ch; syscall.write(1,"*") end
end
local ok, err = syscall.elevate(targetName, cur)
local ok, err = syscall.login(targetUid, cur)
if not ok then
sleep(1)
print("passwd: authentication failure")

View File

@@ -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)

View File

@@ -1,7 +1,12 @@
--:Minify:--
local targetUser = ({ ... })[1] or "root"
local targetUser = ({ ... })[1]
local currentUid = syscall.getuid()
local targetUid = syscall.getuidbyname(targetUser)
local targetUid
if targetUser then
targetUid = syscall.getuidbyname(targetUser)
else
targetUid = 0
end
if not targetUid then
print("su: user '" .. targetUser .. "' does not exist")
@@ -25,20 +30,21 @@ if currentUid ~= 0 then
end
end
local ok, err = syscall.elevate(targetUser, pw)
local ok, err = syscall.login(targetUid, pw)
if not ok then
sleep(1)
print("su: Authentication failure")
syscall.exit(1)
return
end
else
syscall.setuid(targetUid)
end
syscall.setuid(targetUid)
local pwent = syscall.getpasswd(targetUid)
local shell = (pwent and pwent.shell) or "/bin/hysh"
local homedir = (pwent and pwent.homedir) or "/"
local username= (pwent and pwent.username)or "Unknown"
local ok_cd, err_cd = pcall(syscall.chdir, homedir)
if not ok_cd then
@@ -46,11 +52,11 @@ if not ok_cd then
syscall.chdir(homedir)
end
syscall.setEnviron("HOME", homedir)
syscall.setEnviron("USER", targetUser)
syscall.setEnviron("USER", username)
syscall.setEnviron("SHELL", shell)
local ok, err = pcall(syscall.exec, shell)
if not ok then
print("su: cannot exec shell '" .. shell .. "': " .. tostring(err))
syscall.exit(1)
end
end

View File

@@ -1,5 +1,5 @@
--:Minify:--
local fs = require("sys.fs")
local fs = require("fs")
local cmdArgs = {...}
local targetUser = "root"
@@ -55,7 +55,7 @@ if currentUid ~= 0 then
end
end
local ok, err = syscall.elevate("root", pw)
local ok, err = syscall.login(0, pw)
if not ok then
sleep(1)
print("sudo: Authentication failure")

10
Src/hysh/bin/sysdump Normal file
View File

@@ -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)

View File

@@ -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

View File

@@ -1,3 +1,4 @@
--:Minify:--
local args = {...}
while true do
if #args == 0 then

38
Src/iniparse/lib/iniparse Normal file
View File

@@ -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

388
Src/json/lib/json Normal file
View File

@@ -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

View File

@@ -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")
@@ -310,16 +308,16 @@ local history = {}
while true do
local code = getUserInput("lua> ", history)
if code == "" then goto continue end
if code ~= "" then
while isIncomplete(code) do
code = code .. "\n" .. getUserInput("... ", nil)
while isIncomplete(code) do
code = code .. "\n" .. getUserInput("... ", nil)
end
if code ~= history[#history] then
history[#history+1] = code
end
runCode(code)
end
if code ~= history[#history] then
history[#history+1] = code
end
runCode(code)
::continue::
end

View File

@@ -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)

View File

@@ -139,72 +139,68 @@ local function parseCommands(src)
local c = src:sub(pos, pos)
if c == "\n" or c == ";" then
pos = pos + 1
goto continue
end
if c == "#" then
elseif c == "#" then
while pos <= len and src:sub(pos,pos) ~= "\n" do pos = pos + 1 end
goto continue
end
local addr1, addr2
addr1, pos = parseAddr(src, pos)
skip()
if addr1 and pos <= len and src:sub(pos,pos) == "," then
pos = pos + 1
skip()
addr2, pos = parseAddr(src, pos)
end
skip()
if pos > len then break end
local cmd = src:sub(pos, pos)
pos = pos + 1
if cmd == "s" then
local delim = src:sub(pos, pos); pos = pos + 1
local pat, p1 = parseDelim(src, pos, delim); pos = p1
local repl, p2 = parseDelim(src, pos, delim); pos = p2
local flags = ""
while pos <= len and src:sub(pos,pos):match("[giIp]") do
flags = flags .. src:sub(pos,pos); pos = pos + 1
end
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="s",
pat=pat, repl=repl, flags=flags })
elseif cmd == "y" then
local delim = src:sub(pos, pos); pos = pos + 1
local srcch, p1 = parseDelim(src, pos, delim); pos = p1
local dstch, p2 = parseDelim(src, pos, delim); pos = p2
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="y",
src=srcch, dst=dstch })
elseif cmd == "d" or cmd == "p" or cmd == "q" or cmd == "=" then
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd=cmd })
elseif cmd == "{" then
local depth = 1
local start = pos
while pos <= len and depth > 0 do
local ch = src:sub(pos,pos)
if ch == "{" then depth = depth + 1
elseif ch == "}" then depth = depth - 1 end
pos = pos + 1
end
local inner = src:sub(start, pos - 2)
local innerCmds = parseCommands(inner)
for _, ic in ipairs(innerCmds) do
ic.addr1 = ic.addr1 or addr1
ic.addr2 = ic.addr2 or addr2
end
for _, ic in ipairs(innerCmds) do
table.insert(cmds, ic)
end
elseif cmd == "\n" or cmd == ";" then
else
end
::continue::
local addr1, addr2
addr1, pos = parseAddr(src, pos)
skip()
if addr1 and pos <= len and src:sub(pos,pos) == "," then
pos = pos + 1
skip()
addr2, pos = parseAddr(src, pos)
end
skip()
if pos > len then break end
local cmd = src:sub(pos, pos)
pos = pos + 1
if cmd == "s" then
local delim = src:sub(pos, pos); pos = pos + 1
local pat, p1 = parseDelim(src, pos, delim); pos = p1
local repl, p2 = parseDelim(src, pos, delim); pos = p2
local flags = ""
while pos <= len and src:sub(pos,pos):match("[giIp]") do
flags = flags .. src:sub(pos,pos); pos = pos + 1
end
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="s",
pat=pat, repl=repl, flags=flags })
elseif cmd == "y" then
local delim = src:sub(pos, pos); pos = pos + 1
local srcch, p1 = parseDelim(src, pos, delim); pos = p1
local dstch, p2 = parseDelim(src, pos, delim); pos = p2
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="y",
src=srcch, dst=dstch })
elseif cmd == "d" or cmd == "p" or cmd == "q" or cmd == "=" then
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd=cmd })
elseif cmd == "{" then
local depth = 1
local start = pos
while pos <= len and depth > 0 do
local ch = src:sub(pos,pos)
if ch == "{" then depth = depth + 1
elseif ch == "}" then depth = depth - 1 end
pos = pos + 1
end
local inner = src:sub(start, pos - 2)
local innerCmds = parseCommands(inner)
for _, ic in ipairs(innerCmds) do
ic.addr1 = ic.addr1 or addr1
ic.addr2 = ic.addr2 or addr2
end
for _, ic in ipairs(innerCmds) do
table.insert(cmds, ic)
end
elseif cmd == "\n" or cmd == ";" then
else
end
end
end
return cmds

6
Src/spm/bin/spm Normal file
View File

@@ -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"

View File

@@ -0,0 +1 @@
[symlinks]

4
Src/sysinit/sbin/init Normal file
View File

@@ -0,0 +1,4 @@
local args={...}
syscall.remove("/sbin/init")
syscall.symlink("/usr/lib/sysinit/sysinit", "/sbin/init")
syscall.exec("/sbin/init", args)

View File

@@ -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

View File

@@ -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={}

Some files were not shown because too many files have changed in this diff Show More