146 Commits

Author SHA1 Message Date
8762b8f022 Merge pull request '1.2-dev' (#9) from 1.2-dev into main
Reviewed-on: Hyperion/HyperionOS#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: Hyperion/HyperionOS#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: Hyperion/HyperionOS#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: Hyperion/HyperionOS#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: Hyperion/HyperionOS#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: Hyperion/HyperionOS#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: Hyperion/HyperionOS#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
6d9d02edf7 update to start working on SysInit 2025-12-10 22:14:52 -05:00
7bc6d87322 rewrite 2025-12-08 21:42:20 -05:00
236 changed files with 18813 additions and 9647 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"
]
}

164
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,164 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build (Minfiyed)",
"type": "shell",
"windows": {
"command": "powershell",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}\\scripts\\buildMini.ps1"
]
},
"linux": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildMini.sh"
]
},
"osx": {
"command": "bash",
"args": [
"${workspaceFolder}/scripts/buildMini.sh"
]
},
"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": []
}
]
}

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

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

143
Src/Hyperion-core/lib/fs Normal file
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

6
Src/Hyperion-core/lib/io Normal file
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,316 @@
-- :Minify:--
local BOOT_DRIVE_PATH = ({...})[1] or "/$"
---@diagnostic disable-next-line: undefined-global
local term = term
local os = os
local function write(text)
local x, y = term.getCursorPos()
local w, h = term.getSize()
for i = 1, #text do
local c = text:sub(i, i)
if c == "\n" then
y = y + 1
x = 1
elseif c == "\t" then
local tabSize = 4
local spaces = tabSize - ((x - 1) % tabSize)
term.write(string.rep(" ", spaces))
x = x + spaces
elseif c == "\b" then
if x > 1 then
x = x - 1
term.setCursorPos(x, y)
term.write(" ")
term.setCursorPos(x, y)
end
else
if x <= w and y <= h then
term.setCursorPos(x, y)
term.write(c)
x = x + 1
end
end
if x > w then
x = 1
y = y + 1
end
if y - 1 >= h then
term.scroll(1)
y = h
term.setCursorPos(x, y)
end
end
term.setCursorPos(x, y)
end
local function displaySuperBadError(err)
term.setBackgroundColor(0x1)
term.setTextColor(0x4)
term.clear()
term.setCursorPos(1, 1)
term.write("A critical error occurred while loading the system:")
term.setCursorPos(1, 3)
write(err)
while true do end
end
term.setCursorBlink(false)
local ok, err = xpcall(function()
local apis = {BOOT_DRIVE_PATH = BOOT_DRIVE_PATH}
local lua = {
coroutine = true,
debug = true,
_VERSION = true,
assert = true,
collectgarbage = true,
error = true,
gcinfo = true,
getfenv = true,
getmetatable = true,
ipairs = true,
__inext = true,
load = true,
math = true,
next = true,
pairs = true,
pcall = true,
rawequal = true,
rawget = true,
rawlen = true,
rawset = true,
select = true,
setfenv = true,
setmetatable = true,
string = true,
table = true,
tonumber = true,
tostring = true,
type = true,
xpcall = true,
_G = true
}
local debug = debug
for i, v in pairs(_G) do
if not lua[i] or lua[i] == nil then
apis[i] = v
_G[i] = nil
end
end
local acekeys={
[apis.keys.enter]="\n",
[apis.keys.tab]="\t",
[apis.keys.backspace]="\b",
[apis.keys.up]="\17",
[apis.keys.down]="\18",
[apis.keys.left]="\19",
[apis.keys.right]="\20",
}
function sleep(time)
local stoptime = apis.os.clock() + (time)
while stoptime > apis.os.clock() do end
end
apis.term.setPaletteColor(0x1, 0xFFFFFF) -- #000000
apis.term.setPaletteColor(0x2, 0xFF0000) -- #FFFFFF
apis.term.setPaletteColor(0x4, 0x00FF00) -- #FF0000
apis.term.setPaletteColor(0x8, 0x0000FF) -- #00FF00
apis.term.setPaletteColor(0x10, 0x00FFFF) -- #0000FF
apis.term.setPaletteColor(0x20, 0xFF00FF) -- #00FFFF
apis.term.setPaletteColor(0x40, 0xFFFF00) -- #FF00FF
apis.term.setPaletteColor(0x80, 0xFF6D00) -- #FFFF00
apis.term.setPaletteColor(0x100, 0x6DFF55) -- #FF6D00
apis.term.setPaletteColor(0x200, 0x24FFFF) -- #6DFF55
apis.term.setPaletteColor(0x400, 0x924900) -- #24FFFF
apis.term.setPaletteColor(0x800, 0x6D6D55) -- #924900
apis.term.setPaletteColor(0x1000, 0xDBDBAA) -- #6D6D55
apis.term.setPaletteColor(0x2000, 0x6D00FF) -- #DBDBAA
apis.term.setPaletteColor(0x4000, 0xB6FF00) -- #6D00FF
apis.term.setPaletteColor(0x8000, 0x000000) -- #B6FF00
local function getFile(path)
local file = apis.fs.open(path, "r")
if not file then
displaySuperBadError("Could not open file: " .. path)
end
local content = file.readAll()
file.close()
return content
end
local Kernel = load(getFile(BOOT_DRIVE_PATH .. "/boot/kernel.lua"),"@Kernel")
local initFs = load(getFile(BOOT_DRIVE_PATH .. "/boot/cct/initdisks"),"@Init_disks")(apis)
local fs = load(getFile(BOOT_DRIVE_PATH .. "/boot/initfs"), "@InitFs")()
if not Kernel then displaySuperBadError("Could not load kernel.") end
if not initFs then displaySuperBadError("Could not load initdisks.") end
if not fs then displaySuperBadError("Could not load initfs.") end
local eventQueue = {}
local function queueEvent(event, ...)
table.insert(eventQueue, {event, ...})
end
local computer = {
time = function() return apis.os.epoch("utc") end,
clock = function() return apis.os.clock() * 1000 end,
shutdown = apis.os.shutdown,
reboot = apis.os.reboot,
getMachineEvent = function()
if #eventQueue > 0 then
return table.unpack(table.remove(eventQueue, 1))
else
return nil
end
end,
getEEPROM = function() return getFile("/startup.lua") end,
setEEPROM = function(_, text)
local h = apis.fs.open("/startup.lua", "w")
h.write(text)
h.close()
end
}
local icolors = {
[0x1] = 1, -- #000000
[0x2] = 2, -- #FFFFFF
[0x4] = 3, -- #FF0000
[0x8] = 4, -- #00FF00
[0x10] = 5, -- #0000FF
[0x20] = 6, -- #00FFFF
[0x40] = 7, -- #FF00FF
[0x80] = 8, -- #FFFF00
[0x100] = 9, -- #FF6D00
[0x200] = 10, -- #6DFF55
[0x400] = 11, -- #24FFFF
[0x800] = 12, -- #924900
[0x1000] = 13, -- #6D6D55
[0x2000] = 14, -- #DBDBAA
[0x4000] = 15, -- #6D00FF
[0x8000] = 16 -- #B6FF00
}
local colors = {
0x0001, -- #000000
0x0002, -- #FFFFFF
0x0004, -- #FF0000
0x0008, -- #00FF00
0x0010, -- #0000FF
0x0020, -- #00FFFF
0x0040, -- #FF00FF
0x0080, -- #FFFF00
0x0100, -- #FF6D00
0x0200, -- #6DFF55
0x0400, -- #24FFFF
0x0800, -- #924900
0x1000, -- #6D6D55
0x2000, -- #DBDBAA
0x4000, -- #6D00FF
0x8000 -- #B6FF00
}
apis.term.setBackgroundColor(0x8000)
apis.term.setTextColor(0x1000)
apis.term.clear()
apis.term.setCursorPos(1, 1)
local kernelCoro = coroutine.create(function()
---@diagnostic disable-next-line: param-type-mismatch
local ok, err = xpcall(Kernel, debug.traceback, apis, initFs, "cct", "/sbin/init",
{
print = function(_, text) write(text .. "\n") end,
printInline = function(_, text) write(text) end,
clear = function()
apis.term.clear()
apis.term.setCursorPos(1, 1)
end,
setCursorPos = function(_, x, y)
apis.term.setCursorPos(x, y)
end,
getCursorPos = function() return apis.term.getCursorPos() end,
getSize = function() return apis.term.getSize() end,
setBackgroundColor = function(_, color)
apis.term.setBackgroundColor(colors[color])
end,
setTextColor = function(_, color)
apis.term.setTextColor(colors[color])
end,
getBackgroundColor = function()
return icolors[apis.term.getBackgroundColor()]
end,
getTextColor = function()
return icolors[apis.term.getTextColor()]
end
}, computer, fs, "$")
if not ok then displaySuperBadError(err) end
end)
function coroutine.resumeWithTimeout(co, timeout, ...)
local startTime = computer.time()
debug.sethook(co, function()
if computer.time() > startTime + timeout then
return coroutine.yield("timeout")
end
end, "", 1000)
local ret = {coroutine.resume(co, ...)}
if ret[1] and ret[2] == "timeout" then
return "timeout"
elseif ret[1] == false then
return "error", ret[2]
else
debug.sethook(co)
return "success", table.unpack(ret, 2)
end
end
write("Loaded in " .. tostring(apis.os.clock()) .. " seconds.\n")
while true do
local status, err = coroutine.resumeWithTimeout(kernelCoro, 50)
apis.os.queueEvent("NoSleep")
local exit = false
while not exit do
local event = {coroutine.yield()}
if event[1] == "key" then
queueEvent("keyPressed", 1, event[2])
if acekeys[event[2]] then
queueEvent("keyTyped", 1, acekeys[event[2]])
end
elseif event[1] == "char" then
queueEvent("keyTyped", 1, event[2])
elseif event[1] == "key_up" then
queueEvent("keyReleased", 1, event[2])
elseif event[1] == "disk" then
queueEvent("componentAdded", "disk")
elseif event[1] == "disk_eject" then
queueEvent("componentRemoved", "disk")
elseif event[1] == "modem_message" then
queueEvent("modem_message", table.unpack(event, 2))
elseif event[1] == "rednet_message" then
queueEvent("rednet_message", table.unpack(event, 2))
elseif event[1] == "http_success" then
queueEvent("http_success", table.unpack(event, 2))
elseif event[1] == "http_failure" then
queueEvent("http_failure", table.unpack(event, 2))
elseif event[1] == "NoSleep" then
exit = true
end
end
if status == "error" or coroutine.status(kernelCoro) == "dead" then
displaySuperBadError("Kernel error: " .. tostring(err))
coroutine.yield("key")
end
end
end, debug.traceback)
if not ok then displaySuperBadError("Fatal error during boot: " .. err) end
while true do coroutine.yield() end

View File

@@ -1,3 +1,5 @@
--:Minify:--
local BOOT_DRIVE_PATH=({...})[1] or "/$"
-- UnBIOS by JackMacWindows -- 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 -- 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 -- To use, just place a `bios.lua` in the root of the drive, and run this program
@@ -13,7 +15,6 @@
-- * `turtle.equip[Left|Right]` -- * `turtle.equip[Left|Right]`
-- Licensed under the MIT license -- Licensed under the MIT license
local args = {...} 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 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 = {} local t = {}
for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end
@@ -29,7 +30,6 @@ if _G.commands then _G.commands = _G.commands.native end
if _G.turtle then _G.turtle.native, _G.turtle.craft = nil 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"}} 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 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 -- Set up TLCO
-- This functions by crashing `rednet.run` by removing `os.pullEventRaw`. Normally -- 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 -- this would cause `parallel` to throw an error, but we replace `error` with an
@@ -58,22 +58,22 @@ function _G.term.native()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
term.setCursorBlink(true) term.setCursorBlink(true)
term.clear() term.clear()
local file = fs.open("/disk/boot/cc/preboot.cc", "r") local file = fs.open(BOOT_DRIVE_PATH.."/boot/cct/boot.lua", "r")
if file == nil then if file == nil then
term.setCursorBlink(false) term.setCursorBlink(false)
term.setTextColor(16384) term.setTextColor(16384)
term.write("Could not find /boot/cc/bootloader.cc. UnBIOS cannot continue.") term.write("Could not find /boot/cct/boot.lua. UnBIOS cannot continue.")
term.setCursorPos(1, 2) term.setCursorPos(1, 2)
term.write("Press any key to continue") term.write("Press any key to continue")
coroutine.yield("key") coroutine.yield("key")
os.shutdown() os.shutdown()
end end
local fn, err = loadstring(file.readAll(), "@preboot.cc") local fn, err = loadstring(file.readAll(), "@bootloader")
file.close() file.close()
if fn == nil then if fn == nil then
term.setCursorBlink(false) term.setCursorBlink(false)
term.setTextColor(16384) term.setTextColor(16384)
term.write("Could not load /boot/cc/bootloader.cc. UnBIOS cannot continue.") term.write("Could not load /boot/cc/boot.lua. UnBIOS cannot continue.")
term.setCursorPos(1, 2) term.setCursorPos(1, 2)
term.write(err) term.write(err)
term.setCursorPos(1, 3) term.setCursorPos(1, 3)
@@ -85,7 +85,7 @@ function _G.term.native()
local oldshutdown = os.shutdown local oldshutdown = os.shutdown
os.shutdown = function() os.shutdown = function()
os.shutdown = oldshutdown os.shutdown = oldshutdown
return fn(table.unpack(args)) return fn(BOOT_DRIVE_PATH)
end end
end end
if debug then if debug then

View File

@@ -0,0 +1,155 @@
-- :Minify:--
local apis = ({...})[1]
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH or "/$"
local fs = apis.fs
local native = apis.peripheral
local peripheral = {}
local sides = {"top", "bottom", "left", "right", "front", "back"}
function peripheral.getType(name)
if native.isPresent(name) then return native.getType(name) end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "peripheral_hub") and
native.call(side, "isPresentRemote", name) then
return native.call(side, "getTypeRemote", name)
end
end
return nil
end
function peripheral.getNames()
local names = {}
for n = 1, #sides do
local side = sides[n]
if native.isPresent(side) then table.insert(names, side) end
if native.hasType(side, "peripheral_hub") then
local hubSides = native.call(side, "getConnectedSides")
for _, hubSide in ipairs(hubSides) do
table.insert(names, hubSide)
end
end
end
return names
end
local disks = {}
local internal = {}
local function norm(path)
if not path or path == "" then return "/" end
return fs.combine("/", path)
end
local function createDisk(id, basePath, readonly, periph)
basePath = norm(basePath)
local disk = {address = id, isReadOnly = function() return readonly end}
function disk:spaceUsed()
return fs.getCapacity(basePath) - fs.getFreeSpace(basePath)
end
function disk:spaceTotal() return fs.getCapacity(basePath) end
function disk:list(path)
local p = fs.combine(basePath, path)
if not fs.exists(p) or not fs.isDir(p) then
return nil, "not directory"
end
return fs.list(p)
end
function disk:fileExists(path)
local p = fs.combine(basePath, path)
return fs.exists(p) and not fs.isDir(p)
end
function disk:directoryExists(path)
local p = fs.combine(basePath, path)
return fs.exists(p) and fs.isDir(p)
end
function disk:type(path)
local p = fs.combine(basePath, path)
if not fs.exists(p) then
return nil
elseif fs.isDir(p) then
return "directory"
else
return "file"
end
end
function disk:makeDirectory(path)
local p = fs.combine(basePath, path)
fs.makeDir(p)
return true
end
function disk:remove(path)
local p = fs.combine(basePath, path)
if fs.exists(p) then fs.delete(p) end
return true
end
function disk:setLabel(label) periph.setLabel(label) end
function disk:getLabel(label) return periph.getLabel() end
function disk:attributes(path)
local p = fs.combine(basePath, path)
return fs.attributes(p)
end
function disk:open(path, mode)
local p = fs.combine(basePath, path)
return fs.open(p, mode)
end
return disk
end
internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, {
setLabel = function(label)
local h = fs.open("/.label", "w")
h.write(label)
h.close()
end,
getLabel = function()
local h = fs.open("/.label", "r")
if not h then return "$" end
local label = h.readAll()
h.close()
return label
end
})
local function refresh()
for id, _ in pairs(disks) do
if not peripheral.getType(id) then disks[id] = nil end
end
for _, name in ipairs(peripheral.getNames()) do
if peripheral.getType(name) == "disk" then
if not disks[name] then
local mount = disk.getMountPath(name)
if mount then
disks[name] = createDisk(name, mount, false, disk)
end
end
end
end
end
local function iter()
refresh()
local combined = {}
for id, obj in pairs(internal) do combined[id] = obj end
for id, obj in pairs(disks) do combined[id] = obj end
return pairs(combined)
end
return {refresh = refresh, list = iter}

View File

@@ -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.computer: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,314 @@
-- :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
local eventQueue = {}
local function queueEvent(event, ...)
table.insert(eventQueue, {event, ...})
end
local computer = {
time = function() return apis.os.epoch("utc") end,
clock = function() return apis.os.clock() * 1000 end,
shutdown = apis.os.shutdown,
reboot = apis.os.reboot,
getMachineEvent = function()
if #eventQueue > 0 then
return table.unpack(table.remove(eventQueue, 1))
else
return nil
end
end,
getEEPROM = function() return getFile("/startup.lua") end,
setEEPROM = function(_, text)
local h = apis.fs.open("/startup.lua", "w")
h.write(text)
h.close()
end
}
local icolors = {
[0x1] = 1, -- #000000
[0x2] = 2, -- #FFFFFF
[0x4] = 3, -- #FF0000
[0x8] = 4, -- #00FF00
[0x10] = 5, -- #0000FF
[0x20] = 6, -- #00FFFF
[0x40] = 7, -- #FF00FF
[0x80] = 8, -- #FFFF00
[0x100] = 9, -- #FF6D00
[0x200] = 10, -- #6DFF55
[0x400] = 11, -- #24FFFF
[0x800] = 12, -- #924900
[0x1000] = 13, -- #6D6D55
[0x2000] = 14, -- #DBDBAA
[0x4000] = 15, -- #6D00FF
[0x8000] = 16 -- #B6FF00
}
local colors = {
0x0001, -- #000000
0x0002, -- #FFFFFF
0x0004, -- #FF0000
0x0008, -- #00FF00
0x0010, -- #0000FF
0x0020, -- #00FFFF
0x0040, -- #FF00FF
0x0080, -- #FFFF00
0x0100, -- #FF6D00
0x0200, -- #6DFF55
0x0400, -- #24FFFF
0x0800, -- #924900
0x1000, -- #6D6D55
0x2000, -- #DBDBAA
0x4000, -- #6D00FF
0x8000 -- #B6FF00
}
apis.term.setBackgroundColor(0x8000)
apis.term.setTextColor(0x1000)
apis.term.clear()
apis.term.setCursorPos(1, 1)
local kernelCoro = coroutine.create(function()
---@diagnostic disable-next-line: param-type-mismatch
local ok, err = xpcall(Kernel, debug.traceback, apis, initFs, "cct", "/sbin/init",
{
print = function(_, text) write(text .. "\n") end,
printInline = function(_, text) write(text) end,
clear = function()
apis.term.clear()
apis.term.setCursorPos(1, 1)
end,
setCursorPos = function(_, x, y)
apis.term.setCursorPos(x, y)
end,
getCursorPos = function() return apis.term.getCursorPos() end,
getSize = function() return apis.term.getSize() end,
setBackgroundColor = function(_, color)
apis.term.setBackgroundColor(colors[color])
end,
setTextColor = function(_, color)
apis.term.setTextColor(colors[color])
end,
getBackgroundColor = function()
return icolors[apis.term.getBackgroundColor()]
end,
getTextColor = function()
return icolors[apis.term.getTextColor()]
end
}, computer, fs, "$")
if not ok then displaySuperBadError(err) end
end)
function coroutine.resumeWithTimeout(co, timeout, ...)
local startTime = computer.time()
debug.sethook(co, function()
if computer.time() > startTime + timeout then
return coroutine.yield("timeout")
end
end, "", 1000)
local ret = {coroutine.resume(co, ...)}
if ret[1] and ret[2] == "timeout" then
return "timeout"
elseif ret[1] == false then
return "error", ret[2]
else
debug.sethook(co)
return "success", table.unpack(ret, 2)
end
end
write("Loaded in " .. tostring(apis.os.clock()) .. " seconds.\n")
while true do
local status, err = coroutine.resumeWithTimeout(kernelCoro, 50)
apis.os.queueEvent("NoSleep")
local exit = false
while not exit do
local event = {coroutine.yield()}
if event[1] == "key" then
queueEvent("keyPressed", 1, event[2])
if acekeys[event[2]] then
queueEvent("keyTyped", 1, acekeys[event[2]])
end
elseif event[1] == "char" then
queueEvent("keyTyped", 1, event[2])
elseif event[1] == "key_up" then
queueEvent("keyReleased", 1, event[2])
elseif event[1] == "disk" then
queueEvent("componentAdded", "disk")
elseif event[1] == "disk_eject" then
queueEvent("componentRemoved", "disk")
elseif event[1] == "modem_message" then
queueEvent("modem_message", table.unpack(event, 2))
elseif event[1] == "rednet_message" then
queueEvent("rednet_message", table.unpack(event, 2))
elseif event[1] == "http_success" then
queueEvent("http_success", table.unpack(event, 2))
elseif event[1] == "http_failure" then
queueEvent("http_failure", table.unpack(event, 2))
elseif event[1] == "NoSleep" then
exit = true
end
end
if status == "error" or coroutine.status(kernelCoro) == "dead" then
displaySuperBadError("Kernel error: " .. tostring(err))
coroutine.yield("key")
end
end
end, debug.traceback)
if not ok then displaySuperBadError("Fatal error during boot: " .. err) end
while true do coroutine.yield() end

View File

@@ -1,3 +1,5 @@
--:Minify:--
local BOOT_DRIVE_PATH=({...})[1] or "/$"
-- UnBIOS by JackMacWindows -- 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 -- 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 -- To use, just place a `bios.lua` in the root of the drive, and run this program
@@ -12,7 +14,7 @@
-- * `peripheral` -- * `peripheral`
-- * `turtle.equip[Left|Right]` -- * `turtle.equip[Left|Right]`
-- Licensed under the MIT license -- Licensed under the MIT license
if _HOST:find("UnBIOS") then return end 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 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 = {} local t = {}
for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end
@@ -28,7 +30,6 @@ if _G.commands then _G.commands = _G.commands.native end
if _G.turtle then _G.turtle.native, _G.turtle.craft = nil 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"}} 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 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 -- Set up TLCO
-- This functions by crashing `rednet.run` by removing `os.pullEventRaw`. Normally -- 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 -- this would cause `parallel` to throw an error, but we replace `error` with an
@@ -57,22 +58,22 @@ function _G.term.native()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
term.setCursorBlink(true) term.setCursorBlink(true)
term.clear() term.clear()
local file = fs.open("/boot/cc/bootloader.cc", "r") local file = fs.open(BOOT_DRIVE_PATH.."/boot/cct/boot.lua", "r")
if file == nil then if file == nil then
term.setCursorBlink(false) term.setCursorBlink(false)
term.setTextColor(16384) term.setTextColor(16384)
term.write("Could not find /boot/cc/bootloader.cc. UnBIOS cannot continue.") term.write("Could not find /boot/cct/boot.lua. UnBIOS cannot continue.")
term.setCursorPos(1, 2) term.setCursorPos(1, 2)
term.write("Press any key to continue") term.write("Press any key to continue")
coroutine.yield("key") coroutine.yield("key")
os.shutdown() os.shutdown()
end end
local fn, err = loadstring(file.readAll(), "@bootloader.cc") local fn, err = loadstring(file.readAll(), "@bootloader")
file.close() file.close()
if fn == nil then if fn == nil then
term.setCursorBlink(false) term.setCursorBlink(false)
term.setTextColor(16384) term.setTextColor(16384)
term.write("Could not load /boot/cc/bootloader.cc. UnBIOS cannot continue.") term.write("Could not load /boot/cc/boot.lua. UnBIOS cannot continue.")
term.setCursorPos(1, 2) term.setCursorPos(1, 2)
term.write(err) term.write(err)
term.setCursorPos(1, 3) term.setCursorPos(1, 3)
@@ -84,7 +85,7 @@ function _G.term.native()
local oldshutdown = os.shutdown local oldshutdown = os.shutdown
os.shutdown = function() os.shutdown = function()
os.shutdown = oldshutdown os.shutdown = oldshutdown
return fn() return fn(BOOT_DRIVE_PATH)
end end
end end
if debug then if debug then
@@ -116,4 +117,3 @@ if debug then
_G.peripheral = value or peripheral _G.peripheral = value or peripheral
end end
end end
os.shutdown()

View File

@@ -0,0 +1,266 @@
-- :Minify:--
local apis = ({...})[1]
local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH or "/$"
local fs = apis.fs
local native = apis.peripheral
local peripheral = {}
local sides = {"top", "bottom", "left", "right", "front", "back"}
function peripheral.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
})
local function refresh()
for id, _ in pairs(disks) do
if not peripheral.getType(id) then disks[id] = nil end
end
for _, disk in ipairs({peripheral.find("drive")}) do
if disk.isDiskPresent() then
disks[tostring(disk.getDiskID())]=createDisk("cctdisk"..tostring(disk.getDiskID()), disk.getMountPath(), false, fs)
end
end
end
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,411 @@
--: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)
obj.setPaletteColor(0x1, 0xFFFFFF) -- #000000
obj.setPaletteColor(0x2, 0xFF0000) -- #FFFFFF
obj.setPaletteColor(0x4, 0x00FF00) -- #FF0000
obj.setPaletteColor(0x8, 0x0000FF) -- #00FF00
obj.setPaletteColor(0x10, 0x00FFFF) -- #0000FF
obj.setPaletteColor(0x20, 0xFF00FF) -- #00FFFF
obj.setPaletteColor(0x40, 0xFFFF00) -- #FF00FF
obj.setPaletteColor(0x80, 0xFF6D00) -- #FFFF00
obj.setPaletteColor(0x100, 0x6DFF55) -- #FF6D00
obj.setPaletteColor(0x200, 0x24FFFF) -- #6DFF55
obj.setPaletteColor(0x400, 0x924900) -- #24FFFF
obj.setPaletteColor(0x800, 0x6D6D55) -- #924900
obj.setPaletteColor(0x1000, 0xDBDBAA) -- #6D6D55
obj.setPaletteColor(0x2000, 0x6D00FF) -- #DBDBAA
obj.setPaletteColor(0x4000, 0xB6FF00) -- #6D00FF
obj.setPaletteColor(0x8000, 0x000000) -- #B6FF00
kernel.devfs.data["tty"][id] = function(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
local h = {
read=function(amount)
local rv=""
for i=1, amount or 1 do
local event = {ev()}
if event[1] then
rv=rv..event[1]
end
end
if rv=="" then rv=nil end
return rv
end,
write=function(content)
write(content, obj)
end,
size=function()
local s={obj.getSize()}
return table.concat(s,";")
end,
clear=function()
obj.clear()
obj.setCursorPos(1,1)
end,
gpos=function()
local s={obj.getCursorPos()}
return table.concat(s,";")
end,
spos=function(x,y)
return obj.setCursorPos(x,y)
end,
sfgc=function(c)
return obj.setTextColor(colors[c])
end,
sbgc=function(c)
return obj.setBackgroundColor(colors[c])
end,
gfgc=function()
return icolors[obj.getTextColor()]
end,
gbgc=function()
return icolors[obj.getBackgroundColor()]
end,
gctrl=function()
return serializeBool(ctrl)..";"..serializeBool(alt)
end
}
if mode=="rw" then
return h
elseif mode=="r" then
h["write"]=nil
return h
elseif mode=="w" then
h["read"]=nil
return h
end
end
end
end
local fifo = kernel.newFifo()
kernel.processes.cctmond = function()
local timeout = false
while true do
local event = {kernel.computer:getMachineEvent()}
if event[1] then
local eventType = event[1]
local charOrKey = event[3]
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,26 @@
local args={...}
local kernel=args[1]
local driver={}
driver.name="CCT Term Module"
driver.version="0.1.0"
driver.type="gpio"
driver.description="CCT redstone Module Kernel Module"
driver.arch="cct"
driver.author="HyperionOS Dev Team"
driver.license="MIT"
driver.api={}
function driver.load()
-- will
end
function driver.unload()
-- Nothing to unload
end
function driver.main()
-- Nothing to run
end
-- kernel.drivers.register(driver)

View File

@@ -1,6 +1,3 @@
local biosData = ({...})[1]
local apis={}
local lua = { local lua = {
coroutine = true, coroutine = true,
debug = true, debug = true,
@@ -31,20 +28,14 @@ local lua = {
tonumber = true, tonumber = true,
tostring = true, tostring = true,
type = true, type = true,
xpcall = true xpcall = true,
_G=true
} }
for i,v in ipairs(_G) do local apis={}
if not lua[i] then for i,v in pairs(_G) do
if not lua[i] or lua[i]==nil then
apis[i]=v apis[i]=v
_G[i]=nil _G[i]=nil
end end
end end
local function getFile(path)
return biosData.bootDrive:open(path).read()
end
local computer = apis.component.getFirst("computer")
local kernel = load(getFile("/boot/Hyprkrnl.sys"), "@kernel", "t", _G)
kernel("ac", apis, biosData.bootDrive.id, apis.component.getFirst("screen"), computer.getMachineEvent)

View File

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

View File

@@ -0,0 +1 @@
local fs={}

View File

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

View File

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

View File

@@ -0,0 +1,287 @@
--:Minify:--
local args = {...}
local apis = args[1]
local disks = args[2]
local arch = args[3]
local screen = args[5]
local computer = args[6]
local ifs = args[7]
local kernel = {}
kernel.LOG_Text=""
kernel.version="HyperionOS V1.2.3"
kernel.process = "Kernel"
kernel.users={[0]="root",[1]="User"}
kernel.hostname = "hyperion"
kernel.groups = {}
kernel.uid = 0
kernel.gid = 0
kernel.status = "start"
kernel.key = {}
kernel.cache = {}
kernel.cache.preload = {}
kernel._G=_G
kernel.sleep=sleep
_G.sleep=nil
local windowsExp = false
function kernel.log(msg, level, c)
c=c or 12
kernel.LOG_Text = kernel.LOG_Text..string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n"
if kernel.status == "start" then
screen:setTextColor(c)
screen:print(tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg)
elseif kernel.status == "term" then
kernel.standbyTask=kernel.currentTask
kernel.currentTask=kernel.kernelTask
kernel.vfs.devctl(1,"sfgc",c)
kernel.vfs.write(1,tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n")
kernel.currentTask=kernel.standbyTask
end
end
function kernel.PANIC(msg)
if kernel.status~="Panic" then
kernel.log("PANIC: "..msg, "PANIC")
pcall(kernel["saveLog"])
kernel.status="Panic"
kernel.reason=msg
screen:setTextColor(2)
screen:setBackgroundColor(16)
screen:clear()
screen:setCursorPos(1,1)
screen:print(kernel.LOG_Text)
screen:print("KERNEL PANIC!\n"..msg.."\nSystem halted.")
screen:print("Press any key to continue...")
kernel.exitMain = true
end
while true do
local event={computer:getMachineEvent()}
if event[1]=="keyPressed" then
break
end
end
computer:reboot()
end
kernel.panic=kernel.PANIC
if windowsExp then
screen:setTextColor(1)
screen:setBackgroundColor(4)
screen:clear()
local w,h = screen:getSize()
screen:setCursorPos(3,5)
screen:print(":(")
screen:setCursorPos(3,7)
screen:print("Your PC ran into a problem and needs to restart. We're just collecting some error")
screen:setCursorPos(3,8)
screen:print("info, and then we'll restart for you.\n")
screen:setCursorPos(3,h-5)
screen:print("Stop code: average windows experience")
screen:setCursorPos(1,h)
screen:print("Press any key to continue... jk reboot it yourself lazy")
while true do end
end
kernel.log("Kernel loaded.")
kernel.log("Mounting init disks...")
disks.refresh()
ifs.update(disks)
kernel.disks={}
for _,v in disks.list() do
kernel.disks[v.address] = v
end
ifs.mount("$", "/")
local fstab=ifs.readAllText("/boot/fstab")
local split = function(str, delim, maxResultCountOrNil)
assert(#delim == 1, "only delim len 1 supported for now")
maxResultCountOrNil = (maxResultCountOrNil or 0)-1
local rv = {}
local buf = ""
for i = 1, #str do
local c = string.sub(str,i,i)
if #rv ~= maxResultCountOrNil and c == delim then
table.insert(rv, buf)
buf = ""
else
buf = buf..c
end
end
table.insert(rv, buf)
return rv
end
if not ifs.isFile("/boot/boot.cfg") then
kernel.log("First boot detected writing boot.cfg", "INFO", 3)
ifs.writeAllText("/boot/boot.cfg",ifs.readAllText("/boot/safeboot.cfg"))
kernel.firstBoot=true
end
local initCfgFunc, err = load(ifs.readAllText("/boot/boot.cfg"), "@boot.cfg")
if not initCfgFunc then
kernel.PANIC("Failed to load /boot/boot.cfg: "..tostring(err))
end
---@diagnostic disable-next-line: param-type-mismatch
local initCfgStatus, config = pcall(initCfgFunc)
if not initCfgStatus then
kernel.PANIC("Error in /boot/boot.cfg: "..tostring(config))
end
kernel.config = config
for i,v in ipairs(split(fstab,"\n")) do
if v:sub(1,1)=="U" then
local id=""
for i=3,#v do
if v:sub(i,i)==";" then
if i==3 then kernel.log("Invalid fstab line... Skipping.","WARN") goto endline end
id=v:sub(3,i-1)
end
end
local path=v:sub(#id+4)
ifs.mount(id,path)
::endline::
end
end
kernel.log("Disks initialized")
function kernel.saveLog()
ifs.writeAllText("/var/log/syslog.log", kernel.LOG_Text)
end
function kernel.newFifo()
local fifo = {}
fifo.push=function(data)
table.insert(fifo, data)
end
fifo.pop=function()
return table.remove(fifo,1)
end
return fifo
end
function kernel.newUUID()
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
local uuid = ""
for i = 1, #template do
local c = template:sub(i,i)
if c == "x" then
uuid = uuid .. string.format("%x", math.random(0, 15))
elseif c == "y" then
uuid = uuid .. string.format("%x", math.random(8, 11))
else
uuid = uuid .. c
end
end
return uuid
end
kernel.syscalls={}
local modules={[0]={}}
for i=0, 100 do
modules[i]={}
end
kernel.log("Gathering modules")
for _, i in ipairs(ifs.list("/lib/modules")) do
local modlist = ifs.list("/lib/modules/"..i)
if not modlist then
kernel.log("WARNING: could not list /lib/modules/"..i.." (skipping)", "WARN", 8)
else
for _,v in ipairs(modlist) do
local prior=tonumber(v:sub(1,2))
if prior then
modules[prior+1][#modules[prior+1]+1]="/lib/modules/"..i.."/"..v
end
end
end
end
kernel.ifs=ifs
kernel.apis=apis
kernel.computer=computer
kernel.arch=arch
kernel.initdisks=disks
kernel.screen=screen
kernel.processes={}
kernel.fstab=fstab
kernel.kernelTask = {
name="kernel",
status="R",
pid=0,
tgid=0,
uid=0,
fd={},
exit="",
sleep=0,
ivs=0,
vs=0,
children={},
syscallReturn={},
cwd="/",
timeSlice=0,
lastTime=0,
totalTime=0,
numRuns=0
}
kernel.currentTask = kernel.kernelTask
function kernel.shutdown()
kernel.computer:shutdown()
end
function kernel.reboot()
kernel.computer:reboot()
end
kernel.syscalls["time"]=function() return kernel.computer:time() end
kernel.syscalls["log"]=kernel.log
kernel.syscalls["getUptime"]=function() return kernel.computer:clock() end
kernel.syscalls["getUsername"]=function(uid) return kernel.users[uid or kernel.uid] end
kernel.syscalls["getHostname"]=function() return kernel.hostname end
kernel.syscalls["getHost"]=function() return kernel.apis._HOST end
kernel.syscalls["version"]=function() return kernel.version end
kernel.syscalls["setHostname"]=function(name) if kernel.uid~=0 then error("Permission denied") end kernel.hostname=name end
kernel.syscalls["arch"]=function() return arch end
kernel.syscalls["sysdump"]=function()
local rv={}
for i,v in pairs(kernel.syscalls) do
rv[#rv+1] = i
end
return rv
end
kernel.syscalls["reboot"]=function()
kernel.computer:reboot()
end
kernel.syscalls["shutdown"]=function()
kernel.computer:reboot()
end
kernel.log("Running modules")
for _,p in ipairs(modules) do
for _,v in ipairs(p) do
if kernel.config.showModLoad then kernel.log("Loading module "..v, "DBUG", 5) end
local code=ifs.readAllText(v)
if not code then
kernel.log("ModuReadErr: "..v, "WARN", 8)
goto skip
end
local func,err=load(code,"@"..v)
if not func then kernel.panic("ModuLoadErr: "..tostring(err)) goto skip end
local status, err = xpcall(func,debug.traceback, kernel)
if not status then kernel.panic("ModuRunErr: "..tostring(err)) end
if kernel.config.showModLoad then kernel.log("Loaded module "..v, "DBUG", 5) end
::skip::
end
end
kernel.log("Kernel initialized successfully.")
kernel.saveLog()
kernel.status="running"
kernel.main()
if kernel.status=="panic" then
kernel.panic()
end
kernel.PANIC("Execution complete")

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,258 @@
--: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 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,40 @@
--:Minify:--
local kernel = ...
local cache = {}
kernel.searchpaths = {
"/lib/?.lua", "/lib/?", "/usr/lib/?.lua", "/usr/lib/?",
"/usr/local/lib/?.lua", "/usr/local/lib/?", "?.lua", "?"
}
function require(module, ...)
if cache[module] then return cache[module] end
local modpath = module:gsub("%.", "/")
local failed = {}
for _, path in ipairs(kernel.searchpaths) do
local full_path = string.replace(path, "?", modpath)
if full_path:sub(1, 1) ~= "/" then
full_path = kernel.currentTask.cwd .. full_path
end
if kernel.vfs.exists(full_path) then
if kernel.vfs.type(full_path) == "directory" then
full_path = full_path .. "/init"
end
if kernel.vfs.exists(full_path) then
local handle = kernel.vfs.open(full_path, "r")
local file_content = kernel.vfs.read(handle, 1024 * 1024 * 4)
kernel.vfs.close(handle)
return
assert(load(file_content, full_path, "t", kernel._U))(...)
else
table.insert(failed, full_path)
end
else
table.insert(failed, full_path)
end
end
error("Module not found: " .. module .. " (searched paths: " .. table.concat(failed, ", ") .. ")")
end

View File

@@ -0,0 +1,147 @@
--:Minify:--
local kernel = ...
local proxy = {}
local data = {}
proxy.address = "devfs0000"
proxy.isvirt = true
proxy.isReadOnly = function() return false end
proxy.spaceUsed = function() return 0 end
proxy.spaceTotal = function() return 0 end
proxy.makeDirectory = function() error("EACCES") end
proxy.remove = function() error("EACCES") end
proxy.setLabel = function() error("EACCES") end
proxy.getLabel = function() return "devfs" end
proxy.attributes = function(path) return {
size = 0,
modified = 0,
created = 0
} end
function proxy:open(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("open", mode)
end
error("ENFILE")
end
function proxy:type(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
if #steps == 0 then
return "directory"
end
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("type", mode)
end
if type(step[steps[#steps]]) == "table" then
return "directory"
end
error("ENOENT")
end
function proxy:list(path)
local steps = kernel.vfs.splitPath(path)
local step = data
if #steps == 0 then
return table.keys(data)
end
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENOENT") end
step=dat
end
if type(step[steps[#steps]]) == "table" then
return table.keys(step[steps[#steps]])
end
error("ENOENT")
end
function proxy:fileExists(path)
local ok = pcall(function()
return self:type(path)
end)
return ok
end
function data.random(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
return {
read=function(amount)
local str = ""
for i=1, amount or 1 do
str=str..string.char(math.random(0, 255))
end
return str
end
}
elseif mode=="w" or mode=="a" then
return {
write=function() end
}
else error("EACCES")
end
end
end
function data.null(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
return {
read=function(amount) end
}
elseif mode=="w" or mode=="a" then
return {
write=function() end
}
else error("EACCES")
end
end
end
function data.zero(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
return {
read=function(amount)
local str = ""
for i=1, amount or 1 do
str=str..string.char(0)
end
return str
end
}
elseif mode=="w" or mode=="a" then
return {
write=function() end
}
else error("EACCES")
end
end
end
data["disk"]={}
kernel.devfs={}
kernel.devfs.data=data
kernel.devfs.proxy=proxy
kernel.disks["devfs0000"]=proxy

View File

@@ -0,0 +1,265 @@
--:Minify:--
local kernel = ...
local proxy = {}
local data = {}
proxy.address = "procfs0000"
proxy.isvirt = true
proxy.isReadOnly = function() return false end
proxy.spaceUsed = function() return 0 end
proxy.spaceTotal = function() return 0 end
proxy.makeDirectory = function() error("EACCES") end
proxy.remove = function() error("EACCES") end
proxy.setLabel = function() error("EACCES") end
proxy.getLabel = function() return "procfs" end
proxy.attributes = function(path) return {
size = 0,
modified = 0,
created = 0
} end
local function buildMeta(entries, opts)
opts = opts or {}
local uid = opts.uid or 0
local gid = opts.gid or 0
local perms = opts.perms or 0x3F -- default read/write for owner/group/world
local chunks = {}
table.insert(chunks, string.char(0x02)) -- version header
for path, target in pairs(entries) do
local name = path
local nameLen = #name
if nameLen > 255 then
error("Filename too long (>255 bytes): "..name)
end
-- Determine entry type: 0x01 = symlink if target ~= nil and target ~= ""
local entryType = 0x00
local cmeta = ""
if target and target ~= "" then
entryType = 0x01
cmeta = target
end
local cmetaLen = #cmeta
if cmetaLen > 255 then
error("cmeta too long (>255 bytes) for "..name)
end
-- Build entry as bytes
table.insert(chunks, string.char(nameLen)) -- name length
table.insert(chunks, name) -- name
table.insert(chunks, string.char(entryType)) -- entry type
table.insert(chunks, string.char(uid % 256, math.floor(uid/256) % 256)) -- uid
table.insert(chunks, string.char(gid % 256, math.floor(gid/256) % 256)) -- gid
table.insert(chunks, string.char(perms % 256, math.floor(perms/256) % 256)) -- perms
table.insert(chunks, string.char(cmetaLen)) -- cmeta length
if cmetaLen > 0 then
table.insert(chunks, cmeta)
end
end
return table.concat(chunks)
end
local function simpleFile(r,w)
return function(op, mode)
if op=="type" then
return "character device"
elseif op=="open" then
if mode=="r" then
return {
read=r
}
elseif mode=="w" then
return {
write=w
}
end
end
end
end
local function strFile(str)
local dat=tostring(str)
local pos=1
return simpleFile(function(amount)
pos=pos+amount
return dat:sub(pos-amount, pos)
end,function() error("EACCES") end)
end
local function newtaskproxy(task)
local files,siblings,children={},{},{}
if task.fd[0] then files["0"]=task.fd[0].path end
for i,v in ipairs(task.fd) do
files[tostring(i)]=tostring(v.path)
end
for i,v in ipairs(task.siblings) do
siblings[tostring(v.pid)]="/proc/"..tostring(v.pid)
end
for i,v in ipairs(task.children) do
children[tostring(v.pid)]="/proc/"..tostring(v.pid)
end
return {
[".meta"]=strFile(buildMeta({cwd=task.cwd,parent="/proc/"..tostring(task.parent.pid)})),
uid=strFile(task.uid),
comm=strFile(task.name),
fd={
[".meta"]=strFile(buildMeta(files))
},
siblings={
[".meta"]=strFile(buildMeta(siblings))
},
children={
[".meta"]=strFile(buildMeta(children))
}
}
end
function proxy:open(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
if tonumber(steps[1]) then
local task=kernel.tasks[tostring(steps[1])]
local step = newtaskproxy(task)
for i=2, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("open", mode)
end
elseif tostring(steps[1])=="self" then
local task=kernel.currentTask
local step = newtaskproxy(task)
for i=2, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("open", mode)
end
else
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("open", mode)
end
end
error("ENFILE")
end
function proxy:type(path, mode)
local steps = kernel.vfs.splitPath(path)
local step = data
if #steps == 0 then
return "directory"
end
if tonumber(steps[1]) then
local task=kernel.tasks[steps[1]]
if #steps==1 then return "directory" end
local step = newtaskproxy(task)
for i=2, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("type", mode)
end
if type(step[steps[#steps]]) == "table" then
return "directory"
end
elseif tostring(steps[1])=="self" then
local task=kernel.currentTask
if #steps==1 then return "directory" end
local step = newtaskproxy(task)
for i=2, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("type", mode)
end
if type(step[steps[#steps]]) == "table" then
return "directory"
end
else
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENFILE") end
step=dat
end
if type(step[steps[#steps]]) == "function" then
return step[steps[#steps]]("type", mode)
end
if type(step[steps[#steps]]) == "table" then
return "directory"
end
end
error("ENOENT")
end
function proxy:list(path)
local steps = kernel.vfs.splitPath(path)
local step = data
if #steps == 0 then
return table.merge(table.keys(data),table.keys(kernel.tasks),{"self"})
end
if tonumber(steps[1]) then
local task=kernel.tasks[steps[1]]
local step = newtaskproxy(task)
if #steps==1 then return table.keys(step) end
for i=2, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENOENT") end
step=dat
end
if type(step[steps[#steps]]) == "table" then
return table.keys(step[steps[#steps]])
end
elseif tostring(steps[1])=="self" then
local task=kernel.currentTask
local step = newtaskproxy(task)
if #steps==1 then return table.keys(step) end
for i=2, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENOENT") end
step=dat
end
if type(step[steps[#steps]]) == "table" then
return table.keys(step[steps[#steps]])
end
else
for i=1, #steps-1 do
local dat = step[steps[i]]
if type(dat) ~= "table" then error("ENOENT") end
step=dat
end
if type(step[steps[#steps]]) == "table" then
return table.keys(step[steps[#steps]])
end
end
error("ENOENT")
end
function proxy:fileExists(path)
local ok = pcall(function()
return self:type(path)
end)
return ok
end
data.uptime=simpleFile(function()return tostring(kernel.computer:getUptime())end,function()error("EACCES")end)
kernel.procfs={}
kernel.procfs.data=data
kernel.procfs.proxy=proxy
kernel.disks["procfs0000"]=proxy

View File

@@ -0,0 +1,134 @@
--:Minify:--
local kernel = ...
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

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

View File

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

View File

@@ -0,0 +1,556 @@
--:Minify:--
-- Supports:
-- AF_UNIX - local IPC via /var/run/*.sock paths
-- AF_INET - network sockets with three backends:
-- rednet://0.0.B.C or rednet+PROTO://0.0.B.C -> CC rednet (computer B*256+C)
-- modem://0.0.B.C -> raw CC modem frames
-- http://host/path or https://... -> HTTP via CC http API
-- A.B.C.D (dotted quad, non-zero A) -> HTTP
--
-- Socket lifecycle:
-- fd = syscall.socket(domain, socktype) -- "unix"/"inet", "stream"/"dgram"
-- syscall.bind(fd, address) -- server: claim address
-- syscall.listen(fd, backlog) -- server: mark as listening
-- cfd = syscall.accept(fd) -- server: get connected client fd (blocking poll)
-- syscall.connect(fd, address) -- client: connect to server
-- syscall.send(fd, data) -- send bytes
-- syscall.recv(fd, len) -- receive bytes (blocking poll, returns "" on nothing)
-- syscall.sockshutdown(fd) -- half-close send side
-- -- normal vfs.close(fd) closes the socket
local kernel = ...
local sockets = {}
local unixSocks = {}
local nextSockId = 1
local function allocSockId()
local id = nextSockId
nextSockId = nextSockId + 1
return id
end
local function parseAddress(addr)
if not addr then error("EINVAL") end
if addr:sub(1,1) == "/" or addr:sub(1,5) == "unix:" then
local path = addr:sub(1,5) == "unix:" and addr:sub(6) or addr
return { backend="unix", path=path }
end
local rproto, raddr = addr:match("^rednet%+?([^:/]*)://(.+)$")
if raddr then
local a,b,c,d = raddr:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
if not a then error("EINVAL: bad rednet address " .. raddr) end
local compId = tonumber(c)*256 + tonumber(d)
return { backend="rednet", compId=compId,
protocol=(rproto ~= "" and rproto or "hyperion") }
end
local maddr = addr:match("^modem://(.+)$")
if maddr then
local a,b,c,d = maddr:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
if not a then error("EINVAL: bad modem address " .. maddr) end
local compId = tonumber(c)*256 + tonumber(d)
local port = tonumber(maddr:match(":(%d+)$")) or 0
return { backend="modem", compId=compId, port=port }
end
local scheme, rest = addr:match("^(https?)://(.+)$")
if scheme then
return { backend=scheme, url=addr }
end
local a,b,c,d = addr:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)")
if a and tonumber(a) ~= 0 then
return { backend="http", url="http://" .. addr }
end
error("EINVAL: unrecognised address format: " .. tostring(addr))
end
local rednetOpen = false
local function ensureRednet()
if rednetOpen then return end
local rn = kernel.apis and kernel.apis.rednet
if not rn then error("ENODEV: no rednet API available") end
local peripheral = kernel.apis.peripheral
if peripheral then
for _, name in ipairs(peripheral.getNames and peripheral.getNames() or {}) do
if peripheral.getType(name) == "modem" then
pcall(rn.open, name)
end
end
end
rednetOpen = true
end
local function getModem()
local peripheral = kernel.apis and kernel.apis.peripheral
if not peripheral then error("ENODEV") end
for _, name in ipairs(peripheral.getNames and peripheral.getNames() or {}) do
if peripheral.getType(name) == "modem" then
local m = peripheral.wrap(name)
if m then return m, name end
end
end
error("ENODEV: no modem peripheral found")
end
local function pumpEvents()
local ev = kernel.computer:getMachineEvent()
while ev do
if ev == "rednet_message" then
for _, sock in pairs(sockets) do
if sock.backend == "rednet" and sock.bound then
if sock.address.protocol == tostring(select(4, table.unpack({ev}))) or
sock.address.protocol == "hyperion" then
end
end
end
end
ev = kernel.computer:getMachineEvent()
end
end
local function pollEvent()
local results = table.pack(kernel.computer:getMachineEvent())
if results.n == 0 or results[1] == nil then return nil end
return results
end
local function dispatchEvent(ev)
if not ev then return end
local evtype = ev[1]
if evtype == "rednet_message" then
local senderId = ev[2]
local message = ev[3]
local protocol = ev[4] or "hyperion"
for _, sock in pairs(sockets) do
if sock.backend == "rednet" and (sock.listening or sock.connected) then
if sock.address and sock.address.protocol == protocol then
table.insert(sock.rxbuf, { from=senderId, data=message })
end
end
end
elseif evtype == "modem_message" then
local channel = ev[3]
local msg = ev[5]
local fromCh = ev[4]
for _, sock in pairs(sockets) do
if sock.backend == "modem" and sock.modemChannel == channel then
table.insert(sock.rxbuf, { from=fromCh, data=msg })
end
end
elseif evtype == "http_success" then
local url = ev[2]
local handle = ev[3]
for _, sock in pairs(sockets) do
if sock.backend == "http" or sock.backend == "https" then
if sock.pendingUrl == url then
local body = handle.readAll and handle.readAll() or ""
handle.close()
table.insert(sock.rxbuf, { data=body, done=true })
sock.pendingUrl = nil
sock.connected = true
end
end
end
elseif evtype == "http_failure" then
local url = ev[2]
local err = ev[3]
for _, sock in pairs(sockets) do
if (sock.backend == "http" or sock.backend == "https") and
sock.pendingUrl == url then
sock.error = err
sock.pendingUrl = nil
end
end
end
end
local function pumpAll()
local ev = pollEvent()
while ev do
dispatchEvent(ev)
ev = pollEvent()
end
end
local function newSocket(domain, socktype)
local sock = {
id = allocSockId(),
domain = domain, -- "unix" | "inet"
socktype = socktype, -- "stream" | "dgram"
backend = nil,
state = "idle", -- idle | bound | listening | connected | closed
rxbuf = {},
txbuf = {},
backlog = {},
address = nil,
peer = nil,
modemChannel = nil,
modem = nil,
pendingUrl = nil,
bound = false,
listening = false,
connected = false,
error = nil,
}
sockets[sock.id] = sock
return sock
end
local sockSend, sockClose
local function socketToFd(sock)
return {
isSocket = true,
sockId = sock.id,
mode = "rw",
meta = { etype=0, owner=0, group=0, perms=0x1FF, cmeta="" },
type = "socket",
refcount = 1,
handle = {
read = function(count)
pumpAll()
if #sock.rxbuf == 0 then return "" end
local item = table.remove(sock.rxbuf, 1)
local data = type(item) == "table" and (item.data or "") or tostring(item)
if count and #data > count then
table.insert(sock.rxbuf, 1, { data=data:sub(count+1), from=item.from })
data = data:sub(1, count)
end
return data
end,
write = function(data)
if sock.state == "closed" then error("EBADF") end
return sockSend(sock, data)
end,
close = function()
sockClose(sock)
end,
}
}
end
sockSend = function(sock, data)
if sock.backend == "unix" then
local peer = sock.peer
if not peer then error("ENOTCONN") end
table.insert(peer.rxbuf, { data=data })
return #data
elseif sock.backend == "rednet" then
ensureRednet()
local rn = kernel.apis.rednet
rn.send(sock.address.compId, data, sock.address.protocol)
return #data
elseif sock.backend == "modem" then
local modem = sock.modem
if not modem then error("ENOTCONN") end
modem.transmit(sock.address.port, sock.modemChannel or 0, data)
return #data
elseif sock.backend == "http" or sock.backend == "https" then
local http = kernel.apis and kernel.apis.http
if not http then error("ENODEV: no http API") end
local url = sock.address.url
local ok, err = pcall(http.request, url, data, {
["Content-Type"] = "application/octet-stream"
})
if not ok then error("ENETDOWN: " .. tostring(err)) end
sock.pendingUrl = url
return #data
end
error("EPROTONOSUPPORT")
end
sockClose = function(sock)
if sock.state == "closed" then return end
sock.state = "closed"
if sock.backend == "unix" then
if sock.peer then
sock.peer.peer = nil
sock.peer.state = "closed"
end
if sock.bound and sock.address and sock.address.path then
unixSocks[sock.address.path] = nil
end
elseif sock.backend == "modem" and sock.modem and sock.modemChannel then
pcall(sock.modem.close, sock.modemChannel)
elseif sock.backend == "rednet" then
end
sockets[sock.id] = nil
end
kernel.syscalls["socket"] = function(domain, socktype)
domain = domain or "inet"
socktype = socktype or "stream"
if domain ~= "unix" and domain ~= "inet" then error("EAFNOSUPPORT") end
if socktype ~= "stream" and socktype ~= "dgram" then error("EPROTOTYPE") end
local sock = newSocket(domain, socktype)
local fdobj = socketToFd(sock)
local fd = kernel.vfs.newfd(fdobj)
return fd
end
kernel.syscalls["bind"] = function(fd, address)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock then error("EBADF") end
if sock.bound then error("EINVAL") end
local parsed = parseAddress(address)
if parsed.backend == "unix" then
local existing = unixSocks[parsed.path]
if existing then
if existing.state == "closed" then
unixSocks[parsed.path] = nil
else
error("EADDRINUSE")
end
end
sock.backend = "unix"
sock.address = parsed
sock.bound = true
sock.state = "bound"
unixSocks[parsed.path] = sock
elseif parsed.backend == "rednet" then
ensureRednet()
sock.backend = "rednet"
sock.address = parsed
sock.bound = true
sock.state = "bound"
elseif parsed.backend == "modem" then
local modem, side = getModem()
sock.backend = "modem"
sock.address = parsed
sock.modem = modem
sock.modemChannel = parsed.port
sock.bound = true
sock.state = "bound"
modem.open(parsed.port)
else
error("EOPNOTSUPP: cannot bind to " .. parsed.backend .. " address")
end
end
kernel.syscalls["listen"] = function(fd, backlog)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock then error("EBADF") end
if not sock.bound then error("EDESTADDRREQ") end
sock.listening = true
sock.state = "listening"
sock.maxBacklog = backlog or 5
end
kernel.syscalls["accept"] = function(fd)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock then error("EBADF") end
if not sock.listening then error("EINVAL") end
local deadline = kernel.computer:time() + 30000
while #sock.backlog == 0 do
pumpAll()
if kernel.computer:time() > deadline then error("ETIMEDOUT") end
coroutine.yield()
end
local clientSock = table.remove(sock.backlog, 1)
local cfdobj = socketToFd(clientSock)
local newfd = kernel.vfs.newfd(cfdobj)
return newfd
end
kernel.syscalls["connect"] = function(fd, address)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock then error("EBADF") end
if sock.connected then error("EISCONN") end
local parsed = parseAddress(address)
sock.address = parsed
sock.backend = parsed.backend
if parsed.backend == "unix" then
local server = unixSocks[parsed.path]
if not server then error("ECONNREFUSED") end
if not server.listening then error("ECONNREFUSED") end
if #server.backlog >= (server.maxBacklog or 5) then error("ECONNREFUSED") end
local serverPeer = newSocket("unix", sock.socktype)
serverPeer.backend = "unix"
serverPeer.connected = true
serverPeer.state = "connected"
serverPeer.peer = sock
sock.peer = serverPeer
sock.connected = true
sock.state = "connected"
table.insert(server.backlog, serverPeer)
elseif parsed.backend == "rednet" then
ensureRednet()
sock.connected = true
sock.state = "connected"
elseif parsed.backend == "modem" then
local modem, side = getModem()
local replyChannel = math.random(1024, 65534)
sock.modem = modem
sock.modemChannel = replyChannel
sock.connected = true
sock.state = "connected"
modem.open(replyChannel)
elseif parsed.backend == "http" or parsed.backend == "https" then
sock.connected = true
sock.state = "connected"
else
error("EAFNOSUPPORT")
end
end
kernel.syscalls["send"] = function(fd, data)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock then error("EBADF") end
return sockSend(sock, data)
end
kernel.syscalls["recv"] = function(fd, maxlen, timeout_ms)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock then error("EBADF") end
local deadline = kernel.computer:time() + (timeout_ms or 10000)
while #sock.rxbuf == 0 do
pumpAll()
if #sock.rxbuf > 0 then break end
if sock.state == "closed" or sock.error then
if sock.error then error("ECONNRESET: " .. tostring(sock.error)) end
return ""
end
if kernel.computer:time() > deadline then return "" end
coroutine.yield()
end
local item = table.remove(sock.rxbuf, 1)
local data = type(item) == "table" and (item.data or "") or tostring(item)
if maxlen and #data > maxlen then
table.insert(sock.rxbuf, 1, { data=data:sub(maxlen+1), from=item and item.from })
data = data:sub(1, maxlen)
end
return data
end
kernel.syscalls["sockshutdown"] = function(fd)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if sock then sockClose(sock) end
end
kernel.syscalls["getpeername"] = function(fd)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock or not sock.connected then error("ENOTCONN") end
if sock.address then return sock.address end
return nil
end
kernel.syscalls["getsockname"] = function(fd)
local task = kernel.currentTask
local fdobj = task.fd[fd]
if not fdobj or not fdobj.isSocket then error("ENOTSOCK") end
local sock = sockets[fdobj.sockId]
if not sock then error("EBADF") end
return sock.address
end
kernel.syscalls["httpget"] = function(url, headers)
local http = kernel.apis and kernel.apis.http
if not http then error("ENODEV: no http API") end
local ok, err = pcall(http.request, url, nil, headers)
if not ok then error("ENETDOWN: " .. tostring(err)) end
local deadline = kernel.computer:time() + 15000
while true do
local ev = pollEvent()
if ev then
if ev[1] == "http_success" and ev[2] == url then
local handle = ev[3]
local body = handle.readAll and handle.readAll() or ""
handle.close()
return body
elseif ev[1] == "http_failure" and ev[2] == url then
error("ECONNREFUSED: " .. tostring(ev[3]))
else
dispatchEvent(ev)
end
end
if kernel.computer:time() > deadline then error("ETIMEDOUT") end
coroutine.yield()
end
end
kernel.syscalls["resolve"] = function(hostname)
if hostname:match("^%d+%.%d+%.%d+%.%d+$") then return hostname end
local a,b,c,d = hostname:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
if a and tonumber(a) == 0 and tonumber(b) == 0 then
return hostname
end
local http = kernel.apis and kernel.apis.http
if not http then error("ENODEV: no http API for DNS") end
local url = "https://cloudflare-dns.com/dns-query?name=" .. hostname .. "&type=A"
local body = kernel.syscalls["httpget"](url, {
["Accept"] = "application/dns-json"
})
local ip = body:match('"type":1[^}]*"data":"([%d%.]+)"')
if not ip then error("ENOENT: could not resolve " .. hostname) end
return ip
end
kernel.sockets = sockets
kernel.unixSockets = unixSocks
kernel.log("Loaded socket module")

View File

@@ -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,621 @@
--:Minify:--
local kernel = ...
local auth = {}
kernel.auth = auth
-- PASSWD FILE FORMAT: uid:gid:username:homedir:shell
-- SHADOW FILE FORMAT: uid:salt:hash
local function getFile(path)
local file = kernel.vfs.open(path, "r")
if not file then error("Failed to open file: " .. path) end
local content = kernel.vfs.read(file, 1024000)
kernel.vfs.close(file)
return content
end
local function writeFile(path, content)
local file = kernel.vfs.open(path, "w")
if not file then error("Failed to open file for writing: " .. path) end
kernel.vfs.write(file, content)
kernel.vfs.close(file)
end
local blake2s
do
local MOD32 = 2^32
local function norm(x) return x % MOD32 end
local function tobits(x)
x = norm(x)
local t = {}
for i = 0, 31 do local b = x % 2; t[i] = b; x = (x - b) / 2 end
return t
end
local function frombits(t)
local x, p = 0, 1
for i = 0, 31 do if t[i] == 1 then x = x + p end; p = p * 2 end
return norm(x)
end
local function bor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0 end
end
return frombits(bits)
end
local function bxor(...)
local args = {...}
if #args == 0 then return 0 end
local bits = tobits(args[1])
for i = 2, #args do
local b = tobits(args[i])
for j = 0, 31 do bits[j] = (bits[j] ~= b[j]) and 1 or 0 end
end
return frombits(bits)
end
local function lshift(x, n) return norm(norm(x) * 2^n) end
local function rshift(x, n) return math.floor(norm(x) / 2^n) end
local function rotr(x, n) return bor(rshift(x, n), lshift(x, 32 - n)) end
local IV = {
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
}
local SIGMA = {
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
{11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
{7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
{9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
{2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
{12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
{13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0}
}
local function G(v, a, b, c, d, x, y)
v[a] = (v[a] + v[b] + x) % MOD32
v[d] = rotr(bxor(v[d], v[a]), 16)
v[c] = (v[c] + v[d]) % MOD32
v[b] = rotr(bxor(v[b], v[c]), 12)
v[a] = (v[a] + v[b] + y) % MOD32
v[d] = rotr(bxor(v[d], v[a]), 8)
v[c] = (v[c] + v[d]) % MOD32
v[b] = rotr(bxor(v[b], v[c]), 7)
end
local function compress(h, block, t, last)
local v = {}
for i = 1, 8 do v[i] = h[i] end
for i = 1, 8 do v[i + 8] = IV[i] end
v[13] = bxor(v[13], t)
if last then v[15] = bxor(v[15], 0xFFFFFFFF) end
local m = {}
for i = 0, 15 do
local p = i * 4 + 1
m[i] = (block:byte(p) or 0)
+ ((block:byte(p+1) or 0) * 0x100)
+ ((block:byte(p+2) or 0) * 0x10000)
+ ((block:byte(p+3) or 0) * 0x1000000)
end
for r = 1, 10 do
local s = SIGMA[r]
G(v,1,5,9,13, m[s[1]], m[s[2]])
G(v,2,6,10,14, m[s[3]], m[s[4]])
G(v,3,7,11,15, m[s[5]], m[s[6]])
G(v,4,8,12,16, m[s[7]], m[s[8]])
G(v,1,6,11,16, m[s[9]], m[s[10]])
G(v,2,7,12,13, m[s[11]], m[s[12]])
G(v,3,8,9,14, m[s[13]], m[s[14]])
G(v,4,5,10,15, m[s[15]], m[s[16]])
end
for i = 1, 8 do h[i] = bxor(h[i], v[i], v[i+8]) end
end
function blake2s(msg, key)
key = key or ""
local h = {}
for i = 1, 8 do h[i] = IV[i] end
local outlen = 32
h[1] = bxor(h[1], 0x01010000 + lshift(#key, 8) + outlen)
local t = 0
if #key > 0 then
local block = key .. string.rep("\0", 64 - #key)
t = #key
compress(h, block, t, false)
end
for i = 1, #msg, 64 do
local block = msg:sub(i, i + 63)
if #block < 64 then block = block .. string.rep("\0", 64 - #block) end
t = t + math.min(64, #msg - i + 1)
compress(h, block, t, i + 64 > #msg)
end
local out = ""
for i = 1, 8 do out = out .. string.format("%08x", h[i]) end
return out
end
end
if not blake2s then error("Failed to load blake2s") end
if not kernel.vfs.exists("/etc/pam.d/secret") then
kernel.log("PAM SECRET REGENERATING PLEASE USE ROOT")
local key = ""
for i = 1, 256 do key = key .. string.char(math.random(0, 255)) end
local handle = kernel.vfs.open("/etc/pam.d/secret", "w")
kernel.vfs.write(handle, key)
kernel.vfs.close(handle)
end
local pepper = getFile("/etc/pam.d/secret")
local function genSalt()
local chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
local s = ""
for i = 1, 16 do
s = s .. chars:sub(math.random(1, #chars), math.random(1, #chars))
end
return s
end
local function hashPassword(password, salt)
local key = (pepper .. salt):sub(1, 32)
return blake2s(password, key)
end
local passwdFile = getFile("/etc/passwd")
local shadowFile = getFile("/etc/shadow")
local passwdLines = string.split(passwdFile, "\n")
local shadowLines = string.split(shadowFile, "\n")
local passwd, shadow = {}, {}
for _, v in ipairs(passwdLines) do
local fields = string.split(v, ":")
if fields[1] and fields[1] ~= "" then
passwd[#passwd + 1] = fields
end
end
for _, v in ipairs(shadowLines) do
local fields = string.split(v, ":")
if fields[1] and fields[1] ~= "" then
shadow[#shadow + 1] = fields
end
end
for _, v in ipairs(passwd) do
local uid = tonumber(v[1])
if uid then kernel.users[uid] = v[3] end
end
kernel.passwd = passwd
local function flushPasswd()
local lines = {}
for _, v in ipairs(passwd) do
lines[#lines + 1] = table.concat(v, ":")
end
writeFile("/etc/passwd", table.concat(lines, "\n"))
end
local function flushShadow()
local lines = {}
for _, v in ipairs(shadow) do
lines[#lines + 1] = table.concat(v, ":")
end
writeFile("/etc/shadow", table.concat(lines, "\n"))
end
local function getPasswdByUID(uid)
for _, v in ipairs(passwd) do
if tonumber(v[1]) == uid then return v end
end
return nil
end
local function getShadowByUID(uid)
for _, v in ipairs(shadow) do
if tonumber(v[1]) == uid then return v end
end
return nil
end
local function getPasswdByUsername(username)
for _, v in ipairs(passwd) do
if v[3] == username then return v end
end
return nil
end
local function nextUID()
local max = 999
for _, v in ipairs(passwd) do
local uid = tonumber(v[1])
if uid and uid >= 1000 and uid > max then max = uid end
end
return max + 1
end
function auth.login(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,495 @@
--: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 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", 2)
elseif err then
kernel.log("Task " .. tostring(id) .. " exited with code: " .. tostring(err), "INFO")
else
kernel.log("Task " .. tostring(id) .. " exited without code", "INFO")
end
end
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), "INFO"
)
end
return createTask(func, name or path, envars, args, tgid, real_uid, euid)
end
function sys.exec(path, args, envars)
local task = kernel.currentTask
local func, euid, _ = loadExecutable(path, kernel._U)
if task.fd then
for fd, _ in pairs(task.fd) do
if fd > 2 then pcall(kernel.vfs.close, fd) end
end
end
task.euid = euid
task.args = args or {}
task.envars = envars or task.envars
task.name = path
task.coro = coroutine.create(function()
local ok, err = xpcall(func, debug.traceback, table.unpack(task.args))
if kernel.config.logTaskExit then
if not ok then
kernel.log("Task " .. tostring(task.pid) .. " exec '" .. path .. "' err: " .. tostring(err), "ERROR", 2)
else
kernel.log("Task " .. tostring(task.pid) .. " exec '" .. path .. "' exited: " .. tostring(err), "INFO")
end
end
if type(err) == "number" then tasks[tostring(task.pid)].exit = err end
if tasks[tostring(task.pid)].fd then
for fd, _ in pairs(tasks[tostring(task.pid)].fd) do
pcall(kernel.vfs.close, fd)
end
end
tasks[tostring(task.pid)].status = "Z"
end)
task.syscallReturn = {}
coroutine.yield()
end
function sys.sleep(s)
kernel.currentTask.status = "S"
kernel.currentTask.sleep = kernel.computer:time() + s * 1000
coroutine.yield()
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)
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"
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" then
return false, "Cannot stop non-running task"
else
task.status = "T"
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" then
return false, "Task is not stopped"
else
task.status = "R"
return true
end
end
function sys.getpid() return kernel.currentTask.pid end
function sys.getppid() return kernel.currentTask.parent.pid end
function sys.getTasks()
local ret = {}
for _, 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), "INFO")
else
kernel.log("Task " .. tostring(task.pid) .. " exited without code", "INFO")
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
task.coro = nil
task.ivs = nil
task.vs = nil
task.args = nil
task.envars = nil
task.cwd = nil
task.numRuns = nil
task.totalTime = nil
task.lastTime = nil
task.timeSlice = nil
task.syscallReturn = nil
task.sleep = nil
task.fd = nil
task.reapTime = kernel.computer:time() + 30000
elseif task.reapTime and kernel.computer:time() > task.reapTime
and task.status == "Z" then
for _, child in ipairs(task.children) do
child.parent = tasks["1"]
child.siblings = tasks["1"].children
table.insert(tasks["1"].children, child)
end
for i, sibling in ipairs(task.siblings) do
if sibling.pid == task.pid then
table.remove(task.siblings, i)
break
end
end
tasks[pid] = nil
end
end
end
local alpha = 0.85
local C_target = 0.01
local Tmin = 0.0005
local Tmax = 0.5
local lambda_budget = 0.08
local lambda_clamp = 0.03
local lambda_var = 0.02
local k_min = 0.5
local k_max = 0.5
local B = 0.01
function kernel.main()
while not kernel.exitMain do
local N = 0
local Tmin_hit = 0
local Tmax_hit = 0
local totalTaskTime = 0
local taskTimes = {}
for pid, task in pairs(tasks) do
if task.status == "S" and kernel.computer:time() >= task.sleep then
task.status = "R"
task.sleep = 0
end
if task.status == "R" then
kernel.currentTask = task
kernel.uid = task.euid or task.uid
kernel.process = task.name
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 sigret = { coroutine.resume(coro, table.remove(task.sigq, 1)) }
while coroutine.status(coro) ~= "dead" do
if sigret[1] == false then break end
if sigret[2] == "syscall" then
local scname = sigret[3]
local sysret
if kernel.syscalls[scname] then
sysret = { xpcall(kernel.syscalls[scname], debug.traceback, table.unpack(sigret, 4)) }
else
sysret = { false, "Unknown syscall: " .. tostring(scname) }
end
if not sysret[1] then
sigret = { coroutine.resume(coro, false, sysret[2]) }
else
sigret = { coroutine.resume(coro, true, table.unpack(sysret, 2)) }
end
else
sigret = { coroutine.resume(coro) }
end
end
end
if task.status == "R" then
local startTime = kernel.computer:time()
local ret
if kernel.config.preempt then
ret = { resumeWithTimeout(task.coro, task.timeSlice, table.unpack(task.syscallReturn)) }
else
ret = { coroutine.resume(task.coro, table.unpack(task.syscallReturn)) }
end
local elapsed = kernel.computer:time() - startTime
task.lastTime = elapsed
task.totalTime = (task.totalTime or 0) + elapsed
task.numRuns = (task.numRuns or 0) + 1
taskTimes[#taskTimes+1] = elapsed
totalTaskTime = totalTaskTime + elapsed
if elapsed <= Tmin then Tmin_hit = Tmin_hit + 1 end
if elapsed >= Tmax then Tmax_hit = Tmax_hit + 1 end
if ret[1] == "error" or ret[1] == false then
kernel.log("processHandlerException: " .. tostring(ret[2]), "ERROR", 2)
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", 5)
for i = 4, #ret do
kernel.log(" inval[" .. (i-3) .. "] = " .. tostring(ret[i]), "DBUG", 5)
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", 2)
else
kernel.log("Task " .. task.pid .. " syscall " .. scname .. " ok, " .. (#sysret-1) .. " retvals", "DBUG", 5)
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", 5)
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()
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,16 @@
--: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_read"]=function(pin)
if kernel.gpio[pin] then
return kernel.gpio[pin]("r")
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,52 @@
--: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")
local data = kernel.vfs.read(handle, 1024 * 1024 * 4)
kernel.vfs.close(handle)
local initFunc, err = load(data, "@sysinit", "t", kernel._U)
if not initFunc then error("Failed to load init system: " .. err) end
kernel.tasks["1"] = {
coro = coroutine.create(function()
local ok, err = xpcall(initFunc, debug.traceback, kernel)
if not ok then
kernel.panic("Init system crashed: " .. tostring(err))
else
kernel.panic("Init system exited: " .. tostring(err))
end
end),
name = "sysinit",
status = "R",
pid = 1,
tgid = 1,
uid = 0,
fd = {},
envars = {},
args = {},
exit = "",
sleep = 0,
ivs = 0,
vs = 0,
parent = kernel.kernelTask,
siblings = kernel.kernelTask.children,
children = {},
syscallReturn = {},
cwd = "/",
timeSlice = 0,
lastTime = 0,
totalTime = 0,
numRuns = 0
}
kernel.log("created init task with PID 1")
kernel.log("Initializing init system...")

View File

@@ -0,0 +1,9 @@
--:Minify:--
local kernel = ...
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", 2)
end
end

View File

@@ -0,0 +1,120 @@
--:Minify:--
local kernel = ...
local P = kernel.vfs.P
local PERM = kernel.vfs.PERM
local RW_R_R = P.OWNER_R + P.OWNER_W + P.GROUP_R + P.WORLD_R
local RWX_RX_RX = P.OWNER_R + P.OWNER_W + P.OWNER_X
+ P.GROUP_R + P.GROUP_X
+ P.WORLD_R + P.WORLD_X
local RW_R__ = P.OWNER_R + P.OWNER_W + P.GROUP_R
local RW____ = P.OWNER_R + P.OWNER_W
local RWXRWXRWX = PERM.RWXRWXRWX
local SUID_755 = PERM.SUID_755
local META_VERSION = 0x02
local rootDisk = kernel.disks["$"]
local function makeEntry(name, etype, owner, group, perms, cmeta)
cmeta = cmeta or ""
local plo = perms % 256
local phi = math.floor(perms / 256) % 256
local olo = (owner or 0) % 256
local ohi = math.floor((owner or 0) / 256) % 256
local glo = (group or 0) % 256
local ghi = math.floor((group or 0) / 256) % 256
return string.char(#name) .. name
.. string.char(etype, olo, ohi, glo, ghi, plo, phi)
.. string.char(#cmeta) .. cmeta
end
local REG = 0x00
local function mergeMeta(dir, entries)
local diskDir = dir
if diskDir:sub(1,1) == "/" then diskDir = diskDir:sub(2) end
local metaPath = (diskDir == "" and ".meta" or diskDir .. "/.meta")
local existing = {}
local rok, rf = pcall(function() return rootDisk:open(metaPath, "r") end)
if rok and rf then
local raw = rf.read(65535)
if rf.close then rf.close() end
existing = (kernel.vfs._parseMetafile and kernel.vfs._parseMetafile(raw)) or {}
end
for _, e in ipairs(entries) do
local name = e[1]
local etype = e[2] or REG
local owner = e[3] or 0
local group = e[4] or 0
local perms = e[5] or RWX_RX_RX
local cmeta = e[6] or ""
existing[name] = {
etype = etype,
owner = owner,
group = group,
perms = perms,
cmeta = cmeta,
}
end
local data = string.char(META_VERSION)
for name, m in pairs(existing) do
data = data .. makeEntry(
name,
m.etype or REG,
m.owner or 0,
m.group or 0,
m.perms or RWX_RX_RX,
m.cmeta or ""
)
end
local ok, err = pcall(function()
local f = rootDisk:open(metaPath, "w")
f.write(data)
f.close()
end)
if not ok then
kernel.log("permissions: failed to write " .. metaPath .. ": " .. tostring(err), "WARN", 8)
end
end
if kernel.firstBoot then
kernel.log("Seeding filesystem permissions...")
mergeMeta("/", {
{"bin", REG, 0, 0, RWX_RX_RX},
{"boot", REG, 0, 0, RWX_RX_RX},
{"dev", REG, 0, 0, RWXRWXRWX},
{"etc", REG, 0, 0, RWX_RX_RX},
{"home", REG, 0, 0, RWX_RX_RX},
{"lib", REG, 0, 0, RWX_RX_RX},
{"root", REG, 0, 0, RW____ },
{"sbin", REG, 0, 0, RWX_RX_RX},
{"tmp", REG, 0, 0, RWXRWXRWX},
{"usr", REG, 0, 0, RWX_RX_RX},
{"var", REG, 0, 0, RWXRWXRWX},
{"opt", REG, 0, 0, RWX_RX_RX},
})
mergeMeta("/bin", {
{"login", REG, 0, 0, SUID_755 },
{"su", REG, 0, 0, SUID_755 },
{"sudo", REG, 0, 0, SUID_755 },
})
mergeMeta("/etc", {
{"passwd", REG, 0, 0, RW_R_R },
{"shadow", REG, 0, 0, RW____ },
{"pam.d", REG, 0, 0, RW____ },
})
mergeMeta("/etc/pam.d", {
{"secret", REG, 0, 0, RW____},
})
kernel.log("Filesystem permissions seeded.")
end

View File

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

137
Src/bit32/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

3544
Src/deflate/lib/LibDeflate Normal file

File diff suppressed because it is too large Load Diff

9
Src/deflate/lib/deflate Normal file
View File

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

162
Src/hysh/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/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/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/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/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

309
Src/hysh/bin/help Normal file
View File

@@ -0,0 +1,309 @@
--: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="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", 16)
syscall.devctl(1, "sbgc", 13)
local pct = math.floor(math.min(100, (scroll + pageSize) / totalLines * 100))
local status = string.format(" help -- line %d/%d (%d%%) [up/down: scroll q: quit] ",
scroll + 1, totalLines, pct)
if #status > screenW then status = status:sub(1, screenW) end
syscall.devctl(1, "spos", 1, screenH)
syscall.write(1, status .. string.rep(" ", screenW - #status))
syscall.devctl(1, "sfgc", 1)
syscall.devctl(1, "sbgc", 16)
dirty = false
end
if totalLines <= pageSize then
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
for _, line in ipairs(lines) do
syscall.devctl(1, "sfgc", line[2])
syscall.write(1, line[1] .. "\n")
end
syscall.devctl(1, "sfgc", 1)
return
end
render()
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
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", 1)
syscall.devctl(1, "sbgc", 16)

100
Src/hysh/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 = 5 -- cyan
local C_WHITE = 1 -- white
local C_LABEL = 13 -- light grey (key names)
local C_SEP = 12 -- dark grey (---- separator)
local C_USER = 3 -- green (user@host)
local function c(col) syscall.devctl(1, "sfgc", col) end
local username = syscall.getUsername() or "Unknown"
local hostname = syscall.getHostname() or "Unknown"
local userhost = username .. "@" .. hostname
local function formatUptime(ms)
local s = math.floor(ms / 1000)
local m = math.floor(s / 60)
local h = math.floor(m / 60)
local d = math.floor(h / 24)
s = s % 60; m = m % 60; h = h % 24
local parts = {}
if d > 0 then parts[#parts+1] = d .. "d" end
if h > 0 then parts[#parts+1] = h .. "h" end
if m > 0 then parts[#parts+1] = m .. "m" end
parts[#parts+1] = s .. "s"
return table.concat(parts, " ")
end
local host_str = syscall.getHost() or "Unknown"
local cc_ver = host_str:match("ComputerCraft ([%d%.]+)") or host_str
local info = {
{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

1226
Src/hysh/bin/hysh Normal file

File diff suppressed because it is too large Load Diff

19
Src/hysh/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/bin/ll Normal file
View File

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

96
Src/hysh/bin/ln Normal file
View File

@@ -0,0 +1,96 @@
--:Minify:--
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { s = false, f = 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]... TARGET LINK_NAME")
print(" " .. name .. " [OPTION]... TARGET... DIRECTORY")
print("Create links between files.")
print("")
print("Options:")
print(" -s make symbolic links instead of hard links")
print(" -f remove existing destination files")
print(" --help display this help and exit")
print("")
print("With no -s, hard links are not supported (filesystem limitation).")
print("Use -s for symbolic links.")
return
end
if #args < 2 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
if not cloptions.s then
print(name .. ": hard links are not supported; use -s for symbolic links")
syscall.exit(1); return
end
local dest = args[#args]
local destDir = syscall.type(dest) == "directory"
local function cwd()
local d = syscall.getcwd()
if d:sub(-1) ~= "/" then d = d .. "/" end
return d
end
local function absPath(p)
if p:sub(1,1) ~= "/" then p = cwd() .. p end
return p
end
for i = 1, #args - 1 do
local target = args[i]
local linkPath
if destDir then
local basename = target:match("[^/]+$") or target
linkPath = absPath(dest)
if linkPath:sub(-1) ~= "/" then linkPath = linkPath .. "/" end
linkPath = linkPath .. basename
else
linkPath = absPath(dest)
end
if cloptions.f and syscall.exists(linkPath) then
local ok, err = pcall(syscall.remove, linkPath)
if not ok then
print(name .. ": cannot remove '" .. linkPath .. "': " .. tostring(err))
syscall.exit(1); return
end
end
local ok, err = pcall(syscall.symlink, target, linkPath)
if not ok then
print(name .. ": failed to create symlink '" .. linkPath .. "' -> '" .. target .. "': " .. tostring(err))
syscall.exit(1); return
end
end

170
Src/hysh/bin/login Normal file
View File

@@ -0,0 +1,170 @@
--:Minify:--
syscall.open("/dev/tty/1", "r") --stdin (fd 0)
syscall.open("/dev/tty/1", "w") --stdout (fd 1)
syscall.open("/dev/null", "w") --stderr (fd 2)
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", 3)
syscall.write(1, "HyperionOS First Boot Setup\n")
syscall.devctl(1, "sfgc", 1)
syscall.write(1, "No root password is set. Please create one now.\n\n")
while true do
syscall.write(1, "New root password: ")
local pw1 = readLine("*")
syscall.write(1, "Confirm password: ")
local pw2 = readLine("*")
if pw1 ~= pw2 then
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Passwords do not match. Try again.\n\n")
syscall.devctl(1, "sfgc", 1)
elseif #pw1 < 6 then
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Password too short (minimum 6 characters).\n\n")
syscall.devctl(1, "sfgc", 1)
else
local ok, err = syscall.setpassword(0, pw1)
if ok then
syscall.devctl(1, "sfgc", 3)
syscall.write(1, "Root password set.\n\n")
syscall.devctl(1, "sfgc", 1)
sleep(0.5)
break
else
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Error: " .. tostring(err) .. "\n")
syscall.devctl(1, "sfgc", 1)
end
end
end
end
local function spawnShell(username, uid, shell, homedir)
local 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", 1)
syscall.devctl(1, "sbgc", 16)
syscall.devctl(1, "spos", 1, 1)
local hostname = syscall.getHostname() or "hyperion"
syscall.write(1, "HyperionOS\n")
syscall.write(1, hostname .. " login\n\n")
local attempts = 0
while attempts < MAX_ATTEMPTS do
syscall.devctl(1, "sfgc", 1)
syscall.write(1, "Username: ")
local username = readLine(nil)
if username == "" then goto continue end
syscall.write(1, "Password: ")
local password = readLine("*")
local 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", 3)
syscall.write(1, "\nWelcome, " .. username .. "!\n")
syscall.devctl(1, "sfgc", 1)
sleep(0.3)
spawnShell(username, uid, shell, homedir)
return -- back to login prompt
else
attempts = attempts + 1
sleep(1)
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Login incorrect.\n\n")
syscall.devctl(1, "sfgc", 1)
end
::continue::
end
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Maximum login attempts exceeded.\n")
syscall.devctl(1, "sfgc", 1)
sleep(5)
end
firstBoot()
while true do
doLogin()
end

157
Src/hysh/bin/loimgcreate Normal file
View File

@@ -0,0 +1,157 @@
--:Minify:--
-- Usage:
-- loimgcreate <srcdir> <image.hfs> create image from directory
-- loimgcreate -x <image.hfs> <dest> extract image back to a directory
-- loimgcreate --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { x=false, help=false }
for _, v in ipairs({...}) do
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: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.." <srcdir> <image.hfs>")
print(" "..name.." -x <image.hfs> <destdir>")
print("")
print("Pack a directory into a portable HFS image file, or extract one.")
print("")
print(" <srcdir> <image.hfs> recursively pack srcdir into image.hfs")
print(" -x <image.hfs> <dest> extract image.hfs into dest (created if needed)")
print("")
print("HFS images can be mounted with:")
print(" mount -o loop /path/to/image.hfs /mnt/point")
print("")
print("Requires root.")
return
end
local fs = require("fs")
if opts.x then
if #args < 2 then
print(name..": -x requires <image.hfs> and <destdir>")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
local imgPath = args[1]
local destPath = args[2]
if imgPath:sub(1,1) ~= "/" then imgPath = syscall.getcwd().."/"..imgPath end
if destPath:sub(1,1) ~= "/" then destPath = syscall.getcwd().."/"..destPath end
local tmpMnt = "/tmp/._loimgcreate_"..tostring(math.random(100000,999999))
local ok1, loopId = pcall(syscall.losetup, imgPath, true)
if not ok1 then
print(name..": losetup: "..tostring(loopId)); syscall.exit(1); return
end
local ok2, merr = pcall(syscall.mount, tmpMnt, loopId)
if not ok2 then
pcall(syscall.lodetach, loopId)
print(name..": mount: "..tostring(merr)); syscall.exit(1); return
end
if not fs.isDir(destPath) then
local ok3, derr = pcall(syscall.mkdir, destPath)
if not ok3 then
pcall(syscall.umount, tmpMnt); pcall(syscall.lodetach, loopId)
print(name..": mkdir '"..args[2].."': "..tostring(derr))
syscall.exit(1); return
end
end
local count = 0
local function copyTree(src, dst)
local entries = fs.list(src)
if not entries then return end
for _, ent in ipairs(entries) do
local srcFull = src:gsub("/$","").."/"..ent
local dstFull = dst:gsub("/$","").."/"..ent
if fs.isDir(srcFull) then
pcall(syscall.mkdir, dstFull)
copyTree(srcFull, dstFull)
else
local ok, rfd = pcall(syscall.open, srcFull, "r")
if ok then
local ok2, wfd = pcall(syscall.open, dstFull, "w")
if ok2 then
local ok3, data = pcall(syscall.read, rfd, 65536*16)
if ok3 and data then pcall(syscall.write, wfd, data) end
pcall(syscall.close, wfd)
count = count + 1
end
pcall(syscall.close, rfd)
end
end
end
end
copyTree(tmpMnt, destPath)
pcall(syscall.umount, tmpMnt)
pcall(syscall.lodetach, loopId)
syscall.devctl(1, "sfgc", 10)
print(name..": extracted "..count.." file(s) to "..destPath)
syscall.devctl(1, "sfgc", 1)
return
end
if #args < 2 then
print(name..": missing operands — need <srcdir> and <image.hfs>")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
local srcPath = args[1]
local imgPath = args[2]
if srcPath:sub(1,1) ~= "/" then srcPath = syscall.getcwd().."/"..srcPath end
if imgPath:sub(1,1) ~= "/" then imgPath = syscall.getcwd().."/"..imgPath end
if not fs.isDir(srcPath) then
print(name..": '"..args[1].."': not a directory")
syscall.exit(1); return
end
local ok, imgStr = pcall(syscall.loimgcreate, srcPath)
if not ok then
local msg = tostring(imgStr)
if msg:find("EPERM") then msg = "Permission denied"
elseif msg:find("ENOTDIR") then msg = "'"..args[1].."': not a directory" end
print(name..": "..msg); syscall.exit(1); return
end
local ok2, werr = pcall(syscall.loimgwrite, imgStr, imgPath)
if not ok2 then
print(name..": write '"..args[2].."': "..tostring(werr))
syscall.exit(1); return
end
local lineCount = 0
for _ in imgStr:gmatch("\n") do lineCount = lineCount + 1 end
local byteCount = #imgStr
syscall.devctl(1, "sfgc", 10)
print(name..": image written to "..imgPath)
syscall.devctl(1, "sfgc", 14)
print(string.format(" %d records, %d bytes", lineCount - 1, byteCount))
syscall.devctl(1, "sfgc", 1)

129
Src/hysh/bin/losetup Normal file
View File

@@ -0,0 +1,129 @@
--:Minify:--
-- Usage:
-- losetup <path> attach directory or .hfs image; print loop id
-- losetup -d <id> detach loop device
-- losetup -l list attached loop devices
-- losetup -i <path> force image mode (even without .hfs extension)
-- losetup --help
local name = syscall.getTask(syscall.getpid()).name
local args, opts = {}, { d=false, l=false, i=false, help=false }
for _, v in ipairs({...}) do
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: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.." <path>")
print(" "..name.." -i <path>")
print(" "..name.." -d <id>")
print(" "..name.." -l")
print("")
print("Manage loop devices.")
print("")
print(" <path> attach a directory (bind) or .hfs image file")
print(" -i <path> force image mode for the given file")
print(" -d <id> detach loop device by id (must be unmounted first)")
print(" -l list all currently attached loop devices")
print("")
print("Requires root. Loop device ids look like loop0, loop1, …")
return
end
if opts.l then
local ok, devs = pcall(syscall.lolist)
if not ok then
print(name..": "..tostring(devs)); syscall.exit(1); return
end
local any = false
local ids = {}
for id in pairs(devs) do ids[#ids+1] = id end
table.sort(ids)
for _, id in ipairs(ids) do
any = true
local info = devs[id]
local mode = (type(info) == "table" and info.mode) or "bind"
local path = (type(info) == "table" and info.path) or tostring(info)
local colour = mode == "image" and 5 or 4
syscall.devctl(1, "sfgc", 3)
printInline(string.format("%-10s", id))
syscall.devctl(1, "sfgc", colour)
printInline(string.format("%-7s", "["..mode.."]"))
syscall.devctl(1, "sfgc", 1)
print(" "..path)
end
if not any then
syscall.devctl(1, "sfgc", 14)
print(name..": no loop devices attached")
syscall.devctl(1, "sfgc", 1)
end
return
end
if opts.d then
if #args < 1 then
print(name..": -d 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 = "device '"..id.."' is still mounted, unmount first"
end
print(name..": "..msg); syscall.exit(1); return
end
syscall.devctl(1, "sfgc", 10)
print(name..": detached "..id)
syscall.devctl(1, "sfgc", 1)
return
end
if #args < 1 then
print(name..": missing path operand")
print("try '"..name.." --help' for more information.")
syscall.exit(1); return
end
local path = args[1]
if path:sub(1,1) ~= "/" then
path = syscall.getcwd().."/"..path
end
local ftype = syscall.type and syscall.type(path)
if not (ftype == "file" or ftype == "directory") then
print(name..": '"..args[1].."': no such file or directory")
syscall.exit(1); return
end
local ok, result = pcall(syscall.losetup, path, opts.i or nil)
if not ok then
local msg = tostring(result)
if msg:find("EPERM") then msg = "Permission denied"
elseif msg:find("ENOENT") then msg = "'"..args[1].."': no such file"
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..": "..msg); syscall.exit(1); return
end
print(result)

193
Src/hysh/bin/ls Normal file
View File

@@ -0,0 +1,193 @@
--:Minify:--
local cloptions = {
a = false,
h = false,
l = false,
help = false,
}
local inpArgs = { ... }
local args = {}
local name = syscall.getTask(syscall.getpid()).name
for _, v in pairs(inpArgs) 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.")
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.")
return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... [DIR]")
print("List all entries in the specified DIRectory, or cwd if not specified.")
print("")
print("Options:")
print(" -a do not ignore entries starting with .")
print(" -h with -l, print sizes in human readable format")
print(" -l use a long listing format")
print(" --help display this help and exit")
return
end
local fs = require("fs")
local dir = args[1] or ""
if dir:sub(1, 1) ~= "/" then
dir = syscall.getcwd() .. "/" .. dir
end
if dir:sub(-1) ~= "/" then dir = dir .. "/" end
if not fs.isDir(dir) then
print(name .. ": cannot access '" .. (args[1] or dir) .. "': no such directory")
return
end
local function permStr(perms, etype)
local function b(n) return math.floor(perms / (2^n)) % 2 == 1 end
local t
if etype == 0x01 then t = "l"
elseif etype == nil then t = "-"
else t = "-" end
local ur = b(5) and "r" or "-"
local uw = b(4) and "w" or "-"
local ux = b(9) and (b(6) and "s" or "x") or (b(6) and "S" or "-")
local gr = b(3) and "r" or "-"
local gw = b(2) and "w" or "-"
local gx = b(8) and "x" or "-"
local wr = b(1) and "r" or "-"
local ww = b(0) and "w" or "-"
local wx = b(7) and "x" or "-"
return t .. ur .. uw .. ux .. gr .. gw .. gx .. wr .. ww .. wx
end
local sizePrefixes = { "K", "M", "G", "T" }
local function humanSize(size)
local scale = 0
while size >= 1024 and scale < #sizePrefixes do
size = size / 1024
scale = scale + 1
end
if scale == 0 then return tostring(size).."B" end
if size < 10 then
return string.format("%.1f%s", size, sizePrefixes[scale])
end
return math.floor(size) .. sizePrefixes[scale]
end
local screenSizeStr = syscall.devctl(1, "size")
local sizeX = tonumber(screenSizeStr:match("^(%d+)")) or 80
local list = fs.list(dir)
if not cloptions.a then
for i = #list, 1, -1 do
if list[i]:sub(1, 1) == "." then table.remove(list, i) end
end
end
table.sort(list)
if #list == 0 then return end
if cloptions.l then
for _, v in ipairs(list) do
local fullPath = dir .. v
local stat = syscall.lstat and syscall.lstat(fullPath) or syscall.stat(fullPath)
local isDir = fs.isDir(fullPath)
local isSym = stat and stat.etype == 0x01
local typeChar
if isSym then typeChar = "l"
elseif isDir then typeChar = "d"
else typeChar = "-" end
local pstr
if stat and stat.perms then
pstr = permStr(stat.perms, stat.etype)
else
pstr = typeChar .. "---------"
end
local size = (stat and stat.size) or 0
local sizeStr = cloptions.h and humanSize(size) or tostring(size)
local mtime = (stat and stat.modified) and math.floor(stat.modified / 1000) or 0
local owner = (stat and tostring(stat.owner)) or "0"
local group = (stat and tostring(stat.group)) or "0"
printInline(pstr .. " " .. owner .. " " .. group .. " ")
printInline(string.format("%6s", sizeStr) .. " ")
printInline(tostring(mtime) .. " ")
if isSym then
syscall.devctl(1, "sfgc", 6)
printInline(v)
syscall.devctl(1, "sfgc", 1)
local ok, target = pcall(syscall.readlink, fullPath)
if ok then
printInline(" -> ")
local targetExists = pcall(syscall.stat, fullPath)
syscall.devctl(1, "sfgc", targetExists and 6 or 2)
printInline(target)
syscall.devctl(1, "sfgc", 1)
end
elseif isDir then
syscall.devctl(1, "sfgc", 14)
printInline(v)
syscall.devctl(1, "sfgc", 1)
else
local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1)
syscall.devctl(1, "sfgc", isExec and 3 or 1)
printInline(v)
syscall.devctl(1, "sfgc", 1)
end
print("")
end
return
end
local colWidth = 0
for _, v in ipairs(list) do
if #v + 2 > colWidth then colWidth = #v + 2 end
end
local numCols = math.max(1, math.floor(sizeX / colWidth))
for i, v in ipairs(list) do
local fullPath = dir .. v
local isDir = fs.isDir(fullPath)
local stat = syscall.lstat and syscall.lstat(fullPath) or syscall.stat(fullPath)
local isSym = stat and stat.etype == 0x01
if isSym then
syscall.devctl(1, "sfgc", 6)
elseif isDir then
syscall.devctl(1, "sfgc", 14)
else
local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1)
syscall.devctl(1, "sfgc", isExec and 3 or 1)
end
printInline(v)
syscall.devctl(1, "sfgc", 1)
printInline((" "):rep(colWidth - #v))
if i % numCols == 0 then print("") end
end
if #list % numCols ~= 0 then print("") end

19
Src/hysh/bin/lsusers Normal file
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",13)
print(string.format("%-6s %-6s %-16s %-20s %s", "UID", "GID", "Username", "Home", "Shell"))
print(string.rep("-", 65))
syscall.devctl(1,"sfgc",1)
for _, u in ipairs(users) do
local lock_marker = u.locked and " [locked]" or ""
if u.locked then syscall.devctl(1,"sfgc",2) end
print(string.format("%-6d %-6d %-16s %-20s %s%s",
u.uid, u.gid, u.username, u.homedir, u.shell, lock_marker))
if u.locked then syscall.devctl(1,"sfgc",1) end
end

152
Src/hysh/bin/mount Normal file
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", 14)
print("(no loop devices attached)")
syscall.devctl(1, "sfgc", 1)
return
end
for id, info in pairs(loDevs) do
local colour = info.mode == "image" and 5 or 4
syscall.devctl(1, "sfgc", colour)
printInline(info.mode.." "..id)
syscall.devctl(1, "sfgc", 1)
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", 10)
print(name..": "..loopId.." mounted at "..dest)
syscall.devctl(1, "sfgc", 1)
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", 10)
print(name..": "..loopId.." mounted at "..dest)
syscall.devctl(1, "sfgc", 1)
return
end
print(name..": wrong number of arguments")
print("try '"..name.." --help' for more information.")
syscall.exit(1)

80
Src/hysh/bin/passwd Normal file
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 .. "'")

5
Src/hysh/bin/ps Normal file
View File

@@ -0,0 +1,5 @@
--:Minify:--
for i,v in ipairs(syscall.getTasks()) do
local task = syscall.getTask(v)
print(task.pid,task.username,task.name,task.status)
end

82
Src/hysh/bin/readlink Normal file
View File

@@ -0,0 +1,82 @@
--:Minify:--
local name = syscall.getTask(syscall.getpid()).name
local cloptions = { n = false, f = false, e = 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 .. "'")
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 .. "'")
syscall.exit(1); return
end
cloptions[opt] = true
end
else
table.insert(args, v)
end
end
if cloptions.help then
print("Usage: " .. name .. " [OPTION]... FILE...")
print("Print the resolved target of symbolic links.")
print("")
print("Options:")
print(" -f canonicalize: follow every symlink; last component need not exist")
print(" -e like -f but all components must exist")
print(" -n do not output trailing newline")
print(" --help display this help and exit")
return
end
if #args == 0 then
print(name .. ": missing operand")
print("try '" .. name .. " --help' for more information.")
syscall.exit(1); return
end
local function absPath(p)
if p:sub(1,1) ~= "/" then
local d = syscall.getcwd()
if d:sub(-1) ~= "/" then d = d .. "/" end
p = d .. p
end
return p
end
local anyErr = false
for _, path in ipairs(args) do
path = absPath(path)
if cloptions.f or cloptions.e then
local ok, stat = pcall(syscall.stat, path)
if not ok then
if cloptions.e then
print(name .. ": " .. path .. ": " .. tostring(stat))
anyErr = true
else
if not cloptions.n then print(path) else printInline(path) end
end
else
if not cloptions.n then print(path) else printInline(path) end
end
else
local ok, target = pcall(syscall.readlink, path)
if not ok then
print(name .. ": " .. path .. ": " .. tostring(target))
anyErr = true
else
if not cloptions.n then print(target) else printInline(target) end
end
end
end
if anyErr then syscall.exit(1) end

62
Src/hysh/bin/su Normal file
View File

@@ -0,0 +1,62 @@
--:Minify:--
local targetUser = ({ ... })[1]
local currentUid = syscall.getuid()
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/hysh/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

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

@@ -0,0 +1,10 @@
--:Minify:--
local path=...
path=path or "/dev/tty/1"
local syscalls=syscall.sysdump()
local fd=syscall.open(path,"w")
for i=1, #syscalls do
syscall.write(fd,syscalls[i].."\n")
end
syscall.write(fd,"Total # of syscalls: "..tostring(#syscalls))
syscall.close(fd)

111
Src/hysh/bin/umount Normal file
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", 10)
print(name..": detached "..id)
syscall.devctl(1, "sfgc", 1)
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", 10)
print(name..": unmounted "..mpt)
syscall.devctl(1, "sfgc", 1)
if loopIdToDetach then
for _, id in ipairs(loopIdToDetach) do
local dok = pcall(syscall.lodetach, id)
if dok then
syscall.devctl(1, "sfgc", 14)
print(name..": auto-detached "..id)
syscall.devctl(1, "sfgc", 1)
end
end
end

67
Src/hysh/bin/useradd Normal file
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))

49
Src/hysh/bin/userdel Normal file
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 .. "'")

49
Src/hysh/bin/usermod Normal file
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 .. "'")

9
Src/hysh/bin/yes Normal file
View File

@@ -0,0 +1,9 @@
--:Minify:--
local args = {...}
while true do
if #args == 0 then
print("y")
else
print(table.concat(args, " "))
end
end

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

@@ -0,0 +1,38 @@
local ini = {}
function ini.parse(str)
local config = {}
local section = nil
for line in str:gmatch("[^\r\n]+") do
-- trim whitespace
line = line:match("^%s*(.-)%s*$")
-- skip empty lines and comments
if line ~= "" and not line:match("^[;#]") then
-- section
local sec = line:match("^%[(.-)%]$")
if sec then
section = sec
config[section] = config[section] or {}
else
-- key=value
local key, value = line:match("^(.-)=(.*)$")
if key then
key = key:match("^%s*(.-)%s*$")
value = value:match("^%s*(.-)%s*$")
if section then
config[section][key] = value
else
config[key] = value
end
end
end
end
end
return config
end
return ini

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

@@ -0,0 +1,388 @@
--:Minify:--
-- json.lua
--
-- Copyright (c) 2020 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.2" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"",
[ "\b" ] = "b",
[ "\f" ] = "f",
[ "\n" ] = "n",
[ "\r" ] = "r",
[ "\t" ] = "t",
}
local escape_char_map_inv = { [ "/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(1, 4), 16 )
local n2 = tonumber( s:sub(7, 10), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local res = ""
local j = i + 1
local k = j
while j <= #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex)
j = j + #hex
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
end
k = j + 1
elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
end
j = j + 1
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

323
Src/lua/bin/lua Normal file
View File

@@ -0,0 +1,323 @@
--:Minify:--
local C_PROMPT = 7
local C_CONT = 13
local C_OUT = 5
local C_ERR = 2
local C_KEY = 3
local C_STR = 9
local C_NUM = 10
local C_BOOL = 8
local C_NIL = 12
local C_TABLE = 13
local function c(col) syscall.devctl(1, "sfgc", col) end
local function w(s) syscall.write(1, tostring(s)) end
local MAX_DEPTH = 6
local MAX_ENTRIES = 64
local function prettyVal(val, indent, seen)
indent = indent or 0
seen = seen or {}
local t = type(val)
if t == "nil" then
c(C_NIL); w("nil")
elseif t == "boolean" then
c(C_BOOL); w(tostring(val))
elseif t == "number" then
c(C_NUM)
if val ~= val then
w("nan")
elseif val == math.huge then
w("inf")
elseif val == -math.huge then
w("-inf")
elseif val == math.floor(val) and math.abs(val) < 1e15 then
w(tostring(math.floor(val)))
else
w(tostring(val))
end
elseif t == "string" then
c(C_STR)
local s = string.format("%q", val)
w(s)
elseif t == "function" then
c(C_TABLE); w(tostring(val))
elseif t == "table" then
if seen[val] then
c(C_TABLE); w("<circular " .. tostring(val) .. ">")
return
end
if indent >= MAX_DEPTH then
c(C_TABLE); w("<table " .. tostring(val) .. ">")
return
end
seen[val] = true
local pad = string.rep(" ", indent)
local padIn = string.rep(" ", indent + 1)
local arrKeys = {}
local hashKeys = {}
local arrMax = #val
for k in pairs(val) do
if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
arrKeys[#arrKeys+1] = k
else
hashKeys[#hashKeys+1] = k
end
end
table.sort(arrKeys)
table.sort(hashKeys, function(a, b)
local ta, tb = type(a), type(b)
if ta == tb then
if ta == "string" then return a < b end
if ta == "number" then return a < b end
return tostring(a) < tostring(b)
end
return ta < tb
end)
local total = #arrKeys + #hashKeys
if total == 0 then
c(C_TABLE); w("{}")
seen[val] = nil
return
end
c(C_TABLE); w("{\n")
local shown = 0
local function printEntry(k, v, isLast)
shown = shown + 1
if shown > MAX_ENTRIES then return true end
w(padIn)
if type(k) == "number" and arrKeys[k] then
else
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
c(C_KEY); w(k)
else
c(C_TABLE); w("[")
prettyVal(k, indent+1, seen)
c(C_TABLE); w("]")
end
c(C_TABLE); w(" = ")
end
prettyVal(v, indent+1, seen)
c(C_TABLE)
if not isLast then w(",") end
w("\n")
end
for i, k in ipairs(arrKeys) do
w(padIn)
prettyVal(val[k], indent+1, seen)
c(C_TABLE)
if i < total then w(",") end
w("\n")
shown = shown + 1
if shown >= MAX_ENTRIES then
c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
break
end
end
if shown < MAX_ENTRIES then
for i, k in ipairs(hashKeys) do
local isLast = (shown + 1 >= total)
w(padIn)
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
c(C_KEY); w(k)
else
c(C_TABLE); w("[")
prettyVal(k, indent+1, seen)
c(C_TABLE); w("]")
end
c(C_TABLE); w(" = ")
prettyVal(val[k], indent+1, seen)
c(C_TABLE)
shown = shown + 1
if shown < total then w(",") end
w("\n")
if shown >= MAX_ENTRIES then
local rem = total - shown
if rem > 0 then
c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
end
break
end
end
end
c(C_TABLE); w(pad .. "}")
seen[val] = nil
else
c(C_TABLE); w(tostring(val))
end
c(1)
end
local function printResults(...)
local n = select("#", ...)
if n == 0 then return end
for i = 1, n do
if i > 1 then c(C_TABLE); w("\t") end
prettyVal(select(i, ...), 0, {})
end
w("\n")
c(1)
end
local luaEnv = setmetatable({}, {__index = _ENV})
luaEnv._G = luaEnv
luaEnv.print = function(...)
local n = select("#", ...)
for i = 1, n do
if i > 1 then w("\t") end
prettyVal(select(i, ...), 0, {})
end
w("\n")
c(1)
end
luaEnv.pp = function(val)
prettyVal(val, 0, {})
w("\n")
c(1)
end
luaEnv.exit = setmetatable({}, {
__tostring = function() return "function: exit()" end,
__call = function() syscall.exit() end,
})
local function compile(code)
local exprFn = load("return " .. code, "@lua", "t", luaEnv)
if exprFn then return exprFn, true end
local stmtFn, err = load(code, "@lua", "t", luaEnv)
return stmtFn, false, err
end
local function isIncomplete(code)
local _, err = load(code, "@lua", "t", luaEnv)
return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
end
local function cleanErr(msg)
return tostring(msg)
:gsub("^%[string .-%]:", "")
:gsub("^@lua:", "")
:gsub("stack traceback:.*", "")
:match("^%s*(.-)%s*$")
end
local function runCode(code)
local fn, isExpr, err = compile(code)
if not fn then
c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
return
end
local results = table.pack(xpcall(fn, debug.traceback))
local ok = table.remove(results, 1)
results.n = results.n - 1
if not ok then
c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
elseif isExpr and results.n > 0 then
c(C_OUT); w("= ")
printResults(table.unpack(results, 1, results.n))
end
end
local function getUserInput(prompt, history)
c(C_PROMPT); w(prompt); c(1)
local pos = syscall.devctl(1, "gpos")
local ox = tonumber(pos:sub(1, pos:find(";")-1))
local oy = tonumber(pos:sub(pos:find(";")+1))
local input = ""
local cursor = 1
local histIdx = 0
local blink = false
local dirty = true
local function redraw()
syscall.devctl(1, "spos", ox, oy)
w(input:sub(1, cursor-1))
if blink then syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1) end
w(cursor > #input and " " or input:sub(cursor, cursor))
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
w(input:sub(cursor+1) .. " ")
dirty = false
end
while true do
local key = syscall.read(0)
if key and key ~= "" then
if key == "" then
if cursor > 1 then cursor = cursor - 1; dirty = true end
elseif key == "" then
if cursor <= #input then cursor = cursor + 1; dirty = true end
elseif key == "" then
if history and histIdx < #history then
histIdx = histIdx + 1
input = history[#history - histIdx + 1]
cursor = #input + 1; dirty = true
end
elseif key == "" then
if histIdx > 1 then
histIdx = histIdx - 1
input = history[#history - histIdx + 1]
cursor = #input + 1; dirty = true
elseif histIdx == 1 then
histIdx = 0; input = ""; cursor = 1; dirty = true
end
elseif key == "\b" then
if cursor > 1 then
input = input:sub(1, cursor-2) .. input:sub(cursor)
cursor = cursor - 1; dirty = true
end
elseif key == "\n" then
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
syscall.devctl(1,"spos",ox,oy)
w(input .. " \n")
return input
elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then
input=string.sub(input,1,cursor-1)..key..string.sub(input,cursor)
cursor=cursor+1;dirty=true
end
end
local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
if nb ~= blink then blink = nb; dirty = true end
if dirty then redraw() end
end
end
c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
c(C_NIL)
w("Interactive Lua REPL. exit() to quit.\n\n")
c(1)
local history = {}
while true do
local code = getUserInput("lua> ", history)
if code == "" then goto continue end
while isIncomplete(code) do
code = code .. "\n" .. getUserInput("... ", nil)
end
if code ~= history[#history] then
history[#history+1] = code
end
runCode(code)
::continue::
end

419
Src/micro/bin/micro Normal file
View File

@@ -0,0 +1,419 @@
--:Minify:--
-- Arrows move cursor Home/End line start/end
-- PgUp/PgDn page up/down Backspace delete left
-- Ctrl-D/Delete delete right Tab 4 spaces
-- Ctrl-W save Ctrl-X save + quit
-- Ctrl-P quit Ctrl-K cut line
-- Ctrl-U paste Ctrl-F find
-- Ctrl-N find next Ctrl-G go to line
-- Ctrl-A line start Ctrl-E line end
-- Ctrl-B page up Ctrl-L page down
local args = { ... }
local function termSize()
local s = syscall.devctl(1, "size")
return tonumber(s:match("^(%d+)")) or 80,
tonumber(s:match(";(%d+)$")) or 24
end
local function tpos(x,y) syscall.devctl(1,"spos",x,y) end
local function tfg(c) syscall.devctl(1,"sfgc",c) end
local function tbg(c) syscall.devctl(1,"sbgc",c) end
local function twrite(s) if s and s~="" then syscall.write(1,s) end end
local function tclear() syscall.devctl(1,"clear") end
local W, H = termSize()
local ROWS = H - 2
local lines = {""}
local cx = 1
local cy = 1
local scrollY = 0
local dirty = true
local fname = nil
local msg = ""
local msgErr = false
local clip = nil
local sPat = ""
local sLine = 0
local blinkState = false
local function absPath(p)
if p:sub(1,1) == "/" then return p end
local cwd = syscall.getcwd()
cwd = cwd:gsub("/+$", "")
return cwd .. "/" .. p
end
local function loadFile(path)
if not syscall.exists(path) then
lines = {""}; msg = "[new file]"; return
end
local fd = syscall.open(path, "r")
local buf = ""
while true do
local c = syscall.read(fd, 4096)
if not c or c == "" then break end
buf = buf .. c
end
syscall.close(fd)
lines = {}
for ln in (buf.."\n"):gmatch("([^\n]*)\n") do
table.insert(lines, ln)
end
if #lines > 1 and lines[#lines] == "" and buf:sub(-1) == "\n" then
table.remove(lines)
end
if #lines == 0 then lines = {""} end
end
local function saveFile(path)
local ok, err = pcall(function()
local fd = syscall.open(path, "w")
for i, ln in ipairs(lines) do
syscall.write(fd, ln)
if i < #lines then syscall.write(fd, "\n") end
end
syscall.write(fd, "\n")
syscall.close(fd)
end)
if ok then
msg = "Saved: "..path; msgErr = false
else
msg = "Save failed: "..tostring(err); msgErr = true
end
end
local function wrappedRows(lineStr)
return math.max(1, math.ceil(#lineStr / W))
end
local function logicalToScreen(li, col)
return math.floor((col - 1) / W)
end
local function buildScreenMap()
local map = {}
local sr = 0
for li = 1, #lines do
local len = #lines[li]
local nrows = wrappedRows(lines[li])
for r = 0, nrows - 1 do
sr = sr + 1
map[sr] = {li, r * W + 1}
end
end
return map, sr
end
local function cursorScreenRow(map)
local offset = logicalToScreen(cy, cx)
for sr, entry in ipairs(map) do
if entry[1] == cy and math.floor((entry[2]-1)/W) == offset then
return sr
end
end
return 1
end
local function clampCx()
local m = #lines[cy] + 1
if cx > m then cx = m end
if cx < 1 then cx = 1 end
end
local function clampScroll(map)
local csr = cursorScreenRow(map)
if csr - 1 < scrollY then scrollY = csr - 1 end
if csr - 1 >= scrollY + ROWS then scrollY = csr - ROWS end
if scrollY < 0 then scrollY = 0 end
end
local function pad(s, w)
if #s >= w then return s:sub(1, w) end
return s .. string.rep(" ", w - #s)
end
local function drawTop()
tpos(1,1); tbg(4); tfg(16)
local left = " edit" .. (fname and (" - "..fname) or "")
if dirty then left = left .. " [+]" end
local right = tostring(cy)..","..tostring(cx).." "
twrite(pad(left..string.rep(" ", math.max(1, W-#left-#right))..right, W))
tbg(16); tfg(1)
end
local function drawBottom()
tpos(1, H); tbg(4); tfg(16)
if msg ~= "" then
if msgErr then tbg(2) end
twrite(pad(" "..msg, W))
msg = ""; msgErr = false
else
twrite(pad(" ^W Save ^X Quit+Save ^P Quit ^K Cut ^U Paste ^F Find ^G Go", W))
end
tbg(16); tfg(1)
end
local function drawLines(map)
local curSR = cursorScreenRow(map)
local curRowOffset = logicalToScreen(cy, cx)
local curColInRow = cx - curRowOffset * W
for row = 1, ROWS do
local sr = scrollY + row
tpos(1, row + 1)
local entry = map[sr]
if entry then
local li = entry[1]
local startCol = entry[2]
local seg = lines[li]:sub(startCol, startCol + W - 1)
local isCursorRow = (sr == curSR)
if isCursorRow then
local ci = curColInRow
ci = math.min(ci, #seg + 1)
local before = seg:sub(1, ci-1)
local curCh = ci > #seg and " " or seg:sub(ci, ci)
local after = seg:sub(ci+1)
tfg(1); tbg(16); twrite(before)
if blinkState then tfg(16); tbg(1) else tfg(1); tbg(16) end
twrite(curCh)
tfg(1); tbg(16); twrite(after)
local drawn = #before + 1 + #after
if drawn < W then twrite(string.rep(" ", W - drawn)) end
else
if li == sLine and sPat ~= "" and entry[2] == 1 then
local s, e = seg:find(sPat)
if s then
tfg(1); tbg(16); twrite(seg:sub(1,s-1))
tfg(16); tbg(3); twrite(seg:sub(s,e))
tfg(1); tbg(16); twrite(seg:sub(e+1))
twrite(string.rep(" ", W - #seg))
else
tfg(1); tbg(16); twrite(pad(seg, W))
end
else
tfg(1); tbg(16); twrite(pad(seg, W))
end
end
else
tfg(13); tbg(16); twrite(pad("~", W)); tfg(1)
end
end
end
local function redraw()
W, H = termSize(); ROWS = H - 2
local map = buildScreenMap()
clampScroll(map)
drawTop()
drawLines(map)
drawBottom()
tpos(1, H)
tbg(16); tfg(1)
end
local function prompt(label, default)
local inp = default or ""
while true do
tpos(1, H); tbg(3); tfg(16)
twrite(pad(" "..label..inp.." ", W))
tbg(16); tfg(1)
local key = syscall.read(0)
if not key or key == "" then sleep(0.02)
elseif key == "" then return nil
elseif key == "\n" then return inp
elseif key == "\b" then if #inp > 0 then inp = inp:sub(1,-2) end
else
local b = key:byte(1)
if b >= 32 and b < 127 then inp = inp..key:sub(1,1) end
end
end
end
local function insChar(c)
local ln = lines[cy]
lines[cy] = ln:sub(1,cx-1)..c..ln:sub(cx)
cx = cx+1; dirty = true
end
local function delLeft()
if cx > 1 then
local ln = lines[cy]
lines[cy] = ln:sub(1,cx-2)..ln:sub(cx)
cx = cx-1; dirty = true
elseif cy > 1 then
local above = lines[cy-1]
cx = #above+1
lines[cy-1] = above..lines[cy]
table.remove(lines, cy)
cy = cy-1; dirty = true
end
end
local function delRight()
local ln = lines[cy]
if cx <= #ln then
lines[cy] = ln:sub(1,cx-1)..ln:sub(cx+1); dirty = true
elseif cy < #lines then
lines[cy] = ln..lines[cy+1]
table.remove(lines, cy+1); dirty = true
end
end
local function newline()
local ln = lines[cy]
local pre = ln:sub(1,cx-1)
local post = ln:sub(cx)
local ind = pre:match("^(%s*)") or ""
lines[cy] = pre
table.insert(lines, cy+1, ind..post)
cy = cy+1; cx = #ind+1; dirty = true
end
local function cutLine()
clip = lines[cy]
table.remove(lines, cy)
if #lines == 0 then lines = {""} end
if cy > #lines then cy = #lines end
cx = 1; dirty = true; msg = "Cut"
end
local function pasteLine()
if not clip then msg = "Nothing to paste"; return end
table.insert(lines, cy, clip)
cy = cy+1; cx = 1; dirty = true; msg = "Pasted"
end
local function findNext()
if sPat == "" then
local p = prompt("Find: ", "")
if not p or p == "" then dirty = true; return end
sPat = p; sLine = 0
end
local start = sLine > 0 and sLine or cy
for i = 1, #lines do
local idx = (start-1+i) % #lines + 1
if lines[idx]:find(sPat) then
cy = idx; sLine = idx
cx = lines[idx]:find(sPat) or 1
msg = "Found: line "..idx; dirty = true; return
end
end
msg = "Not found: "..sPat; msgErr = true; dirty = true
end
local function goToLine()
local p = prompt("Go to line: ", "")
if not p then dirty = true; return end
local n = tonumber(p)
if not n then msg = "Not a number"; msgErr = true; dirty = true; return end
cy = math.max(1, math.min(#lines, math.floor(n)))
cx = 1; msg = "Line "..cy; dirty = true
end
local function doSave()
if not fname then
local p = prompt("Save as: ", "")
dirty = true
if not p or p == "" then return false end
fname = absPath(p)
end
saveFile(fname); dirty = true; return not msgErr
end
local function moveCursorUp(map)
local csr = cursorScreenRow(map)
if csr <= 1 then return end
local prev = map[csr - 1]
if not prev then return end
local newLi = prev[1]
local newCol = prev[2] + (cx - 1) % W
cx = math.min(newCol, #lines[newLi] + 1)
cy = newLi
end
local function moveCursorDown(map)
local csr = cursorScreenRow(map)
local next = map[csr + 1]
if not next then return end
local newLi = next[1]
local newCol = next[2] + (cx - 1) % W
cx = math.min(newCol, #lines[newLi] + 1)
cy = newLi
end
if args[1] then
fname = absPath(args[1])
loadFile(fname)
end
tclear()
local running = true
while running do
local map = buildScreenMap()
local key = syscall.read(0)
if key and key ~= "" then
local b = key:byte(1)
if key == "" then moveCursorUp(map); dirty=true
elseif key == "" then moveCursorDown(map); dirty=true
elseif key == "" then
if cx <= #lines[cy] then cx=cx+1
elseif cy < #lines then cy=cy+1; cx=1 end
dirty=true
elseif key == "" then
if cx > 1 then cx=cx-1
elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end
dirty=true
elseif key == "" then cx=1; dirty=true
elseif key == "" then cx=#lines[cy]+1; dirty=true
elseif key == "[5~" then for _=1,ROWS do moveCursorUp(map) end; dirty=true
elseif key == "[6~" then for _=1,ROWS do moveCursorDown(map) end; dirty=true
elseif key == "[3~" then delRight()
elseif key == "\n" then newline()
elseif key == "\b" then delLeft()
elseif key == "\t" then for _=1,4 do insChar(" ") end
elseif b == 6 then
local p=prompt("Find: ",sPat); dirty=true
if p then sPat=p; sLine=0; findNext() end
elseif b == 7 then goToLine()
elseif b == 11 then cutLine()
elseif b == 14 then
if sPat=="" then
local p=prompt("Find: ",""); dirty=true
if p then sPat=p; sLine=0 end
end
findNext()
elseif b == 16 then
if dirty then
local p=prompt("Unsaved changes. Quit? [y/N] ","")
dirty=true
if p and p:lower()=="y" then running=false end
else running=false end
elseif b == 21 then pasteLine()
elseif b == 23 then doSave()
elseif b == 24 then doSave(); running=false
else
if b >= 32 and b < 127 then insChar(key:sub(1,1)) end
end
end
local curBlink = (math.floor(syscall.getUptime() / 500) % 2) == 0
if curBlink ~= blinkState then
blinkState = curBlink
dirty = true
end
if dirty then
clampCx()
redraw()
dirty = false
end
end
tclear(); tfg(1); tbg(16); tpos(1,1)
print("edit: exited"..(fname and (" - "..fname) or ""))

429
Src/sed/bin/sed Normal file
View File

@@ -0,0 +1,429 @@
--:Minify:--
-- Supports: s/pat/repl/[gip], d, p, q, =, addr1[,addr2]cmd
-- Addressing: line numbers, $, /regex/
-- Flags: -n (silent), -e script, -i (in-place)
local name = syscall.getTask(syscall.getpid()).name
local scripts = {}
local files = {}
local silent = false
local inplace = false
local args = { ... }
local i = 1
while i <= #args do
local a = args[i]
if a == "-n" then
silent = true
elseif a == "-i" then
inplace = true
elseif a == "-e" then
i = i + 1
if not args[i] then
print(name .. ": option -e requires an argument"); syscall.exit(1); return
end
table.insert(scripts, args[i])
elseif a:sub(1,2) == "-e" then
table.insert(scripts, a:sub(3))
elseif a == "--help" then
print("Usage: " .. name .. " [OPTION]... SCRIPT [FILE...]")
print(" " .. name .. " [OPTION]... -e SCRIPT... [FILE...]")
print("Stream editor. Reads FILE(s) (or stdin) line by line,")
print("applies SCRIPT, and writes results to stdout.")
print("")
print("Commands:")
print(" s/REGEX/REPL/[flags] substitute (flags: g global, i ignore-case, p print)")
print(" d delete line (skip to next)")
print(" p print current line")
print(" q quit")
print(" = print current line number")
print(" y/src/dst/ transliterate characters")
print("")
print("Addressing (prefix any command):")
print(" N line number N")
print(" $ last line")
print(" /REGEX/ lines matching regex")
print(" N,M line range")
print(" N,/REGEX/ from line N until regex match")
print("")
print("Options:")
print(" -n suppress default output")
print(" -e SCRIPT add script expression")
print(" -i edit file in-place")
print(" --help display this help and exit")
return
elseif a:sub(1,1) == "-" then
print(name .. ": unknown option: " .. a)
syscall.exit(1); return
else
if #scripts == 0 then
table.insert(scripts, a)
else
table.insert(files, a)
end
end
i = i + 1
end
if #scripts == 0 then
print(name .. ": no script specified"); syscall.exit(1); return
end
local script = table.concat(scripts, "\n")
local function patEscape(s)
return s:gsub("([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1")
end
local function sedPatToLua(pat, icase)
pat = pat:gsub("\\%(", "("):gsub("\\%)", ")")
pat = pat:gsub("\\1", "%%1"):gsub("\\2", "%%2")
return pat
end
local function parseDelim(s, pos, delim)
local out = {}
while pos <= #s do
local c = s:sub(pos, pos)
if c == "\\" and pos < #s then
pos = pos + 1
local nc = s:sub(pos, pos)
if nc == delim then
table.insert(out, delim)
elseif nc == "n" then
table.insert(out, "\n")
else
table.insert(out, "\\" .. nc)
end
elseif c == delim then
return table.concat(out), pos + 1
else
table.insert(out, c)
end
pos = pos + 1
end
return table.concat(out), pos
end
local function parseAddr(s, pos)
local c = s:sub(pos, pos)
if c == "" then return nil, pos end
if c:match("%d") then
local numstr = s:match("^(%d+)", pos)
return { type="line", n=tonumber(numstr) }, pos + #numstr
elseif c == "$" then
return { type="last" }, pos + 1
elseif c == "/" then
local pat, npos = parseDelim(s, pos + 1, "/")
return { type="regex", pat=pat }, npos
end
return nil, pos
end
local function parseCommands(src)
local cmds = {}
local pos = 1
local len = #src
local function skip()
while pos <= len and (src:sub(pos,pos) == " " or src:sub(pos,pos) == "\t") do
pos = pos + 1
end
end
while pos <= len do
skip()
if pos > len then break end
local c = src:sub(pos, pos)
if c == "\n" or c == ";" then
pos = pos + 1
goto continue
end
if c == "#" then
while pos <= len and src:sub(pos,pos) ~= "\n" do pos = pos + 1 end
goto continue
end
local addr1, addr2
addr1, pos = parseAddr(src, pos)
skip()
if addr1 and pos <= len and src:sub(pos,pos) == "," then
pos = pos + 1
skip()
addr2, pos = parseAddr(src, pos)
end
skip()
if pos > len then break end
local cmd = src:sub(pos, pos)
pos = pos + 1
if cmd == "s" then
local delim = src:sub(pos, pos); pos = pos + 1
local pat, p1 = parseDelim(src, pos, delim); pos = p1
local repl, p2 = parseDelim(src, pos, delim); pos = p2
local flags = ""
while pos <= len and src:sub(pos,pos):match("[giIp]") do
flags = flags .. src:sub(pos,pos); pos = pos + 1
end
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="s",
pat=pat, repl=repl, flags=flags })
elseif cmd == "y" then
local delim = src:sub(pos, pos); pos = pos + 1
local srcch, p1 = parseDelim(src, pos, delim); pos = p1
local dstch, p2 = parseDelim(src, pos, delim); pos = p2
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd="y",
src=srcch, dst=dstch })
elseif cmd == "d" or cmd == "p" or cmd == "q" or cmd == "=" then
table.insert(cmds, { addr1=addr1, addr2=addr2, cmd=cmd })
elseif cmd == "{" then
local depth = 1
local start = pos
while pos <= len and depth > 0 do
local ch = src:sub(pos,pos)
if ch == "{" then depth = depth + 1
elseif ch == "}" then depth = depth - 1 end
pos = pos + 1
end
local inner = src:sub(start, pos - 2)
local innerCmds = parseCommands(inner)
for _, ic in ipairs(innerCmds) do
ic.addr1 = ic.addr1 or addr1
ic.addr2 = ic.addr2 or addr2
end
for _, ic in ipairs(innerCmds) do
table.insert(cmds, ic)
end
elseif cmd == "\n" or cmd == ";" then
else
end
::continue::
end
return cmds
end
local cmds = parseCommands(script)
local inRange = {}
local function addrMatch(cmd, lineNum, line, isLast, ci)
local a1 = cmd.addr1
local a2 = cmd.addr2
if not a1 then return true end
local function matchOne(addr, ln, l)
if addr.type == "line" then return ln == addr.n
elseif addr.type == "last" then return isLast
elseif addr.type == "regex" then return l:find(sedPatToLua(addr.pat)) ~= nil
end
return false
end
if not a2 then
return matchOne(a1, lineNum, line)
end
if inRange[ci] then
local endMatch
if a2.type == "line" then endMatch = (lineNum >= a2.n)
elseif a2.type == "last" then endMatch = isLast
elseif a2.type == "regex" then endMatch = (line:find(sedPatToLua(a2.pat)) ~= nil)
end
if endMatch then inRange[ci] = false end
return true
else
if matchOne(a1, lineNum, line) then
if a2.type == "line" and a2.n <= lineNum then
else
inRange[ci] = true
end
return true
end
return false
end
end
local function doSubst(line, pat, repl, flags)
local global = flags:find("g") ~= nil
local icase = flags:find("[iI]") ~= nil
local luaPat = sedPatToLua(pat, icase)
local function buildRepl(whole, ...)
local caps = { ... }
local out = {}
local rp = repl
local ri = 1
while ri <= #rp do
local rc = rp:sub(ri, ri)
if rc == "&" then
table.insert(out, whole)
elseif rc == "\\" and ri < #rp then
ri = ri + 1
local nc = rp:sub(ri, ri)
if nc:match("%d") then
local idx = tonumber(nc)
table.insert(out, caps[idx] or "")
elseif nc == "n" then
table.insert(out, "\n")
else
table.insert(out, nc)
end
else
table.insert(out, rc)
end
ri = ri + 1
end
return table.concat(out)
end
local result
local changed = false
if global then
result = line:gsub(luaPat, buildRepl)
changed = (result ~= line)
else
local s, e, whole
local parts = { line:find(luaPat) }
if parts[1] then
s = parts[1]; e = parts[2]
local caps = {}
for ci = 3, #parts do caps[#caps+1] = parts[ci] end
local wmatch = line:sub(s, e)
local replStr = buildRepl(wmatch, table.unpack(caps))
result = line:sub(1, s-1) .. replStr .. line:sub(e+1)
changed = true
else
result = line
end
end
return result, changed
end
local function doTranslit(line, src, dst)
local out = {}
for ci = 1, #line do
local c = line:sub(ci, ci)
local idx = src:find(c, 1, true)
if idx and idx <= #dst then
table.insert(out, dst:sub(idx, idx))
else
table.insert(out, c)
end
end
return table.concat(out)
end
local function processLines(lines, outputLines)
local total = #lines
for lineNum, line in ipairs(lines) do
local isLast = (lineNum == total)
local deleted = false
local printed = false
local quit = false
local bare = line:gsub("\n$", "")
for ci, cmd in ipairs(cmds) do
if addrMatch(cmd, lineNum, bare, isLast, ci) then
if cmd.cmd == "d" then
deleted = true; break
elseif cmd.cmd == "p" then
table.insert(outputLines, bare)
elseif cmd.cmd == "=" then
table.insert(outputLines, tostring(lineNum))
elseif cmd.cmd == "q" then
if not silent then table.insert(outputLines, bare) end
quit = true; break
elseif cmd.cmd == "s" then
local newLine, changed = doSubst(bare, cmd.pat, cmd.repl, cmd.flags)
bare = newLine
if changed and cmd.flags:find("p") then
table.insert(outputLines, bare)
end
elseif cmd.cmd == "y" then
bare = doTranslit(bare, cmd.src, cmd.dst)
end
end
end
if quit then break end
if not deleted and not silent then
table.insert(outputLines, bare)
end
end
end
local function readLines(fd)
local lines = {}
local buf = ""
while true do
local chunk = syscall.read(fd, 1024)
if not chunk or chunk == "" then break end
buf = buf .. chunk
end
for line in (buf .. "\n"):gmatch("([^\n]*)\n") do
table.insert(lines, line)
end
if buf ~= "" and buf:sub(-1) ~= "\n" and lines[#lines] == "" then
table.remove(lines)
end
return lines
end
local function runOnFile(path)
local fd
if path then
local ok, err = pcall(function() fd = syscall.open(path, "r") end)
if not ok then
print(name .. ": " .. path .. ": " .. tostring(err))
return false
end
else
fd = 0
end
local lines = readLines(fd)
if path then syscall.close(fd) end
inRange = {}
local outputLines = {}
processLines(lines, outputLines)
if inplace and path then
local wfd = syscall.open(path, "w")
for _, ol in ipairs(outputLines) do
syscall.write(wfd, ol .. "\n")
end
syscall.close(wfd)
else
for _, ol in ipairs(outputLines) do
print(ol)
end
end
return true
end
if #files == 0 then
runOnFile(nil)
else
for _, f in ipairs(files) do
local absf = f
if absf:sub(1,1) ~= "/" then absf = syscall.getcwd() .. "/" .. f end
runOnFile(absf)
end
end

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

@@ -0,0 +1,6 @@
local args={...}
local json=require("json")
local http=require("http")
local rootRepo="https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/spm/spm.src"

View File

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

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

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

View File

@@ -0,0 +1,46 @@
--:Minify:--
local kernel=...
local fs=require("fs")
for i,v in pairs(kernel.processes) do
kernel.log("Spawning kernel task "..i)
syscall.spawn(function()
local status, err = pcall(v)
if not status then
kernel.log("Error executing kernel task '" .. i .. "': " .. err, "ERROR")
else
kernel.log("Successfully executed kernel task: " .. i, "INFO")
end
end, i)
end
if not fs.exists("/bin/startup") then
fs.mkdir("/bin/startup")
end
local files = fs.list("/bin/startup")
if not files then error("Failed to list /bin/startup") end
for i,v in ipairs(files) do
if v:sub(-4) == ".lua" then
local filepath = "/bin/startup/" .. v
kernel.log("Executing startup script: " .. filepath, "INFO")
local startupFunc, err = load(fs.readAllText(filepath), "@" .. filepath)
if not startupFunc then
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR")
else
syscall.spawn(function()
syscall.setuid(1)
local status, err = pcall(startupFunc)
if not status then
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR")
else
kernel.log("Successfully executed startup script: " .. filepath, "INFO")
end
end, "startup:" .. v)
end
end
end
while true do
sleep(5)
kernel.saveLog()
end

View File

@@ -0,0 +1,23 @@
local fs=require("fs")
local units=fs.list("/usr/lib/hunit/")
fs.mkdir("/tmp/hunit/")
local errors={}
for i,v in ipairs(units) do
print("running unit "..v)
local code=fs.readAllText("/usr/lib/hunit/"..v)
local func, err=load(code, "@"..v)
if not func then
print(" [ERROR]:"..err)
end
---@diagnostic disable-next-line: param-type-mismatch
local ok, err=pcall(func)
if not ok then
print(" [ERROR]:"..err)
table.insert(errors, v)
else
print(" [SUCCESS]")
end
end
print(tostring(#errors).." units failed")
return 0

View File

@@ -0,0 +1,427 @@
--:Minify:--
local passed, failed = 0, 0
local function pass(msg) syscall.devctl(1,"sfgc",10); print(" PASS "..msg); syscall.devctl(1,"sfgc",1) end
local function fail(msg) syscall.devctl(1,"sfgc",2); print(" FAIL "..msg); syscall.devctl(1,"sfgc",1) end
local function info(msg) syscall.devctl(1,"sfgc",14); print(" .... "..msg); syscall.devctl(1,"sfgc",1) end
local function head(msg) syscall.devctl(1,"sfgc",4); print("\n"..msg); syscall.devctl(1,"sfgc",1) end
local function check(label, ok, err)
if ok then passed = passed + 1; pass(label)
else failed = failed + 1; fail(label.." - "..tostring(err)) end
end
local function writeFile(path, data)
local ok, fd = pcall(syscall.open, path, "w")
if not ok then return false, fd end
local ok2, err = pcall(syscall.write, fd, data)
pcall(syscall.close, fd)
return ok2, err
end
local function readFile(path)
local ok, fd = pcall(syscall.open, path, "r")
if not ok then return false, fd end
local ok2, data = pcall(syscall.read, fd, 65536)
pcall(syscall.close, fd)
return ok2, data
end
local function rmrf(path)
local t = syscall.type(path)
if t == "directory" then
local ok, entries = pcall(syscall.listdir, path)
if ok then
for _, name in ipairs(entries) do
rmrf(path:gsub("/$","").."/"..name)
end
end
pcall(syscall.remove, path)
elseif t == "file" then
pcall(syscall.remove, path)
end
end
local SCRATCH = "/tmp/looptest_scratch"
local SRC_DIR = SCRATCH.."/src"
local BIND_MNT = SCRATCH.."/bind_mnt"
local IMG_PATH = SCRATCH.."/test.hfs"
local IMG_MNT = SCRATCH.."/img_mnt"
local BIND_LOOP = nil
local IMG_LOOP = nil
rmrf(SCRATCH)
pcall(syscall.mkdir, SCRATCH)
pcall(syscall.mkdir, SRC_DIR)
pcall(syscall.mkdir, BIND_MNT)
pcall(syscall.mkdir, IMG_MNT)
pcall(syscall.mkdir, SRC_DIR.."/subdir")
writeFile(SRC_DIR.."/hello.txt", "hello from hyperion\n")
writeFile(SRC_DIR.."/data.txt", "line1\nline2\nline3\n")
writeFile(SRC_DIR.."/subdir/deep.txt", "deep file\n")
head("[ 1 ] bind mode - losetup on a directory")
do
local ok, id = pcall(syscall.losetup, SRC_DIR)
check("losetup(dir) returns a loop id", ok and type(id) == "string" and id:sub(1,4) == "loop", id)
if ok then
BIND_LOOP = id
info("attached as "..id)
end
end
head("[ 2 ] bind mode - mount and read files")
do
if BIND_LOOP then
local mok = pcall(syscall.mount, BIND_MNT, BIND_LOOP)
check("mount(bind_mnt, "..BIND_LOOP..")", mok, "mount failed")
if mok then
local lok, entries = pcall(syscall.listdir, BIND_MNT)
check("listdir through bind mount", lok and type(entries) == "table", entries)
local rok, data = readFile(BIND_MNT.."/hello.txt")
check("read hello.txt through bind", rok and data == "hello from hyperion\n",
rok and ("got: "..tostring(data):sub(1,40)) or tostring(data))
local rok2, data2 = readFile(BIND_MNT.."/subdir/deep.txt")
check("read subdir/deep.txt through bind", rok2 and data2 == "deep file\n",
rok2 and ("got: "..tostring(data2)) or tostring(data2))
end
else
check("mount (skipped - no loop id)", false, "losetup failed in [1]")
end
end
head("[ 3 ] bind mode - write through loop mount, verify on host")
do
if BIND_LOOP then
local wok, werr = writeFile(BIND_MNT.."/written.txt", "written via loop\n")
check("write new file through bind mount", wok, werr)
local rok, data = readFile(BIND_MNT.."/written.txt")
check("read back through bind mount", rok and data == "written via loop\n",
rok and ("got: "..tostring(data)) or tostring(data))
local rok2, data2 = readFile(SRC_DIR.."/written.txt")
check("file visible on host path (bind is transparent)", rok2 and data2 == "written via loop\n",
rok2 and ("got: "..tostring(data2)) or tostring(data2))
else
check("write (skipped)", false, "bind mount not set up")
end
end
head("[ 4 ] bind mode - lodetach while mounted returns EBUSY")
do
if BIND_LOOP then
local ok = pcall(syscall.lodetach, BIND_LOOP)
check("lodetach while mounted is refused (EBUSY)", not ok, "should have errored")
else
check("lodetach busy check (skipped)", false, "no bind loop")
end
end
head("[ 5 ] bind mode - umount then lodetach")
do
if BIND_LOOP then
local uok = pcall(syscall.umount, BIND_MNT)
check("umount(bind_mnt)", uok, "umount failed")
local dok = pcall(syscall.lodetach, BIND_LOOP)
check("lodetach after umount", dok, "lodetach failed")
if dok then BIND_LOOP = nil end
else
check("umount+lodetach (skipped)", false, "no bind loop")
end
end
head("[ 6 ] loimgcreate - serialise directory to HFS image")
do
local ok, imgStr = pcall(syscall.loimgcreate, SRC_DIR)
check("loimgcreate(srcdir) returns a string", ok and type(imgStr) == "string" and #imgStr > 0, imgStr)
if ok then
info("image size: "..#imgStr.." bytes")
local isBHFS = imgStr:sub(1, 4) == "BHFS"
check("image has BHFS magic header", isBHFS,
"got: "..imgStr:sub(1,4):gsub(".", function(c) return string.format("%02X ", c:byte()) end))
check("image has correct version byte (0x01)", imgStr:byte(5) == 1,
"version byte: "..tostring(imgStr:byte(5)))
check("image contains FILE record (type=0x01)", imgStr:find("\001", 9, true) ~= nil, "no FILE type byte found")
check("image contains DIR record (type=0x02)", imgStr:find("\002", 9, true) ~= nil, "no DIR type byte found")
check("image ends with END record (type=0xFF)", imgStr:byte(#imgStr) == 0xFF,
"last byte: 0x"..string.format("%02X", imgStr:byte(#imgStr)))
local wok, werr = pcall(syscall.loimgwrite, imgStr, IMG_PATH)
check("loimgwrite writes image file", wok, werr)
check("image file exists on disk", syscall.type(IMG_PATH) == "file", "file not found")
end
end
head("[ 7 ] HFS image - losetup attaches image file")
do
if syscall.type(IMG_PATH) == "file" then
local ok, id = pcall(syscall.losetup, IMG_PATH)
check("losetup(image.hfs) returns loop id", ok and type(id) == "string", id)
if ok then
IMG_LOOP = id
info("image attached as "..id)
local lok, devs = pcall(syscall.lolist)
local found = false
if lok then
for lid, info_entry in pairs(devs) do
if lid == id then found = true end
end
end
check("lolist() contains new image device", found, "not found in lolist")
end
else
check("losetup image (skipped - no image file)", false, "image not created in [6]")
end
end
head("[ 8 ] HFS image - mount and read files")
do
if IMG_LOOP then
local mok = pcall(syscall.mount, IMG_MNT, IMG_LOOP)
check("mount(img_mnt, "..IMG_LOOP..")", mok, "mount failed")
if mok then
local lok, entries = pcall(syscall.listdir, IMG_MNT)
check("listdir through image mount returns table", lok and type(entries) == "table", entries)
if lok then
local hasHello = false
local hasSubdir = false
for _, e in ipairs(entries) do
if e == "hello.txt" then hasHello = true end
if e == "subdir" then hasSubdir = true end
end
check("hello.txt visible in image root", hasHello, "not listed")
check("subdir/ visible in image root", hasSubdir, "not listed")
end
local rok, data = readFile(IMG_MNT.."/hello.txt")
check("read hello.txt from image", rok and data == "hello from hyperion\n",
rok and ("got: "..tostring(data):sub(1,40)) or tostring(data))
local rok2, data2 = readFile(IMG_MNT.."/data.txt")
check("read data.txt from image", rok2 and data2 == "line1\nline2\nline3\n",
rok2 and ("got: "..tostring(data2)) or tostring(data2))
end
else
check("image mount read (skipped)", false, "no image loop")
end
end
head("[ 9 ] HFS image - write new files into image mount")
do
if IMG_LOOP then
local wok, werr = writeFile(IMG_MNT.."/newfile.txt", "created inside image\n")
check("write new file into image mount", wok, werr)
local rok, data = readFile(IMG_MNT.."/newfile.txt")
check("read back newly written file", rok and data == "created inside image\n",
rok and ("got: "..tostring(data)) or tostring(data))
local wok2, werr2 = writeFile(IMG_MNT.."/hello.txt", "overwritten\n")
check("overwrite existing file in image", wok2, werr2)
local rok2, data2 = readFile(IMG_MNT.."/hello.txt")
check("overwritten content reads back correctly", rok2 and data2 == "overwritten\n",
rok2 and ("got: "..tostring(data2)) or tostring(data2))
local rok3, orig = readFile(IMG_PATH)
check("disk image file is unchanged after in-memory write",
rok3 and orig and orig:find("/hello%.txt") ~= nil,
rok3 and "filename record missing from image" or tostring(orig))
else
check("image write test (skipped)", false, "image not mounted")
end
end
head("[ 10 ] HFS image - sub-directory traversal")
do
if IMG_LOOP then
local t = syscall.type(IMG_MNT.."/subdir")
check("type(subdir) == 'directory'", t == "directory", "got: "..tostring(t))
local lok, entries = pcall(syscall.listdir, IMG_MNT.."/subdir")
check("listdir(subdir) works", lok and type(entries) == "table", entries)
local rok, data = readFile(IMG_MNT.."/subdir/deep.txt")
check("read subdir/deep.txt from image", rok and data == "deep file\n",
rok and ("got: "..tostring(data)) or tostring(data))
local mok = pcall(syscall.mkdir, IMG_MNT.."/subdir/newdir")
check("mkdir inside image mount", mok, "mkdir failed")
check("new dir has type 'directory'",
syscall.type(IMG_MNT.."/subdir/newdir") == "directory",
"wrong type")
local wok, werr = writeFile(IMG_MNT.."/subdir/newdir/x.txt", "x\n")
check("write file in newly created subdir", wok, werr)
else
check("subdir traversal (skipped)", false, "image not mounted")
end
end
head("[ 11 ] HFS image - lodetach while mounted returns EBUSY")
do
if IMG_LOOP then
local ok = pcall(syscall.lodetach, IMG_LOOP)
check("lodetach image while mounted is refused", not ok, "should have errored EBUSY")
else
check("lodetach busy (skipped)", false, "no image loop")
end
end
head("[ 12 ] HFS image - umount then lodetach")
do
if IMG_LOOP then
local uok = pcall(syscall.umount, IMG_MNT)
check("umount(img_mnt)", uok, "umount failed")
local dok = pcall(syscall.lodetach, IMG_LOOP)
check("lodetach after umount", dok, "lodetach failed")
if dok then
local lok, devs = pcall(syscall.lolist)
local found = false
if lok then
for lid in pairs(devs) do
if lid == IMG_LOOP then found = true end
end
end
check("lolist no longer shows detached device", not found, "still present in lolist")
IMG_LOOP = nil
end
else
check("image umount+lodetach (skipped)", false, "no image loop")
end
end
head("[ 13 ] lolist - reflects attached device count")
do
local ok1, id1 = pcall(syscall.losetup, SRC_DIR)
local ok2, id2 = pcall(syscall.losetup, SRC_DIR)
check("attach first device for lolist test", ok1, id1)
check("attach second device for lolist test", ok2, id2)
if ok1 and ok2 then
local lok, devs = pcall(syscall.lolist)
check("lolist() succeeds", lok, devs)
if lok then
local found1, found2 = false, false
for lid in pairs(devs) do
if lid == id1 then found1 = true end
if lid == id2 then found2 = true end
end
check("lolist contains first device", found1, "missing "..id1)
check("lolist contains second device", found2, "missing "..id2)
local count = 0
for _ in pairs(devs) do count = count + 1 end
info("lolist shows "..count.." device(s)")
end
end
if ok1 then pcall(syscall.lodetach, id1) end
if ok2 then pcall(syscall.lodetach, id2) end
end
head("[ 14 ] losetup - non-existent path returns error")
do
local ok = pcall(syscall.losetup, "/tmp/does_not_exist_xyz_looptest")
check("losetup on missing path errors", not ok, "should have errored")
end
head("[ 15 ] mount - same loop device cannot be mounted twice")
do
local ok, id = pcall(syscall.losetup, SRC_DIR)
check("losetup for double-mount test", ok, id)
if ok then
local m1ok = pcall(syscall.mount, BIND_MNT, id)
check("first mount succeeds", m1ok, "mount failed")
if m1ok then
local m2ok = pcall(syscall.mount, IMG_MNT, id)
check("second mount of same device is refused", not m2ok, "should have errored EBUSY")
pcall(syscall.umount, BIND_MNT)
end
pcall(syscall.lodetach, id)
end
end
head("[ 16 ] loimgcreate - on a regular file returns ENOTDIR")
do
local ok = pcall(syscall.loimgcreate, IMG_PATH)
check("loimgcreate on a file errors (ENOTDIR)", not ok, "should have errored")
end
head("[ 17 ] HFS image - binary round-trip (all byte values)")
do
local bytes = {}
for i = 0, 255 do bytes[i+1] = string.char(i) end
local binData = table.concat(bytes)
local binSrc = SCRATCH.."/binsrc"
pcall(syscall.mkdir, binSrc)
writeFile(binSrc.."/binary.bin", binData)
local ok1, imgStr = pcall(syscall.loimgcreate, binSrc)
check("loimgcreate handles binary content", ok1, imgStr)
if ok1 then
local binImg = SCRATCH.."/binary.hfs"
pcall(syscall.loimgwrite, imgStr, binImg)
local ok2, lid = pcall(syscall.losetup, binImg)
check("losetup on binary image", ok2, lid)
if ok2 then
local mnt = SCRATCH.."/binmnt"
pcall(syscall.mkdir, mnt)
local mok = pcall(syscall.mount, mnt, lid)
check("mount binary image", mok, "mount failed")
if mok then
local rok, readBack = readFile(mnt.."/binary.bin")
check("binary file readable from image", rok, readBack)
check("binary data round-trips without corruption",
rok and readBack == binData,
rok and string.format("length in=%d out=%d", #binData, #(readBack or "")) or tostring(readBack))
pcall(syscall.umount, mnt)
end
pcall(syscall.lodetach, lid)
end
end
end
head("[ 18 ] second-run safety - lolist is empty after full cleanup")
do
local lok, devs = pcall(syscall.lolist)
check("lolist() call succeeds", lok, devs)
if lok then
local count = 0
for _ in pairs(devs) do count = count + 1 end
check("no leftover loop devices after all tests", count == 0,
count.." device(s) still attached: "..
(function()
local ids = {}
for id in pairs(devs) do ids[#ids+1] = id end
return table.concat(ids, ", ")
end)())
end
end
rmrf(SCRATCH)
print("")
syscall.devctl(1, "sfgc", failed == 0 and 10 or 2)
print(string.format("Results: %d passed, %d failed", passed, failed))
syscall.devctl(1, "sfgc", 1)
if failed > 0 then syscall.exit(1) end

View File

@@ -0,0 +1,151 @@
--:Minify:--
local args = { ... }
local target = args[1] or "http://example.com"
local function pass(msg) syscall.devctl(1,"sfgc",10); print(" PASS " .. msg); syscall.devctl(1,"sfgc",1) end
local function fail(msg) syscall.devctl(1,"sfgc",2); print(" FAIL " .. msg); syscall.devctl(1,"sfgc",1) end
local function info(msg) syscall.devctl(1,"sfgc",14); print(" .... " .. msg); syscall.devctl(1,"sfgc",1) end
local function head(msg) syscall.devctl(1,"sfgc",4); print("\n" .. msg); syscall.devctl(1,"sfgc",1) end
local passed, failed = 0, 0
local function check(name, ok, err)
if ok then passed = passed + 1; pass(name)
else failed = failed + 1; fail(name .. " - " .. tostring(err)) end
end
head("[ 1 ] socket() creation")
do
local ok, fd = pcall(syscall.socket, "inet", "stream")
check("socket(inet, stream) returns fd", ok and type(fd) == "number", fd)
if ok then
local cok = pcall(syscall.close, fd)
check("close() on socket fd", cok, "close failed")
end
local ok2, fd2 = pcall(syscall.socket, "unix", "stream")
check("socket(unix, stream) returns fd", ok2 and type(fd2) == "number", fd2)
if ok2 then pcall(syscall.close, fd2) end
local ok3 = pcall(syscall.socket, "ax25", "stream")
check("socket(ax25) returns EAFNOSUPPORT", not ok3, "should have errored")
end
head("[ 2 ] connect() to " .. target)
local sockfd
do
local ok, fd = pcall(syscall.socket, "inet", "stream")
check("socket() before connect", ok, fd)
if ok then
sockfd = fd
local cok, cerr = pcall(syscall.connect, fd, target)
check("connect(" .. target .. ")", cok, cerr)
end
end
head("[ 3 ] send() HTTP GET via socket")
do
if sockfd then
local sok, serr = pcall(syscall.send, sockfd, "")
check("send() does not error", sok, serr)
else
check("send() skipped (no socket)", false, "socket creation failed")
end
end
head("[ 4 ] recv() reads HTTP response")
do
if sockfd then
info("waiting for response (recv blocks up to 10s)...")
local ok, body = pcall(syscall.recv, sockfd, 65536)
check("recv() returns non-empty body", ok and body and #body > 0,
ok and "empty response" or tostring(body))
if ok and body and #body > 0 then
info("received " .. #body .. " bytes")
local preview = body:sub(1, 120):gsub("\r", ""):gsub("\n", " ")
info("preview: " .. preview)
end
pcall(syscall.close, sockfd)
sockfd = nil
else
check("recv() skipped (no socket)", false, "socket creation failed")
end
end
head("[ 5 ] httpget() convenience wrapper")
do
info("GET " .. target .. " ...")
local ok, body = pcall(syscall.httpget, target)
check("httpget() succeeds", ok, body)
if ok then
check("httpget() returns non-empty string", type(body) == "string" and #body > 0, "empty")
if type(body) == "string" and #body > 0 then
info("received " .. #body .. " bytes")
local preview = body:sub(1, 120):gsub("\r", ""):gsub("\n", " ")
info("preview: " .. preview)
local hasHtml = body:lower():find("<html") ~= nil
or body:lower():find("<!doctype") ~= nil
or body:find("{") ~= nil
or body:find("HTTP") ~= nil
check("body looks like HTTP content", hasHtml, "no recognisable content markers")
end
end
end
head("[ 6 ] UNIX socket loopback IPC")
do
local sockPath = "/tmp/socktest.sock"
pcall(syscall.remove, sockPath)
local sok, sfd = pcall(syscall.socket, "unix", "stream")
check("server socket(unix,stream)", sok, sfd)
if sok then
local bok = pcall(syscall.bind, sfd, sockPath)
check("bind(" .. sockPath .. ")", bok, "bind failed")
local lok = pcall(syscall.listen, sfd, 1)
check("listen()", lok, "listen failed")
local cok, cfd = pcall(syscall.socket, "unix", "stream")
check("client socket(unix,stream)", cok, cfd)
if cok then
local connok = pcall(syscall.connect, cfd, sockPath)
check("client connect(" .. sockPath .. ")", connok, "connect failed")
local aok, afd = pcall(syscall.accept, sfd)
check("accept() returns client fd", aok, afd)
if connok and aok then
local sendok = pcall(syscall.send, cfd, "hello hyperion")
check("send() from client", sendok, "send failed")
local rok, data = pcall(syscall.recv, afd, 1024)
check("recv() on server side", rok and data == "hello hyperion",
rok and ("got: " .. tostring(data)) or tostring(data))
local repok = pcall(syscall.send, afd, "hello back")
check("send() reply from server", repok, "send failed")
local rok2, data2 = pcall(syscall.recv, cfd, 1024)
check("recv() reply on client", rok2 and data2 == "hello back",
rok2 and ("got: " .. tostring(data2)) or tostring(data2))
pcall(syscall.close, afd)
end
pcall(syscall.close, cfd)
end
pcall(syscall.close, sfd)
pcall(syscall.remove, sockPath)
end
end
print("")
syscall.devctl(1,"sfgc", failed == 0 and 10 or 2)
print(string.format("Results: %d passed, %d failed", passed, failed))
syscall.devctl(1,"sfgc",1)
if failed > 0 then syscall.exit(1) end

View File

@@ -0,0 +1,3 @@
local fs = require("fs")
assert(fs.mkdir("/tmp/hunit/testdir"), "failed to make directory")
assert(fs.isDir("/tmp/hunit/testdir"), "directory does not exist")

View File

@@ -0,0 +1,10 @@
local fd = syscall.VFS_open("/tmp/hunit/testfile.txt", "w")
syscall.VFS_write(fd, "This is a test file")
syscall.VFS_close(fd)
local fd = syscall.VFS_open("/tmp/hunit/testfile.txt", "r")
local text = syscall.VFS_read(fd, 64)
syscall.VFS_close(fd)
if text~="This is a test file" then
error("File failed to write/read")
end

243
build.py Normal file
View File

@@ -0,0 +1,243 @@
#!/usr/bin/env python3
"""
Usage:
python build.py <target> [--arch cct|oc] [--release|--dev]
Targets:
build
build-mini
build-micro
build-test
build-mini-test
build-micro-test
clean
Arch flags:
--arch cct
--arch oc
Release flags:
--release
--dev
"""
import sys
import shutil
import argparse
import subprocess
from pathlib import Path
from typing import Union
PROJECT_ROOT = Path(__file__).resolve().parent
SRC_ROOT = PROJECT_ROOT / "Src"
TEST_ROOT = PROJECT_ROOT / "Test"
BUILD_ROOT = PROJECT_ROOT / "Build"
ARCH_BOOT_DIR = {
"cct": Path("boot") / "cct",
"oc": Path("boot") / "oc",
}
def clean():
if BUILD_ROOT.exists():
print(f"Removing {BUILD_ROOT} ...")
shutil.rmtree(BUILD_ROOT)
else:
print("Nothing to clean.")
def has_minify_header(path: Path) -> bool:
try:
with path.open("r", encoding="utf-8", errors="ignore") as f:
for _ in range(3):
if "--:Minify:--" in f.readline():
return True
except OSError:
pass
return False
def minify_file(src: Path) -> str:
result = subprocess.run(
["luamin.cmd", "-f", str(src)],
capture_output=True,
text=True
)
if result.returncode != 0:
print(f" ! luamin failed: {result.stderr.strip()}", file=sys.stderr)
sys.exit(1)
return result.stdout
def compress_lz4(data: bytes) -> bytes:
return lz4.frame.compress(data)
def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool):
print(f"Building from {src_root}")
print(f"Output to {out_root}")
print()
for pkg_dir in sorted(src_root.iterdir()):
if not pkg_dir.is_dir():
continue
print(f"== Package: {pkg_dir.name} ==")
for src in sorted(pkg_dir.rglob("*")):
if not src.is_file():
continue
rel = src.relative_to(pkg_dir)
if rel.name=="$PKGCONFIG.ini":
continue
dst = out_root / rel
dst.parent.mkdir(parents=True, exist_ok=True)
print(f" Processing: {src.relative_to(src_root)}")
if has_minify_header(src):
if minify:
print(" > Minifying")
content = minify_file(src)
if micro:
print(" > LZ4 compressing")
compressed = compress_lz4(content.encode("utf-8"))
dst.write_bytes(compressed)
else:
dst.write_text(content, encoding="utf-8")
else:
print(" > Copying")
shutil.copy2(src, dst)
else:
print(" > Copying")
shutil.copy2(src, dst)
print()
def install_bootloader(arch: str, release: bool):
boot_dir = BUILD_ROOT / "$" / ARCH_BOOT_DIR[arch]
eeprom = boot_dir / "eeprom"
eeprom_dst_name = "startup.lua" if release else "eeprom"
print(f" Installing: eeprom -> Build/{eeprom_dst_name}")
shutil.copy2(eeprom, BUILD_ROOT / eeprom_dst_name)
def run_build(minify: bool, micro: bool, include_test: bool, arch: Union[str, None], release: bool):
clean()
BUILD_ROOT.mkdir()
out_root = BUILD_ROOT / "$" if arch else BUILD_ROOT
process_root(SRC_ROOT, out_root, minify, micro)
if include_test:
process_root(TEST_ROOT, out_root, minify, micro)
if arch:
print("Installing bootloader files ...")
install_bootloader(arch, release)
print()
def _make_firstboot_kmod(users):
lines = []
lines.append("local kernel = ...")
lines.append("local auth = kernel.auth")
lines.append("")
for username, password in users:
u = username.replace("\\", "\\\\").replace("'", "\\'")
p = password.replace("\\", "\\\\").replace("'", "\\'")
if username == "root":
lines.append("do")
lines.append(" local ok, err = auth.setPassword(0, '" + p + "')")
lines.append(" if ok then")
lines.append(" kernel.log('FIRSTBOOT: root password set')")
lines.append(" else")
lines.append(" kernel.log('FIRSTBOOT: root password error: ' .. tostring(err), 'ERROR')")
lines.append(" end")
lines.append("end")
else:
lines.append("do")
lines.append(" local uid, err = auth.newUser('" + u + "', '" + p + "')")
lines.append(" if uid then")
lines.append(" kernel.log('FIRSTBOOT: created user " + u + " uid=' .. tostring(uid))")
lines.append(" else")
lines.append(" kernel.log('FIRSTBOOT: failed to create user " + u + ": ' .. tostring(err), 'ERROR')")
lines.append(" end")
lines.append("end")
lines.append("")
lines.append("do")
lines.append(" local ok, err = pcall(function()")
lines.append(" kernel.vfs.remove('/lib/modules/Hyperion/50_firstboot_users.kmod')")
lines.append(" end)")
lines.append(" if not ok then")
lines.append(" kernel.log('FIRSTBOOT: could not self-delete: ' .. tostring(err), 'WARN')")
lines.append(" end")
lines.append("end")
return "\n".join(lines) + "\n"
def inject_makeusers(users, arch):
base = BUILD_ROOT / "$" if arch else BUILD_ROOT
kmod_path = base / "lib" / "modules" / "Hyperion" / "50_firstboot_users.kmod"
kmod_path.parent.mkdir(parents=True, exist_ok=True)
kmod_path.write_text(_make_firstboot_kmod(users), encoding="utf-8")
print(" Wrote first-boot user setup -> " + str(kmod_path.relative_to(BUILD_ROOT)))
def main():
parser = argparse.ArgumentParser(description="HyperionOS build script")
parser.add_argument("target", choices=["build", "build-mini", "build-micro", "build-test", "build-mini-test", "build-micro-test", "clean"])
parser.add_argument("--arch", choices=["cct", "oc"], default=None,
help="Target architecture (cct or oc)")
parser.add_argument("--release", dest="release", action="store_true", default=True,
help="Release build: eeprom placed as startup.lua (default)")
parser.add_argument("--dev", dest="release", action="store_false",
help="Dev build: boot.lua and eeprom copied unchanged")
parser.add_argument(
"--makeuser", metavar=("USERNAME", "PASSWORD"), nargs=2, action="append",
default=[],
help=(
"Pre-create a user on first boot (dev builds only). "
"May be specified multiple times. "
"Example: --makeuser root secretpass --makeuser alice alicepass"
),
)
args = parser.parse_args()
if args.makeuser and args.release:
parser.error("--makeuser is only allowed with --dev builds")
if args.target == "clean":
clean()
return
minify = "mini" in args.target or "micro" in args.target
micro = "micro" in args.target
include_test = "test" in args.target
if micro:
import lz4.block
run_build(minify=minify, micro=micro, include_test=include_test, arch=args.arch, release=args.release)
if args.makeuser:
print("Injecting first-boot user setup ...")
inject_makeusers(args.makeuser, args.arch)
print()
print("Build complete.")
if __name__ == "__main__":
main()

86
building.md Normal file
View File

@@ -0,0 +1,86 @@
## Building
### Linux / `make`-compatible systems
Run:
```bash
make build
```
**Optional variables:**
* **`ARCH=`** Select bootloader type:
* `cct` Build using the CCT bootloader
* `oc` Build using the OC bootloader
* **`DEV=1`** Enable development mode:
* Bootloader does **not** start automatically on system startup
**Default behavior (if `DEV` is not specified):**
* Builds in **release mode**
* Bootloader starts automatically on system startup
**Examples:**
```bash
make build ARCH=cct
make build ARCH=oc DEV=1
```
---
### Windows / Any system with Python 3.7+
Run:
```bash
python build.py build
```
**Optional arguments:**
* **`--arch {cct|oc}`** Select bootloader:
* `cct` Use the CCT bootloader
* `oc` Use the OC bootloader
* **`--dev`** Development mode:
* Bootloader does **not** start automatically
* You must run `eeprom` in CraftOS to start Hyperion
* **`--release`** (default) Release mode:
* Bootloader starts automatically
* **`--makeuser username password`** Pre-create user accounts (only works with `--dev` builds):
```bash
--makeuser root rootpass
--makeuser root rootpass --makeuser alice alicepass
```
* Example: The first command creates the `root` account with the given password on first boot
* Example: The second command creates both `root` and `alice` accounts with defined passwords on first boot
**Examples:**
```bash
python build.py build --arch cct
python build.py build --arch oc --dev
```
---
### Build Requirements
* **`build`** No additional requirements
* **`build-mini`** Requires [`luamin`](https://www.npmjs.com/package/luamin)
* **`build-micro`** Requires:
* [`luamin`](https://www.npmjs.com/package/luamin)
* [`LZ4 binaries`](https://github.com/lz4/lz4/releases)

9
contributors.md Normal file
View File

@@ -0,0 +1,9 @@
# Contributors
- Astronand (dc astronand; web astronand.dev)
- Lead developer, creator of Hyperion
- GHXX (dc ghxx)
- Pentesting, AC development
- Ryan (dc ryan.s.t)
- UI design and implementation, shell design
- SpSf/SpartanSoftware (dc swoshswosh_01578)
- Optimization, systems development

View File

@@ -1,34 +0,0 @@
readAllText(dir) Returns string:contents
reads all text from directory on disk
writeAllText(dir, content) Returns nil
writes all content to file
appendAllText(dir, content) Returns nil
appends all content to file
getAtributes(dir) Returns table:atributes
returns atributes of a file or directory
list(dir) Returns table:files
returns contents of directory
mkdir(dir) Returns nil
makes specified directory
mkfile(dir) Returns nil
makes specified file
-------------------------------------------------------
Atributes table
number:size
size of file in bytes
number:owner
owner UUID
number:group
group UUID
number:perms
file perms 0-21

View File

@@ -1,16 +0,0 @@
# drivers
---
## driver types
---
```
http - internet
lan - local networks
fs - filesystems
disk - normal disks
udisk - byte array disks
terminal - screens that only support text
```
### Driver APIS

View File

@@ -1,8 +0,0 @@
print(text) Returns nil
Prints text to the terminal with a trailing \n
printInline(text) Returns nil
Same as print without the trailing \n
clear() Returns nil
Clears screen

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