166 Commits

Author SHA1 Message Date
f7b64c11b7 added seperate input and working on http / sockets 2026-05-27 17:01:26 -04:00
59e09a5995 Update LICENSE 2026-03-26 10:34:00 -04:00
ab0a10105c formating better for spm packages 2026-03-26 08:12:37 -04:00
22b5021e9d fixed boot crash 2026-03-21 01:10:47 -04:00
462c74686a fixed the installer 2026-03-21 01:04:27 -04:00
0072547beb updated package list 2026-03-21 00:59:46 -04:00
7deefc83ca made newer installer that does not require tar stuff 2026-03-21 00:54:39 -04:00
df62414229 made system use full salt+pepper 2026-03-19 14:10:17 -04:00
055dd4e606 fixed again 2026-03-19 12:10:31 -04:00
5755dd9cbe fixed 2026-03-19 11:32:48 -04:00
8e11faf9ec updated again 2026-03-19 11:23:24 -04:00
0ea42f9454 Updated installer 2026-03-19 11:21:08 -04:00
d0f26a937f made installer 2026-03-19 11:16:05 -04:00
9b338328f0 made unified colors and stuff 2026-03-19 08:33:47 -04:00
4f9eebade2 update tar 2026-03-12 20:54:27 -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
a6d2f6dca7 /home/user owned by user, user starts in cwd /home/user 2026-02-23 23:05:13 -06:00
b015d5880a load vuln fixed, sudo fixed 2026-02-23 22:43:12 -06:00
6694711423 Update contributors.md
Added developer names
2026-02-22 23:05:52 -05:00
40c97ca000 Hyperion v1.2.0 2026-02-22 21:53:02 -06:00
dd2437d4af fix astro's syscall redefiniton so files use it 2026-02-21 14:12:05 -06:00
d026cfbb03 cleaned syscalls 2026-02-21 15:06:54 -05:00
aad7efd055 Merge pull request 'New build system + hysh functionality' (#4) from spsf/HyperionOS:main into main
Reviewed-on: #4
2026-02-21 14:54:01 -05:00
93c3bab263 allow hypervisor to kill non-owned processes 2026-02-21 14:09:12 -05:00
5bd16b8fd4 su kill error handling 2026-02-21 14:07:35 -05:00
3cc1459769 New build system + hysh functionality 2026-02-21 12:49:57 -06:00
0655f2a39e added users (spsf untangled by astronand) 2026-02-20 23:32:05 -05:00
57b1d46837 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-20 23:08:23 -05:00
875759022a fixed printInline & printf 2026-02-20 23:07:31 -05:00
Ryan T
bb14ea34c3 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-20 15:23:48 -05:00
Ryan T
149fb18564 add a couple shell programs 2026-02-20 15:23:46 -05:00
10bc775e64 e 2026-02-20 11:34:53 -05:00
287b146621 Update Src/Hyperion-bash/bin/bash 2026-02-19 11:18:02 -05:00
Ryan T
371954373e Fix lua prompt and add arrow key support 2026-02-16 20:37:13 -05:00
Ryan T
ecef2c6cb0 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-16 17:27:25 -05:00
Ryan T
6770533581 Add cd and ls support to shell 2026-02-16 17:27:23 -05:00
19a9c72c6d Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-15 01:08:57 -05:00
fdb18d4ac5 working on readme 2026-02-15 01:08:55 -05:00
Ryan T
303449e13c fix shell going crazy when hitting bottom of screen 2026-02-15 00:47:35 -05:00
b6d1b9398f fuck me i forgot a semicolon (return) 2026-02-14 23:44:03 -05:00
bb829cdd8e Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-02-14 23:31:48 -05:00
6b9bed5047 fixed task cleanup fd errors 2026-02-14 23:31:45 -05:00
Ryan T
753c34bffa create proper shell 2026-02-14 23:31:33 -05:00
403178c832 made dev files (includeing tern) also fixed differnt keyboard layouts hopefully 2026-02-13 17:01:00 -05:00
33cd291c21 finished vfs for a while 2026-02-12 11:43:41 -05:00
1c4f48bd65 added ps 2026-02-03 20:47:33 -05:00
ec5e63898d fully fixed ghxx exploit 2026-02-01 02:01:35 -05:00
ea2a0e0e94 fixed metafile and list bug 2026-02-01 01:40:28 -05:00
4f50d90b79 fixed ghxx exploit 2026-02-01 01:01:07 -05:00
bf1dc9da7a changed boot orders 2026-01-30 21:59:37 -05:00
1a455a6025 added getuid and made more docs and working on half functional lua 2026-01-30 19:38:50 -05:00
c9ac447484 added setuid syscall 2026-01-30 18:47:37 -05:00
cb73f49962 added file and folder perms 2026-01-30 18:29:01 -05:00
d9caf655fb did mutiple changes 2026-01-30 11:16:20 -05:00
1c3d2c8b48 vfs rewrite lol fml 2026-01-29 20:29:06 -05:00
9bd9cdaba4 fixed keys added more OS syscalls and added colored logging 2026-01-20 11:33:24 -05:00
72bfce7b08 added myself as a node 2026-01-19 10:56:09 -05:00
6e48fcd5b9 Update Src/Hyperion-kernel/lib/modules/Hyperion/90_init.kmod 2026-01-19 10:41:08 -05:00
2d98ff64ce fixed name 2026-01-19 10:16:44 -05:00
6a14b87f44 made log text part of kernel table for access from modules 2026-01-19 10:14:25 -05:00
63bcc2df5c restructure for spm 2026-01-18 22:14:15 -05:00
fd7ee1aa3b made vfs changes 2026-01-18 11:59:45 -05:00
1073362007 readme 2026-01-16 19:09:16 -05:00
da5ad3b5cb bashex 2026-01-16 18:52:14 -05:00
e77fbcaf5d Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-01-16 18:51:20 -05:00
111fe764f8 disable cursor blink 2026-01-16 18:51:19 -05:00
Ryan T
83857d7e87 New Bash 2026-01-16 18:44:51 -05:00
83814311e5 chris proofing the bootloader 2026-01-16 17:13:12 -05:00
55fdddeff8 hotfix 2026-01-16 16:55:41 -05:00
1b21c87654 ooga booga i cant make my own boot script - chris 2026-01-16 16:53:29 -05:00
df4823940d README 2026-01-16 16:35:32 -05:00
70532f6e2c added more hpv funcs and made primshell 2026-01-16 14:17:28 -05:00
bd8fe50770 made lua debugger stop crying and added more docs 2026-01-16 08:40:16 -05:00
e5d6ec9725 made ctrl, shift, and alt detection 2026-01-15 19:40:49 -05:00
c620c4f1ba fixed terminal color 2026-01-15 17:22:56 -05:00
6b48e80157 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-01-15 17:05:24 -05:00
fbbb8b8d65 fixed task log 2026-01-15 17:05:21 -05:00
Ryan T
a88bdc9639 experimenting with events 2026-01-15 17:04:58 -05:00
96c22f5237 added task logging 2026-01-15 16:59:40 -05:00
231b0ced48 gitr ignore 2026-01-15 16:47:33 -05:00
4100ecf0a1 remove build 2026-01-15 16:47:18 -05:00
Ryan T
968f4c54d7 Create bash 2026-01-15 16:46:24 -05:00
70526c76ba git ignore 2026-01-15 16:44:40 -05:00
3a8be2bdb7 hotfix 2026-01-15 16:43:03 -05:00
efffc8f0d2 hotfix 2026-01-15 16:25:26 -05:00
b48f926053 made print log 2026-01-15 16:22:25 -05:00
7d8055a703 remove fshandles arg 2026-01-15 16:14:47 -05:00
1141193fc8 added events pirimitive 2026-01-15 16:12:28 -05:00
6e363a688e Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2026-01-15 15:59:50 -05:00
6a3c19a8a8 delete hello.lua 2026-01-15 15:59:49 -05:00
Ryan T
e63d49dc98 Remove the other old bash lmao 2026-01-15 15:58:04 -05:00
Ryan T
c854b1eab8 Remove the old bash 2026-01-15 15:56:48 -05:00
df1fa69402 boot.cfg change 2026-01-15 15:45:32 -05:00
443149fe8d add linux / osx build scripts 2026-01-15 15:37:13 -05:00
16e4f6b789 added minify tags 2026-01-15 15:27:28 -05:00
e203f9f36d moved stuff to src/ from test/ and made better build scripts 2026-01-15 10:58:27 -05:00
0d46054e56 moved packages 2026-01-14 18:47:15 -08:00
f76f77f770 make sleep work 2026-01-14 17:14:35 -08:00
efe273f2fe remove snip 2026-01-14 14:14:02 -08:00
4b2be8be44 super dupper system update (it runs) 2026-01-14 14:11:50 -08:00
9b268810a7 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2025-12-17 11:53:56 -05:00
e63bb275a0 stuff.mp4 2025-12-17 11:53:54 -05:00
cf2eba6052 Merge branch 'main' of https://git.astronand.dev/Hyperion/HyperionOS 2025-12-11 17:23:07 -05:00
5a4bd5ee11 balls 2025-12-11 17:22:10 -05:00
192 changed files with 28652 additions and 11843 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# Build output
/Build/
/build/
Build/
build/
# VSCodeCounter
/.VSCodeCounter/
.VSCodeCounter/

11
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"Lua.diagnostics.globals": [
"isEqualToAny",
"isEqualToAll",
"syscall",
"printf",
"printInline",
"toHex",
"loadcstr"
]
}

198
.vscode/tasks.json vendored Executable file → Normal file
View File

@@ -1,46 +1,164 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"version": "2.0.0",
"tasks": [
{
"label": "Build (Minfiyed)",
"type": "shell",
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
"if (Test-Path '${workspaceFolder}\\Build') { Remove-Item -LiteralPath '${workspaceFolder}\\Build' -Recurse -Force -ErrorAction SilentlyContinue }; New-Item -ItemType Directory -Path '${workspaceFolder}\\Build' | Out-Null; Get-ChildItem -Path '${workspaceFolder}\\Test' -Directory | ForEach-Object { $base = $_.FullName; Get-ChildItem -Path $base -File -Recurse | ForEach-Object { $rel = $_.FullName.Substring($base.Length).TrimStart(\"\\\"); $destPath = Join-Path '${workspaceFolder}\\Build' $rel; $destDir = Split-Path $destPath; if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir | Out-Null }; Copy-Item -LiteralPath $_.FullName -Destination $destPath -Force } }"
]
},
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}\\scripts\\buildMini.ps1"
]
},
"linux": {
"command": "bash",
"args": [
"-c",
"[ -d \"${workspaceFolder}/Build\" ] && rm -rf \"${workspaceFolder}/Build\"; mkdir -p \"${workspaceFolder}/Build\"; for d in \"${workspaceFolder}/Test\"/*; do if [ -d \"$d\" ]; then find \"$d\" -type f | while read f; do rel=\"${f#$d/}\"; mkdir -p \"${workspaceFolder}/Build/$(dirname \"$rel\")\"; cp \"$f\" \"${workspaceFolder}/Build/$rel\"; done; fi; done"
]
},
"linux": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildMini.sh"
]
},
"osx": {
"command": "bash",
"args": [
"-c",
"[ -d \"${workspaceFolder}/Build\" ] && rm -rf \"${workspaceFolder}/Build\"; mkdir -p \"${workspaceFolder}/Build\"; for d in \"${workspaceFolder}/Test\"/*; do if [ -d \"$d\" ]; then find \"$d\" -type f | while read f; do rel=\"${f#$d/}\"; mkdir -p \"${workspaceFolder}/Build/$(dirname \"$rel\")\"; cp \"$f\" \"${workspaceFolder}/Build/$rel\"; done; fi; done"
]
},
"osx": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildMini.sh"
]
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "Build (Source)",
"type": "shell",
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}\\scripts\\build.ps1"
]
},
"linux": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/build.sh"
]
},
"osx": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/build.sh"
]
},
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "Test (Minfiyed)",
"type": "shell",
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}\\scripts\\buildMiniTest.ps1"
]
},
"linux": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildMiniTest.sh"
]
},
"osx": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildMiniTest.sh"
]
},
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "Test (Source)",
"type": "shell",
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}\\scripts\\buildTest.ps1"
]
},
"linux": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildTest.sh"
]
},
"osx": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildTest.sh"
]
},
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
}
]
}

BIN
Build.tar

Binary file not shown.

View File

@@ -1,303 +0,0 @@
local term = term
local os = os
-- Function to write text to the terminal with special character handling
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
-- Handle wrapping if we go past right edge
if x > w then
x = 1
y = y + 1
end
-- Handle scrolling if we go past bottom
if y-1 > h then
term.scroll(1)
y = h
term.setCursorPos(x, y)
end
end
term.setCursorPos(x, y)
end
-- Function to display critical errors and halt the system
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
-- Wrap all in xpcall to catch errors
local ok, err = xpcall(function()
local apis={}
-- List of standard Lua globals
local lua = {
coroutine = true,
debug = true,
_HOST = true,
_VERSION = true,
assert = true,
collectgarbage = true,
error = true,
gcinfo = true,
getfenv = true,
getmetatable = true,
ipairs = true,
__inext = true,
load = true,
math = true,
next = true,
pairs = true,
pcall = true,
rawequal = true,
rawget = true,
rawlen = true,
rawset = true,
select = true,
setfenv = true,
setmetatable = true,
string = true,
table = true,
tonumber = true,
tostring = true,
type = true,
xpcall = true,
_G=true
}
-- Move all non-Lua standard library globals into the apis table
for i,v in pairs(_G) do
if not lua[i] or lua[i]==nil then
apis[i]=v
_G[i]=nil
end
end
-- Set up terminal default colors
apis.term.setPaletteColor(0x1, 0x000000) -- #000000
apis.term.setPaletteColor(0x2, 0xFFFFFF) -- #FFFFFF
apis.term.setPaletteColor(0x4, 0xFF0000) -- #FF0000
apis.term.setPaletteColor(0x8, 0x00FF00) -- #00FF00
apis.term.setPaletteColor(0x10, 0x0000FF) -- #0000FF
apis.term.setPaletteColor(0x20, 0x00FFFF) -- #00FFFF
apis.term.setPaletteColor(0x40, 0xFF00FF) -- #FF00FF
apis.term.setPaletteColor(0x80, 0xFFFF00) -- #FFFF00
apis.term.setPaletteColor(0x100, 0xFF6D00) -- #FF6D00
apis.term.setPaletteColor(0x200, 0x6DFF55) -- #6DFF55
apis.term.setPaletteColor(0x400, 0x24FFFF) -- #24FFFF
apis.term.setPaletteColor(0x800, 0x924900) -- #924900
apis.term.setPaletteColor(0x1000, 0x6D6D55) -- #6D6D55
apis.term.setPaletteColor(0x2000, 0xDBDBAA) -- #DBDBAA
apis.term.setPaletteColor(0x4000, 0x6D00FF) -- #6D00FF
apis.term.setPaletteColor(0x8000, 0xB6FF00) -- #B6FF00
-- Wrapper to read files
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
-- Load kernel first if it fails, we can't continue so we display an error
local Kernel = load(getFile("/$/boot/kernel.lua"),"@Kernel")
local initFs = load(getFile("/$/boot/cct/initdisks","@Init_disks"))(apis)
local fs = load(getFile("/$/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
-- Set up event queue
local eventQueue = {}
local lkeys={}
lkeys[apis.keys.enter]="\n"
lkeys[apis.keys.backspace]="\b"
lkeys[apis.keys.tab]="\t"
-- Function to queue events
local function queueEvent(event, ...)
table.insert(eventQueue, {event, ...})
end
-- Set up computer api
local computer = {
time = function() return apis.os.epoch("utc") end,
clock = apis.os.clock,
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
}
-- Set up terminal colors
local icolors={
[0x1] =0, -- #000000
[0x2] =1, -- #FFFFFF
[0x4] =2, -- #FF0000
[0x8] =3, -- #00FF00
[0x10] =4, -- #0000FF
[0x20] =5, -- #00FFFF
[0x40] =6, -- #FF00FF
[0x80] =7, -- #FFFF00
[0x100] =8, -- #FF6D00
[0x200] =9, -- #6DFF55
[0x400] =10, -- #24FFFF
[0x800] =11, -- #924900
[0x1000] =12, -- #6D6D55
[0x2000] =13, -- #DBDBAA
[0x4000] =14, -- #6D00FF
[0x8000] =15 -- #B6FF00
}
local colors={
[0]=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(0x1)
apis.term.setTextColor(0x1000)
apis.term.clear()
apis.term.setCursorPos(1, 1)
-- Make the kernel coroutine so we can hook its execution
local kernelCoro = coroutine.create(function()
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)
-- Diffine a coroutine.resumeWithTimeout function to avoid hanging the system, time is in milliseconds
function coroutine.resumeWithTimeout(co, timeout, ...)
local startTime = computer.time()
debug.sethook(co, function()
if computer.time() > startTime + timeout then
return coroutine.yield("timeout")
end
end, "", 1000)
local ret = {coroutine.resume(co, ...)}
if ret[1]=="timeout" then
return nil, "Coroutine timed out"
elseif ret[1]==false then
return false, ret[2]
else
debug.sethook(co)
return true, table.unpack(ret)
end
end
write("Loaded in "..tostring(apis.os.clock()).." seconds.\n")
-- Main loop to run the kernel coroutine
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] == "char" then
queueEvent("keyTyped", 1, event[2])
elseif event[1] == "key" then
queueEvent("keyPressed", 1, event[2])
if lkeys[event[2]] then
queueEvent("keyTyped", 1, lkeys[event[2]])
end
elseif event[1] == "key_up" then
queueEvent("keyReleased", 1, event[2])
elseif event[1] == "disk" then
queueEvent("componentAdded", "disk")
elseif event[1] == "disk_eject" then
queueEvent("componentRemoved", "disk")
elseif event[1] == "NoSleep" then
exit=true
end
end
if status == false 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

@@ -1,201 +0,0 @@
local apis = ({...})[1]
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
---------------------------------------------------------
-- STORAGE
---------------------------------------------------------
local disks = {} -- real disks
local internal = {} -- "$" disk
---------------------------------------------------------
-- HELPERS
---------------------------------------------------------
local function norm(path)
if not path or path == "" then return "/" end
return fs.combine("/", path)
end
--- Creates a disk object given a base path
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:readAllText(path)
local p = fs.combine(basePath, path)
if not fs.exists(p) then return nil, "file not found" end
local h = fs.open(p, "r")
local t = h.readAll()
h.close()
return t
end
function disk:writeAllText(path, text)
local p = fs.combine(basePath, path)
local h = fs.open(p, "w")
if not h then return nil, "cannot write" end
h.write(text)
h.close()
return true
end
function disk:appendAllText(path, text)
local p = fs.combine(basePath, path)
local h = fs.open(p, "a")
if not h then return nil, "cannot append" end
h.write(text)
h.close()
return true
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: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:size(path)
local p = fs.combine(basePath, path)
return fs.getSize(p)
end
return disk
end
---------------------------------------------------------
-- INTERNAL DISK "$" (mapped to "/")
---------------------------------------------------------
internal["$"] = createDisk("$", "/$", false, {
setLabel=function(label)
local h = fs.open("/.label", "w")
h.write(label)
h.close()
end,
getLabel=function()
local h = fs.open("/.label", "r")
local label = h.readAll()
h.close()
return label
end
})
---------------------------------------------------------
-- SCAN REAL DISK PERIPHERALS
---------------------------------------------------------
local function refresh()
-- remove disks that no longer exist
for id, _ in pairs(disks) do
if not peripheral.getType(id) then
disks[id] = nil
end
end
-- detect new disks
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
---------------------------------------------------------
-- ITERATOR
---------------------------------------------------------
local function iter()
refresh()
-- first internal
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
---------------------------------------------------------
-- MODULE RETURN
---------------------------------------------------------
return {
refresh = refresh,
list = iter
}

View File

@@ -1,164 +0,0 @@
local args = {...}
local apis = args[1]
local disks = args[2]
local arch = args[3]
local initPath = args[4]
local screen = args[5]
local computer = args[6]
local ifs = args[7]
local LOG_Text = ""
local kernel = {}
kernel.process = "Kernel"
kernel.user = "root"
kernel.group = "root"
kernel.groups = {0}
kernel.uid = 0
kernel.gid = 0
kernel.status = "start"
kernel.key = {}
kernel.cache = {}
kernel.cache.preload = {}
kernel._G=_G
local windowsExp = false
function kernel.log(msg, level)
LOG_Text = LOG_Text..tostring(computer.time()).." "..kernel.user.." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n"
if kernel.status == "start" then
screen:print(tostring(computer.time()).." "..kernel.user.." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg)
end
end
function kernel.PANIC(msg)
if kernel.status~="Panic" then
kernel.log("PANIC: "..msg, "PANIC")
pcall(kernel["saveLog"])
kernel.status="Panic"
kernel.reason=msg
screen:setTextColor(2)
screen:setBackgroundColor(0)
screen:clear()
screen:setCursorPos(1,1)
screen:print(LOG_Text)
screen:print("KERNEL PANIC!\n"..msg.."\nSystem halted.")
screen:print("Press any key to continue...")
end
while true do
local event={computer:getMachineEvent()}
if event[1]=="keyPressed" then
break
end
end
computer:reboot()
end
kernel.panic=kernel.PANIC
if windowsExp then
screen:setTextColor(1)
screen:setBackgroundColor(4)
screen:clear()
local w,h = screen:getSize()
screen:setCursorPos(3,5)
screen:print(":(")
screen:setCursorPos(3,7)
screen:print("Your PC ran into a problem and needs to restart. We're just collecting some error")
screen:setCursorPos(3,8)
screen:print("info, and then we'll restart for you.\n")
screen:setCursorPos(3,h-5)
screen:print("Stop code: average windows experience")
screen:setCursorPos(1,h)
screen:print("Press any key to continue... jk reboot it yourself lazy")
while true do end
end
kernel.log("Kernel loaded.")
kernel.log("Mounting init disks...")
disks.refresh()
ifs.update(disks)
ifs.mount("$", "/")
local fstab=ifs.readAllText("/etc/fstab")
local split = function(str, delim, maxResultCountOrNil)
assert(#delim == 1, "only delim len 1 supported for now")
maxResultCountOrNil = (maxResultCountOrNil or 0)-1
local rv = {}
local buf = ""
for i = 1, #str do
local c = string.sub(str,i,i)
if #rv ~= maxResultCountOrNil and c == delim then
table.insert(rv, buf)
buf = ""
else
buf = buf..c
end
end
table.insert(rv, buf)
return rv
end
for i,v in ipairs(split(fstab,"\n")) do
if v:sub(1,1)=="U" then
local id=""
for i=3,#v do
if v:sub(i,i)==";" then
if i==3 then kernel.log("Invalid fstab line... Skipping.","WARN") goto endline end
id=v:sub(3,i-1)
end
end
local path=v:sub(#id+4)
ifs.mount(id,path)
::endline::
end
end
kernel.log("Disks initialized")
function kernel.saveLog()
ifs.writeAllText("/var/log/syslog.log", LOG_Text)
end
ifs.remove("/tmp")
ifs.makeDir("/tmp")
local drivers={}
drivers.raw={}
drivers.processes={}
drivers.type={}
function drivers.register(object)
drivers.raw[#drivers.raw+1]=object
if object.main then drivers.processes[#drivers.processes+1]=object.main end
if drivers.type[object.type]==nil then drivers.type[object.type]={} end
drivers.type[object.type][#drivers.type[object.type]+1]=object
end
local modules={[0]={}}
for i=0, 100 do
modules[i]={}
end
kernel.log("Gathering modules")
for i,v in ipairs(ifs.list("/lib/modules/Hyperion/")) do
local prior=tonumber(v:sub(1,2))
modules[prior+1][#modules[prior+1]+1]="/lib/modules/Hyperion/"..v
end
kernel.drivers=drivers
kernel.ifs=ifs
kernel.apis=apis
kernel.computer=computer
kernel.initPath=initPath
kernel.arch=arch
kernel.initdisks=disks
kernel.screen=screen
kernel.log("Running modules")
for _,p in ipairs(modules) do
for _,v in ipairs(p) do
local code=ifs.readAllText(v)
local func,err=load(code,"@"..v)
if not func then kernel.log("ModuLoadErr: "..tostring(err), "WARN") goto skip end
local status, err = xpcall(func,debug.traceback, kernel)
if not status then kernel.log("ModuRunErr: "..tostring(err), "WARN") end
::skip::
end
end
kernel.PANIC("Execution complete")

View File

@@ -1,3 +0,0 @@
U $;/
U devfs0000;/dev/
U sysfs0000;/sys/

View File

@@ -1,26 +0,0 @@
local args={...}
local kernel=args[1]
local driver={}
driver.name="CCT Term Module"
driver.version="0.1.0"
driver.type="gpio"
driver.description="CCT redstone Module Kernel Module"
driver.arch="cct"
driver.author="HyperionOS Dev Team"
driver.license="MIT"
driver.api={}
function driver.load()
-- will
end
function driver.unload()
-- Nothing to unload
end
function driver.main()
-- Nothing to run
end
kernel.drivers.register(driver)

View File

@@ -1,105 +0,0 @@
local args={...}
local kernel=args[1]
local apis=kernel.apis
local native=apis.peripheral
local driver={}
local sides = {"top", "bottom", "left", "right", "front", "back"}
local function 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
local function 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
driver.name="CCT TTY Module"
driver.version="0.1.0"
driver.type="tty"
driver.description="CCT TTY Module Kernel Module"
driver.arch="cct"
driver.author="HyperionOS Dev Team"
driver.license="MIT"
driver.api={}
local colors={
[0]=0x000000, -- #000000
0xFFFFFF, -- #FFFFFF
0xFF0000, -- #FF0000
0x00FF00, -- #00FF00
0x0000FF, -- #0000FF
0x00FFFF, -- #00FFFF
0xFF00FF, -- #FF00FF
0xFFFF00, -- #FFFF00
0xFF6D00, -- #FF6D00
0x6DFF55, -- #6DFF55
0x24FFFF, -- #24FFFF
0x924900, -- #924900
0x6D6D55, -- #6D6D55
0xDBDBAA, -- #DBDBAA
0x6D00FF, -- #6D00FF
0xB6FF00 -- #B6FF00
}
local icolors={
[0x1] =0, -- #000000
[0x2] =1, -- #FFFFFF
[0x4] =2, -- #FF0000
[0x8] =3, -- #00FF00
[0x10] =4, -- #0000FF
[0x20] =5, -- #00FFFF
[0x40] =6, -- #FF00FF
[0x80] =7, -- #FFFF00
[0x100] =8, -- #FF6D00
[0x200] =9, -- #6DFF55
[0x400] =10, -- #24FFFF
[0x800] =11, -- #924900
[0x1000] =12, -- #6D6D55
[0x2000] =13, -- #DBDBAA
[0x4000] =14, -- #6D00FF
[0x8000] =15 -- #B6FF00
}
local function getAllScreens()
end
local function wrapScreens()
end
function driver.load()
-- Nothing to load
end
function driver.unload()
-- Nothing to unload
end
function driver.main()
-- Nothing to run
end
kernel.drivers.register(driver)

View File

@@ -1,196 +0,0 @@
local args={...}
local kernel=args[1]
function string.hasSuffix(str, suffix)
return string.sub(str, #suffix+1) == suffix
end
function string.hasPrefix(str, prefix)
return string.sub(str, 1, #prefix) == prefix
end
function string.getSuffix(str, prefix)
return string.sub(str, #prefix+1)
end
function string.getPrefix(str, suffix)
return string.sub(str, 1, #suffix)
end
function string.join(str, ...)
return table.concat(table.pack(str, ...))
end
function string.delim(str, ...)
return table.concat(table.pack(...), str)
end
function string.split(str, delim, maxResultCountOrNil)
assert(#delim == 1, "only delim len 1 supported for now")
maxResultCountOrNil = (maxResultCountOrNil or 0)-1
local rv = {}
local buf = ""
for i = 1, #str do
local c = string.sub(str,i,i)
if #rv ~= maxResultCountOrNil and c == delim then
table.insert(rv, buf)
buf = ""
else
buf = buf..c
end
end
table.insert(rv, buf)
return rv
end
function string.replace(str, search, replacement)
local rv = ""
local consumedLen = 1
local i = 1
while i<#str do
if string.sub(str, i, i+#search-1) == search then
rv = rv .. string.sub(str, consumedLen, i-1) .. replacement
i=i+#search
consumedLen = i
end
i=i+1
end
return rv .. string.sub(str, consumedLen)
end
function table.deepcopy(orig, copies)
copies = copies or {}
if type(orig) ~= 'table' then
return orig
elseif copies[orig] then
return copies[orig]
end
local copy = {}
copies[orig] = copy
for k, v in next, orig, nil do
local copied_key = table.deepcopy(k, copies)
local copied_val = table.deepcopy(v, copies)
copy[copied_key] = copied_val
end
return copy
end
function table.hasKey(tabl, query)
for i,v in pairs(tabl) do
if i==query then
return true
end
end
return false
end
function table.hasVal(tabl, query)
for i,v in pairs(tabl) do
if v==query then
return true
end
end
return false
end
function table.proxy(tbl)
local proxies = setmetatable({}, {__mode = "k"}) -- Weak table to avoid cycles
local function createProxy(t)
if type(t) ~= "table" then return t end
if proxies[t] then return proxies[t] end -- reuse proxy for the same table (handle cycles)
local proxy = {}
proxies[t] = proxy
local mt
mt = {
__index = function(_, k)
local value = t[k]
if type(value) == "table" then
return createProxy(value) -- recursively proxy subtables
else
return value
end
end,
__newindex = function()
error("Attempt to modify read-only table", 2)
end,
__pairs = function()
return function(_, k)
local nextKey, nextValue = next(t, k)
if type(nextValue) == "table" then
nextValue = createProxy(nextValue)
end
return nextKey, nextValue
end, nil, nil
end,
__ipairs = function()
local i = 0
local n = #t
return function()
i = i + 1
if i <= n then
local v = t[i]
if type(v) == "table" then
v = createProxy(v)
end
return i, v
end
end
end,
__metatable = false
}
setmetatable(proxy, mt)
return proxy
end
return createProxy(tbl)
end
local function serialize(table)
local output = "{"
for i,v in pairs(table) do
local coma=true
if type(i) == "string" then
output=output.."[\""..i.."\"]="
end
if type(v) == "table" then
if v == table then
output=string.sub(output,1,#output-(#i+1))
coma=false
else
output=output..serialize(v)
end
elseif type(v) == "string" then
output=output.."[=["..v.."]=]"
elseif type(v) == "number" then
output=output..tostring(v)
elseif type(v) == "boolean" then
if v == true then
output=output.."true"
else
output=output.."false"
end
elseif type(v) == "function" then
output=output..tostring(v)
else
error("serialization of type \""..type(v).."\" is not supported")
end
if coma then
output=output..","
end
end
if #table>0 or string.sub(output,#output,#output) == "," then
output=string.sub(output,1,#output-1)
end
output=output.."}"
return output
end
table.serialize=serialize
kernel.log("Loaded stdlib")

View File

@@ -1,3 +0,0 @@
local args={...}
local kernel=args[1]
kernel._U=table.proxy(_G)

View File

@@ -1,321 +0,0 @@
local args = {...}
local kernel = args[1]
local fs = {}
-- =====================================================================
-- INTERNAL STATE
-- =====================================================================
local disks = {} -- address → disk object
local mounts = {["/"] = "$"} -- mountpoint → disk address (root = boot disk)
local SYMLINK_PREFIX = "#!@SYMLINK["
local SYMLINK_SUFFIX = "]"
local SYMLINK_MAX_DEPTH = 64
-- =====================================================================
-- PATH NORMALIZATION
-- =====================================================================
local function splitPath(p)
local t = {}
for part in p:gmatch("[^/]+") do t[#t+1] = part end
return t
end
local function normalizePath(path)
if not path or path == "" then return "/" end
-- ensure absolute
if path:sub(1,1) ~= "/" then
path = "/" .. path
end
local parts = splitPath(path)
local out = {}
for _,part in ipairs(parts) do
if part == ".." then
if #out > 0 then table.remove(out) end
elseif part ~= "." and part ~= "" then
out[#out+1] = part
end
end
return "/" .. table.concat(out, "/")
end
-- =====================================================================
-- DISK & MOUNT RESOLUTION
-- =====================================================================
-- Finds which disk owns an absolute path
local function resolveMount(abs)
local best = "/"
for mount, addr in pairs(mounts) do
if abs:sub(1, #mount) == mount then
if #mount > #best then
best = mount
end
end
end
local disk = disks[mounts[best]]
if not disk then
error("No disk registered for mount: " .. best)
end
local sub = abs:sub(#best + 1)
if sub == "" then sub = "/" end
return disk, sub
end
-- =====================================================================
-- SYMLINK HANDLING
-- =====================================================================
local function isSymlinkRaw(disk, p)
if not disk:fileExists(p) then return false end
local text = disk:readAllText(p)
return text and text:sub(1, #SYMLINK_PREFIX) == SYMLINK_PREFIX
end
local function readSymlinkRaw(disk, p)
local text = disk:readAllText(p)
if not text then return nil end
if text:sub(1, #SYMLINK_PREFIX) ~= SYMLINK_PREFIX then return nil end
local t = text:sub(#SYMLINK_PREFIX + 1)
if t:sub(-1) == SYMLINK_SUFFIX then
t = t:sub(1, -2)
end
return t
end
-- =====================================================================
-- FULL PATH RESOLUTION (FOLLOWS SYMLINKS)
-- =====================================================================
local function resolveSymlink(path)
local abs = normalizePath(path)
local parts = splitPath(abs)
local out = {}
local depth = 0
local idx = 1
while idx <= #parts do
local comp = parts[idx]
if comp == "." then
-- nothing
elseif comp == ".." then
if #out > 0 then table.remove(out) end
else
local curAbs = "/" .. table.concat(out, "/")
if curAbs ~= "/" then curAbs = curAbs .. "/" end
curAbs = curAbs .. comp
local disk, dpath = resolveMount(curAbs)
if isSymlinkRaw(disk, dpath) then
depth = depth + 1
if depth > SYMLINK_MAX_DEPTH then
error("Too many symlink levels: " .. path)
end
local target = readSymlinkRaw(disk, dpath)
if target then
local newAbs
if target:sub(1,1) == "/" then
-- absolute target
newAbs = normalizePath(target)
else
-- relative to current out[]
local tmp = "/" .. table.concat(out, "/")
if tmp ~= "/" then tmp = tmp .. "/" end
tmp = tmp .. target
newAbs = normalizePath(tmp)
end
-- rebuild remaining parts
local remaining = {}
for j = idx + 1, #parts do
remaining[#remaining+1] = parts[j]
end
-- restart symlink resolution with new path
abs = newAbs
parts = splitPath(abs)
-- append remaining
for _,x in ipairs(remaining) do parts[#parts+1] = x end
out = {}
idx = 0
else
out[#out+1] = comp
end
else
out[#out+1] = comp
end
end
idx = idx + 1
end
local finalAbs = "/" .. table.concat(out, "/")
return resolveMount(finalAbs)
end
-- =====================================================================
-- PUBLIC API
-- =====================================================================
-- MOUNT OPERATIONS -----------------------------------------------------
function fs.virtDisk(diskObj)
if kernel.uid ~= 0 then error("Permission Denied") end
if disks[diskObj.address] then
error("Disk exists: " .. diskObj.address)
end
disks[diskObj.address] = diskObj
end
function fs.mount(disk, mountPoint)
if kernel.uid ~= 0 then error("Permission Denied") end
mountPoint = normalizePath(mountPoint)
local drive, path = resolveMount(normalizePath(mountPoint))
if not drive:directoryExists(path) then error("Must mount on folder") end
if mountPoint ~= "/" and mounts[mountPoint] then
error("Already mounted: " .. mountPoint)
end
mounts[mountPoint] = disk
end
function fs.unmount(mountPoint)
if kernel.uid ~= 0 then error("Permission Denied") end
mountPoint = normalizePath(mountPoint)
if mountPoint == "/" then error("Cannot unmount root") end
mounts[mountPoint] = nil
end
function fs.eject(addr)
if kernel.uid ~= 0 then error("Permission Denied") end
disks[addr] = nil
end
function fs.isMount(path)
if mounts[normalizePath(path)] then return true, mounts[normalizePath(path)] end
end
-- SYMLINK API ----------------------------------------------------------
function fs.symlink(target, linkPath)
kernel.log("WARNING: Symlinks are a untested feature if you find any bugs please report them to https://git.astronand.dev/Hyperion/HyperionOS","WARN")
local disk, p = resolveMount(normalizePath(linkPath))
return disk:writeAllText(p, SYMLINK_PREFIX .. target .. SYMLINK_SUFFIX)
end
function fs.isLink(path)
local disk, p = resolveMount(normalizePath(path))
return isSymlinkRaw(disk, p)
end
function fs.readLink(path)
local disk, p = resolveMount(normalizePath(path))
return readSymlinkRaw(disk, p)
end
-- FILE OPERATIONS ------------------------------------------------------
function fs.exists(path, ...)
local disk, p = resolveSymlink(path)
return disk:fileExists(p, ...) or disk:directoryExists(p, ...)
end
function fs.isFile(path, ...)
local disk, p = resolveSymlink(path)
return disk:fileExists(p, ...)
end
function fs.isDir(path, ...)
local disk, p = resolveSymlink(path)
return disk:directoryExists(p, ...)
end
function fs.list(path, ...)
local disk, p = resolveSymlink(path)
return disk:list(p, ...)
end
function fs.makeDir(path, ...)
local disk, p = resolveSymlink(path)
return disk:makeDirectory(p, ...)
end
-- remove does NOT follow symlinks (UNIX semantics)
function fs.remove(path, ...)
if fs.isMount(path) then return "Cannot delete mounted folder" end
local abs = normalizePath(path)
local disk, p = resolveMount(abs)
return disk:remove(p, ...)
end
function fs.readAllText(path, ...)
local disk, p = resolveSymlink(path)
return disk:readAllText(p, ...)
end
function fs.writeAllText(path, text, ...)
local disk, p = resolveSymlink(path)
return disk:writeAllText(p, text, ...)
end
function fs.appendAllText(path, text, ...)
local disk, p = resolveSymlink(path)
return disk:appendAllText(p, text, ...)
end
function fs.load(path, name, ...)
return load(fs.readAllText(path, ...), name or path, nil, kernel._U)
end
function fs.getSize(path, ...)
local disk, p = resolveSymlink(path)
return disk:getSize(p, ...)
end
-- =====================================================================
-- INIT
-- =====================================================================
kernel.log("Loading disks for vfs")
local ok,err = xpcall(function()
for _,v in kernel.initdisks.list() do
fs.virtDisk(v)
end
end, debug.traceback)
if not ok then kernel.panic(err) end
kernel.disks=disks
kernel.mounts=mounts
kernel.fs = fs
kernel.cache.preload.fs = table.proxy(kernel.fs)
kernel.cache.preload.filesystem = kernel.cache.preload.fs

View File

@@ -1,179 +0,0 @@
local args = {...}
local kernel = args[1]
function kernel.fs.mkvirtfs(address, readOnly, label, autoRegister)
if kernel.uid ~= 0 then error("Permission Denied") end
local disk = {}
disk.address = address
local isRO = readOnly or false
local backend = {
["/"] = { __dir = true }
}
-----------------------------------------------------------------
-- Helpers
-----------------------------------------------------------------
local function norm(path)
if not path or path == "" then return "/" end
if path:sub(1,1) ~= "/" then path = "/" .. path end
path = path:gsub("/+", "/")
return path
end
local function split(path)
local parts = {}
for p in path:gmatch("[^/]+") do table.insert(parts, p) end
return parts
end
local function getNode(path)
path = norm(path)
if path == "/" then return backend["/"] end
local node = backend["/"]
for _, part in ipairs(split(path)) do
node = node[part]
if not node then return nil end
end
return node
end
local function getParent(path)
local parts = split(norm(path))
local name = table.remove(parts)
local parentPath = "/" .. table.concat(parts, "/")
return getNode(parentPath), name
end
local function ensureWrite()
if isRO then return false, "read-only" end
return true
end
-----------------------------------------------------------------
-- File IO (supports string or file-object with read/write funcs)
-----------------------------------------------------------------
local function fileRead(node, ...)
if type(node) == "string" then
return node
elseif type(node) == "table" and node.__file and node.read then
return node.read(...)
end
return nil
end
local function fileWrite(parent, name, text, ...)
local node = parent[name]
if type(node) == "string" then
local ok, err = ensureWrite(); if not ok then return false, err end
parent[name] = text
return true
elseif type(node) == "table" and node.__file and node.write then
return node.write(text, ...)
end
parent[name] = text
return true
end
local function fileAppend(parent, name, text, ...)
local node = parent[name]
if type(node) == "string" then
local ok, err = ensureWrite(); if not ok then return false, err end
parent[name] = node .. text
return true
elseif type(node) == "table" and node.__file and node.write then
return node.write((node.read() or "") .. text, ...)
end
parent[name] = text
return true
end
-----------------------------------------------------------------
-- API
-----------------------------------------------------------------
function disk:isReadOnly() return isRO end
function disk:spaceUsed() return 0 end
function disk:spaceTotal() return 0 end
function disk:readAllText(path, ...)
local node = getNode(path)
if not node then return nil, "file not found" end
return fileRead(node, ...)
end
function disk:writeAllText(path, text, ...)
local parent, name = getParent(path)
if not parent or not parent.__dir then return false, "invalid directory" end
return fileWrite(parent, name, tostring(text), ...)
end
function disk:appendAllText(path, text, ...)
local parent, name = getParent(path)
if not parent or not parent.__dir then return false, "invalid directory" end
return fileAppend(parent, name, tostring(text), ...)
end
function disk:list(path)
local node = getNode(path)
if not node or not node.__dir then return nil, "not a directory" end
local out = {}
for k, _ in pairs(node) do if k ~= "__dir" then table.insert(out, k) end end
return out
end
function disk:fileExists(path)
local node = getNode(path)
return type(node) == "string" or (type(node) == "table" and node.__file)
end
function disk:directoryExists(path)
local node = getNode(path)
return node and type(node) == "table" and node.__dir or false
end
function disk:makeDirectory(path)
local ok, err = ensureWrite(); if not ok then return false, err end
local parent, name = getParent(path)
if not parent or not parent.__dir then return false, "invalid parent directory" end
parent[name] = { __dir = true }
return true
end
function disk:remove(path)
local ok, err = ensureWrite(); if not ok then return false, err end
local parent, name = getParent(path)
if not parent or not parent[name] then return false, "not found" end
parent[name] = nil
return true
end
function disk:setLabel(new)
local ok, err = ensureWrite(); if not ok then return false, err end
label = tostring(new)
end
function disk:getLabel()
return label
end
function disk:size(path, ...)
local node = getNode(path)
if type(node) == "string" then return #node end
if type(node) == "table" and node.__file and node.read then
local v = node.read(...)
return v and #v or 0
end
return 0
end
-----------------------------------------------------------------
-- Auto-register with kernel and return backend
-----------------------------------------------------------------
if autoRegister then
kernel.fs.virtDisk(disk)
end
return backend, disk
end

View File

@@ -1,56 +0,0 @@
local args = {...}
local kernel = args[1]
-- List of search paths
local paths = {
"/lib/?",
"/usr/lib/?",
"/usr/local/lib/?"
}
-- Custom require implementation
function require(module)
-- Return cached module if it already exists
if kernel.cache.preload[module]~=nil then
return kernel.cache.preload[module]
end
local err = {}
for _, path in ipairs(paths) do
-- Replace "?" with module name
local filePath = string.replace(path, "?", module)
-- Try to open file
local file = kernel.fs.isFile(filePath)
if file then
local content = kernel.fs.readAllText(filePath)
-- Load the module as Lua code
local chunk, loadErr = load(content, filePath)
if not chunk then
table.insert(err, "Error loading: " .. filePath .. ": " .. loadErr)
else
-- Execute the module. If the module returns a value, cache it.
local ok, result = xpcall(chunk, debug.traceback)
if not ok then
table.insert(err, "Error executing: "..filePath..": "..tostring(result))
else
if result ~= nil then
kernel.cache.preload[module] = result
return result
end
-- If module doesn't return anything, cache `true` like Lua does
kernel.cache.preload[module] = true
return true
end
end
else
table.insert(err, "Module not found: " .. filePath)
end
end
-- If nothing worked, raise an error with all reasons
error("Unable to require module '" .. module .. "':\n" .. table.concat(err, "\n"))
end
kernel.log("Created require")

View File

@@ -1,57 +0,0 @@
local args = {...}
local kernel = args[1]
local data = kernel.fs.mkvirtfs("devfs0000", true, "devfs", true)
if not kernel.fs.isDir("/dev") then kernel.fs.makeDir("/dev") end
kernel.devfs={}
kernel.devfs.data=data
data["/"]["OSVERSION"]="Hyperion 1.0.0"
data["/"]["eeprom"]={
__file=true,
read=function()
return kernel.computer:getEEPROM()
end,
write=function(text)
if kernel.uid ~= 0 then error("Permission Denied") end
kernel.computer:setEEPROM(text)
end
}
data["/"]["null"]={
__file=true,
read=function() end,
write=function() end
}
data["/"]["random"]={
__file=true,
read=function(amount)
local s = ""
for i = 1, amount do
s = s .. string.char(math.random(0, 255))
end
return s
end,
write=function() end
}
data["/"]["zero"]={
__file=true,
read=function(amount)
return ("\0"):rep(amount)
end,
write=function() end
}
data["/"]["rtc0"]={
__file=true,
read=function()
return kernel.computer:time()
end,
write=function() end
}
data["/"]["rtc"]={
__file=true,
read=function()
return kernel.computer:time()
end,
write=function() end
}
kernel.log("Created devfs")

View File

@@ -1,18 +0,0 @@
local args = {...}
local kernel = args[1]
local data = kernel.fs.mkvirtfs("sysfs0000", true, "sysfs", true)
if not kernel.fs.isDir("/sys") then kernel.fs.makeDir("/sys") end
data["/"]["OSVERSION"]="Hyperion 1.0.0"
data["/"]["eeprom"]={
__file=true,
read=function()
return kernel.computer:getEEPROM()
end,
write=function(text)
if kernel.uid ~= 0 then error("Permission Denied") end
kernel.computer:setEEPROM(text)
end
}
kernel.log("Created sysfs")

View File

@@ -1,26 +0,0 @@
local args = {...}
local kernel = args[1]
local ifs=kernel.ifs
kernel.log("Mounting fstab")
local fstab=ifs.readAllText("/etc/fstab")
local entrys = string.split(fstab,"\n")
for i,v in ipairs(entrys) do
if v:sub(1,1)=="U" then
local id=""
for i=3,#v do
if v:sub(i,i)==";" then
if i==3 then kernel.log("Invalid fstab line... Skipping.","WARN") goto endline end
id=v:sub(3,i-1)
end
end
local path=v:sub(#id+4,#v)
if i~=#entrys then
path=path:sub(1,#path-1)
end
kernel.log("Mounted "..id.." to "..path)
kernel.fs.mount(id,path)
::endline::
end
end
kernel.log("Mounted all disks")

View File

@@ -1,13 +0,0 @@
local args = {...}
local kernel = args[1]
kernel.drivers.processes[#kernel.drivers.processes+1]=function()
while true do
local event={kernel.computer:getMachineEvent()}
while event[1]~=nil do
kernel.hpv.sendSig("all", table.unpack(event))
event={kernel.computer:getMachineEvent()}
end
coroutine.yield()
end
end
kernel.log("Created keventd daemon")

View File

@@ -1,22 +0,0 @@
local args = {...}
local kernel = args[1]
kernel.log("Loading third party drivers")
for _,subf in ipairs(kernel.fs.list("/lib/modules/")) do
if kernel.fs.isDir("/lib/modules/"..subf) then
if subf~="Hyperion" then
for _,driver in ipairs(kernel.fs.list("/lib/modules/"..subf)) do
kernel.log("Compiling driver \""..subf..":"..driver.."\"")
local code = kernel.fs.readAllText("/lib/modules/"..subf.."/"..driver)
local func, err = load(code, "@"..driver)
if not func then
kernel.log("DriverCompileErr: "..tostring(err), "ERROR")
else
local ok, err = xpcall(func, debug.traceback, table.unpack(args))
if not ok then
kernel.log("DriverExecErr: "..tostring(err), "ERROR")
end
end
end
end
end
end

View File

@@ -1,14 +0,0 @@
local args = {...}
local kernel = args[1]
kernel.log("initializing third party drivers")
for _,v in ipairs(kernel.drivers.raw) do
if v.arch==kernel.arch then
if v.load then
kernel.log("Loading "..v.name)
local ok,err = xpcall(v.load, debug.traceback)
if not ok then
kernel.log("DriverLoadErr: "..tostring(err))
end
end
end
end

View File

@@ -1,210 +0,0 @@
local args = {...}
local kernel = args[1]
local tasks={}
local currentTask={}
local signals={}
local tid=2
local gid=2
local hookuuid=0
local sys={}
function sys.hookSig(sig, func)
if not currentTask.signal[sig] then
currentTask.signal[sig]={}
end
hookuuid=hookuuid+1
currentTask.signal[sig][tostring(hookuuid)]=func
callbackid=tostring(hookuuid)
return {
remove=function()
currentTask.signal[sig][callbackid]=nil
end
}
end
function sys.clearSigHooks(typ)
if not typ or typ == "all" then
signals[tostring(currentTask.pid)]={}
currentTask.signal=signals[tostring(currentTask.pid)]
else
if currentTask.signal[typ] then
currentTask.signal[typ]={}
end
end
end
function sys.sendSig(pid, signal, ...)
if pid=="all" then
for i,v in pairs(tasks) do+
v.sigQ[#v.sigQ+1]={signal, ...}
end
return
end
if not tasks[tostring(pid)] then return false end
tasks[tostring(pid)].sigQ[#tasks[tostring(pid)].sigQ+1]={signal, ...}
return true
end
function sys.flushSigs()
local sigs = {}
for i,v in ipairs(currentTask.sigQ) do
sigs[i]=v
end
for i=1, #sigs do
local sig = sigs[i]
if currentTask.signal[sig[1]] then
for k,v in pairs(currentTask.signal[sig[1]]) do
coroutine.resumeWithTimeout(coroutine.create(function()
local ok,err = xpcall(v,debug.traceback,table.unpack(sig))
if not ok and sig[1]~="callbackErr" then
sys.sendSig(currentTask.pid, "callbackErr", sig[1], k, err)
end
end),10)
end
else
for k,v in pairs(currentTask.signal["unhandledSignal"]) do
coroutine.resumeWithTimeout(coroutine.create(function()
local ok,err = xpcall(v,debug.traceback,table.unpack(sig))
if not ok and sig[1]~="callbackErr" then
sys.sendSig(currentTask.pid, "callbackErr", sig[1], k, err)
end
end),10)
end
end
end
end
function sys.spawn(func, name, evars, args, stdin, stdout, stderr)
local id=tid
tid=tid+1
name=name or tostring(id)
signals[tostring(id)]={}
tasks[tostring(id)]={
coro=coroutine.create(function()
local ret = {xpcall(func, debug.traceback, table.unpack(args))}
if not ret[1] then
sys.sendSig(currentTask.ppid, "ChildTaskError", id, err)
else
sys.sendSig(currentTask.ppid, "ChildTaskExit", id, table.unpack(ret, 2))
end
currentTask.status="Z"
end),
name=name,
pid=id,
ppid=currentTask.pid,
tgid=currentTask.tgid,
user=kernel.user,
uid=kernel.uid,
evars=evars,
args=args,
vy=0,
ivy=0,
status="R",
sleep=0,
signal=signals[tostring(id)],
parent=currentTask,
children={},
sibling=currentTask.children,
sigQ={}
}
end
function sys.exit(...)
sys.sendSig(currentTask.ppid, "ChildTaskExit", currentTask.pid, ...)
currentTask.status="Z"
coroutine.yield()
end
local function collectZombieProc()
local ret = {}
for _,v in pairs(tasks) do
if v.status=="Z" then
local pid = v.pid
for _,c in ipairs(v.children) do
c.parent=tasks["1"]
c.sibling=tasks["1"].children
c.ppid=1
c.tgid=1
end
signals[tostring(pid)]=nil
tasks[tostring(pid)]=nil
table.insert(ret, pid)
end
end
return ret
end
kernel.log("initPath is: " .. tostring(kernel.initPath))
signals["1"]={}
tasks["1"]={
coro=coroutine.create(function()
local ok, code_or_err = xpcall(function() return kernel.fs.readAllText(kernel.initPath) end, debug.traceback)
if not ok then currentTask.status="Z"; kernel.panic(code_or_err) end
local code = code_or_err
local func, err = load(code, "@SysInit", nil, kernel._U)
if not func then currentTask.status="Z"; kernel.panic(err) end
local ok, err = xpcall(func, debug.traceback, kernel)
if not ok then
currentTask.status="Z"
kernel.panic(err)
else
currentTask.status="Z"
kernel.panic("Attempted to kill init!")
end
end),
name="SysInit",
pid=1,
ppid=0,
tgid=1,
user="root",
uid=0,
evars={},
args={kernel},
vy=0,
ivy=0,
status="R",
sleep=0,
signal=signals["1"],
parent={name="Hyprkrnl",pid=0},
children={},
sibling={},
sigQ={}
}
tasks["1"].sibling={tasks["1"]}
kernel.log("Created pid 1")
kernel.cache.preload.sys=table.proxy(sys)
kernel.cache.preload.system=kernel.cache.preload.sys
kernel.cache.preload.os=kernel.cache.preload.sys
kernel.hpv=sys
kernel.tasks=tasks
kernel.signals=signals
kernel.currentTask=currentTask
kernel.saveLog()
kernel.status="running"
while kernel.status~="Panic" do
for k,v in pairs(tasks) do
currentTask=v
kernel.currentTask=v
kernel.process=currentTask.name
kernel.user=currentTask.user
kernel.uid=currentTask.uid
sys.flushSigs()
local status = coroutine.resumeWithTimeout(currentTask.coro, 50)
if status then
currentTask.vy=currentTask.vy+1
else
currentTask.ivy=currentTask.ivy+1
end
end
collectZombieProc()
end
kernel.process="Kernel"
kernel.user="root"
kernel.uid=0
kernel.panic(kernel.reason or "Exited pid 0")

View File

@@ -1,3 +0,0 @@
local args={...}
local kernel=args[1]
kernel.fs.load("/bin/bash")()

212
LICENSE Executable file → Normal file
View File

@@ -1,18 +1,202 @@
MIT License
Copyright (c) 2025 Astronand
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
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:
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
1. Definitions.
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.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

20
Makefile Normal file
View File

@@ -0,0 +1,20 @@
PYTHON := python3
ARCH_FLAG := $(if $(ARCH),--arch $(ARCH),)
MODE_FLAG := $(if $(DEV),--dev,--release)
.PHONY: build build-mini build-test build-mini-test clean
build:
$(PYTHON) build.py build $(ARCH_FLAG) $(MODE_FLAG)
build-mini:
$(PYTHON) build.py build-mini $(ARCH_FLAG) $(MODE_FLAG)
build-test:
$(PYTHON) build.py build-test $(ARCH_FLAG) $(MODE_FLAG)
build-mini-test:
$(PYTHON) build.py build-mini-test $(ARCH_FLAG) $(MODE_FLAG)
clean:
$(PYTHON) build.py clean

52
README.md Executable file → Normal file
View File

@@ -1,3 +1,53 @@
[![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
A OS made for lua enviroments.
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.
---
## Building
See `building.md`.
---
## Features
- Functionality is split into kernel modules (`.kmod`)
- Task-based lightweight thread/task preemptive scheduler with process isolation and IPC support
- Virtual filesystem unified interface for disk, RAM, and virtual filesystems
- TTY & Shell
---
## Kernel Modules
Modules are loaded in priority order from `/lib/modules/`.
You can add your own `.kmod` files to extend kernel functionality without modifying the core.
---
## Debugging & Logging
The kernel logs to `/var/log/syslog.log` during runtime.
you can add to it by doing `syscall.log(text, tag, color)`
---
## Contributing
Contributions are welcome, though please follow these guidelines:
1. No AI-generated kernel code, keep the core human written.
2. Modularize, new features should go into kernel modules where possible.
3. Document, update comments and docs when adding/changing functionality.
4. Test, ensure your changes dont break existing functionality.
Add your name to `contributors.md` when your PR is merged.
---
## License
This project is licensed under the MIT License. See the LICENSE file for details.

View File

@@ -0,0 +1,18 @@
--:Minify:--
return {
white=0xFFFFFF,
red=0xFF0000,
green=0x00FF00,
blue=0x0000FF,
cyan=0x00FFFF,
yellow=0xFFFF00,
purple=0xFF00FF,
black=0x000000,
gray=0x888888,
lightgrey=0xBBBBBB,
darkgrey=0x444444,
orange=0xFF8800,
mint=0x00FF88,
brown=0xa52a2a,
chocolate=0xd2691e
}

View File

@@ -0,0 +1,143 @@
--:Minify:--
local fs={}
-- "open" : open
-- "read" : read
-- "write" : write
-- "close" : close
function fs.open(path, mode)
local fd=syscall.open(path,mode)
local ret={
close=function()
-- close file
return syscall.close(fd)
end,
flush=function()
-- close and reopen file to flush buffers
syscall.fsync(fd)
end
}
if mode=="r" then
ret.read=function(count)
local data = syscall.read(fd,count)
return data
end
ret.readAll=function(chunkSize)
local chunks={} -- to store read chunks
while true do
local chunk=syscall.read(fd,chunkSize or 65536)
if chunk==nil or #chunk==0 then break end
table.insert(chunks,chunk)
end
return table.concat(chunks)
end
ret.readLine = function(chunkSize)
local buffer = {} -- stores leftover data
local buffer_str = "" -- concatenated buffer
local chunk_size = chunkSize or 65536 -- adjust chunk size for performance
local eof = false
while true do
-- Try to find a newline in the current buffer
local line_end = buffer_str:find("\n")
if line_end then
local line = buffer_str:sub(1, line_end - 1)
buffer_str = buffer_str:sub(line_end + 1)
return line
end
-- If EOF was reached previously and buffer is empty, stop
if eof then
if buffer_str ~= "" then
local last_line = buffer_str
buffer_str = ""
return last_line
else
return nil
end
end
-- Read the next chunk
local chunk = syscall.read(fd, chunk_size)
if not chunk or chunk == "" then
eof = true
else
buffer_str = buffer_str .. chunk
end
end
end
elseif mode=="w" then
ret.write=function(data)
-- write data to file
return syscall.write(fd,data)
end
elseif mode=="a" then
ret.write=function(data)
-- append data to file
return syscall.write(fd,data)
end
else
error("Invalid mode '"..mode.."'",2)
end
return ret
end
function fs.readAllText(path)
local file=fs.open(path,"r")
if not file then return false end
local content=file.readAll()
file.close()
return content
end
function fs.writeAllText(path, data)
local file=fs.open(path,"w")
file.write(data)
file.close()
end
function fs.appendAllText(path, data)
local file=fs.open(path,"a")
if not file then return false end
file.write(data)
file.close()
end
function fs.mkdir(path)
return syscall.mkdir(path)
end
function fs.remove(path)
return syscall.remove(path)
end
function fs.list(path)
return syscall.listdir(path)
end
function fs.type(path)
return syscall.type(path)
end
function fs.stat(path)
return syscall.stat(path)
end
function fs.exists(path)
return syscall.exists(path)
end
function fs.getcwd()
return syscall.getcwd()
end
function fs.chdir(path)
return syscall.chdir(path)
end
function fs.isDir(path)
return syscall.type(path) == "directory"
end
return fs

View File

@@ -0,0 +1,201 @@
--:Minify:--
local http = {}
local syscall = syscall
local function parseUrl(url)
local proto, host, path =
url:match(
"^(https?://)([^/]+)(/.*)$"
)
if not proto then
proto, host =
url:match(
"^(https?://)([^/]+)$"
)
path = "/"
end
if not proto then
return nil, "EINVAL"
end
return host, path
end
local function buildRequest(req)
local host, path =
parseUrl(req.url)
if not host then
return nil, path
end
local method =
req.method or "GET"
local body =
req.body or ""
local headers =
table.deepcopy(
req.headers or {}
)
headers.Host =
headers.Host or host
headers.Connection =
headers.Connection or "close"
if body ~= "" then
headers["Content-Length"] =
tostring(#body)
end
local out = {
method ..
" " ..
path ..
" HTTP/1.1"
}
for k, v in pairs(headers) do
out[#out + 1] =
tostring(k) ..
": " ..
tostring(v)
end
out[#out + 1] = ""
out[#out + 1] = body
return table.concat(out, "\r\n")
end
local function parseResponse(raw)
local headerEnd =
raw:find(
"\r\n\r\n",
1,
true
)
if not headerEnd then
return nil, "EBADMSG"
end
local headerPart =
raw:sub(1, headerEnd - 1)
local body =
raw:sub(headerEnd + 4)
local lines = {}
for line in headerPart:gmatch("[^\r\n]+") do
lines[#lines + 1] = line
end
local _, code, msg =
lines[1]:match(
"^(HTTP/%S+)%s+(%d+)%s*(.*)$"
)
local headers = {}
for i = 2, #lines do
local k, v =
lines[i]:match(
"^([^:]+):%s*(.*)$"
)
if k then
headers[k:lower()] = v
end
end
return {
code = tonumber(code),
message = msg,
headers = headers,
body = body
}
end
local function readAll(fd)
local out = ""
while true do
local chunk =
syscall.read(fd, 4096)
if not chunk or chunk == "" then
break
end
out = out .. chunk
end
return out
end
function http.request(req)
local raw, err =
buildRequest(req)
if not raw then
return nil, err
end
local fd =
syscall.socket(0, 0)
if not fd then
return nil, "ESOCKET"
end
local ok, err =
syscall.connect(fd, req.url)
if not ok then
syscall.close(fd)
return nil, err
end
local ok2, err2 =
syscall.write(fd, raw)
if not ok2 then
syscall.close(fd)
return nil, err2
end
local resp =
readAll(fd)
syscall.close(fd)
return parseResponse(resp)
end
function http.get(url, headers)
return http.request({
url = url,
method = "GET",
headers = headers
})
end
function http.post(url, body, headers)
return http.request({
url = url,
method = "POST",
body = body,
headers = headers
})
end
return http

View File

@@ -0,0 +1,6 @@
local io = {}
local fs = require("fs")
function io.open(path, mode)
return fs.open(path, mode)
end

View File

@@ -0,0 +1,344 @@
--:Minify:--
local args={...}
local bootdrive=args[1]
local gpu=components:getFirst("gpu")
local screenTextBuffer=nil
local cursorX,cursorY=0,0
local screenSizeX,screenSizeY=128,25
if gpu then
screenTextBuffer=gpu:newBuffer(screenSizeX,screenSizeY)
for t,v in components:list() do
if t == "screen" then
gpu:assignBuffer(screenTextBuffer, v)
end
end
end
local function write(text)
cursorX, cursorY = screenTextBuffer:pasteText(cursorX, cursorY, "SCROLL_SPILL_CLEAR", text)
cursorY = cursorY + 1
cursorX = 0
if cursorY >= screenSizeY then
cursorY = screenSizeY-1
screenTextBuffer:newline()
end
end
local function displaySuperBadError(err)
gpu:freeAllBuffers()
screenTextBuffer=gpu:newBuffer(screenSizeX,screenSizeY)
for t,v in components:list() do
if t == "screen" then
gpu:assignBuffer(screenTextBuffer, v)
end
end
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
local ok, err = xpcall(function()
local apis = {}
local lua = {
coroutine = true,
debug = true,
_VERSION = true,
assert = true,
collectgarbage = true,
error = true,
gcinfo = 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,
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 function string_file(file)
local str = file:read()
local buf = {str or ""}
local pos = 1
local closed = false
local function content()
return table.concat(buf)
end
local function set_content(s)
buf = {s}
end
local function flush()
file.write(content())
end
local file = {}
function file.read(n)
assert(not closed, "file is closed")
local s = content()
local len = #s
if not n then
local out = s:sub(pos)
pos = len + 1
return out
end
local out = s:sub(pos, pos + n - 1)
pos = pos + #out
return out
end
function file.write(data)
assert(not closed, "file is closed")
local s = content()
local before = s:sub(1, pos - 1)
local after = s:sub(pos + #data)
set_content(before .. data .. after)
pos = pos + #data
return true
end
function file.seek(whence, offset)
assert(not closed, "file is closed")
local s = content()
local len = #s
whence = whence or "cur"
offset = offset or 0
if whence == "set" then
pos = offset + 1
elseif whence == "cur" then
pos = pos + offset
elseif whence == "end" then
pos = len + offset + 1
else
error("invalid whence")
end
if pos < 1 then pos = 1 end
if pos > len + 1 then pos = len + 1 end
return pos - 1
end
function file:close()
assert(not closed, "file is closed")
flush()
closed = true
end
function file:flush()
assert(not closed, "file is closed")
flush()
end
return file
end
local function getFile(path)
local file = bootdrive:open(path, "r")
if not file then
displaySuperBadError("Could not open file: " .. path)
end
local content = file:read()
return content
end
local Kernel = load(getFile("/boot/kernel.lua"),"@Kernel")
local initFs = load(getFile("/boot/ac/initdisks"),"@Init_disks")(apis)
local fs = load(getFile("/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 computercomp=apis.components:getFirst("computer")
local uefi=apis.components:getFirst("uefi")
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()
return computercomp:getMachineEvent()
end,
getEEPROM = function() return uefi.data end,
setEEPROM = function(_, text)
uefi.data=text
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,387 @@
--: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,
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,
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
if not apis.fs.exists("/nvram.dat") then
local file = apis.fs.open("/nvram.dat", "w")
file.write("Hello, World!")
file.close()
end
local eeprom
if apis.fs.exists("/startup.lua") then
eeprom="/startup.lua"
elseif apis.fs.exists("/eeprom") then
eeprom="/eeprom"
end
local eventQueue = {}
local function queueEvent(event, ...)
table.insert(eventQueue, {event, ...})
end
local colors = {
[0x000000]=0x0001,
[0xFFFFFF]=0x0002,
[0xFF0000]=0x0004,
[0x00FF00]=0x0008,
[0x0000FF]=0x0010,
[0x00FFFF]=0x0020,
[0xFF00FF]=0x0040,
[0xFFFF00]=0x0080,
[0xFF6D00]=0x0100,
[0x6DFF55]=0x0200,
[0x24FFFF]=0x0400,
[0x924900]=0x0800,
[0x6D6D55]=0x1000,
[0xDBDBAA]=0x2000,
[0x6D00FF]=0x4000,
[0xB6FF00]=0x8000
}
local fg,bg=0x6D6D55,0x000000
local l1f,l1d,l2,ops={},{},{},0
local function findClosest(tbl, target)
local closest = nil
local smallestDiff = math.huge
for k, _ in pairs(tbl) do
local diff = math.abs(k - target)
if diff < smallestDiff then
smallestDiff = diff
closest = k
end
end
return closest
end
local function aprox(c24)
ops = ops + 1
if ops % 1024 == 0 then
l1d = {}
l1f = {}
end
if ops % 8192 == 0 then
l2 = {}
end
if l2[c24] ~= nil then
return l2[c24]
end
if l1d[c24] ~= nil then
l1f[c24] = l1f[c24] + 1
if l1f[c24] >= 16 then
l2[c24] = l1d[c24]
l1d[c24] = nil
l1f[c24] = nil
return l2[c24]
end
return l1d[c24]
end
local closestKey = findClosest(colors, c24)
if not closestKey then return nil end
local value = colors[closestKey]
l1d[c24] = value
l1f[c24] = 1
return value
end
local EFI = {
getEpochMs = function() return apis.os.epoch("utc") end,
getUptime = function() return apis.os.clock() * 1000 end,
date = function() return apis.os.date("!%Y-%m-%dT%H:%M:%SZ", apis.os.epoch("utc") / 1000) end,
getMachineEvent = function()
if #eventQueue > 0 then
return table.unpack(table.remove(eventQueue, 1))
else
return nil
end
end,
getEEPROM = function() return getFile(eeprom) end,
setEEPROM = function(_, text)
local h = apis.fs.open(eeprom, "w")
h.write(text)
h.close()
end,
initfs=fs,
disks=initFs,
screenCtl={
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(aprox(color))
end,
setTextColor = function(_, color)
apis.term.setTextColor(aprox(color))
end,
getBackgroundColor = function()
return bg
end,
getTextColor = function()
return fg
end,
enable=function() end,
disable=function() end
},
architecture="cct",
getNvram = function() return getFile("/nvram.dat") end,
setNvram = function(_, text)
local h = apis.fs.open("/nvram.dat", "w")
h.write(text)
h.close()
end,
firmware=apis,
reboot=false
}
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, EFI)
if not ok and not EFI.reboot then displaySuperBadError(err) end
if err then
apis.os.reboot()
else
apis.os.shutdown()
end
end)
function coroutine.resumeWithTimeout(co, timeout, ...)
local startTime = EFI.getEpochMs()
debug.sethook(co, function()
if EFI.getEpochMs() > 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
if EFI.reboot then
apis.os.reboot()
end
displaySuperBadError("Kernel error: " .. tostring(err))
coroutine.yield("key")
end
initFs:refresh()
end
end, debug.traceback)
if not ok then displaySuperBadError("Fatal error during boot: " .. err) end
while true do coroutine.yield("key") end

View File

@@ -1,119 +1,119 @@
-- 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 = {...}
if _HOST:find("UnBIOS") then return end
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
_G._HOST = _G._HOST .. " (UnBIOS)"
-- 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/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(table.unpack(args))
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
--: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,272 @@
--:Minify:--
local apis = ({...})[1]
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH
local fs = apis.fs
local native = apis.peripheral
local peripheral = {}
local sides = {"top", "bottom", "left", "right", "front", "back"}
function peripheral.getNames()
local results = {}
for n = 1, #sides do
local side = sides[n]
if native.isPresent(side) then
table.insert(results, side)
if native.hasType(side, "peripheral_hub") then
local remote = native.call(side, "getNamesRemote")
for _, name in ipairs(remote) do
table.insert(results, name)
end
end
end
end
return results
end
function peripheral.isPresent(name)
if native.isPresent(name) then
return true
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return true
end
end
return false
end
function peripheral.getType(peripheral)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.getType(peripheral)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "getTypeRemote", peripheral)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return table.unpack(mt.types)
end
end
function peripheral.hasType(peripheral, peripheral_type)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.hasType(peripheral, peripheral_type)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.types[peripheral_type] ~= nil
end
end
function peripheral.getMethods(name)
if native.isPresent(name) then
return native.getMethods(name)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "getMethodsRemote", name)
end
end
return nil
end
function peripheral.getName(peripheral)
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.name
end
function peripheral.call(name, method, ...)
if native.isPresent(name) then
return native.call(name, method, ...)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "callRemote", name, method, ...)
end
end
return nil
end
function peripheral.wrap(name)
local methods = peripheral.getMethods(name)
if not methods then
return nil
end
local types = { peripheral.getType(name) }
for i = 1, #types do types[types[i]] = true end
local result = setmetatable({}, {
__name = "peripheral",
name = name,
type = types[1],
types = types,
})
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)
end
end
return result
end
function peripheral.find(ty, filter)
local results = {}
for _, name in ipairs(peripheral.getNames()) do
if peripheral.hasType(name, ty) then
local wrapped = peripheral.wrap(name)
if filter == nil or filter(name, wrapped) then
table.insert(results, wrapped)
end
end
end
return table.unpack(results)
end
local 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
})
internal["rom"] = createDisk("rom", "/rom", true, {
setLabel = function(label)
error("Device is read-only")
end,
getLabel = function()
return "cctrom"
end
})
local function refresh()
disks={}
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
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

@@ -0,0 +1,394 @@
--:Minify:--
local kernel = ...
local apis = kernel.apis
local native = apis.peripheral
local sides = {"top", "bottom", "left", "right", "front", "back"}
local peripheral={}
function peripheral.getNames()
local results = {}
for n = 1, #sides do
local side = sides[n]
if native.isPresent(side) then
table.insert(results, side)
if native.hasType(side, "peripheral_hub") then
local remote = native.call(side, "getNamesRemote")
for _, name in ipairs(remote) do
table.insert(results, name)
end
end
end
end
return results
end
function peripheral.isPresent(name)
if native.isPresent(name) then
return true
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return true
end
end
return false
end
function peripheral.getType(peripheral)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.getType(peripheral)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "getTypeRemote", peripheral)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return table.unpack(mt.types)
end
end
function peripheral.hasType(peripheral, peripheral_type)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.hasType(peripheral, peripheral_type)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.types[peripheral_type] ~= nil
end
end
function peripheral.getMethods(name)
if native.isPresent(name) then
return native.getMethods(name)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "getMethodsRemote", name)
end
end
return nil
end
function peripheral.getName(peripheral)
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.name
end
function peripheral.call(name, method, ...)
if native.isPresent(name) then
return native.call(name, method, ...)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "callRemote", name, method, ...)
end
end
return nil
end
function peripheral.wrap(name)
local methods = peripheral.getMethods(name)
if not methods then
return nil
end
local types = { peripheral.getType(name) }
for i = 1, #types do types[types[i]] = true end
local result = setmetatable({}, {
__name = "peripheral",
name = name,
type = types[1],
types = types,
})
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)
end
end
return result
end
function peripheral.find(ty, filter)
local results = {}
for _, name in ipairs(peripheral.getNames()) do
if peripheral.hasType(name, ty) then
local wrapped = peripheral.wrap(name)
if filter == nil or filter(name, wrapped) then
table.insert(results, wrapped)
end
end
end
return table.unpack(results)
end
local icolors = {
[0x1] = 1, -- #000000
[0x2] = 2, -- #FFFFFF
[0x4] = 3, -- #FF0000
[0x8] = 4, -- #00FF00
[0x10] = 5, -- #0000FF
[0x20] = 6, -- #00FFFF
[0x40] = 7, -- #FF00FF
[0x80] = 8, -- #FFFF00
[0x100] = 9, -- #FF6D00
[0x200] = 10, -- #6DFF55
[0x400] = 11, -- #24FFFF
[0x800] = 12, -- #924900
[0x1000] = 13, -- #6D6D55
[0x2000] = 14, -- #DBDBAA
[0x4000] = 15, -- #6D00FF
[0x8000] = 16 -- #B6FF00
}
local colors = {
0x0001, -- #000000
0x0002, -- #FFFFFF
0x0004, -- #FF0000
0x0008, -- #00FF00
0x0010, -- #0000FF
0x0020, -- #00FFFF
0x0040, -- #FF00FF
0x0080, -- #FFFF00
0x0100, -- #FF6D00
0x0200, -- #6DFF55
0x0400, -- #24FFFF
0x0800, -- #924900
0x1000, -- #6D6D55
0x2000, -- #DBDBAA
0x4000, -- #6D00FF
0x8000 -- #B6FF00
}
local function write(text, term)
local x, y = term.getCursorPos()
local w, h = term.getSize()
for i = 1, #text do
local c = text:sub(i, i)
if c == "\n" then
y = y + 1
x = 1
elseif c == "\t" then
local tabSize = 4
local spaces = tabSize - ((x - 1) % tabSize)
term.write(string.rep(" ", spaces))
x = x + spaces
elseif c == "\b" then
if x > 1 then
x = x - 1
term.setCursorPos(x, y)
term.write(" ")
term.setCursorPos(x, y)
end
else
if x <= w and y <= h then
term.setCursorPos(x, y)
term.write(c)
x = x + 1
end
end
if x > w then
x = 1
y = y + 1
end
if y - 1 >= h then
term.scroll(1)
y = h
term.setCursorPos(x, y)
end
end
term.setCursorPos(x, y)
end
kernel.devfs.data.tty={}
local ctrl,alt = false, false
local function serializeBool(bool)
if bool then
return "T"
else
return "F"
end
end
local function newtty(obj, id, ev)
kernel.devfs.data["tty"][id] = function(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
local h = {
read=function(amount)
local rv=""
for i=1, amount or 1 do
local event = {ev()}
if event[1] then
rv=rv..event[1]
end
end
if rv=="" then rv=nil end
return rv
end,
write=function(content)
write(content, obj)
end,
size=function()
local s={obj.getSize()}
return table.concat(s,";")
end,
clear=function()
obj.clear()
obj.setCursorPos(1,1)
end,
gpos=function()
local s={obj.getCursorPos()}
return table.concat(s,";")
end,
spos=function(x,y)
return obj.setCursorPos(x,y)
end,
sfgc=function(c)
return obj.setTextColor(colors[c])
end,
sbgc=function(c)
return obj.setBackgroundColor(colors[c])
end,
gfgc=function()
return icolors[obj.getTextColor()]
end,
gbgc=function()
return icolors[obj.getBackgroundColor()]
end,
gctrl=function()
return serializeBool(ctrl)..";"..serializeBool(alt)
end
}
if mode=="rw" then
return h
elseif mode=="r" then
h["write"]=nil
return h
elseif mode=="w" then
h["read"]=nil
return h
end
end
end
end
local fifo = kernel.newFifo()
kernel.processes.cctmond = function()
local timeout = false
while true do
local event = {kernel.EFI:getMachineEvent()}
if event[1] then
local eventType = event[1]
local charOrKey = event[3]
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
elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then
alt = true
end
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
if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then
ctrl = false
elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then
alt = false
end
elseif eventType == "keyTyped" then
if charOrKey then fifo.push(charOrKey) end
end
timeout = false
else
timeout = true
end
if timeout then
sleep(0.05)
end
end
end
newtty(apis.term, "1", fifo.pop)
for i,v in ipairs({peripheral.find("monitor")}) do
v.setTextScale(.5)
v.write("Initializing...")
newtty(v,tostring(i+1),function () end)
end

View File

@@ -0,0 +1,560 @@
--:Minify:--
local BOOT_DRIVE_PATH = ({...})[1] or "/$"
---@diagnostic disable-next-line: undefined-global
local lterm = term
local function write(text, term)
local x, y = term.getCursorPos()
local w, h = term.getSize()
for i = 1, #text do
local c = text:sub(i, i)
if c == "\n" then
y = y + 1
x = 1
elseif c == "\t" then
local tabSize = 4
local spaces = tabSize - ((x - 1) % tabSize)
term.write(string.rep(" ", spaces))
x = x + spaces
elseif c == "\b" then
if x > 1 then
x = x - 1
term.setCursorPos(x, y)
term.write(" ")
term.setCursorPos(x, y)
end
else
if x <= w and y <= h then
term.setCursorPos(x, y)
term.write(c)
x = x + 1
end
end
if x > w then
x = 1
y = y + 1
end
if y - 1 >= h then
term.scroll(1)
y = h
term.setCursorPos(x, y)
end
end
term.setCursorPos(x, y)
end
local function displaySuperBadError(err)
lterm.setBackgroundColor(0x1)
lterm.setTextColor(0x4)
lterm.clear()
lterm.setCursorPos(1, 1)
lterm.write("A critical error occurred while loading the system:")
lterm.setCursorPos(1, 3)
write(err, lterm)
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,
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,
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
if not apis.fs.exists("/nvram.dat") then
local file = apis.fs.open("/nvram.dat", "w")
file.write("Hello, World!")
file.close()
end
local eeprom
if apis.fs.exists("/startup.lua") then
eeprom="/startup.lua"
elseif apis.fs.exists("/eeprom") then
eeprom="/eeprom"
end
local eventQueue = {}
local function queueEvent(event, ...)
table.insert(eventQueue, {event, ...})
end
local colors = {
[0xFFFFFF]=0x0001,
[0xFF0000]=0x0002,
[0x00FF00]=0x0004,
[0x0000FF]=0x0008,
[0x00FFFF]=0x0010,
[0xFF00FF]=0x0020,
[0xFFFF00]=0x0040,
[0xFF6D00]=0x0080,
[0x6DFF55]=0x0100,
[0x24FFFF]=0x0200,
[0x924900]=0x0400,
[0x6D6D55]=0x0800,
[0xDBDBAA]=0x1000,
[0x6D00FF]=0x2000,
[0xB6FF00]=0x4000,
[0x000000]=0x8000
}
local fg,bg=0x6D6D55,0x000000
local l1f,l1d,l2,ops={},{},{},0
local function findClosest(tbl, target)
local closest = nil
local smallestDiff = math.huge
for k, _ in pairs(tbl) do
if k==target then return k end
local diff = math.abs(k - target)
if diff < smallestDiff then
smallestDiff = diff
closest = k
end
end
return closest
end
local function aprox(c24)
ops = ops + 1
if ops % 1024 == 0 then
l1d = {}
l1f = {}
end
if ops % 8192 == 0 then
l2 = {}
end
if l2[c24] ~= nil then
return l2[c24]
end
if l1d[c24] ~= nil then
l1f[c24] = l1f[c24] + 1
if l1f[c24] >= 16 then
l2[c24] = l1d[c24]
l1d[c24] = nil
l1f[c24] = nil
return l2[c24]
end
return l1d[c24]
end
local closestKey = findClosest(colors, c24)
if not closestKey then return nil end
local value = colors[closestKey]
l1d[c24] = value
l1f[c24] = 1
return value
end
local peripheral={}
local native = apis.peripheral
local sides = {"top", "bottom", "left", "right", "front", "back"}
function peripheral.getNames()
local results = {}
for n = 1, #sides do
local side = sides[n]
if native.isPresent(side) then
table.insert(results, side)
if native.hasType(side, "peripheral_hub") then
local remote = native.call(side, "getNamesRemote")
for _, name in ipairs(remote) do
table.insert(results, name)
end
end
end
end
return results
end
function peripheral.isPresent(name)
if native.isPresent(name) then
return true
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return true
end
end
return false
end
function peripheral.getType(peripheral)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.getType(peripheral)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "getTypeRemote", peripheral)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return table.unpack(mt.types)
end
end
function peripheral.hasType(peripheral, peripheral_type)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.hasType(peripheral, peripheral_type)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.types[peripheral_type] ~= nil
end
end
function peripheral.getMethods(name)
if native.isPresent(name) then
return native.getMethods(name)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "getMethodsRemote", name)
end
end
return nil
end
function peripheral.getName(peripheral)
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.name
end
function peripheral.call(name, method, ...)
if native.isPresent(name) then
return native.call(name, method, ...)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "callRemote", name, method, ...)
end
end
return nil
end
function peripheral.wrap(name)
local methods = peripheral.getMethods(name)
if not methods then
return nil
end
local types = { peripheral.getType(name) }
for i = 1, #types do types[types[i]] = true end
local result = setmetatable({}, {
__name = "peripheral",
name = name,
type = types[1],
types = types,
})
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)
end
end
return result
end
function peripheral.find(ty, filter)
local results = {}
for _, name in ipairs(peripheral.getNames()) do
if peripheral.hasType(name, ty) then
local wrapped = peripheral.wrap(name)
if filter == nil or filter(name, wrapped) then
table.insert(results, wrapped)
end
end
end
return table.unpack(results)
end
local allscreens = {peripheral.find("monitor")}
for i=1, #allscreens do
allscreens[i].setTextScale(.5)
allscreens[i].clear()
allscreens[i].setCursorPos(1,1)
end
allscreens[#allscreens+1] = apis.term
local EFI = {
getEpochMs = function() return apis.os.epoch("utc") end,
getUptime = function() return apis.os.clock() * 1000 end,
date = function() return apis.os.date("!%Y-%m-%dT%H:%M:%SZ", apis.os.epoch("utc") / 1000) end,
getMachineEvent = function()
if #eventQueue > 0 then
return table.unpack(table.remove(eventQueue, 1))
else
return nil
end
end,
getEEPROM = function() return getFile(eeprom) end,
setEEPROM = function(_, text)
local h = apis.fs.open(eeprom, "w")
h.write(text)
h.close()
end,
initfs=fs,
disks=initFs,
screenCtl={
print = function(_, text)
for i=1, #allscreens do
write(text.."\n", allscreens[i])
end
end,
printInline = function(_, text)
for i=1, #allscreens do
write(text, allscreens[i])
end
end,
clear = function()
for i=1, #allscreens do
allscreens[i].clear()
allscreens[i].setCursorPos(1, 1)
end
end,
resetCursor = function()
for i=1, #allscreens do
allscreens[i].setCursorPos(1, 1)
end
end,
setBackgroundColor = function(_, color)
bg=color
for i=1, #allscreens do
allscreens[i].setBackgroundColor(aprox(color))
end
end,
setTextColor = function(_, color)
fg=color
for i=1, #allscreens do
allscreens[i].setTextColor(aprox(color))
end
end,
getBackgroundColor = function()
return bg
end,
getTextColor = function()
return fg
end,
enable=function() end,
disable=function() end
},
architecture="cct",
getNvram = function() return getFile("/nvram.dat") end,
setNvram = function(_, text)
local h = apis.fs.open("/nvram.dat", "w")
h.write(text)
h.close()
end,
firmware=apis,
reboot=false,
beep=function() end
}
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, EFI)
if not ok and not EFI.reboot then displaySuperBadError(err) end
if err then
apis.os.reboot()
else
apis.os.shutdown()
end
end)
function coroutine.resumeWithTimeout(co, timeout, ...)
local startTime = EFI.getEpochMs()
debug.sethook(co, function()
if EFI.getEpochMs() > 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
EFI.screenCtl:print("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
if EFI.reboot then
apis.os.reboot()
end
displaySuperBadError("Kernel error: " .. tostring(err))
coroutine.yield("key")
end
initFs:refresh()
end
end, debug.traceback)
if not ok then displaySuperBadError("Fatal error during boot: " .. err) end
while true do coroutine.yield("key") end

View File

@@ -1,119 +1,119 @@
-- 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 = {...}
if _HOST:find("UnBIOS") then return end
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
_G._HOST = _G._HOST .. " (UnBIOS)"
-- 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/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(table.unpack(args))
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
--: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,272 @@
--:Minify:--
local apis = ({...})[1]
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH
local fs = apis.fs
local native = apis.peripheral
local peripheral = {}
local sides = {"top", "bottom", "left", "right", "front", "back"}
function peripheral.getNames()
local results = {}
for n = 1, #sides do
local side = sides[n]
if native.isPresent(side) then
table.insert(results, side)
if native.hasType(side, "peripheral_hub") then
local remote = native.call(side, "getNamesRemote")
for _, name in ipairs(remote) do
table.insert(results, name)
end
end
end
end
return results
end
function peripheral.isPresent(name)
if native.isPresent(name) then
return true
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return true
end
end
return false
end
function peripheral.getType(peripheral)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.getType(peripheral)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "getTypeRemote", peripheral)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return table.unpack(mt.types)
end
end
function peripheral.hasType(peripheral, peripheral_type)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.hasType(peripheral, peripheral_type)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.types[peripheral_type] ~= nil
end
end
function peripheral.getMethods(name)
if native.isPresent(name) then
return native.getMethods(name)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "getMethodsRemote", name)
end
end
return nil
end
function peripheral.getName(peripheral)
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.name
end
function peripheral.call(name, method, ...)
if native.isPresent(name) then
return native.call(name, method, ...)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "callRemote", name, method, ...)
end
end
return nil
end
function peripheral.wrap(name)
local methods = peripheral.getMethods(name)
if not methods then
return nil
end
local types = { peripheral.getType(name) }
for i = 1, #types do types[types[i]] = true end
local result = setmetatable({}, {
__name = "peripheral",
name = name,
type = types[1],
types = types,
})
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)
end
end
return result
end
function peripheral.find(ty, filter)
local results = {}
for _, name in ipairs(peripheral.getNames()) do
if peripheral.hasType(name, ty) then
local wrapped = peripheral.wrap(name)
if filter == nil or filter(name, wrapped) then
table.insert(results, wrapped)
end
end
end
return table.unpack(results)
end
local 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
})
internal["rom"] = createDisk("rom", "/rom", true, {
setLabel = function(label)
error("Device is read-only")
end,
getLabel = function()
return "cctrom"
end
})
local function refresh()
disks={}
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
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

@@ -0,0 +1,152 @@
--:Minify:--
-- CCT driver peripheral helper
local kernel=...
kernel.cct={}
kernel.cct.peripheral={}
local peripheral=kernel.cct.peripheral
local apis = kernel.apis
local native = apis.peripheral
local sides = {"top", "bottom", "left", "right", "front", "back"}
function peripheral.getNames()
local results = {}
for n = 1, #sides do
local side = sides[n]
if native.isPresent(side) then
table.insert(results, side)
if native.hasType(side, "peripheral_hub") then
local remote = native.call(side, "getNamesRemote")
for _, name in ipairs(remote) do
table.insert(results, name)
end
end
end
end
return results
end
function peripheral.isPresent(name)
if native.isPresent(name) then
return true
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return true
end
end
return false
end
function peripheral.getType(peripheral)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.getType(peripheral)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "getTypeRemote", peripheral)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return table.unpack(mt.types)
end
end
function peripheral.hasType(peripheral, peripheral_type)
if type(peripheral) == "string" then
if native.isPresent(peripheral) then
return native.hasType(peripheral, peripheral_type)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.types[peripheral_type] ~= nil
end
end
function peripheral.getMethods(name)
if native.isPresent(name) then
return native.getMethods(name)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "getMethodsRemote", name)
end
end
return nil
end
function peripheral.getName(peripheral)
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.name
end
function peripheral.call(name, method, ...)
if native.isPresent(name) then
return native.call(name, method, ...)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then
return native.call(side, "callRemote", name, method, ...)
end
end
return nil
end
function peripheral.wrap(name)
local methods = peripheral.getMethods(name)
if not methods then
return nil
end
local types = { peripheral.getType(name) }
for i = 1, #types do types[types[i]] = true end
local result = setmetatable({}, {
__name = "peripheral",
name = name,
type = types[1],
types = types,
})
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)
end
end
return result
end
function peripheral.find(ty, filter)
local results = {}
for _, name in ipairs(peripheral.getNames()) do
if peripheral.hasType(name, ty) then
local wrapped = peripheral.wrap(name)
if filter == nil or filter(name, wrapped) then
table.insert(results, wrapped)
end
end
end
return table.unpack(results)
end

View File

@@ -0,0 +1,306 @@
--:Minify:--
local kernel = ...
local handler = {}
local http = kernel.apis.http
kernel.cct.httpqueue = kernel.cct.httpqueue or {}
kernel.cct.httpresponse = kernel.cct.httpresponse or {}
kernel.cct.httperror = kernel.cct.httperror or {}
local function parseRawRequest(raw)
local sepLen = 4
local headerEnd =
raw:find("\r\n\r\n", 1, true)
if not headerEnd then
headerEnd =
raw:find("\n\n", 1, true)
sepLen = 2
end
local headerPart
local body = ""
if headerEnd then
headerPart = raw:sub(1, headerEnd - 1)
body = raw:sub(headerEnd + sepLen)
else
headerPart = raw
end
local lines = {}
for line in headerPart:gmatch("[^\r\n]+") do
lines[#lines + 1] = line
end
if #lines == 0 then
return nil, "EINVAL"
end
local method, path =
lines[1]:match("^(%S+)%s+(%S+)")
if not method or not path then
return nil, "EBADMSG"
end
local headers = {}
for i = 2, #lines do
local k, v =
lines[i]:match("^([^:]+):%s*(.*)$")
if k then
headers[k] = v
end
end
local host = headers.Host or headers.host
if not host and not path:match("^https?://") then
return nil, "EHOSTUNREACH"
end
local url
if path:match("^https?://") then
url = path
else
url = "http://" .. host .. path
end
local req = {
url = url,
method = method,
headers = headers
}
if body ~= "" then
req.body = body
end
return req
end
local function buildResponse(resp)
if not resp then
return nil, "EINVAL"
end
local code, msg = resp.getResponseCode()
local headers =
resp:getResponseHeaders()
local body =
resp:readAll() or ""
local out = {
"HTTP/1.1 " ..
tostring(code) ..
" " ..
tostring(msg)
}
local hasLength = false
for k, v in pairs(headers or {}) do
if k:lower() == "content-length" then
hasLength = true
end
out[#out + 1] =
tostring(k) ..
": " ..
tostring(v)
end
if not hasLength then
out[#out + 1] =
"Content-Length: " ..
tostring(#body)
end
out[#out + 1] = ""
out[#out + 1] = body
return table.concat(out, "\r\n")
end
function handler.connect(fd, address)
local fdo = kernel.currentTask.fd[fd]
if not fdo then
return nil, "EBADF"
end
fdo.socket.rbuf = ""
fdo.socket.closed = false
fdo.socket.httpid = nil
fdo.handle.write = function(raw)
local req, err =
parseRawRequest(raw)
if not req then
return nil, err
end
local id =
tostring(kernel.uuid())
fdo.socket.httpid = id
kernel.cct.httpqueue[id] = true
local ok, err =
http.request(req, id)
if not ok then
kernel.cct.httpqueue[id] = nil
return nil, err
end
return true
end
fdo.handle.read = function(count)
count = count or 4096
local sock = fdo.socket
if #sock.rbuf > 0 then
local out =
sock.rbuf:sub(1, count)
sock.rbuf =
sock.rbuf:sub(count + 1)
return out
end
local id = sock.httpid
if not id then
return ""
end
local function finish(resp)
sock.rbuf =
buildResponse(resp) or ""
local out =
sock.rbuf:sub(1, count)
sock.rbuf =
sock.rbuf:sub(count + 1)
return out
end
if kernel.cct.httpresponse[id] then
local resp =
kernel.cct.httpresponse[id]
kernel.cct.httpresponse[id] = nil
kernel.cct.httpqueue[id] = nil
return finish(resp)
end
if kernel.cct.httperror[id] then
local err =
kernel.cct.httperror[id]
kernel.cct.httperror[id] = nil
kernel.cct.httpqueue[id] = nil
return nil, err
end
kernel.currentTask.status = "D"
local coro
coro = function()
if kernel.cct.httpresponse[id] then
local resp =
kernel.cct.httpresponse[id]
kernel.cct.httpresponse[id] = nil
kernel.cct.httpqueue[id] = nil
kernel.asyncReturn(
finish(resp)
)
return
end
if kernel.cct.httperror[id] then
local err =
kernel.cct.httperror[id]
kernel.cct.httperror[id] = nil
kernel.cct.httpqueue[id] = nil
kernel.asyncReturn(
nil,
err
)
return
end
coroutine.yield()
end
kernel.currentTask.ksh =
coroutine.create(function()
local ok, err =
xpcall(
coro,
debug.traceback
)
if not ok then
kernel.asyncReturn(
nil,
err
)
end
end)
end
fdo.handle.close = function()
fdo.socket.closed = true
local id =
fdo.socket.httpid
if id then
kernel.cct.httpqueue[id] = nil
kernel.cct.httpresponse[id] = nil
kernel.cct.httperror[id] = nil
end
return true
end
return true
end
kernel.socket.registerProtocal(
"http://",
handler
)
kernel.socket.registerProtocal(
"https://",
handler
)

View File

@@ -0,0 +1,284 @@
--:Minify:--
local kernel = ...
local peripheral=kernel.cct.peripheral
local colors = {
[0xFFFFFF]=0x0001,
[0xFF0000]=0x0002,
[0x00FF00]=0x0004,
[0x0000FF]=0x0008,
[0x00FFFF]=0x0010,
[0xFF00FF]=0x0020,
[0xFFFF00]=0x0040,
[0xFF6D00]=0x0080,
[0x6DFF55]=0x0100,
[0x24FFFF]=0x0200,
[0x924900]=0x0400,
[0x6D6D55]=0x0800,
[0xDBDBAA]=0x1000,
[0x6D00FF]=0x2000,
[0xB6FF00]=0x4000,
[0x000000]=0x8000
}
local plt = {
0xFFFFFF,
0xFF0000,
0x00FF00,
0x0000FF,
0x00FFFF,
0xFF00FF,
0xFFFF00,
0xFF6D00,
0x6DFF55,
0x24FFFF,
0x924900,
0x6D6D55,
0xDBDBAA,
0x6D00FF,
0xB6FF00,
0x000000
}
local fg,bg=0x6D6D55,0x000000
local l1f,l1d,l2,ops={},{},{},0
local function findClosest(tbl, target)
local closest = nil
local smallestDiff = math.huge
for k, _ in pairs(tbl) do
if k==target then return k end
local diff = math.abs(k - target)
if diff < smallestDiff then
smallestDiff = diff
closest = k
end
end
return closest
end
local function aprox(c24)
ops = ops + 1
if ops % 1024 == 0 then
l1d = {}
l1f = {}
end
if ops % 8192 == 0 then
l2 = {}
end
if l2[c24] ~= nil then
return l2[c24]
end
if l1d[c24] ~= nil then
l1f[c24] = l1f[c24] + 1
if l1f[c24] >= 16 then
l2[c24] = l1d[c24]
l1d[c24] = nil
l1f[c24] = nil
return l2[c24]
end
return l1d[c24]
end
local closestKey = findClosest(colors, c24)
if not closestKey then return nil end
local value = colors[closestKey]
l1d[c24] = value
l1f[c24] = 1
return value
end
local function write(text, term)
local x, y = term.getCursorPos()
local w, h = term.getSize()
for i = 1, #text do
local c = text:sub(i, i)
if c == "\n" then
y = y + 1
x = 1
elseif c == "\t" then
local tabSize = 4
local spaces = tabSize - ((x - 1) % tabSize)
term.write(string.rep(" ", spaces))
x = x + spaces
elseif c == "\b" then
if x > 1 then
x = x - 1
term.setCursorPos(x, y)
term.write(" ")
term.setCursorPos(x, y)
end
else
if x <= w and y <= h then
term.setCursorPos(x, y)
term.write(c)
x = x + 1
end
end
if x > w then
x = 1
y = y + 1
end
if y - 1 >= h then
term.scroll(1)
y = h
term.setCursorPos(x, y)
end
end
term.setCursorPos(x, y)
end
kernel.devfs.data.tty={}
kernel.cct.ctrl,kernel.cct.alt = false, false
local function serializeBool(bool)
if bool then
return "T"
else
return "F"
end
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 "Terminal"
elseif op=="open" then
local h = {
read=function(amount)
local rv=""
for i=1, amount or 1 do
local event = {ev()}
if event[1] then
rv=rv..event[1]
end
end
if rv=="" then rv=nil end
return rv
end,
write=function(content)
write(content, obj)
end,
size=function()
local s={obj.getSize()}
return table.concat(s,";")
end,
clear=function()
obj.clear()
obj.setCursorPos(1,1)
end,
gpos=function()
local s={obj.getCursorPos()}
return table.concat(s,";")
end,
spos=function(x,y)
return obj.setCursorPos(x,y)
end,
sfgc=function(c)
fg=c
return obj.setTextColor(aprox(c))
end,
sbgc=function(c)
bg=c
return obj.setBackgroundColor(aprox(c))
end,
gfgc=function()
return fg
end,
gbgc=function()
return bg
end,
gctrl=function()
return serializeBool(kernel.cct.ctrl)..";"..serializeBool(kernel.cct.alt)
end,
isvirt=function()
return false
end,
gplt=function()
return plt
end
}
if mode=="rw" then
return h
elseif mode=="r" then
h["write"]=nil
return h
elseif mode=="w" then
h["read"]=nil
return h
end
end
end
end
local fifo = kernel.newFifo()
kernel.cct.fifo=fifo
newtty(kernel.apis.term, "1", function() end)
for i,v in ipairs({peripheral.find("monitor")}) do
newtty(v,tostring(i+1),function () end)
end
kernel.devfs.data["input"]["keyboard1"] = function(op, mode)
if op=="type" then
return "Keyboard"
elseif op=="open" then
local h = {
read=function(amount)
local rv=""
for i=1, amount or 1 do
local event = {fifo.pop()}
if event[1] then
rv=rv..event[1]
end
end
if rv=="" then rv=nil end
return rv
end,
write=function(content)
end
}
if mode=="rw" then
return h
elseif mode=="r" then
h["write"]=nil
return h
elseif mode=="w" then
h["read"]=nil
return h
end
end
end

View File

@@ -0,0 +1,95 @@
--:Minify:--
local kernel=...
local keys=kernel.apis.keys
kernel.processes.cctdeamon = function()
local timeout = false
kernel.log("CCT deamon started")
while true do
local event = {kernel.EFI:getMachineEvent()}
if event[1] then
local eventType = event[1]
local charOrKey = event[3]
local ctrlKeyMap = {
[keys.a]=1, [keys.b]=2, [keys.c]=3,
[keys.d]=4, [keys.e]=5, [keys.f]=6,
[keys.g]=7, [keys.h]=8, [keys.i]=9,
[keys.j]=10, [keys.k]=11, [keys.l]=12,
[keys.m]=13, [keys.n]=14, [keys.o]=15,
[keys.p]=16, [keys.q]=17, [keys.r]=18,
[keys.s]=19, [keys.t]=20, [keys.u]=21,
[keys.v]=22, [keys.w]=23, [keys.x]=24,
[keys.y]=25, [keys.z]=26,
}
if eventType == "keyPressed" then
if charOrKey == keys.leftCtrl or charOrKey == keys.rightCtrl then
kernel.cct.ctrl = true
elseif charOrKey == keys.leftAlt or charOrKey == keys.rightAlt then
kernel.cct.alt = true
end
if kernel.cct.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
kernel.cct.fifo.push(string.char(ctrlByte))
end
end
else
local specialKeyMap = {
[keys.up] = "",
[keys.down] = "",
[keys.right] = "",
[keys.left] = "",
[keys.home] = "",
[keys["end"]] = "",
[keys.pageUp] = "[5~",
[keys.pageDown] = "[6~",
[keys.delete] = "[3~",
}
local special = specialKeyMap[charOrKey]
if special then kernel.cct.fifo.push(special) end
end
elseif eventType == "keyReleased" then
if charOrKey == keys.leftCtrl or charOrKey == keys.rightCtrl then
kernel.cct.ctrl = false
elseif charOrKey == keys.leftAlt or charOrKey == keys.rightAlt then
kernel.cct.alt = false
end
elseif eventType == "keyTyped" then
if charOrKey then kernel.cct.fifo.push(charOrKey) end
elseif eventType == "mouse_scroll" then
if event[2] == 1 then
kernel.cct.fifo.push("S")
else
kernel.cct.fifo.push("T")
end
elseif eventType == "http_success" then
kernel.cct.httpqueue[event[2]]=nil
kernel.cct.httpresponse[event[2]]=event[3]
elseif eventType == "http_failure" then
kernel.cct.httpqueue[event[2]]=nil
kernel.cct.httperror[event[2]]=event[3]
end
timeout = false
else
timeout = true
end
if timeout then
sleep(0.05)
end
end
end
kernel.log("CCT deamon queued for execution")

View File

@@ -0,0 +1,25 @@
--:Minify:--
local kernel=...
local rs=kernel.apis.rs
local sides = {top=1, bottom=2, left=3, right=4, front=5, back=6}
local function newGPIO(side)
return function(mode, data)
if mode=="w" then
if type(data)~="boolean" then error("data: expected bool") end
rs.setOutput(side, data)
elseif mode=="wa" then
if type(data)~="number" then error("data: expected bool") end
rs.setAnalogOutput(side, data)
elseif mode=="r" then
return rs.getInput(side)
elseif mode=="ra" then
return rs.getAnalogInput(side)
end
end
end
for side, alt in pairs(sides) do
local func=newGPIO(side)
kernel.gpio[side]=func
kernel.gpio[alt]=func
end

View File

@@ -1,40 +1,41 @@
local lua = {
coroutine = true,
debug = true,
_HOST = true,
_VERSION = true,
assert = true,
collectgarbage = true,
error = true,
gcinfo = true,
getfenv = true,
getmetatable = true,
ipairs = true,
__inext = true,
load = true,
math = true,
next = true,
pairs = true,
pcall = true,
rawequal = true,
rawget = true,
rawlen = true,
rawset = true,
select = true,
setfenv = true,
setmetatable = true,
string = true,
table = true,
tonumber = true,
tostring = true,
type = true,
xpcall = true,
_G=true
}
for i,v in pairs(_G) do
if not lua[i] or lua[i]==nil then
apis[i]=v
_G[i]=nil
end
local lua = {
coroutine = true,
debug = true,
_HOST = true,
_VERSION = true,
assert = true,
collectgarbage = true,
error = true,
gcinfo = true,
getfenv = true,
getmetatable = true,
ipairs = true,
__inext = true,
load = true,
math = true,
next = true,
pairs = true,
pcall = true,
rawequal = true,
rawget = true,
rawlen = true,
rawset = true,
select = true,
setfenv = true,
setmetatable = true,
string = true,
table = true,
tonumber = true,
tostring = true,
type = true,
xpcall = true,
_G=true
}
local apis={}
for i,v in pairs(_G) do
if not lua[i] or lua[i]==nil then
apis[i]=v
_G[i]=nil
end
end

View File

@@ -1,18 +1,18 @@
checkArg=nil
local oldcomputer=computer
_G.computer=nil
local os=os
_G.os=nil
function component.wrap(address)
local methods=oldcomponent.methods(address)
local object={}
for _,method in ipairs(methods) do
object[method]=function(_,...)
return oldcomponent.invoke(address,method,...)
end
end
return object
end
checkArg=nil
local oldcomputer=computer
_G.computer=nil
local os=os
_G.os=nil
function component.wrap(address)
local methods=oldcomponent.methods(address)
local object={}
for _,method in ipairs(methods) do
object[method]=function(_,...)
return oldcomponent.invoke(address,method,...)
end
end
return object
end
local

View File

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

View File

@@ -1,69 +1,84 @@
local fs = {}
local disks = {}
local mounts = {}
local function resolve(path)
local mountPoint = "/"
for mount, disk in pairs(mounts) do
if path:sub(1, #mount) == mount then
if not mountPoint or #mount > #mountPoint then
mountPoint = mount
end
end
end
local newPath = path:sub(#mountPoint + 1)
return disks[mounts[mountPoint]], newPath
end
---------------------------------------------------------
function fs.update(initdisks)
disks = {}
for k, v in initdisks.list() do
disks[k] = v
end
end
function fs.exists(path)
local disk, newPath = resolve(path)
return disk:directoryExists(newPath) or disk:fileExists(newPath)
end
function fs.isFile(path)
local disk, newPath = resolve(path)
return disk:fileExists(newPath)
end
function fs.isDir(path)
local disk, newPath = resolve(path)
return disk:directoryExists(newPath)
end
function fs.list(path)
local disk, newPath = resolve(path)
return disk:list(newPath)
end
function fs.makeDir(path)
local disk, newPath = resolve(path)
return disk:makeDirectory(newPath)
end
function fs.remove(path)
local disk, newPath = resolve(path)
return disk:remove(newPath)
end
function fs.readAllText(path)
local disk, newPath = resolve(path)
return disk:readAllText(newPath)
end
function fs.writeAllText(path, text)
local disk, newPath = resolve(path)
return disk:writeAllText(newPath, text)
end
function fs.appendAllText(path, text)
local disk, newPath = resolve(path)
return disk:appendAllText(newPath, text)
end
function fs.load(path)
return load(fs.readAllText(path), path)
end
function fs.mount(disk, mountPoint)
if not disks[disk] then return end
mounts[mountPoint] = disk
end
return fs
-- :Minify:--
local fs = {}
local disks = {}
local mounts = {}
local function resolve(path)
local mountPoint = "/"
for mount, disk in pairs(mounts) do
if path:sub(1, #mount) == mount then
if not mountPoint or #mount > #mountPoint then
mountPoint = mount
end
end
end
local newPath = path:sub(#mountPoint + 1)
return disks[mounts[mountPoint]], newPath
end
function fs.update(initdisks)
disks = {}
for k, v in initdisks.list() do disks[k] = v end
end
function fs.exists(path)
local disk, newPath = resolve(path)
return disk:directoryExists(newPath) or disk:fileExists(newPath)
end
function fs.isFile(path)
local disk, newPath = resolve(path)
return disk:fileExists(newPath)
end
function fs.isDir(path)
local disk, newPath = resolve(path)
return disk:directoryExists(newPath)
end
function fs.list(path)
local disk, newPath = resolve(path)
return disk:list(newPath)
end
function fs.makeDir(path)
local disk, newPath = resolve(path)
return disk:makeDirectory(newPath)
end
function fs.remove(path)
local disk, newPath = resolve(path)
return disk:remove(newPath)
end
function fs.readAllText(path)
local disk, newPath = resolve(path)
local handle = disk:open(newPath, "r")
if not handle then return nil end
local content = handle.readAll()
handle.close()
return content
end
function fs.writeAllText(path, text)
local disk, newPath = resolve(path)
local handle = disk:open(newPath, "w")
handle.write(text)
handle.close()
end
function fs.appendAllText(path, text)
local disk, newPath = resolve(path)
local handle = disk:open(newPath, "a")
handle.write(text)
handle.close()
end
function fs.load(path) return load(fs.readAllText(path), path) end
function fs.mount(disk, mountPoint)
if not disks[disk] then return end
mounts[mountPoint] = disk
end
return fs

View File

@@ -0,0 +1,334 @@
--:Minify:--
local EFI=...
EFI.beep(440, 500)
local screen=EFI.screenCtl
local ifs=EFI.initfs
local disks=EFI.disks
local arch=EFI.architecture
local kernel = {}
kernel.LOG_Text=""
kernel.version="HyperionOS V1.2.4-dev_5"
kernel.tag="1.2.4-dev_5"
kernel.process = "Kernel"
kernel.users={[0]="root",[1]="User"}
kernel.hostname = "hyperion"
kernel.groups = {}
kernel.uid = 0
kernel.gid = 0
kernel.status = "start"
kernel.key = {}
kernel.cache = {}
kernel.cache.preload = {}
kernel.unixSockets={}
kernel._G=_G
kernel.sleep=sleep
_G.sleep=nil
local windowsExp = false
function kernel.log(msg, level, c, nopanic)
local f=function()
c=c or 0x6D6D6D
kernel.LOG_Text = kernel.LOG_Text..tostring(EFI:date()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n"
if kernel.status == "start" then
screen:setTextColor(c)
screen:print(tostring(EFI:date()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg)
elseif kernel.status == "term" then
kernel.standbyTask=kernel.currentTask
kernel.currentTask=kernel.kernelTask
local file=kernel.vfs.open("/dev/console", "w")
kernel.vfs.devctl(file,"sfgc",c)
kernel.vfs.write(file,tostring(EFI:date()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n")
kernel.vfs.close(file)
kernel.currentTask=kernel.standbyTask
end
end
local ok,err = xpcall(f,debug.traceback)
if not ok and not nopanic then
kernel.panic(err)
end
end
function kernel.PANIC(msg)
if kernel.status~="Panic" then
kernel.log("PANIC: "..msg, "PANIC", 0xFF0000)
pcall(kernel["saveLog"])
kernel.status="Panic"
kernel.reason=msg
screen:enable()
screen:setTextColor(0xFF0000)
screen:setBackgroundColor(0x000000)
screen:clear()
screen:resetCursor()
screen:print(kernel.LOG_Text)
screen:print("KERNEL PANIC!\n"..msg.."\nSystem halted.")
screen:print("Press any key to continue...")
kernel.exitMain = true
end
while true do
local event={EFI:getMachineEvent()}
if event[1]=="keyPressed" then
break
end
end
EFI.reboot=true
error("KERNEL PANIC")
end
kernel.panic=kernel.PANIC
if windowsExp then
screen:setTextColor(0xFFFFFF)
screen:setBackgroundColor(0x0000FF)
screen:clear()
screen:resetCursor()
screen:print("\n\n\n\n\n :(")
screen:print("\n Your PC ran into a problem and needs to restart. We're just collecting some error")
screen:print(" info, and then we'll restart for you.\n")
screen:print("\n\n\n Stop code: average windows experience")
while true do end
end
kernel.log("Kernel loaded.")
kernel.log("Mounting init disks...")
disks.refresh()
ifs.update(disks)
kernel.disks={}
for _,v in disks.list() do
kernel.disks[v.address] = v
end
ifs.mount("$", "/")
local fstab=ifs.readAllText("/boot/fstab")
local split = function(str, delim, maxResultCountOrNil)
assert(#delim == 1, "only delim len 1 supported for now")
maxResultCountOrNil = (maxResultCountOrNil or 0)-1
local rv = {}
local buf = ""
for i = 1, #str do
local c = string.sub(str,i,i)
if #rv ~= maxResultCountOrNil and c == delim then
table.insert(rv, buf)
buf = ""
else
buf = buf..c
end
end
table.insert(rv, buf)
return rv
end
if not ifs.isFile("/boot/boot.cfg") then
kernel.log("First boot detected writing boot.cfg", "INFO", 0x00FF00)
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")
if not initCfgFunc then
kernel.PANIC("Failed to load /boot/boot.cfg: "..tostring(err))
end
---@diagnostic disable-next-line: param-type-mismatch
local initCfgStatus, config = pcall(initCfgFunc)
if not initCfgStatus then
kernel.PANIC("Error in /boot/boot.cfg: "..tostring(config))
end
kernel.config = config
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", 0xFF8800) skip = true break end
id=v:sub(3,i-1)
end
end
if not skip then
local path=v:sub(#id+4)
ifs.mount(id,path)
else
skip=false
end
end
end
kernel.log("Disks initialized")
function kernel.saveLog()
if kernel.status=="running" then
local file = kernel.vfs.open("/var/log/syslog.log", "w")
kernel.vfs.write(file, kernel.LOG_Text)
kernel.vfs.close(file)
else
ifs.writeAllText("/var/log/syslog.log", kernel.LOG_Text)
end
end
function kernel.newFifo()
local fifo = {}
fifo.push=function(data)
table.insert(fifo, data)
end
fifo.pop=function()
return table.remove(fifo,1)
end
return fifo
end
function kernel.newUUID()
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
local uuid = ""
for i = 1, #template do
local c = template:sub(i,i)
if c == "x" then
uuid = uuid .. string.format("%x", math.random(0, 15))
elseif c == "y" then
uuid = uuid .. string.format("%x", math.random(8, 11))
else
uuid = uuid .. c
end
end
return uuid
end
kernel.syscalls={}
local modules={[0]={}}
for i=0, 100 do
modules[i]={}
end
kernel.log("Gathering modules")
for _, i in ipairs(ifs.list("/lib/modules")) do
local modlist = ifs.list("/lib/modules/"..i)
if not modlist then
kernel.log("WARNING: could not list /lib/modules/"..i.." (skipping)", "WARN", 0xFF8800)
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
kernel.ifs=ifs
kernel.apis=EFI.firmware
kernel.EFI=EFI
kernel.arch=arch
kernel.initdisks=disks
kernel.screen=screen
kernel.processes={}
kernel.fstab=fstab
kernel.kernelTask = {
name="kernel",
status="R",
pid=0,
tgid=0,
uid=0,
fd={},
exit="",
sleep=0,
ivs=0,
vs=0,
children={},
syscallReturn={},
cwd="/",
timeSlice=0,
lastTime=0,
totalTime=0,
numRuns=0
}
kernel.currentTask = kernel.kernelTask
function kernel.shutdown()
kernel.exitMain=true
kernel.status="shutdown"
end
function kernel.reboot()
kernel.exitMain=true
kernel.status="reboot"
end
function kernel.halt()
kernel.exitMain=true
kernel.status="halt"
end
function kernel.asyncReturn(...)
kernel.currentTask.syscallReturn = {...}
end
kernel.syscalls["time"]=function() return kernel.EFI:getEpochMs() end
kernel.syscalls["date"]=function() return kernel.EFI:date() end
kernel.syscalls["log"]=kernel.log
kernel.syscalls["tag"]=function() return kernel.tag end
kernel.syscalls["getUptime"]=function() return kernel.EFI:getUptime() end
kernel.syscalls["getUsername"]=function(uid) return kernel.users[uid or kernel.uid] end
kernel.syscalls["getHostname"]=function() return kernel.hostname end
kernel.syscalls["getHost"]=function() return kernel.apis._HOST end
kernel.syscalls["version"]=function() return kernel.version end
kernel.syscalls["setHostname"]=function(name) if kernel.uid~=0 then error("Permission denied") end kernel.hostname=name end
kernel.syscalls["arch"]=function() return arch end
kernel.syscalls["sysdump"]=function()
local rv={}
for i,v in pairs(kernel.syscalls) do
rv[#rv+1] = i
end
return rv
end
kernel.syscalls["reboot"]=function()
if kernel.uid==0 or kernel.groups.wheel then
kernel.reboot()
end
end
kernel.syscalls["shutdown"]=function()
if kernel.uid==0 or kernel.groups.wheel then
kernel.shutdown()
end
end
kernel.syscalls["halt"]=function()
if kernel.uid==0 or kernel.groups.wheel then
kernel.halt()
end
end
kernel.log("Running modules")
for _,p in ipairs(modules) do
for _,v in ipairs(p) do
if kernel.config.showModLoad then kernel.log("Loading module "..v, "DBUG", 0x00FFFF) end
local code=ifs.readAllText(v)
if not code then
kernel.panic("Failed to read module "..v)
end
local func,err=load(code,"@"..v)
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", 0x00FFFF) end
end
end
kernel.log("Kernel initialized successfully.")
kernel.saveLog()
kernel.status="running"
screen:disable()
local ok,err = xpcall(kernel.main, debug.traceback)
if not ok then
kernel.panic(err)
end
if kernel.status=="panic" then
kernel.panic(kernel.reason)
end
if kernel.status=="reboot" then
EFI.reboot=true
return true
elseif kernel.status=="halt" then
kernel.log("System halted.")
kernel.saveLog()
while true do end
end

View File

@@ -0,0 +1,11 @@
-- DO NOT EDIT THIS FILE IF YOU DO NOT KNOW WHAT YOU ARE DOING!
-- DOING SO MAY RENDER YOUR SYSTEM UNBOOTABLE!
-- This file is auto-generated during the build process.
-- DEFAULT BOOT CONFIGURATION FILE
return {
initPath = "/sbin/init",
maxOpenFiles = 128,
maxFilesPerTask = 16,
preempt = true,
}

View File

@@ -0,0 +1 @@
0:0:root:/root:/bin/hysh

View File

@@ -0,0 +1,275 @@
--:Minify:--
--- @diagnostic disable: duplicate-set-field
local kernel = ...
kernel.allowGlobalOverwrites = true
function string.hasSuffix(str, suffix)
return string.sub(str, #suffix + 1) == suffix
end
function string.hasPrefix(str, prefix)
return string.sub(str, 1, #prefix) == prefix
end
function string.getSuffix(str, prefix) return string.sub(str, #prefix + 1) end
function string.getPrefix(str, suffix) return string.sub(str, 1, #suffix) end
function string.join(str, ...) return table.concat(table.pack(str, ...)) end
function string.delim(str, ...) return table.concat(table.pack(...), str) end
function string.split(str, delim, maxResultCountOrNil)
assert(#delim == 1, "only delim len 1 supported for now")
if not str then return false end
maxResultCountOrNil = (maxResultCountOrNil or 0) - 1
local rv = {}
local buf = ""
for i = 1, #str do
local c = string.sub(str, i, i)
if #rv ~= maxResultCountOrNil and c == delim then
table.insert(rv, buf)
buf = ""
else
buf = buf .. c
end
end
table.insert(rv, buf)
return rv
end
function table.deepcopy(orig, copies)
copies = copies or {}
if type(orig) ~= 'table' then
return orig
elseif copies[orig] then
return copies[orig]
end
local copy = {}
copies[orig] = copy
for k, v in next, orig, nil do
local copied_key = table.deepcopy(k, copies)
local copied_val = table.deepcopy(v, copies)
copy[copied_key] = copied_val
end
return copy
end
function table.hasKey(tabl, query)
for i, v in pairs(tabl) do if i == query then return v end end
return false
end
function table.hasVal(tabl, query)
for i, v in pairs(tabl) do if v == query then return i end end
return false
end
local function serialize(tbl, seen)
seen = seen or {}
if seen[tbl] then return '"[Circular Reference]"' end
seen[tbl] = true
local output = "{"
local first = true
for i, v in pairs(tbl) do
if not first then output = output .. "," end
first = false
if type(i) == "string" then
output = output .. "[\"" .. i .. "\"]="
elseif type(i) == "number" then
output = output .. "[" .. tostring(i) .. "]="
end
if type(v) == "table" then
output = output .. serialize(v, seen)
elseif type(v) == "string" then
output = output .. "[=[" .. v .. "]=]"
elseif type(v) == "number" or type(v) == "boolean" then
output = output .. tostring(v)
elseif type(v) == "function" then
output = output .. "\"" .. tostring(v) .. "\""
elseif type(v) == "thread" then
output = output .. "\"" .. tostring(v) .. "\""
else
error("serialization of type \"" .. type(v) .. "\" is not supported")
end
end
seen[tbl] = nil
output = output .. "}"
return output
end
local oldtype = type
local oldgetmetatable = getmetatable
function type(object, trueType)
if trueType then return oldtype(object) end
if oldtype(object) ~= "table" then
return oldtype(object)
else
if oldtype(oldgetmetatable(object)) == "table" then
local metatable = oldgetmetatable(object)
---@diagnostic disable-next-line: need-check-nil
if metatable.__type then return metatable.__type end
else
return "table"
end
end
end
function getmetatable(object)
if oldtype(object) ~= "table" then return end
if oldtype(oldgetmetatable(object)) == "table" then
if oldgetmetatable(object).__isuserdata then
if oldtype(oldgetmetatable(object).__usermeta) == "function" then
return oldgetmetatable(object).__usermeta()
else
return oldgetmetatable(object).__usermeta
end
else
return oldgetmetatable(object)
end
else
return oldgetmetatable(object)
end
end
function isEqualToAny(a, ...)
local args = {...}
for i = 0, #args do if a == args[i] then return true end end
return false
end
function isEqualToAll(a, ...)
local args = {...}
for i = 0, #args do if a ~= args[i] then return false end end
return true
end
function table.keys(t)
local a = {}
for n in pairs(t) do table.insert(a, n) end
return a
end
function table.values(t)
local a = {}
for _, n in pairs(t) do table.insert(a, n) end
return a
end
function table.indexOf(t, value)
for i, v in ipairs(t) do if v == value then return i end end
return -1
end
function 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
local n = #s
local t_len = #target
while i <= n do
local match = true
if i + t_len - 1 <= n then
for j = 1, t_len do
if s:sub(i + j - 1, i + j - 1) ~= target:sub(j, j) then
match = false
break
end
end
else
match = false
end
if match then
table.insert(result, repl)
i = i + t_len
else
table.insert(result, s:sub(i, i))
i = i + 1
end
end
return table.concat(result)
end
function toHex(num)
return string.format("%X", num)
end
local st={
push=function(self,...) table.insert(self.contents,{...}) end,
pop=function(self) return table.unpack(table.remove(self.contents)) end,
}
local q={
push=function(self,...) table.insert(self.contents,{...}) end,
pop=function(self) return table.unpack(table.remove(self.contents,1)) end,
}
function stack()
return setmetatable({contents={}}, {__index=st})
end
function queue()
return setmetatable({contents={}}, {__index=q})
end
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,
__newindex = function(self, k, v)
rawset(self, k, v)
end,
__metatable=false
})
end
syscall = makeSyscallProxy()
_makeSyscallProxy = makeSyscallProxy
table.serialize = serialize

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
--:Minify:--
local kernel = ...
local cache = {}
kernel.searchpaths = {
"?", "?.lua", "/lib/?", "/lib/?.lua", "/usr/lib/?", "/usr/lib/?.lua",
"/usr/local/lib/?", "/usr/local/lib/?.lua"
}
kernel.reqcache = cache
local function require(module, ...)
if cache[module] then return cache[module].ret end
kernel.currentTask.status = "D"
local args = {...}
kernel.currentTask.ksh = coroutine.create(function()
local coro = function()
for _, path in ipairs(kernel.searchpaths) do
local filepath = path:gsub("?", module)
if kernel.vfs.exists(filepath) then
if kernel.vfs.type(filepath) == "directory" then
filepath = filepath .. "/init.lua"
if kernel.vfs.type(filepath) == "directory" then
kernel.asyncReturn(false, "Module not found: "..module)
return
end
end
local fd = kernel.vfs.open(filepath, "r")
local chunks = {}
while true do
local chunk = kernel.vfs.read(fd, 4096)
if not chunk or chunk == "" then break end
chunks[#chunks + 1] = chunk
coroutine.yield()
end
kernel.vfs.close(fd)
local data = table.concat(chunks)
local func, err = load(data, "@"..module, "t", kernel._U)
if not func then
kernel.asyncReturn(false, "Error loading module "..module..": "..tostring(err))
return
end
local ok, ret = xpcall(func, debug.traceback, table.unpack(args))
if not ok then
kernel.asyncReturn(false, "Error running module "..module..": "..tostring(ret))
return
end
cache[module] = {ret = ret, expires = kernel.EFI:getEpochMs() + 120000}
kernel.asyncReturn(true, ret)
return
end
coroutine.yield()
end
kernel.asyncReturn(false, "Module not found: "..module)
end
local status, err = xpcall(coro, debug.traceback)
if not status then
kernel.asyncReturn(false, "Error in require coroutine for module "..module..": "..tostring(err))
end
end)
end
kernel.syscalls["require"] = require
_G.require = function(...) return syscall.require(...) end

View File

@@ -0,0 +1,301 @@
--:Minify:--
local kernel = ...
local proxy = {}
local data = {}
proxy.address = "devfs0000"
proxy.isvirt = true
proxy.isReadOnly = function() return false end
proxy.spaceUsed = function() return 0 end
proxy.spaceTotal = function() return 0 end
proxy.makeDirectory = function() error("EACCES") end
proxy.remove = function() error("EACCES") end
proxy.setLabel = function() error("EACCES") end
proxy.getLabel = function() return "devfs" end
proxy.attributes = function(path) return {
size = 0,
modified = 0,
created = 0
} end
function proxy:open(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("open", mode)
end
error("ENFILE")
end
function proxy:type(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
if #steps == 0 then
return "directory"
end
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("type", mode)
end
if type(step[steps[#steps]]) == "table" then
return "directory"
end
error("ENOENT")
end
function proxy:list(path)
local steps = kernel.vfs.splitPath(path)
local step = data
if #steps == 0 then
return table.keys(data)
end
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENOENT") end
step=dat
end
if type(step[steps[#steps]]) == "table" then
return table.keys(step[steps[#steps]])
end
error("ENOENT")
end
function proxy:fileExists(path)
local ok = pcall(function()
return self:type(path)
end)
return ok
end
function data.random(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
return {
read=function(amount)
local str = ""
for i=1, amount or 1 do
str=str..string.char(math.random(0, 255))
end
return str
end
}
elseif mode=="w" or mode=="a" then
return {
write=function() end
}
else error("EACCES")
end
end
end
function data.null(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
return {
read=function(amount) end
}
elseif mode=="w" or mode=="a" then
return {
write=function() end
}
else error("EACCES")
end
end
end
function data.zero(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
return {
read=function(amount)
local str = ""
for i=1, amount or 1 do
str=str..string.char(0)
end
return str
end
}
elseif mode=="w" or mode=="a" then
return {
write=function() end
}
else error("EACCES")
end
end
end
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 "file"
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
data[".meta"]=strFile(buildMeta({
stdin="/proc/self/fd/0",
stdout="/proc/self/fd/1",
log="/var/log/syslog.log"
}))
if kernel.EFI:getEEPROM() then
function data.eeprom(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
local ptr,eeprom=1,kernel.EFI:getEEPROM()
return {
read=function(amount)
ptr=ptr+amount
return eeprom:sub(ptr-amount, ptr)
end
}
elseif mode=="w" then
if kernel.uid~=0 then error("EACCES") end
local firstwrite=true
return {
write=function(data)
if firstwrite then
kernel.EFI:setEEPROM(data)
else
kernel.EFI:setEEPROM(kernel.EFI:getEEPROM()..data)
end
end
}
elseif mode=="a" then
if kernel.uid~=0 then error("EACCES") end
return {
write=function(data)
kernel.EFI:setEEPROM(kernel.EFI:getEEPROM()..data)
end
}
else error("EACCES")
end
end
end
end
if kernel.EFI:getNvram() then
function data.nvram(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
local ptr,nvram=1,kernel.EFI:getNvram()
return {
read=function(amount)
ptr=ptr+amount
return nvram:sub(ptr-amount, ptr)
end
}
elseif mode=="w" then
if kernel.uid~=0 then error("EACCES") end
local firstwrite=true
return {
write=function(data)
if firstwrite then
kernel.EFI:setNvram(data)
else
kernel.EFI:setNvram(kernel.EFI:getNvram()..data)
end
end
}
elseif mode=="a" then
if kernel.uid~=0 then error("EACCES") end
return {
write=function(data)
kernel.EFI:setNvram(kernel.EFI:getNvram()..data)
end
}
else error("EACCES")
end
end
end
end
data["disk"]={["by-id"]={}, ["by-path"]={}, ["by-label"]={}}
data["input"]={}
kernel.devfs={}
kernel.devfs.data=data
kernel.devfs.proxy=proxy
kernel.disks["devfs0000"]=proxy

View File

@@ -0,0 +1,266 @@
--:Minify:--
local kernel = ...
local proxy = {}
local data = {}
proxy.address = "procfs0000"
proxy.isvirt = true
proxy.isReadOnly = function() return true 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))
},
pid=strFile(task.pid)
}
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.EFI:getUptime())end,function()error("EACCES")end)
kernel.procfs={}
kernel.procfs.data=data
kernel.procfs.proxy=proxy
kernel.disks["procfs0000"]=proxy

View File

@@ -0,0 +1,141 @@
--:Minify:--
local kernel = ...
kernel.vfs.remove("/tmp")
kernel.vfs.mkdir("/tmp")
if not kernel.config.tmpToDisk then
local proxy = {}
local data = {}
proxy.address = "tmpfs0000"
proxy.isvirt = true
proxy.isReadOnly = function() return false end
proxy.spaceUsed = function() return 0 end
proxy.spaceTotal = function() return 0 end
proxy.makeDirectory = function(_, path)
local steps = kernel.vfs.splitPath(path)
local step = data
for i=1,#steps do
if not step[steps[i]] then
step[steps[i]] = {}
elseif type(step[steps[i]]) ~= "table" then
error("ENOTDIR")
end
step = step[steps[i]]
end
end
proxy.remove = function(_, path)
local steps = kernel.vfs.splitPath(path)
local step = data
for i=1,#steps-1 do
step = step[steps[i]]
if not step then error("ENOENT") end
end
step[steps[#steps]] = nil
end
proxy.setLabel = function(_, label) end
proxy.getLabel = function() return "tmpfs" end
proxy.attributes = function(_, path)
local steps = kernel.vfs.splitPath(path)
local step = data
for i=1,#steps do
step = step[steps[i]]
if not step then error("ENOENT") end
end
return {
size = type(step) == "string" and #step or 0,
modified = 0,
created = 0,
}
end
function proxy:open(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
for i=1,#steps-1 do
if not step[steps[i]] then
if mode == "w" then step[steps[i]] = {} else error("ENOENT") end
elseif type(step[steps[i]]) ~= "table" then
error("ENOTDIR")
end
step = step[steps[i]]
end
local filename = steps[#steps]
if mode == "r" then
if type(step[filename]) ~= "string" then error("ENOENT") end
local content = step[filename]
local pos = 1
return {
read = function(amount)
amount = amount or #content
local chunk = content:sub(pos, pos+amount-1)
pos = pos + #chunk
return chunk
end,
close = function() end,
}
elseif mode == "w" then
step[filename] = ""
local buf = {}
return {
write = function(str)
buf[#buf + 1] = str
end,
close = function()
step[filename] = table.concat(buf)
end,
}
elseif mode == "a" then
if type(step[filename]) ~= "string" then step[filename] = "" end
return {
write = function(str)
step[filename] = step[filename] .. str
end,
close = function() end,
}
else
error("EACCES")
end
end
function proxy:type(path)
local steps = kernel.vfs.splitPath(path)
local step = data
if #steps == 0 then return "directory" end
for i=1,#steps do
step = step[steps[i]]
if not step then return false end
end
if type(step) == "table" then return "directory" end
if type(step) == "string" then return "file" end
end
function proxy:list(path)
local steps = kernel.vfs.splitPath(path)
local step = data
for i=1,#steps do
step = step[steps[i]]
if not step then error("ENOENT") end
end
if type(step) ~= "table" then error("ENOTDIR") end
local keys = {}
for k,_ in pairs(step) do table.insert(keys, k) end
return keys
end
function proxy:fileExists(path)
local t = self:type(path)
return t == "file" or t == "directory"
end
kernel.disks["tmpfs0000"] = proxy
else
kernel.log("tmpToDisk enabled, skipping tmpfs module")
kernel.log("tmpfs0000 will passthrough to /tmp on the disk")
end

View File

@@ -0,0 +1,540 @@
--:Minify:--
-- Loop device driver:
--
-- BIND (directory) - re-routes VFS calls into a host directory subtree.
-- Identical to the original behaviour.
--
-- IMAGE (*.hfs file) - mounts a Hyperion Filesystem Image. The image is
-- loaded entirely into memory; reads and writes operate
-- on the in-memory tree, so the image file is only
-- touched on attach/detach.
--
-- BHFS v1 - Binary Hyperion Filesystem Image format:
--
-- File header (8 bytes):
-- [0-3] magic: 0x42 0x48 0x46 0x53 ("BHFS")
-- [4] version: 0x01
-- [5] flags: bit0 = per-file deflate compression enabled
-- [6-7] reserved: 0x00 0x00
--
-- Records (repeated until END record):
-- [0] type: 0x01=file 0x02=dir 0x03=symlink 0xFF=end
-- [1-4] path_len (uint32 LE) - byte length of the path string
-- [5-8] raw_size (uint32 LE) - original uncompressed data size (0 for dirs)
-- [9-12] stored_size (uint32 LE) - bytes that follow in stream
-- (< raw_size means deflate-compressed;
-- = raw_size means stored as-is)
-- [13 .. 13+path_len-1] path bytes (no null terminator)
-- [.. +stored_size] data bytes
--
-- Dirs have raw_size=0, stored_size=0, zero data bytes.
-- Symlinks store the target path as data; stored_size == raw_size (no compression).
--
-- Syscalls:
-- id = syscall.losetup(path) attach dir OR .hfs image
-- id = syscall.losetup(path, true) force image mode
-- syscall.lodetach(id) detach (must be unmounted first)
-- tbl = syscall.lolist() {id -> {path,mode}, ...}
-- str = syscall.loimgcreate(srcdir) serialise VFS dir -> BHFS binary string
-- syscall.loimgwrite(str, dest) write BHFS string to a file (binary)
local kernel = ...
local _deflate = nil
local function getDeflate()
if _deflate == nil then
local ok, lib = pcall(require, "store.deflate")
_deflate = ok and lib or false
end
return _deflate or nil
end
local function pack32(n)
n = math.floor(n) % 4294967296
return string.char(
n % 256,
math.floor(n / 256) % 256,
math.floor(n / 65536) % 256,
math.floor(n / 16777216) % 256
)
end
local function unpack32(s, i)
local a, b, c, d = s:byte(i, i + 3)
return (a or 0)
+ (b or 0) * 256
+ (c or 0) * 65536
+ (d or 0) * 16777216
end
local BHFS_MAGIC = "BHFS"
local BHFS_VERSION = "\001"
local BHFS_FLAG_COMPRESS = 1
local TYPE_FILE = "\001"
local TYPE_DIR = "\002"
local TYPE_LINK = "\003"
local TYPE_END = "\255"
local B64D = {}
do
local a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for i = 1, #a do B64D[a:sub(i, i)] = i - 1 end
end
local function b64dec(s)
s = s:gsub("[^A-Za-z0-9+/=]", "")
local t, i = {}, 1
while i <= #s do
local c1 = B64D[s:sub(i, i )] or 0
local c2 = B64D[s:sub(i+1, i+1)] or 0
local c3 = B64D[s:sub(i+2, i+2)] or 0
local c4 = B64D[s:sub(i+3, i+3)] or 0
local n = c1*262144 + c2*4096 + c3*64 + c4
t[#t+1] = string.char(math.floor(n/65536) % 256)
if s:sub(i+2, i+2) ~= "=" then t[#t+1] = string.char(math.floor(n/256) % 256) end
if s:sub(i+3, i+3) ~= "=" then t[#t+1] = string.char(n % 256) end
i = i + 4
end
return table.concat(t)
end
local loopDevs = {}
local nextLoop = 0
local function makeBindDisk(id, dirPath)
local disk = { address = id, isvirt = false }
disk.isReadOnly = function() return false end
disk.spaceUsed = function() return 0 end
disk.spaceTotal = function() return 0 end
disk.setLabel = function() end
disk.getLabel = function() return id end
local function resolveBase()
local mp, mid = "/", "$"
for id2, m in pairs(kernel.vfs.mounts) do
if dirPath == m or (m == "/" and dirPath:sub(1,1) == "/")
or dirPath:sub(1, #m+1) == m.."/" then
if #m > #mp then mp = m; mid = id2 end
end
end
return kernel.vfs.disks[mid], dirPath:sub(#mp+1)
end
local function dp(path)
local hd, base = resolveBase()
local b = (base == "" or base == "/") and "" or base:gsub("^/+","")
local p = path:gsub("^/+","")
local c = ((b=="") and "/"..p or "/"..b.."/"..p):gsub("//+","/")
local r = c:sub(2); if r == "" then r = "/" end
return hd, r
end
function disk:open(path,mode) local h,r=dp(path); return h:open(r,mode) end
function disk:type(path) local h,r=dp(path); return h:type(r) end
function disk:list(path) local h,r=dp(path); return h:list(r) end
function disk:fileExists(path) local h,r=dp(path); return h:fileExists(r) end
function disk:attributes(path) local h,r=dp(path); return h:attributes(r) end
function disk:makeDirectory(path) local h,r=dp(path); return h:makeDirectory(r) end
function disk:remove(path) local h,r=dp(path); return h:remove(r) end
return disk
end
local function makeImageDisk(id, imageStr)
local root = { kind="dir", children={} }
local function getNode(path, create)
local parts = {}
for p in path:gmatch("[^/]+") do parts[#parts+1] = p end
local node = root
for i = 1, #parts do
local name = parts[i]
if not node.children then
if not create then return nil end
node.children = {}
end
if not node.children[name] then
if not create then return nil end
node.children[name] = { kind="dir", children={} }
end
node = node.children[name]
end
return node
end
local function ensureParent(path)
local par = path:match("^(.*)/[^/]+$") or ""
if par ~= "" then
local n = getNode(par, true)
if not n.children then n.children = {} end
end
end
if imageStr:sub(1, 4) == BHFS_MAGIC then
local pos = 9
while pos <= #imageStr do
local rtype = imageStr:sub(pos, pos)
pos = pos + 1
if rtype == TYPE_END then break end
local path_len = unpack32(imageStr, pos); pos = pos + 4
local raw_size = unpack32(imageStr, pos); pos = pos + 4
local stored_size = unpack32(imageStr, pos); pos = pos + 4
local path = imageStr:sub(pos, pos + path_len - 1)
pos = pos + path_len
local stored_data = imageStr:sub(pos, pos + stored_size - 1)
pos = pos + stored_size
local data = stored_data
if stored_size < raw_size then
local deflate = getDeflate()
if deflate then
data = deflate.decompress(stored_data) or stored_data
end
end
if rtype == TYPE_DIR then
if path ~= "" and path ~= "/" then
ensureParent(path)
local n = getNode(path, true)
n.kind = "dir"; n.children = n.children or {}
end
elseif rtype == TYPE_FILE then
ensureParent(path)
local n = getNode(path, true)
n.kind="file"; n.data=data; n.size=#data; n.children=nil
elseif rtype == TYPE_LINK then
ensureParent(path)
local n = getNode(path, true)
n.kind="link"; n.target=data; n.children=nil
end
end
else
for line in (imageStr.."\n"):gmatch("([^\n]*)\n") do
if line == "END" then
break
elseif line:sub(1,4) == "DIR " then
local p = line:sub(5):match("^%s*(.-)%s*$")
if p and p ~= "" and p ~= "/" then
ensureParent(p)
local n = getNode(p, true)
n.kind = "dir"; n.children = n.children or {}
end
elseif line:sub(1,5) == "FILE " then
local p, sz, body = line:sub(6):match("^(%S+)%s+(%d+)%s*(.-)%s*$")
if p then
ensureParent(p)
local data = (tonumber(sz) or 0) > 0 and b64dec(body) or ""
local n = getNode(p, true)
n.kind="file"; n.data=data; n.size=#data; n.children=nil
end
elseif line:sub(1,5) == "LINK " then
local p, tgt = line:sub(6):match("^(%S+)%s+(.+)$")
if p then
ensureParent(p)
local n = getNode(p, true)
n.kind="link"; n.target=tgt; n.children=nil
end
end
end
end
local disk = { address=id, isvirt=false }
disk.isReadOnly = function() return false end
disk.spaceTotal = function() return 1024*1024*64 end
disk.spaceUsed = function()
local tot = 0
local function w(n)
if n.kind=="file" then tot = tot + (n.size or 0)
elseif n.kind=="dir" then for _,c in pairs(n.children or {}) do w(c) end end
end
w(root); return tot
end
disk.setLabel = function() end
disk.getLabel = function() return id end
local function norm(path)
return path:gsub("^/+",""):gsub("/+$","")
end
function disk:type(path)
local p = norm(path)
if p == "" then return "directory" end
local n = getNode(p)
if not n then return nil end
if n.kind == "dir" then return "directory" end
return "file"
end
function disk:fileExists(path)
local p = norm(path)
if p == "" then return true end
return getNode(p) ~= nil
end
function disk:list(path)
local p = norm(path)
local node = (p=="") and root or getNode(p)
if not node or node.kind ~= "dir" then return {} end
local out = {}
for name in pairs(node.children or {}) do out[#out+1] = name end
return out
end
function disk:attributes(path)
local p = norm(path)
local node = (p=="") and root or getNode(p)
if not node then return nil end
return {
size = node.kind=="file" and (node.size or 0) or 0,
isDir = node.kind=="dir",
isReadOnly = false,
created = 0,
modified = 0,
}
end
function disk:open(path, mode)
local p = norm(path)
local node = getNode(p)
if mode == "r" then
if not node or node.kind ~= "file" then error("ENOENT: "..path) end
local data, pos = node.data or "", 1
return {
read = function(n)
if pos > #data then return nil end
local chunk = data:sub(pos, pos + (n or 1) - 1)
pos = pos + #chunk; return chunk
end,
readAll = function()
local all = data:sub(pos); pos = #data + 1; return all
end,
readLine = function()
if pos > #data then return nil end
local nl = data:find("\n", pos, true)
local line
if nl then line=data:sub(pos, nl-1); pos=nl+1
else line=data:sub(pos); pos=#data+1 end
return line
end,
seek = function(w, o)
o = o or 0
if w == "set" then pos = o + 1
elseif w == "cur" then pos = pos + o
elseif w == "end" then pos = #data + 1 + o end
return pos - 1
end,
close = function() end,
}
elseif mode == "w" or mode == "a" then
local buf = (mode=="a" and node and node.kind=="file")
and {node.data or ""} or {}
local done = false
local function commit()
if done then return end; done = true
local data = table.concat(buf)
if not node then ensureParent(p); node = getNode(p, true) end
node.kind="file"; node.data=data; node.size=#data; node.children=nil
end
return {
write = function(s) buf[#buf+1] = tostring(s) end,
writeLine = function(s) buf[#buf+1] = tostring(s).."\n" end,
flush = function() end,
close = commit,
}
else
error("EINVAL: unknown mode: "..tostring(mode))
end
end
function disk:makeDirectory(path)
local p = norm(path)
if p == "" then return end
ensureParent(p)
local n = getNode(p, true)
n.kind="dir"; n.children=n.children or {}; n.data=nil; n.size=nil
end
function disk:remove(path)
local p = norm(path)
if p == "" then error("EBUSY: cannot remove root") end
local par = p:match("^(.*)/[^/]+$") or ""
local name = p:match("([^/]+)$")
local pn = (par=="") and root or getNode(par)
if pn and pn.children then pn.children[name] = nil end
end
disk._root = root
return disk
end
local function serializeDir(srcPath)
local deflate = getDeflate()
local useCompress = deflate ~= nil
local flags = useCompress and BHFS_FLAG_COMPRESS or 0
local parts = {
BHFS_MAGIC,
BHFS_VERSION,
string.char(flags),
"\0\0",
}
srcPath = srcPath:gsub("/$", "")
local MIN_COMPRESS = 64
local function walk(vpath)
local ftype = kernel.vfs.type(vpath)
if ftype == "directory" then
if vpath ~= srcPath then
local relPath = vpath:sub(#srcPath + 1)
parts[#parts+1] = TYPE_DIR
parts[#parts+1] = pack32(#relPath)
parts[#parts+1] = pack32(0)
parts[#parts+1] = pack32(0)
parts[#parts+1] = relPath
end
local ok, entries = pcall(kernel.vfs.listdir, vpath)
if ok and entries then
table.sort(entries)
for _, name in ipairs(entries) do
walk(vpath:gsub("/$","").."/"..name)
end
end
elseif ftype == "file" then
local relPath = vpath:sub(#srcPath + 1)
local ok, fd = pcall(kernel.vfs.open, vpath, "r")
if ok then
local rawData = ""
local ok2, content = pcall(kernel.vfs.read, fd, 1024*1024)
if ok2 then rawData = content or "" end
pcall(kernel.vfs.close, fd)
local storedData = rawData
if useCompress and #rawData >= MIN_COMPRESS then
local compressed = deflate.compress(rawData)
if compressed and #compressed < #rawData then
storedData = compressed
end
end
parts[#parts+1] = TYPE_FILE
parts[#parts+1] = pack32(#relPath)
parts[#parts+1] = pack32(#rawData)
parts[#parts+1] = pack32(#storedData)
parts[#parts+1] = relPath
parts[#parts+1] = storedData
end
end
end
walk(srcPath)
parts[#parts+1] = TYPE_END
return table.concat(parts)
end
kernel.syscalls["losetup"] = function(filePath, forceImage)
if not filePath then error("EINVAL") end
local task = kernel.currentTask
local euid = (task and (task.euid or task.uid)) or kernel.uid
if euid ~= 0 then error("EPERM") end
filePath = filePath:gsub("/$", "")
local id = "loop" .. tostring(nextLoop)
nextLoop = nextLoop + 1
local ftype = kernel.vfs.type(filePath)
local disk, mode
if not forceImage and ftype == "directory" then
disk = makeBindDisk(id, filePath)
mode = "bind"
elseif ftype == "file" or forceImage then
if ftype ~= "file" then error("ENOENT: not a file: "..filePath) end
local img
local ok, fd = pcall(kernel.vfs.open, filePath, "rb")
if ok then
local ok2, data = pcall(kernel.vfs.read, fd, 1024*1024*16)
pcall(kernel.vfs.close, fd)
if ok2 and data then img = data end
end
if not img then
local ok2, fd2 = pcall(kernel.vfs.open, filePath, "r")
if not ok2 then error("EIO: cannot open image: "..filePath) end
local ok3, data = pcall(kernel.vfs.read, fd2, 1024*1024*16)
pcall(kernel.vfs.close, fd2)
if not ok3 or not data then error("EIO: cannot read image: "..filePath) end
img = data
end
disk = makeImageDisk(id, img)
mode = "image"
else
error("EINVAL: path must be a directory or .hfs image file")
end
kernel.vfs.disks[id] = disk
loopDevs[id] = { path=filePath, disk=disk, mode=mode }
kernel.log("losetup: attached "..id.." ("..mode..") -> "..filePath, "INFO")
return id
end
kernel.syscalls["lodetach"] = function(id)
local task = kernel.currentTask
local euid = (task and (task.euid or task.uid)) or kernel.uid
if euid ~= 0 then error("EPERM") end
if not loopDevs[id] then error("ENXIO") end
for mid in pairs(kernel.vfs.mounts) do
if mid == id then error("EBUSY: loop device is still mounted") end
end
kernel.vfs.disks[id] = nil
loopDevs[id] = nil
kernel.log("lodetach: detached "..id, "INFO")
end
kernel.syscalls["lolist"] = function()
local rv = {}
for id, info in pairs(loopDevs) do
rv[id] = { path=info.path, mode=info.mode }
end
return rv
end
kernel.syscalls["loimgcreate"] = function(srcPath)
local task = kernel.currentTask
local euid = (task and (task.euid or task.uid)) or kernel.uid
if euid ~= 0 then error("EPERM") end
if not srcPath then error("EINVAL") end
if kernel.vfs.type(srcPath) ~= "directory" then error("ENOTDIR: "..srcPath) end
return serializeDir(srcPath)
end
kernel.syscalls["loimgwrite"] = function(imgStr, destPath)
local task = kernel.currentTask
local euid = (task and (task.euid or task.uid)) or kernel.uid
if euid ~= 0 then error("EPERM") end
if not imgStr or not destPath then error("EINVAL") end
local ok, fd = pcall(kernel.vfs.open, destPath, "wb")
if not ok then
ok, fd = pcall(kernel.vfs.open, destPath, "w")
if not ok then error("EIO: cannot write: "..tostring(destPath)) end
end
local ok2, werr = pcall(kernel.vfs.write, fd, imgStr)
pcall(kernel.vfs.close, fd)
if not ok2 then error("EIO: write failed: "..tostring(werr)) end
end
kernel.log("Loop device driver loaded (bind + BHFS binary image + legacy HFS compat)")

View File

@@ -0,0 +1,35 @@
--:Minify:--
local kernel = ...
local function trim(str)
local s, e = 1, #str
while s <= e and (str:sub(s,s) == " " or str:sub(s,s) == "\t") do s = s + 1 end
while e >= s and (str:sub(e,e) == " " or str:sub(e,e) == "\t" or str:sub(e,e) == "\n" or str:sub(e,e) == "\r") do e = e - 1 end
if s > e then return "" end
return str:sub(s,e)
end
for _, line in ipairs(string.split(kernel.fstab, "\n")) do
line = trim(line)
if line ~= "" and line:sub(1,1) == "U" then
local semicolon_pos
for i = 3, #line do
if line:sub(i,i) == ";" then
semicolon_pos = i
break
end
end
if not semicolon_pos or semicolon_pos == 3 then
kernel.log("Invalid fstab line: "..line.." ... Skipping.", "WARN", 0xFF8800)
else
local id = line:sub(3, semicolon_pos - 1)
local path = trim(line:sub(semicolon_pos + 1))
kernel.log("Mounting '"..id.."' to '"..path.."'")
if id ~= "$" then
kernel.vfs.mount(path, id)
end
kernel.log("Mounted "..id.." to "..path)
end
end
end

View File

@@ -0,0 +1,38 @@
--:Minify:--
local kernel = ...
local signal = {}
kernel.signal=signal
function signal.sigsend(pid, sig)
if sig<0 or sig>256 then error("EINVAL") end
local task = kernel.tasks[tostring(pid)]
if not task then error("ENOENT") end
if not task.sigq then return end
task.sigq[#task.sigq+1] = sig
end
function signal.sigcatch(handler)
local task=kernel.currentTask
task.sigh=handler
if not task.sigq then task.sigq={} end
local handle={
error="",
active=true
}
if task.sigd then task.sigd.active=false; end
task.sigd=handle
return handle
end
function signal.sigignore()
local task=kernel.currentTask
task.sigh=nil
task.sigq=nil
if task.sigd then task.sigd.active=false end
task.sigd=nil
end
local s=kernel.syscalls
s["sigsend"] = signal.sigsend
s["sigcatch"] = signal.sigcatch
s["sigignore"] = signal.sigignore

View File

@@ -0,0 +1,194 @@
--:Minify:--
local kernel = ...
local socket = {}
socket.handlers = {}
kernel.socket = socket
local P = kernel.vfs.P
local sys = kernel.syscalls
function socket.registerProtocal(proto, handler)
socket.handlers[proto] = handler
end
local function getHandler(address)
for proto, handler in pairs(socket.handlers) do
if string.hasPrefix(address, proto) then
return handler
end
end
end
function socket.socket(domain, socktype)
local fd = kernel.vfs.newfd({
handle = {},
type = "socket",
refcount = 1,
socket = {
connected = false,
protocol = nil,
address = nil
},
meta = {
owner = kernel.currentTask.uid,
group = kernel.currentTask.uid,
etype = 2,
perms =
P.OWNER_R +
P.OWNER_W +
P.GROUP_R +
P.GROUP_W
},
isvirt = true
})
local fdo = kernel.currentTask.fd[fd]
fdo.handle.read = function()
return ""
end
fdo.handle.write = function()
return nil, "ENOTCONN"
end
fdo.handle.close = function()
fdo.socket.connected = false
return true
end
return fd
end
function socket.connect(fd, address)
local fdo = kernel.currentTask.fd[fd]
if not fdo then
return nil, "EBADF"
end
if fdo.type ~= "socket" then
return nil, "ENOTSOCK"
end
local handler =
getHandler(address)
if not handler then
return nil, "EPROTONOSUPPORT"
end
fdo.socket.protocol = handler
fdo.socket.address = address
local ok, err =
handler.connect(fd, address)
if not ok then
return nil, err
end
fdo.socket.connected = true
return true
end
function socket.listen(fd, backlog)
local fdo =
kernel.currentTask.fd[fd]
if not fdo then
return nil, "EBADF"
end
if not fdo.handle.listen then
return nil, "EOPNOTSUPP"
end
return fdo.handle.listen(backlog)
end
function socket.send(fd, data)
local fdo =
kernel.currentTask.fd[fd]
if not fdo then
return nil, "EBADF"
end
if fdo.type ~= "socket" then
return nil, "ENOTSOCK"
end
if not fdo.socket.connected then
return nil, "ENOTCONN"
end
if not fdo.handle.write then
return nil, "EOPNOTSUPP"
end
return fdo.handle.write(data)
end
function socket.recv(fd, amount)
local fdo =
kernel.currentTask.fd[fd]
if not fdo then
return nil, "EBADF"
end
if fdo.type ~= "socket" then
return nil, "ENOTSOCK"
end
if not fdo.socket.connected then
return nil, "ENOTCONN"
end
if not fdo.handle.read then
return nil, "EOPNOTSUPP"
end
return fdo.handle.read(amount)
end
function socket.sockshutdown(fd)
local fdo =
kernel.currentTask.fd[fd]
if not fdo then
return nil, "EBADF"
end
if fdo.type ~= "socket" then
return nil, "ENOTSOCK"
end
if fdo.handle.close then
return fdo.handle.close()
end
fdo.socket.connected = false
return true
end
sys.socket = socket.socket
sys.connect = socket.connect
sys.listen = socket.listen
sys.send = socket.send
sys.recv = socket.recv
sys.sockshutdown = socket.sockshutdown
kernel.log("Loaded socket module")

View File

@@ -0,0 +1,4 @@
--:Minify:--
local kernel=...
kernel.unixSockets={}
local socket=kernel.socket

View File

@@ -0,0 +1,73 @@
--:Minify:--
local args = {...}
local kernel = args[1]
kernel._G = _G
local function readonly(tbl)
return setmetatable({}, {
__index = function(_, key)
local value = tbl[key]
if type(value) == "table" then return readonly(value) end
return value
end,
__newindex = function(t, k, v)
if kernel.config.allowGlobalOverwrites or
kernel.allowGlobalOverwrites then
rawset(tbl, k, v)
return
end
error("Attempt to modify global variable '" .. k .. "'", 2)
end,
__pairs = function(self)
local function iter(_, key)
local nextKey, value = next(tbl, key)
if type(value) == "table" then
value = readonly(value)
end
return nextKey, value
end
return iter, self, nil
end,
__ipairs = function()
local i = 0
return function()
i = i + 1
local value = tbl[i]
if value == nil then return end
if type(value) == "table" then
value = readonly(value)
end
return i, value
end
end,
__len = function() return #tbl end,
__metatable = false
})
end
local origLoad = load
kernel._U = readonly(kernel._G)
kernel._U._G = kernel._U
kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end
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

@@ -0,0 +1,129 @@
--:Minify:--
local kernel = ...
local vterms = {}
local function createVt(id, width, height)
local vt = {
id = id,
width = width,
height = height,
buffer = {},
cursorX = 1,
cursorY = 1,
fgColor = 0xFFFFFF,
bgColor = 0x000000,
obj = {}
}
for y = 1, height do
vt.buffer[y] = {}
for x = 1, width do
vt.buffer[y][x] = {char = " ", fgColor = vt.fgColor, bgColor = vt.bgColor}
end
end
local function scroll(lines)
for _ = 1, lines do
table.remove(vt.buffer, 1)
vt.buffer[vt.height] = {}
for x = 1, vt.width do
vt.buffer[vt.height][x] = {char = " ", fgColor = vt.fgColor, bgColor = vt.bgColor}
end
end
end
function vt.obj:write(content)
local x, y = vt.cursorX, vt.cursorY
if x>vt.width then return end
if y>vt.height then return end
for i = 1, #content do
local c = content: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)
for _ = 1, spaces do
vt.buffer[y][x] = {char = " ", fgColor = vt.fgColor, bgColor = vt.bgColor}
x = x + 1
if x > vt.width then
x = 1
y = y + 1
end
end
elseif c == "\b" then
if x > 1 then
x = x - 1
vt.buffer[y][x] = {char = " ", fgColor = vt.fgColor, bgColor = vt.bgColor}
end
else
if x <= vt.width and y <= vt.height then
vt.buffer[y][x] = {char = c, fgColor = vt.fgColor, bgColor = vt.bgColor}
x = x + 1
end
end
if x > vt.width then
x = 1
y = y + 1
end
if y > vt.height then
scroll(1)
y = vt.height
end
end
vt.cursorX, vt.cursorY = x, y
end
function vt.obj:spos(x, y)
vt.cursorX = tonumber(x)
vt.cursorY = tonumber(y)
end
function vt.obj:gpos()
return tostring(vt.cursorX)..";"..tostring(vt.cursorY)
end
function vt.obj:sfgc(color)
vt.fgColor = tonumber(color)
end
function vt.obj:sbgc(color)
vt.bgColor = tonumber(color)
end
function vt.obj:gfgc()
return vt.fgColor
end
function vt.obj:gbgc()
return vt.bgColor
end
function vt.obj:gplt()
return 24
end
function vt.obj:clear()
for y = 1, vt.height do
for x = 1, vt.width do
vt.buffer[y][x] = {char = " ", fgColor = vt.fgColor, bgColor = vt.bgColor}
end
end
vt.cursorX, vt.cursorY = 1, 1
end
function vt.obj:isvirt()
return true
end
function vt.obj:gctrl()
return serializeBool(vt.ctrl)..";"..serializeBool(vt.alt)
end
return vt
end

View File

@@ -0,0 +1,616 @@
--:Minify:--
local kernel = ...
local auth = {}
kernel.auth = auth
-- PASSWD FILE FORMAT: uid:gid:username:homedir:shell
-- SHADOW FILE FORMAT: uid:salt:hash
local function getFile(path)
local file = kernel.vfs.open(path, "r")
if not file then error("Failed to open file: " .. path) end
local content = kernel.vfs.read(file, 1024000)
kernel.vfs.close(file)
return content
end
local function writeFile(path, content)
local file = kernel.vfs.open(path, "w")
if not file then error("Failed to open file for writing: " .. path) end
kernel.vfs.write(file, content)
kernel.vfs.close(file)
end
local blake2s
do
local MOD32 = 2^32
local function norm(x) return x % MOD32 end
local function tobits(x)
x = norm(x)
local t = {}
for i = 0, 31 do local b = x % 2; t[i] = b; x = (x - b) / 2 end
return t
end
local function frombits(t)
local x, p = 0, 1
for i = 0, 31 do if t[i] == 1 then x = x + p end; p = p * 2 end
return norm(x)
end
local function bor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0 end
end
return frombits(bits)
end
local function bxor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do bits[j] = (bits[j] ~= b[j]) and 1 or 0 end
end
return frombits(bits)
end
local function lshift(x, n) return norm(norm(x) * 2^n) end
local function rshift(x, n) return math.floor(norm(x) / 2^n) end
local function rotr(x, n) return bor(rshift(x, n), lshift(x, 32 - n)) end
local IV = {
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
}
local SIGMA = {
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
{11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
{7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
{9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
{2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
{12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
{13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0}
}
local function G(v, a, b, c, d, x, y)
v[a] = (v[a] + v[b] + x) % MOD32
v[d] = rotr(bxor(v[d], v[a]), 16)
v[c] = (v[c] + v[d]) % MOD32
v[b] = rotr(bxor(v[b], v[c]), 12)
v[a] = (v[a] + v[b] + y) % MOD32
v[d] = rotr(bxor(v[d], v[a]), 8)
v[c] = (v[c] + v[d]) % MOD32
v[b] = rotr(bxor(v[b], v[c]), 7)
end
local function compress(h, block, t, last)
local v = {}
for i = 1, 8 do v[i] = h[i] end
for i = 1, 8 do v[i + 8] = IV[i] end
v[13] = bxor(v[13], t)
if last then v[15] = bxor(v[15], 0xFFFFFFFF) end
local m = {}
for i = 0, 15 do
local p = i * 4 + 1
m[i] = (block:byte(p) or 0)
+ ((block:byte(p+1) or 0) * 0x100)
+ ((block:byte(p+2) or 0) * 0x10000)
+ ((block:byte(p+3) or 0) * 0x1000000)
end
for r = 1, 10 do
local s = SIGMA[r]
G(v,1,5,9,13, m[s[1]], m[s[2]])
G(v,2,6,10,14, m[s[3]], m[s[4]])
G(v,3,7,11,15, m[s[5]], m[s[6]])
G(v,4,8,12,16, m[s[7]], m[s[8]])
G(v,1,6,11,16, m[s[9]], m[s[10]])
G(v,2,7,12,13, m[s[11]], m[s[12]])
G(v,3,8,9,14, m[s[13]], m[s[14]])
G(v,4,5,10,15, m[s[15]], m[s[16]])
end
for i = 1, 8 do h[i] = bxor(h[i], v[i], v[i+8]) end
end
function blake2s(msg, key)
key = key or ""
local h = {}
for i = 1, 8 do h[i] = IV[i] end
local outlen = 32
h[1] = bxor(h[1], 0x01010000 + lshift(#key, 8) + outlen)
local t = 0
if #key > 0 then
local block = key .. string.rep("\0", 64 - #key)
t = #key
compress(h, block, t, false)
end
for i = 1, #msg, 64 do
local block = msg:sub(i, i + 63)
if #block < 64 then block = block .. string.rep("\0", 64 - #block) end
t = t + math.min(64, #msg - i + 1)
compress(h, block, t, i + 64 > #msg)
end
local out = ""
for i = 1, 8 do out = out .. string.format("%08x", h[i]) end
return out
end
end
if not blake2s then error("Failed to load blake2s") end
if not kernel.vfs.exists("/etc/pam.d/secret") then
kernel.log("PAM SECRET REGENERATING PLEASE USE ROOT", "WARN", 0xFF8800)
local key = ""
for i = 1, 256 do key = key .. string.char(math.random(0, 255)) end
local handle = kernel.vfs.open("/etc/pam.d/secret", "w")
kernel.vfs.write(handle, key)
kernel.vfs.close(handle)
end
local pepper = getFile("/etc/pam.d/secret")
local function genSalt()
return toHex(math.random(0,2^32))
end
local function hashPassword(password, salt)
local key = (pepper .. salt)
return blake2s(password, key)
end
local passwdFile = getFile("/etc/passwd")
local shadowFile = getFile("/etc/shadow")
local passwdLines = string.split(passwdFile, "\n")
local shadowLines = string.split(shadowFile, "\n")
local passwd, shadow = {}, {}
for _, v in ipairs(passwdLines) do
local fields = string.split(v, ":")
if fields[1] and fields[1] ~= "" then
passwd[#passwd + 1] = fields
end
end
for _, v in ipairs(shadowLines) do
local fields = string.split(v, ":")
if fields[1] and fields[1] ~= "" then
shadow[#shadow + 1] = fields
end
end
for _, v in ipairs(passwd) do
local uid = tonumber(v[1])
if uid then kernel.users[uid] = v[3] end
end
kernel.passwd = passwd
local function flushPasswd()
local lines = {}
for _, v in ipairs(passwd) do
lines[#lines + 1] = table.concat(v, ":")
end
writeFile("/etc/passwd", table.concat(lines, "\n"))
end
local function flushShadow()
local lines = {}
for _, v in ipairs(shadow) do
lines[#lines + 1] = table.concat(v, ":")
end
writeFile("/etc/shadow", table.concat(lines, "\n"))
end
local function getPasswdByUID(uid)
for _, v in ipairs(passwd) do
if tonumber(v[1]) == uid then return v end
end
return nil
end
local function getShadowByUID(uid)
for _, v in ipairs(shadow) do
if tonumber(v[1]) == uid then return v end
end
return nil
end
local function getPasswdByUsername(username)
for _, v in ipairs(passwd) do
if v[3] == username then return v end
end
return nil
end
local function nextUID()
local max = 999
for _, v in ipairs(passwd) do
local uid = tonumber(v[1])
if uid and uid >= 1000 and uid > max then max = uid end
end
return max + 1
end
function auth.login(uid, password)
if type(uid) ~= "number" or type(password) ~= "string" then
return nil, "Authentication failure"
end
local entry = getPasswdByUID(uid)
if not entry then
-- timing attack resistance
hashPassword(password, "aaaaaaaaaaaaaaaa")
return nil, "Authentication failure"
end
local sEntry = getShadowByUID(uid)
if not sEntry then
hashPassword(password, "aaaaaaaaaaaaaaaa")
return nil, "Authentication failure"
end
local salt = sEntry[2]
local storedHash = sEntry[3]
local computed = hashPassword(password, salt)
if computed ~= storedHash then
return nil, "Authentication failure"
end
kernel.currentUID = uid
local _task = kernel.currentTask
if _task then
_task.uid = uid
_task.euid = uid
_task.gid = tonumber(entry[2]) or uid
_task.egid = tonumber(entry[2]) or uid
end
kernel.log("AUTH: login uid=" .. tostring(uid) .. " (" .. getPasswdByUID(uid)[3] .. ")")
return true
end
function auth.setPassword(uid, newPassword)
uid = tonumber(uid)
if not uid then return nil, "Invalid uid" end
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
or kernel.currentUID or 0
if callerUID ~= 0 and callerUID ~= uid then
return nil, "Permission denied"
end
if type(newPassword) ~= "string" or #newPassword == 0 then
return nil, "Password may not be empty"
end
if #newPassword < 6 then
return nil, "Password is too short (minimum 6 characters)"
end
local salt = genSalt()
local hash = hashPassword(newPassword, salt)
local sEntry = getShadowByUID(uid)
if sEntry then
sEntry[2] = salt
sEntry[3] = hash
else
shadow[#shadow + 1] = { tostring(uid), salt, hash }
end
flushShadow()
kernel.log("AUTH: password changed for uid=" .. tostring(uid))
return true
end
function auth.setUsername(uid, newUsername)
uid = tonumber(uid)
if not uid then return nil, "Invalid uid" end
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
or kernel.currentUID or 0
if callerUID ~= 0 then
return nil, "Permission denied (root only)"
end
if type(newUsername) ~= "string" or #newUsername == 0 then
return nil, "Invalid username"
end
if not newUsername:match("^[a-z_][a-z0-9_%-]*$") or #newUsername > 32 then
return nil, "Invalid username format"
end
if getPasswdByUsername(newUsername) then
return nil, "Username already taken"
end
local entry = getPasswdByUID(uid)
if not entry then return nil, "No such user" end
local oldName = entry[3]
entry[3] = newUsername
kernel.users[uid] = newUsername
flushPasswd()
kernel.log("AUTH: uid=" .. tostring(uid) .. " renamed '" .. oldName .. "' → '" .. newUsername .. "'")
return true
end
function auth.newUser(username, password, gid, homedir, shell)
local callerUID = (kernel.currentProcess and kernel.currentProcess.euid)
or kernel.currentUID or 0
if callerUID ~= 0 then
return nil, "Permission denied (root only)"
end
if type(username) ~= "string" or #username == 0 then
return nil, "Invalid username"
end
if not username:match("^[a-z_][a-z0-9_%-]*$") or #username > 32 then
return nil, "Invalid username format"
end
if getPasswdByUsername(username) then
return nil, "Username already exists"
end
if type(password) ~= "string" or #password < 6 then
return nil, "Password is too short (minimum 6 characters)"
end
local uid = nextUID()
gid = tonumber(gid) or uid
homedir = homedir or ("/home/" .. username)
shell = shell or "/bin/hysh"
passwd[#passwd + 1] = {
tostring(uid),
tostring(gid),
username,
homedir,
shell
}
kernel.users[uid] = username
local salt = genSalt()
local hash = hashPassword(password, salt)
shadow[#shadow + 1] = { tostring(uid), salt, hash }
flushPasswd()
flushShadow()
if kernel.vfs.mkdir and not kernel.vfs.exists(homedir) then
kernel.vfs.mkdir(homedir)
-- Homedir must be owned by the new user, not root
pcall(kernel.vfs.chown, homedir, uid, uid)
end
kernel.log("AUTH: new user '" .. username .. "' uid=" .. tostring(uid))
return uid
end
function auth.whoami()
local uid = (kernel.currentProcess and kernel.currentProcess.euid)
or kernel.currentUID
if not uid then return nil, "Not logged in" end
return kernel.users[uid] or ("uid=" .. tostring(uid))
end
function auth.getUID(username)
local entry = getPasswdByUsername(username)
if entry then return tonumber(entry[1]) end
return nil
end
function auth.getPasswd(uid)
uid = tonumber(uid)
local entry = getPasswdByUID(uid)
if not entry then return nil end
return {
uid = tonumber(entry[1]),
gid = tonumber(entry[2]),
username = entry[3],
homedir = entry[4],
shell = entry[5],
}
end
function auth.deleteUser(uid)
uid = tonumber(uid)
if not uid then return nil, "Invalid uid" end
local callerUID = kernel.uid or 0
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
if uid == 0 then return nil, "Cannot delete root" end
local entry = getPasswdByUID(uid)
if not entry then return nil, "No such user" end
local username = entry[3]
for i, v in ipairs(passwd) do
if tonumber(v[1]) == uid then table.remove(passwd, i); break end
end
for i, v in ipairs(shadow) do
if tonumber(v[1]) == uid then table.remove(shadow, i); break end
end
kernel.users[uid] = nil
flushPasswd()
flushShadow()
kernel.log("AUTH: deleted user '" .. username .. "' uid=" .. tostring(uid))
return true
end
function auth.lockUser(uid)
uid = tonumber(uid)
if not uid then return nil, "Invalid uid" end
local callerUID = kernel.uid or 0
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
if uid == 0 then return nil, "Cannot lock root" end
local sEntry = getShadowByUID(uid)
if not sEntry then return nil, "No shadow entry for uid" end
if sEntry[3]:sub(1,1) ~= "!" then
sEntry[3] = "!" .. sEntry[3]
end
flushShadow()
kernel.log("AUTH: locked uid=" .. tostring(uid))
return true
end
function auth.unlockUser(uid)
uid = tonumber(uid)
if not uid then return nil, "Invalid uid" end
local callerUID = kernel.uid or 0
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
local sEntry = getShadowByUID(uid)
if not sEntry then return nil, "No shadow entry for uid" end
if sEntry[3]:sub(1,1) == "!" then
sEntry[3] = sEntry[3]:sub(2)
end
flushShadow()
kernel.log("AUTH: unlocked uid=" .. tostring(uid))
return true
end
function auth.listUsers()
local result = {}
for _, v in ipairs(passwd) do
local uid = tonumber(v[1])
local sEntry = getShadowByUID(uid)
local locked = sEntry and sEntry[3]:sub(1,1) == "!"
result[#result+1] = {
uid = uid,
gid = tonumber(v[2]),
username = v[3],
homedir = v[4],
shell = v[5],
locked = locked or false,
}
end
return result
end
function auth.setShell(uid, shell)
uid = tonumber(uid)
if not uid then return nil, "Invalid uid" end
local callerUID = kernel.uid or 0
if callerUID ~= 0 and callerUID ~= uid then
return nil, "Permission denied"
end
if type(shell) ~= "string" or #shell == 0 then
return nil, "Invalid shell"
end
local entry = getPasswdByUID(uid)
if not entry then return nil, "No such user" end
entry[5] = shell
flushPasswd()
kernel.log("AUTH: uid=" .. tostring(uid) .. " shell -> " .. shell)
return true
end
function auth.setHomedir(uid, homedir)
uid = tonumber(uid)
if not uid then return nil, "Invalid uid" end
local callerUID = kernel.uid or 0
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
if type(homedir) ~= "string" or #homedir == 0 then
return nil, "Invalid homedir"
end
local entry = getPasswdByUID(uid)
if not entry then return nil, "No such user" end
entry[4] = homedir
flushPasswd()
kernel.log("AUTH: uid=" .. tostring(uid) .. " homedir -> " .. homedir)
return true
end
function auth.setGID(uid, gid)
uid = tonumber(uid)
gid = tonumber(gid)
if not uid or not gid then return nil, "Invalid uid or gid" end
local callerUID = kernel.uid or 0
if callerUID ~= 0 then return nil, "Permission denied (root only)" end
local entry = getPasswdByUID(uid)
if not entry then return nil, "No such user" end
entry[2] = tostring(gid)
flushPasswd()
kernel.log("AUTH: uid=" .. tostring(uid) .. " gid -> " .. tostring(gid))
return true
end
function auth.elevate(targetUsername, password)
if type(targetUsername) ~= "string" or type(password) ~= "string" then
return nil, "Authentication failure"
end
local entry = getPasswdByUsername(targetUsername)
if not entry then
hashPassword(password, "aaaaaaaaaaaaaaaa") -- timing resistance
return nil, "Authentication failure"
end
local uid = tonumber(entry[1])
local sEntry = getShadowByUID(uid)
if not sEntry then
hashPassword(password, "aaaaaaaaaaaaaaaa")
return nil, "Authentication failure"
end
local computed = hashPassword(password, sEntry[2])
if computed ~= sEntry[3] then
return nil, "Authentication failure"
end
local task = kernel.currentTask
local prevUid = task.uid
task.uid = 0
task.euid = 0
task.gid = 0
task.egid = 0
kernel.uid = 0
kernel.log("AUTH: elevate uid=" .. tostring(prevUid) .. " -> 0 (via " .. targetUsername .. ")")
return true, uid
end
if kernel.syscalls then
kernel.syscalls["login"] = auth.login
kernel.syscalls["setpassword"] = auth.setPassword
kernel.syscalls["setusername"] = auth.setUsername
kernel.syscalls["newuser"] = auth.newUser
kernel.syscalls["whoami"] = auth.whoami
kernel.syscalls["getuidbyname"]= auth.getUID
kernel.syscalls["getpasswd"] = auth.getPasswd
kernel.syscalls["elevate"] = auth.elevate
kernel.syscalls["deleteuser"] = auth.deleteUser
kernel.syscalls["lockuser"] = auth.lockUser
kernel.syscalls["unlockuser"] = auth.unlockUser
kernel.syscalls["listusers"] = auth.listUsers
kernel.syscalls["setshell"] = auth.setShell
kernel.syscalls["sethomedir"] = auth.setHomedir
kernel.syscalls["setgid"] = auth.setGID
end

View File

@@ -0,0 +1,571 @@
--:Minify:--
local kernel = ...
local tasks = {}
local sys = {}
local nextpid = 2
kernel.exitMain = false
local resumeWithTimeout = coroutine.resumeWithTimeout
local function bit_is_set(num, bit)
return math.floor(num / (2 ^ bit)) % 2 == 1
end
local function loadExecutable(path)
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 env = kernel.freshUserEnv()
local func, err = load(data, "@" .. path, "t", env)
if not func then kernel.log("Failed to load executable: " .. tostring(err).. " : ".. tostring(path)); error("ENOEXEC: " .. tostring(err)) end
local meta = kernel.vfs.lstat(path)
local suid_set = bit_is_set(meta.perms, 6)
local caller_uid = kernel.currentTask and kernel.currentTask.uid or kernel.uid
local euid = suid_set and meta.owner or caller_uid
return func, euid, suid_set
end
local function createTask(func, name, envars, args, tgid, real_uid, eff_uid)
local id = nextpid
nextpid = nextpid + 1
tasks[tostring(id)] = {
coro = coroutine.create(function()
local ok, err = xpcall(func, debug.traceback, table.unpack(args or {}))
if kernel.config.logTaskExit then
if not ok then
kernel.log("Task " .. tostring(id) .. " exited with err: " .. tostring(err), "ERROR", 0xFF0000)
elseif err then
kernel.log("Task " .. tostring(id) .. " exited with code: " .. tostring(err), "DBUG", 0x00FFFF)
else
kernel.log("Task " .. tostring(id) .. " exited without code", "DBUG", 0x00FFFF)
end
end
if type(err) == "number" then
tasks[tostring(id)].exit = err
end
if tasks[tostring(id)].fd then
for fd, _ in pairs(tasks[tostring(id)].fd) do
pcall(kernel.vfs.close, fd)
end
end
tasks[tostring(id)].status = "Z"
end),
name = name or ("task" .. tostring(id)),
envars = envars or (kernel.currentTask and kernel.currentTask.envars or {}),
args = args or {},
status = "R",
pid = id,
tgid = tgid or (kernel.currentTask and kernel.currentTask.tgid or id),
uid = real_uid,
euid = eff_uid,
gid = (kernel.currentTask and kernel.currentTask.gid) or 0,
groups = (kernel.currentTask and kernel.currentTask.groups) or {},
fd = {},
sleep = 0,
ivs = 0,
vs = 0,
children = {},
parent = kernel.currentTask or kernel.kernelTask,
siblings = (kernel.currentTask and kernel.currentTask.children) or kernel.kernelTask.children,
syscallReturn = {},
cwd = (kernel.currentTask and kernel.currentTask.cwd) or "/",
timeSlice = 0,
lastTime = 0,
totalTime = 0,
numRuns = 0,
}
table.insert(
(kernel.currentTask and kernel.currentTask.children) or kernel.kernelTask.children,
tasks[tostring(id)]
)
return id
end
function sys.spawn(func, name, envars, args, tgid)
local caller = kernel.currentTask
local real_uid = caller and caller.uid or kernel.uid
local eff_uid = caller and caller.euid or real_uid
return createTask(func, name, envars, args, tgid, real_uid, eff_uid)
end
function sys.execspawn(path, name, envars, args, tgid)
local func, euid, suid_active = loadExecutable(path, kernel._U)
local caller = kernel.currentTask
local real_uid = caller and caller.uid or kernel.uid
if suid_active then
kernel.log(
"execspawn: suid exec '" .. path ..
"' caller_uid=" .. tostring(real_uid) ..
" -> euid=" .. tostring(euid)
)
end
return createTask(func, name or path, envars, args, tgid, real_uid, euid)
end
function sys.exec(path, args, envars)
local task = kernel.currentTask
local func, euid, _ = loadExecutable(path, kernel._U)
if task.fd then
for fd, _ in pairs(task.fd) do
if fd > 2 then pcall(kernel.vfs.close, fd) end
end
end
task.euid = euid
task.args = args or {}
task.envars = envars or task.envars
task.name = path
task.coro = coroutine.create(function()
local ok, err = xpcall(func, debug.traceback, table.unpack(task.args))
if kernel.config.logTaskExit then
if not ok then
kernel.log("Task " .. tostring(task.pid) .. " exec '" .. path .. "' err: " .. tostring(err), "ERROR", 0xFF0000)
else
kernel.log("Task " .. tostring(task.pid) .. " exec '" .. path .. "' exited: " .. tostring(err), "DBUG", 0x00FFFF)
end
end
if type(err) == "number" then tasks[tostring(task.pid)].exit = err end
if tasks[tostring(task.pid)].fd then
for fd, _ in pairs(tasks[tostring(task.pid)].fd) do
pcall(kernel.vfs.close, fd)
end
end
tasks[tostring(task.pid)].status = "Z"
end)
task.syscallReturn = {}
coroutine.yield()
end
function sys.sleep(s)
kernel.currentTask.status = "S"
kernel.currentTask.sleep = kernel.EFI:getEpochMs() + s * 1000
end
function sys.getTask(pid)
local task = tasks[tostring(pid)]
if not task then return nil end
local children, siblings = {}, {}
for i, v in ipairs(task.children) do children[i] = v.pid end
for i, v in ipairs(task.siblings) do siblings[i] = v.pid end
return {
name = task.name,
status = task.status,
pid = task.pid,
tgid = task.tgid,
username = kernel.users[task.uid],
uid = task.uid,
euid = task.euid,
exit = task.exit,
sleep = task.sleep,
ivs = task.ivs,
vs = task.vs,
children = children,
siblings = siblings,
parent = task.parent.pid,
cwd = task.cwd,
term = task.term,
}
end
function sys.collect(pid)
local children = {}
for _, v in ipairs(kernel.currentTask.children) do children[#children+1] = v.pid end
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif not isEqualToAny(task.pid, table.unpack(children)) then
return false, "You do not own this task"
elseif task.status ~= "Z" then
return false, "Task must exit to collect status"
else
task.reapTime = 0
return true, task.exit
end
end
function sys.kill(pid, force)
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif task.status == "Z" then
return false, "Task is already dead"
elseif task.status == "D" and not force then
return false, "Cannot kill task waiting for IO"
elseif task.euid ~= kernel.uid and kernel.uid ~= 0 then
return false, "Different user"
end
local caller = kernel.currentTask
local ceuid = caller and (caller.euid or caller.uid) or kernel.uid
if ceuid ~= 0 and task.uid ~= (caller and caller.uid or kernel.uid) then
return false, "EPERM"
end
task.status = "Z"
return true
end
function sys.stop(pid)
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif task.status ~= "R" and task.status ~= "S" then
return false, "Cannot stop non-running task"
elseif task.euid ~= kernel.uid and kernel.uid ~= 0 then
return false, "Different user"
else
if task.status == "S" then
task.status = "ST"
else
task.status = "T"
end
return true
end
end
function sys.continue(pid)
local task = tasks[tostring(pid)]
if not task then
return false, "Task does not exist"
elseif task.status ~= "T" and task.status ~= "ST" then
return false, "Task is not stopped"
elseif task.euid ~= kernel.uid and kernel.uid ~= 0 then
return false, "Different user"
else
if task.status == "ST" then
task.status = "S"
else
task.status = "R"
end
return true
end
end
function sys.getpid() return kernel.currentTask.pid end
function sys.getppid() return kernel.currentTask.parent.pid end
function sys.getTasks()
local ret = {}
for _, v in pairs(tasks) do ret[#ret+1] = v.pid end
return ret
end
function sys.getEnviron(key) return kernel.currentTask.envars[key] end
function sys.setEnviron(key, val) kernel.currentTask.envars[key] = val end
function sys.exit(code)
local task = kernel.currentTask
if kernel.config.logTaskExit then
if code then
kernel.log("Task " .. tostring(task.pid) .. " exited with code: " .. tostring(code), "DBUG", 0x00FFFF)
else
kernel.log("Task " .. tostring(task.pid) .. " exited without code", "DBUG", 0x00FFFF)
end
end
tasks[tostring(task.pid)].status = "Z"
if type(code) == "number" then
tasks[tostring(task.pid)].exit = code
end
end
function sys.setuid(uid)
local task = kernel.currentTask
if task.euid ~= 0 and task.uid ~= uid then
error("EPERM")
end
task.uid = uid
task.euid = uid
kernel.uid = uid
end
function sys.geteuid()
return kernel.currentTask.euid
end
function sys.getuid() return kernel.currentTask.uid end
local function reapDeadTasks()
for pid, task in pairs(tasks) do
if task.status == "Z" and not task.reapTime then
if task.pid == 1 then kernel.panic("Attempted to gc init!") end
task.coro = nil
task.ivs = nil
task.vs = nil
task.args = nil
task.envars = nil
task.cwd = nil
task.numRuns = nil
task.totalTime = nil
task.lastTime = nil
task.timeSlice = nil
task.syscallReturn = nil
task.sleep = nil
task.fd = nil
task.reapTime = kernel.EFI:getEpochMs() + 30000
elseif task.reapTime and kernel.EFI:getEpochMs() > task.reapTime
and task.status == "Z" then
for _, child in ipairs(task.children) do
child.parent = tasks["1"]
child.siblings = tasks["1"].children
table.insert(tasks["1"].children, child)
end
for i, sibling in ipairs(task.siblings) do
if sibling.pid == task.pid then
table.remove(task.siblings, i)
break
end
end
tasks[pid] = nil
end
end
end
local alpha = 0.85
local C_target = 0.01
local Tmin = 0.0005
local Tmax = 0.5
local lambda_budget = 0.08
local lambda_clamp = 0.03
local lambda_var = 0.02
local k_min = 0.5
local k_max = 0.5
local B = 0.01
function kernel.main()
kernel.log("Starting main loop...")
kernel.saveLog()
local stopLog=5
local logTasks=100
while not kernel.exitMain do
if kernel.config.logTasks then
if logTasks<0 then
kernel.log("Active Tasks:")
for i,v in pairs(tasks) do
kernel.log(v.name.." : "..v.status.." : "..tostring(v.pid).." : "..tostring(v.exit))
end
kernel.log("[END BLOCK]")
logTasks=100
end
logTasks=logTasks-1
end
local N = 0
local Tmin_hit = 0
local Tmax_hit = 0
local totalTaskTime = 0
local taskTimes = {}
for pid, task in pairs(tasks) do
if kernel.exitMain then break end
kernel.currentTask = task
kernel.uid = task.euid or task.uid
kernel.process = task.name
if task.status == "S" and kernel.EFI:getEpochMs() >= task.sleep then
task.status = "R"
task.sleep = 0
end
if task.status == "ST" and kernel.EFI:getEpochMs() >= task.sleep then
task.status = "T"
task.sleep = 0
end
if task.status == "DS" and kernel.EFI:getEpochMs() >= task.sleep then
task.status = "D"
task.sleep = 0
end
if task.status == "D" then
if task.ksh then
coroutine.resume(task.ksh)
if coroutine.status(task.ksh) == "dead" then
task.ksh = nil
if task.status == "D" then
task.status = "R"
end
if kernel.config.debugSyscalls then
kernel.log("Task " .. task.pid .. " IO wait completed", "DBUG", 0x00FFFF)
local sysret = task.syscallReturn
for i = 2, #sysret do
local v = type(sysret[i]) == "table" and table.serialize(sysret[i]) or tostring(sysret[i])
kernel.log(" retval[" .. (i-1) .. "] = " .. v, "DBUG", 0x00FFFF)
end
end
end
end
end
if task.status == "R" then
N = N + 1
task.timeSlice = math.min(Tmax, math.max(Tmin, B / (N ^ alpha)))
if task.sigq and #task.sigq ~= 0 and task.sigh then
local coro = coroutine.create(task.sigh)
local status,err
if kernel.config.preempt then
status,err=coroutine.resumeWithTimeout(coro, 100, table.remove(task.sigq, 1))
else
status,err=coroutine.resume(coro, table.remove(task.sigq, 1))
end
if status=="error" or status==false then
task.sigd.error=err or "Unknown"
task.sigd.active=false
task.sigh=nil
task.sigq=nil
task.sigd=nil
elseif status=="success" or status==true then
if err=="syscall" then
task.sigd.error="Cannot execute syscalls from signals"
task.sigd.active=false
task.sigh=nil
task.sigq=nil
task.sigd=nil
end
end
end
if task.status == "R" then
local startTime = kernel.EFI:getEpochMs()
local ret
if stopLog > 0 then
kernel.log("Running task " .. tostring(task.pid) .. " (" .. task.name .. ") with time slice " .. string.format("%.3f", task.timeSlice) .. "s", "DBUG", 0x00FFFF)
end
if kernel.config.preempt then
if not task.debugger then
ret = { resumeWithTimeout(task.coro, task.timeSlice, table.unpack(task.syscallReturn)) }
else
ret = { coroutine.resume(task.coro, table.unpack(task.syscallReturn)) }
end
else
ret = { coroutine.resume(task.coro, table.unpack(task.syscallReturn)) }
end
local elapsed = kernel.EFI:getEpochMs() - startTime
task.lastTime = elapsed
task.totalTime = (task.totalTime or 0) + elapsed
task.numRuns = (task.numRuns or 0) + 1
taskTimes[#taskTimes+1] = elapsed
totalTaskTime = totalTaskTime + elapsed
if elapsed <= Tmin then Tmin_hit = Tmin_hit + 1 end
if elapsed >= Tmax then Tmax_hit = Tmax_hit + 1 end
if ret[1] == "error" or ret[1] == false then
kernel.log("processHandlerException: " .. tostring(ret[2]), "ERROR", 0xFF0000)
task.status = "Z"
task.exit = "processHandlerException: " .. tostring(ret[2])
elseif ret[1] == "timeout" then
task.ivs = task.ivs + 1
task.syscallReturn = {}
elseif ret[1] == "success" or ret[1] == true then
task.vs = task.vs + 1
if ret[2] == "syscall" then
local scname = ret[3]
if kernel.syscalls[scname] then
if kernel.config.debugSyscalls then
kernel.log("Task " .. task.pid .. " syscall: " .. scname, "DBUG", 0x00FFFF)
for i = 4, #ret do
kernel.log(" inval[" .. (i-3) .. "] = " .. tostring(ret[i]), "DBUG", 0x00FFFF)
end
end
local sysret = { xpcall(kernel.syscalls[scname], debug.traceback, table.unpack(ret, 4)) }
if kernel.config.debugSyscalls then
if not sysret[1] then
kernel.log("Task " .. task.pid .. " syscall " .. scname .. " failed: " .. tostring(sysret[2]), "ERROR", 0xFF0000)
else
kernel.log("Task " .. task.pid .. " syscall " .. scname .. " ok, " .. (#sysret-1) .. " retvals", "DBUG", 0x00FFFF)
for i = 2, #sysret do
local v = type(sysret[i]) == "table" and table.serialize(sysret[i]) or tostring(sysret[i])
kernel.log(" retval[" .. (i-1) .. "] = " .. v, "DBUG", 0x00FFFF)
end
end
end
if not sysret[1] then
task.syscallReturn = { false, sysret[2] }
else
task.syscallReturn = { true, table.unpack(sysret, 2) }
end
else
task.syscallReturn = { false, "Unknown syscall: " .. tostring(scname) }
end
end
end
end
end
end
local T_prev_avg = (N > 0) and (totalTaskTime / N) or 0
local T_prev_var = 0
for _, t in ipairs(taskTimes) do
T_prev_var = T_prev_var + (t - T_prev_avg) ^ 2
end
if N > 0 then T_prev_var = T_prev_var / N end
if N > 0 then
local f_clamp = k_min * (Tmin_hit / N) - k_max * (Tmax_hit / N)
local B_budget = (C_target * (N ^ (alpha - 1))) / math.max(T_prev_avg, 1e-8)
B = B + lambda_budget * (B_budget - B) + lambda_clamp * f_clamp - lambda_var * T_prev_var
end
reapDeadTasks()
if stopLog > 0 then
kernel.log("Executed " .. N .. " tasks, avg time: " .. string.format("%.2f", T_prev_avg) .. "ms, var: " .. string.format("%.2f", T_prev_var) .. ", B: " .. string.format("%.5f", B))
stopLog = stopLog - 1
kernel.saveLog()
end
end
end
local sysc = kernel.syscalls
sysc["spawn"] = sys.spawn
sysc["execspawn"] = sys.execspawn
sysc["exec"] = sys.exec
sysc["sleep"] = sys.sleep
sysc["getTask"] = sys.getTask
sysc["collect"] = sys.collect
sysc["kill"] = sys.kill
sysc["stop"] = sys.stop
sysc["continue"] = sys.continue
sysc["getpid"] = sys.getpid
sysc["getppid"] = sys.getppid
sysc["getTasks"] = sys.getTasks
sysc["setEnviron"] = sys.setEnviron
sysc["getEnviron"] = sys.getEnviron
sysc["exit"] = sys.exit
sysc["setuid"] = sys.setuid
sysc["getuid"] = sys.getuid
sysc["geteuid"] = sys.geteuid
kernel._G.sleep = function(...) coroutine.yield("syscall", "sleep", ...) end
kernel.tasks = tasks
kernel.hpv = sys

View File

@@ -0,0 +1,7 @@
--:Minify:--
local kernel=...
local debug=debug
kernel._G.debug={
getinfo=debug.getinfo,
traceback=debug.traceback
}

View File

@@ -0,0 +1,28 @@
--:Minify:--
local kernel=...
local sysc=kernel.syscalls
kernel.gpio={}
sysc["gpio_write"]=function(pin, data)
if kernel.gpio[pin] then
return kernel.gpio[pin]("w", data)
end
end
sysc["gpio_writeAnalog"]=function(pin, data)
if kernel.gpio[pin] then
return kernel.gpio[pin]("wa", data)
end
end
sysc["gpio_read"]=function(pin)
if kernel.gpio[pin] then
return kernel.gpio[pin]("r")
end
end
sysc["gpio_readAnalog"]=function(pin)
if kernel.gpio[pin] then
return kernel.gpio[pin]("ra")
end
end

View File

@@ -0,0 +1,22 @@
--:Minify:--
local kernel = ...
function print(...)
local args = {...}
local output = ""
for i = 1, #args do output = output .. tostring(args[i]) .. "\t" end
output = output:sub(1, -2)
syscall.write(1, output.."\n")
end
function printf(fmt, ...)
local output = string.format(fmt, ...)
syscall.write(1, output.."\n")
end
function printInline(...)
local args = {...}
local output = ""
for i = 1, #args do output = output .. tostring(args[i]) .. "\t" end
output = output:sub(1, -2)
syscall.write(1, output)
end

View File

@@ -0,0 +1,54 @@
--:Minify:--
local kernel = ...
kernel.log("Loading init system...")
kernel.log("InitPath: " .. kernel.config.initPath)
local initOk, initErr = pcall(kernel.vfs.access, kernel.config.initPath, "rx")
if not initOk then
kernel.PANIC("Init binary not executable: " .. kernel.config.initPath .. " (" .. tostring(initErr) .. ")")
end
local handle = kernel.vfs.open(kernel.config.initPath, "r")
if not handle then kernel.panic("Failed to open "..kernel.config.initPath) end
local data = kernel.vfs.read(handle, 1024 * 1024 * 4)
if not handle then kernel.panic("Failed to read "..kernel.config.initPath) end
kernel.vfs.close(handle)
local initFunc, err = load(data, "@sysinit", "t", kernel._U)
if not initFunc then kernel.PANIC("Failed to load init system: " .. err) end
kernel.tasks["1"] = {
coro = coroutine.create(function()
local ok, err = xpcall(initFunc, debug.traceback, kernel)
if not ok then
kernel.panic("Init system crashed: " .. tostring(err))
else
kernel.panic("Init system exited: " .. tostring(err))
end
end),
name = "sysinit",
status = "R",
pid = 1,
tgid = 1,
uid = 0,
fd = {},
envars = {},
args = {},
exit = "",
sleep = 0,
ivs = 0,
vs = 0,
parent = kernel.kernelTask,
siblings = kernel.kernelTask.children,
children = {},
syscallReturn = {},
cwd = "/",
timeSlice = 0,
lastTime = 0,
totalTime = 0,
numRuns = 0
}
kernel.log("created init task with PID 1")
kernel.log("Initializing init system...")

View File

@@ -0,0 +1,11 @@
local kernel = ...
kernel.processes.kgc = function()
while true do
for i,v in pairs(kernel.reqcache) do
if v.expires and kernel.EFI:getEpochMs() > v.expires then
kernel.reqcache[i] = nil
end
end
kernel.sleep(5000)
end
end

View File

@@ -0,0 +1,10 @@
--:Minify:--
local kernel = ...
if not kernel.vfs.exists("/root") then kernel.vfs.mkdir("/root") end
kernel.processes.login = function()
local ok, err = pcall(kernel.hpv.execspawn, "/bin/login", "login")
if not ok then
kernel.log("Failed to exec /bin/login: " .. tostring(err), "ERROR", 0xFF0000)
end
end

View File

@@ -0,0 +1,121 @@
--:Minify:--
local kernel = ...
if kernel.firstBoot then
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", 0xFF8800)
end
end
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},
{"proc", REG, 0, 0, RWXRWXRWX},
{"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

137
Src/bit32/data/lib/bit32 Normal file
View File

@@ -0,0 +1,137 @@
--:Minify:--
local bit32 = {}
local MOD32 = 2^32
local MOD31 = 2^31
local function norm(x)
return x % MOD32
end
-- Convert number to bit table
local function tobits(x)
x = norm(x)
local t = {}
for i = 0, 31 do
local b = x % 2
t[i] = b
x = (x - b) / 2
end
return t
end
-- Convert bit table to number
local function frombits(t)
local x = 0
local p = 1
for i = 0, 31 do
if t[i] == 1 then
x = x + p
end
p = p * 2
end
return norm(x)
end
-- ===== Logical ops =====
function bit32.band(...)
local args = {...}
if #args == 0 then return 0xFFFFFFFF end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do
bits[j] = (bits[j] == 1 and b[j] == 1) and 1 or 0
end
end
return frombits(bits)
end
function bit32.bor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do
bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0
end
end
return frombits(bits)
end
function bit32.bxor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do
bits[j] = (bits[j] ~= b[j]) and 1 or 0
end
end
return frombits(bits)
end
function bit32.bnot(x)
local bits = tobits(x)
for i = 0, 31 do
bits[i] = bits[i] == 1 and 0 or 1
end
return frombits(bits)
end
-- ===== Shifts =====
function bit32.lshift(x, n)
return norm(norm(x) * 2^n)
end
function bit32.rshift(x, n)
return math.floor(norm(x) / 2^n)
end
function bit32.arshift(x, n)
x = norm(x)
if x >= MOD31 then
return math.floor((x - MOD32) / 2^n)
else
return math.floor(x / 2^n)
end
end
-- ===== Rotates =====
function bit32.lrotate(x, n)
n = n % 32
x = norm(x)
local left = (x * 2^n) % MOD32
local right = math.floor(x / 2^(32 - n))
return norm(left + right)
end
function bit32.rrotate(x, n)
n = n % 32
x = norm(x)
local right = math.floor(x / 2^n)
local left = (x * 2^(32 - n)) % MOD32
return norm(left + right)
end
-- ===== Bit fields =====
function bit32.extract(x, field, width)
width = width or 1
return bit32.band(bit32.rshift(x, field), 2^width - 1)
end
function bit32.replace(x, v, field, width)
width = width or 1
local mask = bit32.lshift(2^width - 1, field)
x = bit32.band(x, bit32.bnot(mask))
return bit32.bor(x, bit32.lshift(v, field))
end
return bit32

View File

@@ -0,0 +1,117 @@
--:Minify:--
-- blake2s.lua
-- Pure Lua 5.2, 32-bit only, supports keyed hashing
local bit32 = require("bit32")
local band, bor, bxor = bit32.band, bit32.bor, bit32.bxor
local rshift, lshift = bit32.rshift, bit32.lshift
local MOD32 = 2^32
local function rotr(x, n)
return bor(rshift(x, n), lshift(x, 32 - n))
end
local IV = {
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
}
local SIGMA = {
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
{11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
{7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
{9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
{2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
{12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
{13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0}
}
local function G(v, a, b, c, d, x, y)
v[a] = (v[a] + v[b] + x) % MOD32
v[d] = rotr(bxor(v[d], v[a]), 16)
v[c] = (v[c] + v[d]) % MOD32
v[b] = rotr(bxor(v[b], v[c]), 12)
v[a] = (v[a] + v[b] + y) % MOD32
v[d] = rotr(bxor(v[d], v[a]), 8)
v[c] = (v[c] + v[d]) % MOD32
v[b] = rotr(bxor(v[b], v[c]), 7)
end
local function compress(h, block, t, last)
local v = {}
for i = 1, 8 do v[i] = h[i] end
for i = 1, 8 do v[i + 8] = IV[i] end
v[13] = bxor(v[13], t)
if last then
v[15] = bxor(v[15], 0xFFFFFFFF)
end
local m = {}
for i = 0, 15 do
local p = i * 4 + 1
m[i] =
(block:byte(p) or 0) +
((block:byte(p + 1) or 0) * 0x100) +
((block:byte(p + 2) or 0) * 0x10000) +
((block:byte(p + 3) or 0) * 0x1000000)
end
for r = 1, 10 do
local s = SIGMA[r]
G(v,1,5,9,13, m[s[1]], m[s[2]])
G(v,2,6,10,14, m[s[3]], m[s[4]])
G(v,3,7,11,15, m[s[5]], m[s[6]])
G(v,4,8,12,16, m[s[7]], m[s[8]])
G(v,1,6,11,16, m[s[9]], m[s[10]])
G(v,2,7,12,13, m[s[11]], m[s[12]])
G(v,3,8,9,14, m[s[13]], m[s[14]])
G(v,4,5,10,15, m[s[15]], m[s[16]])
end
for i = 1, 8 do
h[i] = bxor(h[i], v[i], v[i + 8])
end
end
local function blake2s(msg, key)
key = key or ""
local h = {}
for i = 1, 8 do h[i] = IV[i] end
local outlen = 32 -- bytes
h[1] = bxor(
h[1],
0x01010000 + lshift(#key, 8) + outlen
)
local t = 0
if #key > 0 then
local block = key .. string.rep("\0", 64 - #key)
t = #key
compress(h, block, t, false)
end
for i = 1, #msg, 64 do
local block = msg:sub(i, i + 63)
if #block < 64 then
block = block .. string.rep("\0", 64 - #block)
end
t = t + math.min(64, #msg - i + 1)
compress(h, block, t, i + 64 > #msg)
end
local out = ""
for i = 1, 8 do
out = out .. string.format("%08x", h[i])
end
return out
end
return blake2s

View File

@@ -0,0 +1,169 @@
--:Minify:--
syscall.open("/dev/input/keyboard1", "r") --stdin (fd 0)
syscall.open("/dev/tty/1", "w") --stdout (fd 1)
syscall.open("/dev/null", "w") --stderr (fd 2)
local MAX_ATTEMPTS = 3
local function readLine(mask)
local input = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then
syscall.write(1, "\n")
return input
elseif ch == "\b" then
if #input > 0 then
input = input:sub(1, -2)
syscall.write(1, "\b \b")
end
else
input = input .. ch
syscall.write(1, mask or ch)
end
end
end
local function firstBoot()
local shadow = ""
local _fd, _fderr = pcall(function()
local fd = syscall.open("/etc/shadow", "r")
shadow = syscall.read(fd, 65535) or ""
syscall.close(fd)
end)
if shadow:match("%S") then return end
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
syscall.devctl(1, "sfgc", 0x00FF00)
syscall.write(1, "HyperionOS First Boot Setup\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.write(1, "No root password is set. Please create one now.\n\n")
while true do
syscall.write(1, "New root password: ")
local pw1 = readLine("*")
syscall.write(1, "Confirm password: ")
local pw2 = readLine("*")
if pw1 ~= pw2 then
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Passwords do not match. Try again.\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
elseif #pw1 < 6 then
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Password too short (minimum 6 characters).\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
else
local ok, err = syscall.setpassword(0, pw1)
if ok then
syscall.devctl(1, "sfgc", 0x00FF00)
syscall.write(1, "Root password set.\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
sleep(0.5)
break
else
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Error: " .. tostring(err) .. "\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
end
end
end
end
local function spawnShell(username, uid, shell, homedir)
local existsOk, existsErr = pcall(syscall.exists, shell)
if not existsOk or not existsErr then
syscall.write(1, "login: shell not found: " .. shell .. "\n")
sleep(2)
return false
end
local accessOk, accessErr = pcall(syscall.access, shell, "rx")
syscall.setEnviron("HOME", homedir)
syscall.setEnviron("USER", username)
syscall.setEnviron("SHELL", shell)
syscall.setEnviron("PATH", "/bin/")
local setuidOk, setuidErr = pcall(syscall.setuid, uid)
if not setuidOk then
syscall.write(1, "login: setuid failed: " .. tostring(setuidErr) .. "\n")
sleep(2)
return false
end
local chdirOk, chdirErr = pcall(syscall.chdir, homedir)
if not chdirOk then
pcall(syscall.chdir, "/")
end
local ok, err = pcall(syscall.execspawn, shell, username .. ":shell")
if not ok then
syscall.write(1, "login: failed to launch shell: " .. tostring(err) .. "\n")
sleep(2)
return false
end
syscall.exit(0)
end
local function doLogin()
syscall.devctl(1, "clear")
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.devctl(1, "sbgc", 0x000000)
syscall.devctl(1, "spos", 1, 1)
local hostname = syscall.getHostname() or "hyperion"
syscall.write(1, "HyperionOS\n")
syscall.write(1, hostname .. " login\n\n")
local attempts = 0
while attempts < MAX_ATTEMPTS do
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.write(1, "Username: ")
local username = readLine(nil)
if username ~= "" then
syscall.write(1, "Password: ")
local password = readLine("*")
local uid = syscall.getuidbyname(username)
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 "/"
syscall.devctl(1, "sfgc", 0x00FF00)
syscall.write(1, "\nWelcome, " .. username .. "!\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
sleep(0.3)
spawnShell(username, uid, shell, homedir)
return -- back to login prompt
else
attempts = attempts + 1
sleep(1)
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Login incorrect.\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
end
end
end
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Maximum login attempts exceeded.\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
sleep(5)
end
firstBoot()
while true do
doLogin()
end

View File

@@ -0,0 +1,19 @@
--:Minify:--
local users = syscall.listusers()
if not users or #users == 0 then
print("No users found.")
return
end
syscall.devctl(1,"sfgc",0xDBDBDB)
print(string.format("%-6s %-6s %-16s %-20s %s", "UID", "GID", "Username", "Home", "Shell"))
print(string.rep("-", 65))
syscall.devctl(1,"sfgc",0xFFFFFF)
for _, u in ipairs(users) do
local lock_marker = u.locked and " [locked]" or ""
if u.locked then syscall.devctl(1,"sfgc",0xFF0000) end
print(string.format("%-6d %-6d %-16s %-20s %s%s",
u.uid, u.gid, u.username, u.homedir, u.shell, lock_marker))
if u.locked then syscall.devctl(1,"sfgc",0xFFFFFF) end
end

View File

@@ -0,0 +1,152 @@
--:Minify:--
-- Usage:
-- mount list all current mounts
-- mount <id> <mountpoint> mount loop device id at mountpoint
-- mount -o loop <src> <dest> attach <src> as loop device and mount at <dest>
-- mount --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { help=false, o=nil }
local i = 1
local rawArgs = {...}
while i <= #rawArgs do
local v = rawArgs[i]
if v:sub(1,2) == "--" then
local o = v:sub(3)
if o == "help" then opts.help = true
else print(name..": unrecognised option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
elseif v == "-o" then
i = i + 1
opts.o = rawArgs[i]
elseif v:sub(1,1) == "-" then
local rest = v:sub(2)
if rest:sub(1,1) == "o" then
if #rest > 1 then opts.o = rest:sub(2)
else i = i + 1; opts.o = rawArgs[i] end
else
print(name..": invalid option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
else
table.insert(args, v)
end
i = i + 1
end
if opts.help then
print("Usage: "..name)
print(" "..name.." <id> <mountpoint>")
print(" "..name.." -o loop <source> <mountpoint>")
print("")
print("Mount a loop device or filesystem.")
print("")
print(" (no args) list all active mount points")
print(" <id> <mountpoint> mount an already-attached loop device")
print(" -o loop <src> <dest> attach src as loop device and mount at dest")
print(" src can be a directory (bind) or .hfs image")
print("")
print("Requires root for all operations except listing.")
return
end
if #args == 0 and not opts.o then
local ok, mounts = pcall(syscall.mounts or function()
error("ENOSYS")
end)
local loDevs = {}
local lok, ld = pcall(syscall.lolist)
if lok then
for id, info in pairs(ld) do
local path = (type(info)=="table" and info.path) or tostring(info)
local mode = (type(info)=="table" and info.mode) or "bind"
loDevs[id] = { path=path, mode=mode }
end
end
if next(loDevs) == nil then
syscall.devctl(1, "sfgc", 0xFF00FF)
print("(no loop devices attached)")
syscall.devctl(1, "sfgc", 0xFFFFFF)
return
end
for id, info in pairs(loDevs) do
local colour = info.mode == "image" and 0x00FFFF or 0x0000FF
syscall.devctl(1, "sfgc", colour)
printInline(info.mode.." "..id)
syscall.devctl(1, "sfgc", 0xFFFFFF)
print(" on "..info.path)
end
return
end
if opts.o and opts.o:lower() == "loop" then
if #args < 2 then
print(name..": -o loop requires <source> and <mountpoint>")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
local src = args[1]
local dest = args[2]
if src:sub(1,1) ~= "/" then src = syscall.getcwd().."/"..src end
if dest:sub(1,1) ~= "/" then dest = syscall.getcwd().."/"..dest end
local ok, loopId = pcall(syscall.losetup, src)
if not ok then
local msg = tostring(loopId)
if msg:find("EPERM") then msg = "Permission denied"
elseif msg:find("EINVAL") then msg = "'"..args[1].."': not a directory or .hfs image"
elseif msg:find("EIO") then msg = "'"..args[1].."': I/O error reading image"
end
print(name..": losetup: "..msg); syscall.exit(1); return
end
local ok2, merr = pcall(syscall.mount, dest, loopId)
if not ok2 then
pcall(syscall.lodetach, loopId)
local msg = tostring(merr)
if msg:find("EPERM") then msg = "Permission denied"
elseif msg:find("EBUSY") then msg = "'"..dest.."' is already a mount point"
elseif msg:find("ENODEV") then msg = "loop device not found (internal error)"
end
print(name..": mount: "..msg); syscall.exit(1); return
end
syscall.devctl(1, "sfgc", 0x00FFFF)
print(name..": "..loopId.." mounted at "..dest)
syscall.devctl(1, "sfgc", 0xFFFFFF)
return
end
if #args == 2 then
local loopId = args[1]
local dest = args[2]
if dest:sub(1,1) ~= "/" then dest = syscall.getcwd().."/"..dest end
local ok, err = pcall(syscall.mount, dest, loopId)
if not ok then
local msg = tostring(err)
if msg:find("EPERM") then msg = "Permission denied"
elseif msg:find("ENODEV") then msg = "'"..loopId.."': no such device - use losetup first"
elseif msg:find("EBUSY") then msg = "'"..dest.."' is already a mount point"
elseif msg:find("EINVAL") then msg = "invalid arguments"
end
print(name..": "..msg); syscall.exit(1); return
end
syscall.devctl(1, "sfgc", 0x00FFFF)
print(name..": "..loopId.." mounted at "..dest)
syscall.devctl(1, "sfgc", 0xFFFFFF)
return
end
print(name..": wrong number of arguments")
print("try '"..name.." --help' for more information.")
syscall.exit(1)

View File

@@ -0,0 +1,80 @@
--:Minify:--
-- passwd: change a user's password
-- Usage: passwd [username] (default: current user)
local args = {...}
local targetName = args[1]
local currentUid = syscall.getuid()
local targetUid
if targetName then
targetUid = syscall.getuid()
if not targetUid then
print("passwd: user '" .. targetName .. "' does not exist")
syscall.exit(1); return
end
-- Only root can change another user's password
if currentUid ~= 0 and targetUid ~= currentUid then
print("passwd: permission denied")
syscall.exit(1); return
end
else
targetUid = currentUid
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
end
-- Non-root must verify their current password first
if currentUid ~= 0 then
printInline("Current password: ")
local cur = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
else cur=cur..ch; syscall.write(1,"*") end
end
local ok, err = syscall.login(targetUid, cur)
if not ok then
sleep(1)
print("passwd: authentication failure")
syscall.exit(1); return
end
end
printInline("New password: ")
local pw1 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
else pw1=pw1..ch; syscall.write(1,"*") end
end
printInline("Confirm password: ")
local pw2 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
else pw2=pw2..ch; syscall.write(1,"*") end
end
if pw1 ~= pw2 then
print("passwd: passwords do not match")
syscall.exit(1); return
end
local ok, err = syscall.setpassword(targetUid, pw1)
if not ok then
print("passwd: " .. tostring(err))
syscall.exit(1); return
end
print("passwd: password updated for '" .. targetName .. "'")

65
Src/coreutils/data/bin/su Normal file
View File

@@ -0,0 +1,65 @@
--:Minify:--
local targetUser = ({ ... })[1]
local currentUid = syscall.getuid()
if syscall.geteuid()~=0 then
syscall.exec("/bin/su", {...})
end
local targetUid
if targetUser then
targetUid = syscall.getuidbyname(targetUser)
else
targetUid = 0
end
if not targetUid then
print("su: user '" .. targetUser .. "' does not exist")
syscall.exit(1)
return
end
if currentUid ~= 0 then
printInline("Password: ")
local pw = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then
syscall.write(1, "\n")
break
elseif ch == "\b" then
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
else
pw = pw .. ch; syscall.write(1, "*")
end
end
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
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
homedir = "/"
syscall.chdir(homedir)
end
syscall.setEnviron("HOME", homedir)
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

110
Src/coreutils/data/bin/sudo Normal file
View File

@@ -0,0 +1,110 @@
--:Minify:--
local fs = require("fs")
local cmdArgs = {...}
local targetUser = "root"
local i = 1
if cmdArgs[i] == "-u" then
i = i + 1
local uarg = cmdArgs[i] or "root"
local numUid = tonumber(uarg)
if numUid then
local pwent = syscall.getpasswd(numUid)
targetUser = (pwent and pwent.username) or uarg
else
targetUser = uarg
end
i = i + 1
end
local cmd = cmdArgs[i]
if not cmd or cmd == "" then
print("usage: sudo [-u user] <command> [args...]")
syscall.exit(1)
return
end
local restArgs = {}
for j = i + 1, #cmdArgs do restArgs[#restArgs + 1] = cmdArgs[j] end
local currentUid = syscall.getuid()
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
local targetUid = syscall.getuidbyname(targetUser)
if not targetUid then
print("sudo: user '" .. targetUser .. "' does not exist")
syscall.exit(1)
return
end
if currentUid ~= 0 then
printInline("[sudo] password for root: ")
local pw = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then
syscall.write(1, "\n")
break
elseif ch == "\b" then
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
else
pw = pw .. ch
syscall.write(1, "*")
end
end
local ok, err = syscall.login(0, pw)
if not ok then
sleep(1)
print("sudo: Authentication failure")
syscall.exit(1)
return
end
if targetUid ~= currentUid then
syscall.setuid(targetUid)
end
else
if targetUid ~= currentUid then
syscall.setuid(targetUid)
end
end
local cmdPath = ""
if cmd:find("/") then
if fs.exists(cmd) then cmdPath = cmd end
else
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
for _, p in ipairs(paths) do
local full = p .. cmd
if fs.exists(full) then cmdPath = full; break end
end
end
if cmdPath == "" then
print("sudo: command not found: " .. cmd)
syscall.exit(1)
return
end
local text = fs.readAllText(cmdPath)
local program, loadErr = load(text, "@" .. cmdPath)
if not program then
print("sudo: cannot load " .. cmd .. ": " .. tostring(loadErr))
syscall.exit(1)
return
end
local pwent = syscall.getpasswd(targetUid)
if pwent and pwent.homedir then
syscall.setEnviron("HOME", pwent.homedir)
end
syscall.setEnviron("USER", targetUser)
local ok, err = xpcall(program, debug.traceback, table.unpack(restArgs))
if not ok then
print("sudo: " .. cmd .. ": " .. tostring(err))
syscall.exit(1)
end

View File

@@ -0,0 +1,111 @@
--:Minify:--
-- Usage:
-- umount <mountpoint> unmount; auto-detach loop device if one is found
-- umount -l <id> detach loop device without unmounting (force)
-- umount --no-detach <mpt> unmount but leave loop device attached
-- umount --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { l=false, ["no-detach"]=false, help=false }
for _, v in ipairs({...}) do
if v:sub(1,2) == "--" then
local o = v:sub(3)
if opts[o] ~= nil then opts[o] = true
else print(name..": unrecognised option '"..v.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
elseif v:sub(1,1) == "-" then
for i = 2, #v do
local c = v:sub(i,i)
if opts[c] ~= nil then opts[c] = true
else print(name..": invalid option '-"..c.."'")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return end
end
else
table.insert(args, v)
end
end
if opts.help then
print("Usage: "..name.." <mountpoint>")
print(" "..name.." --no-detach <mountpoint>")
print(" "..name.." -l <loopid>")
print("")
print("Unmount a filesystem mounted at <mountpoint>.")
print("")
print(" <mountpoint> unmount and auto-detach any loop device")
print(" --no-detach unmount but keep the loop device attached")
print(" -l <loopid> forcibly detach a loop device (no unmount)")
print("")
print("Requires root.")
return
end
if opts.l then
if #args < 1 then
print(name..": -l requires a loop device id")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
local id = args[1]
local ok, err = pcall(syscall.lodetach, id)
if not ok then
local msg = tostring(err)
if msg:find("EPERM") then msg = "Permission denied"
elseif msg:find("ENXIO") then msg = "no such loop device '"..id.."'"
elseif msg:find("EBUSY") then msg = "'"..id.."' is still mounted - unmount first or omit -l"
end
print(name..": "..msg); syscall.exit(1); return
end
syscall.devctl(1, "sfgc", 0x00FFFF)
print(name..": detached "..id)
syscall.devctl(1, "sfgc", 0xFFFFFF)
return
end
if #args < 1 then
print(name..": missing mount point operand")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
local mpt = args[1]
if mpt:sub(1,1) ~= "/" then mpt = syscall.getcwd().."/"..mpt end
local loopIdToDetach = nil
if not opts["no-detach"] then
local lok, devs = pcall(syscall.lolist)
if lok then
loopIdToDetach = {}
for id in pairs(devs) do
loopIdToDetach[#loopIdToDetach + 1] = id
end
end
end
local ok, err = pcall(syscall.umount, mpt)
if not ok then
local msg = tostring(err)
if msg:find("EPERM") then msg = "Permission denied"
elseif msg:find("EINVAL") then msg = "'"..args[1].."' is not a mount point"
elseif msg:find("EBUSY") then msg = "'"..args[1].."' is busy - close open files first"
end
print(name..": "..msg); syscall.exit(1); return
end
syscall.devctl(1, "sfgc", 0x00FFFF)
print(name..": unmounted "..mpt)
syscall.devctl(1, "sfgc", 0xFFFFFF)
if loopIdToDetach then
for _, id in ipairs(loopIdToDetach) do
local dok = pcall(syscall.lodetach, id)
if dok then
syscall.devctl(1, "sfgc", 0xFF00FF)
print(name..": auto-detached "..id)
syscall.devctl(1, "sfgc", 0xFFFFFF)
end
end
end

View File

@@ -0,0 +1,67 @@
--:Minify:--
local args = {...}
local i = 1
local opt = { createHome = true }
while i <= #args do
local a = args[i]
if a == "-p" then i=i+1; opt.password = args[i]
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
elseif a == "-d" then i=i+1; opt.homedir = args[i]
elseif a == "-s" then i=i+1; opt.shell = args[i]
elseif a == "-M" then opt.createHome = false
elseif a:sub(1,1) ~= "-" then opt.username = a
else print("useradd: unknown option: " .. a); return end
i = i + 1
end
if not opt.username then
print("Usage: useradd [-p password] [-g gid] [-d homedir] [-s shell] [-M] <username>")
syscall.exit(1); return
end
local password = opt.password
if not password then
printInline("New password: ")
password = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #password > 0 then password=password:sub(1,-2); syscall.write(1,"\b \b") end
else password=password..ch; syscall.write(1,"*") end
end
printInline("Confirm password: ")
local pw2 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
else pw2=pw2..ch; syscall.write(1,"*") end
end
if password ~= pw2 then
print("useradd: passwords do not match")
syscall.exit(1); return
end
end
local uid, err = syscall.newuser(
opt.username, password, opt.gid, opt.homedir, opt.shell or "/bin/hysh"
)
if not uid then
print("useradd: " .. tostring(err))
syscall.exit(1); return
end
if opt.createHome then
local home = opt.homedir or ("/home/" .. opt.username)
local ok, e = pcall(syscall.mkdir, home)
if not ok then
print("useradd: warning: could not create home " .. home .. ": " .. tostring(e))
end
end
print("useradd: created user '" .. opt.username .. "' with uid=" .. tostring(uid))

View File

@@ -0,0 +1,49 @@
--:Minify:--
local args = {...}
local removeHome = false
local username = nil
for _, a in ipairs(args) do
if a == "-r" then removeHome = true
elseif a:sub(1,1) ~= "-" then username = a
else print("userdel: unknown option: " .. a); syscall.exit(1); return end
end
if not username then
print("Usage: userdel [-r] <username>")
syscall.exit(1); return
end
local uid = syscall.getuid(username)
if not uid then
print("userdel: user '" .. username .. "' does not exist")
syscall.exit(1); return
end
local pwent = syscall.getpasswd(uid)
local ok, err = syscall.deleteuser(uid)
if not ok then
print("userdel: " .. tostring(err))
syscall.exit(1); return
end
if removeHome and pwent and pwent.homedir then
local fs = require("fs")
local ok2, err2 = pcall(function()
local function rmdir(path)
for _, f in ipairs(fs.list(path) or {}) do
local full = path .. "/" .. f
if fs.isDir(full) then rmdir(full)
else syscall.remove(full) end
end
syscall.remove(path)
end
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
end)
if not ok2 then
print("userdel: warning: could not remove home: " .. tostring(err2))
end
end
print("userdel: deleted user '" .. username .. "'")

View File

@@ -0,0 +1,49 @@
--:Minify:--
local args = {...}
local i = 1
local opt = {}
while i <= #args do
local a = args[i]
if a == "-l" then i=i+1; opt.newname = args[i]
elseif a == "-p" then i=i+1; opt.password = args[i]
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
elseif a == "-d" then i=i+1; opt.homedir = args[i]
elseif a == "-s" then i=i+1; opt.shell = args[i]
elseif a == "-L" then opt.lock = true
elseif a == "-U" then opt.unlock = true
elseif a:sub(1,1) ~= "-" then opt.username = a
else print("usermod: unknown option: " .. a); syscall.exit(1); return end
i = i + 1
end
if not opt.username then
print("Usage: usermod [-l newname] [-p password] [-g gid] [-d homedir] [-s shell] [-L] [-U] <username>")
syscall.exit(1); return
end
if opt.lock and opt.unlock then
print("usermod: -L and -U are mutually exclusive")
syscall.exit(1); return
end
local uid = syscall.getuid(opt.username)
if not uid then
print("usermod: user '" .. opt.username .. "' does not exist")
syscall.exit(1); return
end
local function apply(fn, ...)
local ok, err = fn(...)
if not ok then print("usermod: " .. tostring(err)); syscall.exit(1) end
end
if opt.newname then apply(syscall.setusername, uid, opt.newname) end
if opt.password then apply(syscall.setpassword, uid, opt.password) end
if opt.gid then apply(syscall.setgid, uid, opt.gid) end
if opt.homedir then apply(syscall.sethomedir, uid, opt.homedir) end
if opt.shell then apply(syscall.setshell, uid, opt.shell) end
if opt.lock then apply(syscall.lockuser, uid) end
if opt.unlock then apply(syscall.unlockuser, uid) end
print("usermod: updated user '" .. opt.username .. "'")

7085
Build/lib/LibDeflate → Src/deflate/data/lib/LibDeflate Executable file → Normal file

File diff suppressed because it is too large Load Diff

16
Build/lib/deflate → Src/deflate/data/lib/deflate Executable file → Normal file
View File

@@ -1,9 +1,9 @@
local deflate=require("LibDeflate")
local lib={}
lib.compress=function(data)
return deflate:CompressDeflate(data)
end
lib.decompress=function(data)
return deflate:DecompressDeflate(data)
end
local deflate=require("LibDeflate")
local lib={}
lib.compress=function(data)
return deflate:CompressDeflate(data)
end
lib.decompress=function(data)
return deflate:DecompressDeflate(data)
end
return lib

View File

@@ -0,0 +1,3 @@
{
"/bin/sh": "/bin/hysh"
}

162
Src/hysh/data/bin/chattr Normal file
View File

@@ -0,0 +1,162 @@
--:Minify:--
-- supports +i/-i (immutable) stored in the file's cmeta/xattr field
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { R = false, help = false }
local args = {}
local modeStr = nil
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" and not v:match("^%-[%+%-]") then
local isFlag = true
for i = 2, #v do
local c = v:sub(i,i)
if cloptions[c] ~= nil then
cloptions[c] = true
else
isFlag = false; break
end
end
if not isFlag then
modeStr = v
end
elseif v:sub(1,1) == "+" or (v:sub(1,1) == "-" and v:match("^%-[a-zA-Z]")) then
modeStr = v
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... +-= ATTRS FILE...")
print("Change file attributes on a filesystem.")
print("")
print("Attributes:")
print(" i immutable: file cannot be modified, renamed, or deleted")
print(" a append-only: file can only be appended to")
print("")
print("Operators: +attr add, -attr remove")
print("Example: " .. name .. " +i /etc/passwd")
print("")
print("Options:")
print(" -R operate on files and directories recursively")
print(" --help display this help and exit")
return
end
if not modeStr or #args < 1 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local op = modeStr:sub(1, 1)
local attrs = modeStr:sub(2)
if op ~= "+" and op ~= "-" then
print(name .. ": invalid operator '" .. op .. "' (use + or -)")
syscall.exit(1); return
end
local function getXattr(path)
local stat = pcall(function() return syscall.stat(path) end) and syscall.stat(path)
if stat then return stat.xattr or "" end
return ""
end
local IMMUTABLE_TAG = "|i"
local APPENDONLY_TAG = "|a"
local function attrTag(c)
if c == "i" then return IMMUTABLE_TAG
elseif c == "a" then return APPENDONLY_TAG
else return nil end
end
local function chattrPath(path)
local stat = syscall.stat(path)
if not stat then
print(name .. ": cannot stat '" .. path .. "': No such file or directory")
return false
end
if stat.etype == 0x01 then
return true
end
if not syscall.setxattr then
print(name .. ": kernel does not expose setxattr syscall; cannot modify attributes")
syscall.exit(1); return false
end
local xattr = stat.xattr or ""
for i = 1, #attrs do
local c = attrs:sub(i, i)
local tag = attrTag(c)
if not tag then
print(name .. ": unsupported attribute '" .. c .. "'")
syscall.exit(1); return false
end
local hasTag = xattr:find(tag, 1, true)
if op == "+" and not hasTag then
xattr = xattr .. tag
elseif op == "-" and hasTag then
xattr = xattr:gsub(tag:gsub("|", "%%|"), "")
end
end
local ok, err = pcall(syscall.setxattr, path, xattr)
if not ok then
print(name .. ": cannot set attributes on '" .. path .. "': " .. tostring(err))
return false
end
return true
end
local function chattrRecursive(path)
if not chattrPath(path) then return end
if syscall.type(path) == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, entry in ipairs(list) do
local child = path
if child:sub(-1) ~= "/" then child = child .. "/" end
chattrRecursive(child .. entry)
end
end
end
end
local cwd = syscall.getcwd()
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
return p
end
if not syscall.setxattr then
print(name .. ": kernel does not expose setxattr; attributes cannot be persisted")
print(name .. ": add sys[\"setxattr\"] = vfs.setxattr to 10_vfs.kmod to enable this")
syscall.exit(1); return
end
local exitCode = 0
for i = 1, #args do
local path = absPath(args[i])
if not syscall.exists(path) then
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
exitCode = 1
elseif cloptions.R then
chattrRecursive(path)
else
if not chattrPath(path) then exitCode = 1 end
end
end
syscall.exit(exitCode)

117
Src/hysh/data/bin/chgrp Normal file
View File

@@ -0,0 +1,117 @@
--:Minify:--
-- chgrp: change group ownership
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { R = false, help = false }
local args = {}
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" then
for i = 2, #v do
local opt = v:sub(i, i)
if cloptions[opt] == nil then
print(name .. ": invalid option '-" .. opt .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... GROUP FILE...")
print("Change the group of each FILE to GROUP.")
print("GROUP may be a group name or numeric ID.")
print("")
print("Options:")
print(" -R operate on files and directories recursively")
print(" --help display this help and exit")
return
end
if #args < 2 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local groupStr = args[1]
local function resolveGid(s)
local n = tonumber(s)
if n then return n end
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
if uid then
local pwent = syscall.getpasswd(uid)
if pwent then return pwent.gid end
end
print(name .. ": invalid group: '" .. s .. "'")
syscall.exit(1)
end
local newGid = resolveGid(groupStr)
local function chgrpPath(path)
local stat = syscall.stat(path)
if not stat then
print(name .. ": cannot stat '" .. path .. "': no such file or directory")
return false
end
local ok, err = pcall(syscall.chown, path, stat.owner, newGid)
if not ok then
local msg = tostring(err)
if msg:find("EPERM") or msg:find("EACCES") then
msg = "operation not permitted (must be root)"
elseif msg:find("ENOENT") then
msg = "no such file or directory"
end
print(name .. ": cannot change group of '" .. path .. "': " .. msg)
return false
end
return true
end
local function chgrpRecursive(path)
if not chgrpPath(path) then return end
if syscall.type(path) == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, entry in ipairs(list) do
local child = path
if child:sub(-1) ~= "/" then child = child .. "/" end
chgrpRecursive(child .. entry)
end
end
end
end
local cwd = syscall.getcwd()
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
return p
end
local exitCode = 0
for i = 2, #args do
local path = absPath(args[i])
if not syscall.exists(path) then
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
exitCode = 1
elseif cloptions.R then
chgrpRecursive(path)
else
if not chgrpPath(path) then exitCode = 1 end
end
end
syscall.exit(exitCode)

268
Src/hysh/data/bin/chmod Normal file
View File

@@ -0,0 +1,268 @@
--:Minify:--
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { R = false, help = false }
local args = {}
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" then
for i = 2, #v do
local opt = v:sub(i, i)
if cloptions[opt] == nil then
print(name .. ": invalid option '-" .. opt .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... MODE FILE...")
print("Change the file mode bits of each FILE to MODE.")
print("")
print("MODE may be octal (e.g. 755) or symbolic (e.g. u+x, go-w, a=r).")
print("")
print("Octal bit layout (Hyperion):")
print(" owner: r=32 w=16 x=512 group: r=8 w=4 x=256")
print(" world: r=2 w=1 x=128 suid=64")
print(" Common: 644=rw-r--r-- 755=rwxr-xr-x 700=rwx------")
print("")
print("Symbolic: [ugoa][+-=][rwxs] (comma-separated list)")
print("")
print("Options:")
print(" -R change files and directories recursively")
print(" --help display this help and exit")
return
end
if #args < 2 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local modeArg = args[1]
local P = {
OWNER_R = 32, OWNER_W = 16, OWNER_X = 512,
GROUP_R = 8, GROUP_W = 4, GROUP_X = 256,
WORLD_R = 2, WORLD_W = 1, WORLD_X = 128,
SUID = 64,
}
local function bit_is_set(num, bit)
return math.floor(num / (2 ^ bit)) % 2 == 1
end
local function parseOctal(s)
local n = tonumber(s, 8)
if not n then return nil end
local result = 0
if bit_is_set(n, 8) then result = result + P.OWNER_R end -- 0400
if bit_is_set(n, 7) then result = result + P.OWNER_W end -- 0200
if bit_is_set(n, 6) then result = result + P.OWNER_X end -- 0100
if bit_is_set(n, 5) then result = result + P.GROUP_R end -- 040
if bit_is_set(n, 4) then result = result + P.GROUP_W end -- 020
if bit_is_set(n, 3) then result = result + P.GROUP_X end -- 010
if bit_is_set(n, 2) then result = result + P.WORLD_R end -- 004
if bit_is_set(n, 1) then result = result + P.WORLD_W end -- 002
if bit_is_set(n, 0) then result = result + P.WORLD_X end -- 001
if bit_is_set(n, 11) then result = result + P.SUID end
return result
end
local function applySymbolic(modeStr, existingPerms)
local perms = existingPerms
for clause in (modeStr .. ","):gmatch("([^,]+),") do
local who_str, rest = clause:match("^([ugoa]*)([+%-=].+)$")
if not who_str then
print(name .. ": invalid mode: '" .. clause .. "'")
syscall.exit(1); return nil
end
if who_str == "" or who_str == "a" then who_str = "ugo" end
local op = rest:sub(1, 1)
local bits_str = rest:sub(2)
local mask = 0
for i = 1, #bits_str do
local c = bits_str:sub(i, i)
for j = 1, #who_str do
local w = who_str:sub(j, j)
if c == "r" then
if w == "u" then mask = mask + P.OWNER_R
elseif w == "g" then mask = mask + P.GROUP_R
elseif w == "o" then mask = mask + P.WORLD_R end
elseif c == "w" then
if w == "u" then mask = mask + P.OWNER_W
elseif w == "g" then mask = mask + P.GROUP_W
elseif w == "o" then mask = mask + P.WORLD_W end
elseif c == "x" then
if w == "u" then mask = mask + P.OWNER_X
elseif w == "g" then mask = mask + P.GROUP_X
elseif w == "o" then mask = mask + P.WORLD_X end
elseif c == "s" then
if w == "u" then mask = mask + P.SUID end
end
end
end
if op == "+" then
perms = perms + (mask - (perms % (mask + 1) - perms % mask > 0 and 0 or 0))
perms = perms - (perms % 1)
local function bor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) or (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
perms = bor(perms, mask)
elseif op == "-" then
local function band(a, b)
local result, bit = 0, 1
while a > 0 and b > 0 do
if (a % 2 == 1) and (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
local function bxor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) ~= (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
perms = bxor(perms, band(perms, mask))
elseif op == "=" then
local clearMask = 0
for j = 1, #who_str do
local w = who_str:sub(j, j)
if w == "u" then clearMask = clearMask + P.OWNER_R + P.OWNER_W + P.OWNER_X + P.SUID
elseif w == "g" then clearMask = clearMask + P.GROUP_R + P.GROUP_W + P.GROUP_X
elseif w == "o" then clearMask = clearMask + P.WORLD_R + P.WORLD_W + P.WORLD_X end
end
local function bxor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) ~= (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
local function band(a, b)
local result, bit = 0, 1
while a > 0 and b > 0 do
if (a % 2 == 1) and (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
local function bor(a, b)
local result, bit = 0, 1
while a > 0 or b > 0 do
if (a % 2 == 1) or (b % 2 == 1) then result = result + bit end
a = math.floor(a / 2); b = math.floor(b / 2); bit = bit * 2
end
return result
end
perms = bxor(perms, band(perms, clearMask))
perms = bor(perms, mask)
else
print(name .. ": invalid operator in mode: '" .. clause .. "'")
syscall.exit(1); return nil
end
end
return perms
end
local function resolveMode(modeStr, existingPerms)
if modeStr:match("^[0-7]+$") then
local p = parseOctal(modeStr)
if p then return p end
end
return applySymbolic(modeStr, existingPerms)
end
local function chmodPath(path)
local stat, err = pcall(syscall.stat, path)
local existingPerms = 0
if stat then
local s = syscall.stat(path)
existingPerms = s and s.perms or 0
end
local newPerms = resolveMode(modeArg, existingPerms)
if newPerms == nil then return false end
local ok, cerr = pcall(syscall.chmod, path, newPerms)
if not ok then
local msg = tostring(cerr)
if msg:find("EACCES") or msg:find("EPERM") then
msg = "permission denied"
elseif msg:find("ENOENT") then
msg = "no such file or directory"
end
print(name .. ": cannot change permissions of '" .. path .. "': " .. msg)
return false
end
return true
end
local function chmodRecursive(path)
if not chmodPath(path) then return end
if syscall.type(path) == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, entry in ipairs(list) do
local child = path
if child:sub(-1) ~= "/" then child = child .. "/" end
chmodRecursive(child .. entry)
end
end
end
end
local cwd = syscall.getcwd()
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
return p
end
local exitCode = 0
for i = 2, #args do
local path = absPath(args[i])
if not syscall.exists(path) then
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
exitCode = 1
elseif cloptions.R then
chmodRecursive(path)
else
if not chmodPath(path) then exitCode = 1 end
end
end
syscall.exit(exitCode)

150
Src/hysh/data/bin/chown Normal file
View File

@@ -0,0 +1,150 @@
--:Minify:--
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { R = false, help = false }
local args = {}
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if cloptions[opt] == nil then
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
elseif v:sub(1, 1) == "-" then
for i = 2, #v do
local opt = v:sub(i, i)
if cloptions[opt] == nil then
print(name .. ": invalid option '-" .. opt .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... OWNER[:GROUP] FILE...")
print(" " .. name .. " [OPTION]... :GROUP FILE...")
print("Change the owner and/or group of each FILE.")
print("OWNER and GROUP may be names or numeric IDs.")
print("")
print("Options:")
print(" -R operate on files and directories recursively")
print(" --help display this help and exit")
return
end
if #args < 2 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local spec = args[1]
local ownerStr, groupStr
if spec:sub(1,1) == ":" then
groupStr = spec:sub(2)
else
local colon = spec:find(":", 1, true)
if colon then
ownerStr = spec:sub(1, colon - 1)
groupStr = spec:sub(colon + 1)
if groupStr == "" then groupStr = nil end
else
ownerStr = spec
end
end
local function resolveUid(s)
if not s or s == "" then return nil end
local n = tonumber(s)
if n then return n end
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
if uid then return uid end
print(name .. ": invalid user: '" .. s .. "'")
syscall.exit(1)
end
local function resolveGid(s)
if not s or s == "" then return nil end
local n = tonumber(s)
if n then return n end
local uid = syscall.getuidbyname and syscall.getuidbyname(s)
if uid then
local pwent = syscall.getpasswd(uid)
if pwent then return pwent.gid end
end
print(name .. ": invalid group: '" .. s .. "'")
syscall.exit(1)
end
local newUid = resolveUid(ownerStr)
local newGid = resolveGid(groupStr)
if newUid == nil and newGid == nil then
print(name .. ": no owner or group specified")
syscall.exit(1); return
end
local function chownPath(path)
local stat = syscall.stat(path)
if not stat then
print(name .. ": cannot stat '" .. path .. "': no such file or directory")
return false
end
local uid = newUid ~= nil and newUid or stat.owner
local gid = newGid ~= nil and newGid or stat.group
local ok, err = pcall(syscall.chown, path, uid, gid)
if not ok then
local msg = tostring(err)
if msg:find("EPERM") or msg:find("EACCES") then
msg = "operation not permitted (must be root)"
elseif msg:find("ENOENT") then
msg = "no such file or directory"
end
print(name .. ": cannot change owner of '" .. path .. "': " .. msg)
return false
end
return true
end
local function chownRecursive(path)
if not chownPath(path) then return end
if syscall.type(path) == "directory" then
local ok, list = pcall(syscall.listdir, path)
if ok then
for _, entry in ipairs(list) do
local child = path
if child:sub(-1) ~= "/" then child = child .. "/" end
chownRecursive(child .. entry)
end
end
end
end
local cwd = syscall.getcwd()
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd .. "/" .. p end
return p
end
local exitCode = 0
for i = 2, #args do
local path = absPath(args[i])
if not syscall.exists(path) then
print(name .. ": cannot access '" .. args[i] .. "': No such file or directory")
exitCode = 1
elseif cloptions.R then
chownRecursive(path)
else
if not chownPath(path) then exitCode = 1 end
end
end
syscall.exit(exitCode)

83
Src/hysh/data/bin/chroot Normal file
View File

@@ -0,0 +1,83 @@
--:Minify:--
local name = syscall.getTask(syscall.getpid()).name
local args = {}
local cloptions = { help = false }
for _, v in ipairs({ ... }) do
if v:sub(1, 2) == "--" then
local opt = v:sub(3)
if opt == "help" then
cloptions.help = true
else
print(name .. ": unrecognized option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
elseif v:sub(1, 1) == "-" then
print(name .. ": invalid option '" .. v .. "'")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " NEWROOT [COMMAND [ARG]...]")
print("Run COMMAND with root directory set to NEWROOT.")
print("If COMMAND is omitted, runs the current user's shell.")
print("")
print("Requires root (uid 0).")
return
end
if #args < 1 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local euid = syscall.geteuid and syscall.geteuid() or syscall.getuid()
if euid ~= 0 then
print(name .. ": cannot change root directory: Permission denied")
syscall.exit(1); return
end
local newRoot = args[1]
if newRoot:sub(1,1) ~= "/" then
newRoot = syscall.getcwd() .. "/" .. newRoot
end
if not syscall.exists(newRoot) then
print(name .. ": cannot change root directory to '" .. args[1] .. "': No such file or directory")
syscall.exit(1); return
end
if syscall.type(newRoot) ~= "directory" then
print(name .. ": '" .. args[1] .. "': Not a directory")
syscall.exit(1); return
end
local ok, err = pcall(syscall.chroot, newRoot)
if not ok then
print(name .. ": cannot change root directory to '" .. args[1] .. "': " .. tostring(err))
syscall.exit(1); return
end
local shell
if #args >= 2 then
shell = args[2]
else
local uid = syscall.getuid()
local pwent = syscall.getpasswd(uid)
shell = (pwent and pwent.shell) or "/bin/hysh"
end
local execArgs = {}
for i = 3, #args do table.insert(execArgs, args[i]) end
local execOk, execErr = pcall(syscall.exec, shell, execArgs)
if not execOk then
print(name .. ": failed to run command '" .. shell .. "': " .. tostring(execErr))
syscall.exit(127)
end

310
Src/hysh/data/bin/help Normal file
View File

@@ -0,0 +1,310 @@
--:Minify:--
local COMMANDS = {
{ name="cd", usage="cd [dir]", desc="Change working directory. Use '-' to return to previous directory.", flags={} },
{ name="pwd", usage="pwd", desc="Print current working directory.", flags={} },
{ name="ls", usage="ls [-alh] [dir]", desc="List directory contents. Coloured by type: dirs=blue, symlinks=cyan, executables=green.", flags={
{"-a","Show hidden files (starting with .)"},
{"-l","Long format: permissions, owner, group, size, mtime, name"},
{"-h","Human-readable file sizes (with -l)"},
{"--help","Display help and exit"},
}},
{ name="find", usage="find [path] [-name PAT] [-type f|d|l] [-maxdepth N]", desc="Walk the filesystem tree and print matching paths.", flags={
{"-name PAT", "Match filename against shell glob (* and ?)"},
{"-type f|d|l","Filter by file, directory, or symlink"},
{"-maxdepth N","Descend at most N directory levels"},
{"-mindepth N","Skip entries shallower than N levels"},
{"-empty", "Match empty files or empty directories"},
}},
{ name="cp", usage="cp [-rRp] SOURCE... DEST", desc="Copy files or directories.", flags={
{"-r,-R","Recurse into directories"},
{"-p", "Preserve permissions"},
{"--help","Display help and exit"},
}},
{ name="mv", usage="mv [-f] SOURCE... DEST", desc="Move or rename files and directories.", flags={
{"-f", "Do not prompt before overwriting (default)"},
{"--help","Display help and exit"},
}},
{ name="rm", usage="rm [-rRf] FILE...", desc="Remove files or directories.", flags={
{"-r,-R","Recursively remove directories and their contents"},
{"-f", "Ignore nonexistent files, never prompt"},
{"--help","Display help and exit"},
}},
{ name="touch", usage="touch FILE...", desc="Create an empty file, or no-op if it already exists.", flags={} },
{ name="mkdir", usage="mkdir <dir>", desc="Create a directory.", flags={} },
{ name="ln", usage="ln -s [-f] TARGET LINK", desc="Create a symbolic link. Multiple targets can be linked into a directory.", flags={
{"-s", "Create a symbolic link (required; hard links not supported)"},
{"-f", "Remove existing destination before creating link"},
{"--help","Display help and exit"},
}},
{ name="cat", usage="cat [file...]", desc="Print file(s) to stdout. Reads stdin if no file given.", flags={} },
{ name="head", usage="head [-n N] [file...]", desc="Print the first N lines of each file (default 10).", flags={
{"-n N","Number of lines to print"},
{"--help","Display help and exit"},
}},
{ name="tail", usage="tail [-n N] [file...]", desc="Print the last N lines of each file (default 10).", flags={
{"-n N","Number of lines to print"},
{"--help","Display help and exit"},
}},
{ name="wc", usage="wc [-lwc] [file...]", desc="Count lines, words, and bytes in files.", flags={
{"-l","Print line count"},
{"-w","Print word count"},
{"-c","Print byte count"},
{"--help","Display help and exit"},
}},
{ name="grep", usage="grep [-ivnlcrR] PATTERN [file...]", desc="Search for lines matching a Lua pattern.", flags={
{"-i","Ignore case"},
{"-v","Invert: select non-matching lines"},
{"-n","Prefix output with line numbers"},
{"-l","Print only filenames that contain a match"},
{"-c","Print count of matching lines per file"},
{"-r,-R","Recurse into directories"},
{"--help","Display help and exit"},
}},
{ name="sed", usage="sed 's/PAT/REPL/' [file...]", desc="Stream editor. Applies substitution commands to each line.", flags={} },
{ name="sort", usage="sort [-rnu] [file...]", desc="Sort lines of text.", flags={
{"-r","Reverse the sort order"},
{"-n","Numeric sort"},
{"-u","Suppress duplicate lines"},
{"--help","Display help and exit"},
}},
{ name="uniq", usage="uniq [-cdui] [input [output]]", desc="Filter adjacent duplicate lines.", flags={
{"-c","Prefix each line with its repetition count"},
{"-d","Print only lines that appear more than once"},
{"-u","Print only lines that appear exactly once"},
{"-i","Ignore case when comparing"},
{"--help","Display help and exit"},
}},
{ name="tee", usage="tee [-a] [file...]", desc="Copy stdin to stdout and to each FILE simultaneously.", flags={
{"-a","Append to files instead of overwriting"},
{"--help","Display help and exit"},
}},
{ name="basename", usage="basename STRING [SUFFIX]", desc="Strip directory and optional suffix from a path.", flags={} },
{ name="dirname", usage="dirname STRING...", desc="Strip the last component from a path.", flags={} },
{ name="readlink", usage="readlink [-fenq] file...", desc="Print the target of a symbolic link.", flags={
{"-f","Canonicalize: follow every symlink component"},
{"-e","Like -f but all components must exist"},
{"-n","Do not output trailing newline"},
{"--help","Display help and exit"},
}},
{ name="stat", usage="stat file...", desc="Display file type, size, owner, group, and permissions.", flags={
{"--help","Display help and exit"},
}},
{ name="chmod", usage="chmod [-R] MODE file...", desc="Change file permissions. MODE may be octal (755) or symbolic (u+x).", flags={
{"-R", "Recurse into directories"},
{"--help","Display help and exit"},
}},
{ name="chown", usage="chown [-R] USER[:GROUP] file...", desc="Change file owner and/or group.", flags={
{"-R","Recurse into directories"},
{"--help","Display help and exit"},
}},
{ name="chgrp", usage="chgrp [-R] GROUP file...", desc="Change file group ownership.", flags={
{"-R","Recurse into directories"},
{"--help","Display help and exit"},
}},
{ name="chattr", usage="chattr [+-=][attrs] file...", desc="Change file attributes.", flags={} },
{ name="echo", usage="echo [text...]", desc="Print arguments to stdout.", flags={} },
{ name="whoami", usage="whoami", desc="Print the current username.", flags={} },
{ name="id", usage="id [username]", desc="Print user identity (uid, gid).", flags={} },
{ name="ps", usage="ps", desc="List running tasks with pid, user, name, and status.", flags={} },
{ name="hyperctl", usage="hyperctl <command> [service]", desc="Control system services (start, stop, restart, status, list, enable, disable).", flags={} },
{ name="hostname", usage="hostname [NAME]", desc="Print or set the system hostname.", flags={} },
{ name="uname", usage="uname [-asnrm]", desc="Print system information (OS name, hostname, release, machine).", flags={
{"-a","Print all fields"},
{"-s","Kernel name"},
{"-n","Node hostname"},
{"-r","Kernel release"},
{"-m","Machine hardware name"},
{"--help","Display help and exit"},
}},
{ name="df", usage="df [-h] [path...]", desc="Report filesystem disk space usage.", flags={
{"-h","Human-readable sizes (K, M, G)"},
{"--help","Display help and exit"},
}},
{ name="stat", usage="stat file...", desc="Display file status: type, size, permissions, owner.", flags={} },
{ name="env", usage="env [KEY=VAL]... [CMD]", desc="Print the environment, or run a command with modified environment.", flags={} },
{ name="printenv", usage="printenv [NAME...]", desc="Print environment variable values (all if no names given).", flags={} },
{ name="sleep", usage="sleep N[smhd]", desc="Pause for N seconds (or minutes/hours/days with m/h/d suffix).", flags={} },
{ name="true", usage="true", desc="Do nothing, exit successfully (status 0).", flags={} },
{ name="false", usage="false", desc="Do nothing, exit unsuccessfully (status 1).", flags={} },
{ name="yes", usage="yes [text]", desc="Repeatedly print 'y' (or given text) until interrupted.", flags={} },
{ name="mount", usage="mount [-o loop] [SRC DEST | ID MNT]", desc="Mount a loop device or show all current mounts.", flags={
{"-o loop","Attach SRC as a loop device and mount at DEST in one step"},
{"--help","Display help and exit"},
}},
{ name="umount", usage="umount [--no-detach] MOUNTPOINT | -l LOOPID", desc="Unmount a filesystem and auto-detach its loop device.", flags={
{"--no-detach","Unmount but keep loop device attached"},
{"-l LOOPID","Force-detach a loop device without unmounting"},
{"--help","Display help and exit"},
}},
{ name="losetup", usage="losetup [-dil] [path]", desc="Attach a directory or .hfs image as a loop device.", flags={
{"-d ID","Detach loop device"},
{"-i path","Force image mode (even without .hfs extension)"},
{"-l","List all attached loop devices"},
{"--help","Display help and exit"},
}},
{ name="loimgcreate", usage="loimgcreate [-x] SRC DEST", desc="Pack a directory into a portable HFS image, or extract one.", flags={
{"-x","Extract image to destination directory"},
{"--help","Display help and exit"},
}},
{ name="useradd", usage="useradd [-p pw] [-g gid] [-d home] [-s shell] [-M] <user>", desc="Create a new user account.", flags={
{"-p pw", "Set password"},
{"-g gid", "Set primary group id"},
{"-d home", "Set home directory (default /home/username)"},
{"-s shell","Set login shell (default /bin/hysh)"},
{"-M", "Do not create home directory"},
}},
{ name="userdel", usage="userdel [-r] <user>", desc="Delete a user account.", flags={
{"-r","Also remove the user's home directory"},
}},
{ name="usermod", usage="usermod [-l name] [-p pw] [-g gid] [-d home] [-s shell] [-LU] <user>", desc="Modify an existing user account.", flags={
{"-l name", "Rename the user"},
{"-p pw", "Set new password"},
{"-g gid", "Change primary group id"},
{"-d home", "Change home directory"},
{"-s shell","Change login shell"},
{"-L", "Lock the account"},
{"-U", "Unlock the account"},
}},
{ name="passwd", usage="passwd [username]", desc="Change a user password.", flags={} },
{ name="lsusers", usage="lsusers", desc="List all user accounts with uid, gid, home, and shell.", flags={} },
{ name="su", usage="su [username]", desc="Switch user. Defaults to root. Root can switch without a password.", flags={} },
{ name="sudo", usage="sudo [-u user] CMD [args...]", desc="Run a command as another user (default root).", flags={
{"-u user","Run as the specified user (name or uid)"},
}},
{ name="exit", usage="exit [N]", desc="Exit the shell with optional status code N.", flags={} },
{ name="clear", usage="clear", desc="Clear the terminal screen.", flags={} },
{ name="help", usage="help [command]", desc="Display this command reference. Pass a command name to filter.", flags={} },
{ name="lua", usage="lua", desc="Interactive Lua REPL prompt.", flags={} },
{ name="micro", usage="micro [file]", desc="Full-screen terminal text editor.", flags={} },
{ name="hfetch", usage="hfetch", desc="Display system information in a neofetch-style layout.", flags={} },
{ name="sysdump", usage="sysdump", desc="List all registered kernel syscalls.", flags={} },
{ name="chroot", usage="chroot DIR [CMD]", desc="Run a command with a different root directory.", flags={} },
}
do
local seen = {}
local deduped = {}
for _, cmd in ipairs(COMMANDS) do
if not seen[cmd.name] then
seen[cmd.name] = true
table.insert(deduped, cmd)
end
end
COMMANDS = deduped
end
local C_HEAD = 7
local C_CMD = 5
local C_USAGE = 1
local C_DESC = 13
local C_FLAG = 3
local C_DIM = 12
local lines = {}
local function push(text, col) lines[#lines+1] = {text, col or 1} end
push("HyperionOS Command Reference", C_HEAD)
push(string.rep("=", 50), C_DIM)
push("", 1)
local args = {...}
local filter = args[1]
local function addCmd(cmd)
push(cmd.name, C_CMD)
push(" Usage: " .. cmd.usage, C_USAGE)
push(" " .. cmd.desc, C_DESC)
if #cmd.flags > 0 then
for _, f in ipairs(cmd.flags) do
push(" " .. f[1], C_FLAG)
push(" " .. f[2], C_DESC)
end
end
push("", 1)
end
if filter then
local found = false
for _, cmd in ipairs(COMMANDS) do
if cmd.name == filter then addCmd(cmd); found = true; break end
end
if not found then
push("help: unknown command '" .. filter .. "'", 2)
push("Run 'help' with no arguments for the full list.", C_DESC)
end
else
push("Run 'help <command>' for details on a specific command.", C_DESC)
push("", 1)
for _, cmd in ipairs(COMMANDS) do addCmd(cmd) end
end
local sizeStr = syscall.devctl(1, "size")
local screenW = tonumber(sizeStr:match("^(%d+)")) or 51
local screenH = tonumber(sizeStr:match(";(%d+)")) or 19
local pageSize = screenH - 2
local scroll = 0
local totalLines = #lines
local dirty = true
local function render()
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
for row = 1, pageSize do
local li = scroll + row
if li <= totalLines then
local text, col = lines[li][1], lines[li][2]
syscall.devctl(1, "sfgc", col)
if #text > screenW then text = text:sub(1, screenW) end
syscall.write(1, text .. "\n")
else
syscall.write(1, "\n")
end
end
syscall.devctl(1, "sfgc", 0x000000)
syscall.devctl(1, "sbgc", 0xDBDBDB)
local pct = math.floor(math.min(100, (scroll + pageSize) / totalLines * 100))
local status = string.format(" help -- line %d/%d (%d%%) [up/down: scroll q: quit] ",
scroll + 1, totalLines, pct)
if #status > screenW then status = status:sub(1, screenW) end
syscall.devctl(1, "spos", 1, screenH)
syscall.write(1, status .. string.rep(" ", screenW - #status))
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.devctl(1, "sbgc", 0x000000)
dirty = false
end
if totalLines <= pageSize then
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
for _, line in ipairs(lines) do
syscall.devctl(1, "sfgc", line[2])
syscall.write(1, line[1] .. "\n")
end
syscall.devctl(1, "sfgc", 0xFFFFFF)
return
end
render()
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "q" or ch == "Q" then
break
elseif ch == "\17" then
if scroll > 0 then scroll = scroll - 1; dirty = true end
elseif ch == "\18" then
if scroll + pageSize < totalLines then scroll = scroll + 1; dirty = true end
elseif ch == "\19" then
scroll = math.max(0, scroll - pageSize); dirty = true
elseif ch == "\20" then
scroll = math.min(totalLines - pageSize, scroll + pageSize); dirty = true
end
if dirty then render() end
end
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.devctl(1, "sbgc", 0x000000)

100
Src/hysh/data/bin/hfetch Normal file
View File

@@ -0,0 +1,100 @@
--:Minify:--
-- Color indices (sfgc):
-- 1=white, 2=red, 3=green, 4=blue, 5=cyan, 6=magenta, 7=yellow
-- 8=orange, 9=lime, 10=lightcyan, 11=brown, 12=darkgrey, 13=lightgrey, 14=purple, 15=chartreuse, 16=black
local C_LOGO = 0x00FFFF -- cyan
local C_WHITE = 0xFFFFFF -- white
local C_LABEL = 0xDBDBDB -- light grey (key names)
local C_SEP = 0x6D6D6D -- dark grey (---- separator)
local C_USER = 0x00FF00 -- green (user@host)
local function c(col) syscall.devctl(1, "sfgc", col) end
local username = syscall.getUsername() or "Unknown"
local hostname = syscall.getHostname() or "Unknown"
local userhost = username .. "@" .. hostname
local function formatUptime(ms)
local s = math.floor(ms / 1000)
local m = math.floor(s / 60)
local h = math.floor(m / 60)
local d = math.floor(h / 24)
s = s % 60; m = m % 60; h = h % 24
local parts = {}
if d > 0 then parts[#parts+1] = d .. "d" end
if h > 0 then parts[#parts+1] = h .. "h" end
if m > 0 then parts[#parts+1] = m .. "m" end
parts[#parts+1] = s .. "s"
return table.concat(parts, " ")
end
local host_str = syscall.getHost() or "Unknown"
local cc_ver = host_str:match("ComputerCraft ([%d%.]+)") or host_str
local info = {
{nil, userhost},
{nil, string.rep("-", #userhost)},
{"OS", syscall.version() or "Unknown"},
{"Host", cc_ver},
{"Arch", syscall.arch() or "Unknown"},
{"Uptime", formatUptime(syscall.getUptime() or 0)},
{"Tasks", tostring(#(syscall.getTasks() or {}))},
{"Shell", syscall.getEnviron("SHELL") or "Unknown"},
{"Terminal", "tty1"},
{"UID", tostring(syscall.getuid())},
{"Packages", "n/a (spm)"},
}
local logo = {
".. *. .. ",
" *= +@* +* ",
" .@#. -@@@= :#@. ",
" =@@+ *@@@# +@@= ",
" %@@%: *@@@# -%@@% ",
" :@@@@+ *@@@# .*@@@@: ",
" :*@@@%- *@@@# -@@@@*: ",
" =%@@#. *@@@# .#@@%= ",
" :=. :*@@= *@@@# =@@+: .=: ",
" %@#=..*# +@@@# #*..=#@# ",
" .@@@@+=# .%@%: #=+@@@@. ",
" .....=# -@= *+...:. ",
" -*%*-@= - =@-*%*- ",
" -@*. -@%. :%@- :*@- ",
" .#@#@* ",
" -#- ",
" ",
}
local lines = math.max(#logo, #info)
for i = 1, lines do
local logo_str = logo[i] or string.rep(" ", 36)
c(C_LOGO)
printInline(logo_str)
c(C_LABEL)
printInline("| ")
local row = info[i]
if row then
if row[1] == nil and i == 1 then
-- user@host line
c(C_USER)
printInline(row[2])
elseif row[1] == nil and i == 2 then
-- separator line
c(C_SEP)
printInline(row[2])
elseif row[1] then
-- label: value
c(C_LABEL)
printInline(row[1] .. ": ")
c(C_WHITE)
printInline(row[2])
end
end
c(C_WHITE)
print("")
end

1260
Src/hysh/data/bin/hysh Normal file

File diff suppressed because it is too large Load Diff

19
Src/hysh/data/bin/id Normal file
View File

@@ -0,0 +1,19 @@
--:Minify:--
local args = {...}
local uid
if args[1] then
uid = syscall.getuid(args[1])
if not uid then
print("id: user '" .. args[1] .. "' does not exist")
syscall.exit(1); return
end
else
uid = syscall.getuid()
end
local pwent = syscall.getpasswd(uid)
local name = (pwent and pwent.username) or tostring(uid)
local gid = (pwent and pwent.gid) or uid
print(string.format("uid=%d(%s) gid=%d(%s)", uid, name, gid, name))

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

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

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